目錄
一,作用
二淋袖,本質(zhì)
三,同名方法調(diào)用優(yōu)先級(jí)
一锯梁,作用
1即碗,給系統(tǒng)類或第三方類添加屬性
- 因?yàn)闊o(wú)法修改系統(tǒng)類或第三方類的代碼,所以只能利用分類進(jìn)行添加
- 因?yàn)樵诜诸愔胁荒芴砑映蓡T變量涝桅,所以系統(tǒng)不會(huì)自動(dòng)生成帶下劃線的成員變量和
get/set
方法 - 如果想正常使用分類中的屬性拜姿,那必須利用
runtime
的關(guān)聯(lián)函數(shù)來(lái)手動(dòng)實(shí)現(xiàn)get/set
方法
@interface UIView (Add)
@property (nonatomic, copy) NSString *name;
@end
@implementation UIView (Add)
- (NSString *)name {
return objc_getAssociatedObject(self, @"name");
}
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
2烙样,給系統(tǒng)類或第三方類添加方法
- 因?yàn)闊o(wú)法修改系統(tǒng)類或第三方類的代碼冯遂,所以只能利用分類進(jìn)行添加
@interface UIView (Add)
- (void)removeAllSubviews;
@end
@implementation UIView (Add)
- (void)removeAllSubviews {
[self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj removeFromSuperview];
}];
}
@end
3,拆分自定義類的代碼
- 如果某類的代碼量比較大谒获,可以將代碼按照功能拆分到各個(gè)分類中
@interface ApiRequest : NSObject
@end
@interface ApiRequest (Login)
@end
@interface ApiRequest (Home)
@end
@interface ApiRequest (My)
@end
二蛤肌,本質(zhì)
@interface Person (Add) <NSCopying>
@property (nonatomic, assign) NSInteger age;
- (void)eat;
+ (void)run;
@end
@implementation Person (Add)
- (void)eat {
NSLog(@"eat");
}
+ (void)run {
NSLog(@"run");
}
@end
將上述代碼用clang
轉(zhuǎn)為C++代碼,Person (Add)
的底層代碼如下批狱,可以看到分類的本質(zhì)是結(jié)構(gòu)體
// 底層結(jié)構(gòu)
struct _category_t {
const char *name; // 類名
struct _class_t *cls; // 指向類的指針
const struct _method_list_t *instance_methods; // 實(shí)例方法列表
const struct _method_list_t *class_methods; // 類方法列表
const struct _protocol_list_t *protocols; // 協(xié)議列表
const struct _prop_list_t *properties; // 屬性列表
};
// 用_category_t實(shí)例化Person (Add)
static struct _category_t _OBJC_$_CATEGORY_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Add,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Add,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Add,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Add,
};
// Person (Add)的實(shí)例方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"eat", "v16@0:8", (void *)_I_Person_Add_eat}}
};
// Person (Add)的類方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"run", "v16@0:8", (void *)_C_Person_Add_run}}
};
// Person (Add)的協(xié)議列表
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
// Person (Add)的屬性列表
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"age","Tq,N"}}
};
注意:因?yàn)?code>_category_t中沒(méi)有成員變量列表裸准,所以在分類中不能添加成員變量
三,同名方法調(diào)用優(yōu)先級(jí)
- 原類與分類:優(yōu)先調(diào)用分類的
- 多個(gè)分類:優(yōu)先調(diào)用后編譯的(編譯順序從上往下)
1赔硫, 代碼驗(yàn)證
// Person
@interface Person : NSObject
- (void)eat;
@end
@implementation Person
- (void)eat {
NSLog(@"Person eat");
}
@end
// Person (Add1)
@interface Person (Add1)
- (void)eat;
@end
@implementation Person (Add1)
- (void)eat {
NSLog(@"Person (Add1) eat");
}
@end
// Person (Add2)
@interface Person (Add2)
- (void)eat;
@end
@implementation Person (Add2)
- (void)eat {
NSLog(@"Person (Add2) eat");
}
@end
// 使用
Person *person = [Person new];
[person eat];
// 打印
Person (Add1) eat
2炒俱,源碼分析(源碼下載地址)
- 方法一:重新組織類中的方法
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
// 獲取所有的分類
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// 方法二
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
- 方法二:將分類中的方法附加到原類中
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
// 創(chuàng)建方法列表數(shù)組
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
// 倒序遍歷分類列表
while (i--) {
// 取出分類
auto& entry = cats->list[i];
// 取出分類中的方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
// 將方法列表正序放入方法列表數(shù)組中
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 方法三
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
- 方法三:將分類的方法列表數(shù)組附加到原類的方法列表數(shù)組中
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
// 擴(kuò)大原類方法列表數(shù)組的容量
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
// 將原類的方法列表移動(dòng)到數(shù)組末位
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 將分類的方法列表數(shù)組復(fù)制到原類方法列表數(shù)組的首位
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
- 上述三個(gè)方法的邏輯圖
3,注意點(diǎn)
分類是在運(yùn)行期合并到原類中的
分類方法并沒(méi)有覆蓋原類方法,只是放在原類方法的前面权悟,而方法調(diào)用是順序查找砸王,所以優(yōu)先調(diào)用分類方法
- (void)viewDidLoad {
[super viewDidLoad];
[self logMethodNamesWithClass:[Person class]];
}
// 打印類對(duì)象或元類對(duì)象中的方法名
- (void)logMethodNamesWithClass:(Class)cls {
unsigned int count;
Method *methodList = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
for (int i = 0; i < count; i++) {
Method method = methodList[i];
SEL selector = method_getName(method);
NSString *methodName = NSStringFromSelector(selector);
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
free(methodList);
NSLog(@"%@---%@", cls, methodNames);
}
// 打印
Person---eat, eat, eat,