目录

  • 一、分类Category和扩展Extension
    • 1.分类Category(运行期)
    • 2.扩展Extension(编译期)
    • 3.分类和扩展的区别
  • 二、分类Category的实质
    • 1.分类的结构
    • 2.分类结构的源码解析
      • 2.1 分类的三大元素
      • 2.2 分类的对象方法列表结构体
      • 2.3 分类的类方法列表结构体
      • 2.4 分类的协议列表结构体
      • 2.5 分类的属性列表结构体
    • 分类结构总结
  • 三、分类Category的源码分析
    • 1._objc_init
    • 2.map_images
    • 3.addUnattachedCategoryForClass
    • 4.remethodizeClass
    • 5.attachCategories
    • 6. attachLists
    • 总结一下分类是怎么实现方法添加的:
      • 为什么分类不能添加成员变量?
  • 四、load方法和initialize方法
    • 1.load方法的调用顺序
    • 2.initialize方法调用顺序
    • load和initialize区别
  • 五、关联对象
    • 1.关联对象的使用
      • 1.1 objc_setAssociatedObject
      • 1.2 objc_getAssociatedObject
      • 1.3 objc_removeAssociatedObjects
      • 举例:
    • 2.关联对象的底层结构
      • 2.1 AssociationsManager
      • 2.2 AssociationsHashMap
      • 2.3 ObjectAssociationMap
      • 2.4 ObjcAssociation
    • 3.关联对象的底层原理
      • 3.1 objc_setAssociatedObject
      • 3.2 objc_getAssociatedObject
      • 3.3 objc_removeAssociatedObjects

一、分类Category和扩展Extension

1.分类Category(运行期)

  • 分类就是为已经存在的类添加新方法的一种新机制,当然也可以用作类各种逻辑代码的分区。
  • 它可以在运行时阶段动态的为已有的类添加新行为。
  • 分类还可以将 framework 私有方法公开化。

    例如,我们有一个类中有一个私有方法A,外界当然是调用不到的,但是我们可以通过创建该类新的分类,其中也定义一个方法A,只声明不实现,那么此时我们调用这个分类中的方法A就会调用该类中的私有方法A了,这就将私有方法公开化了。

  • 分类可以添加实例方法、类方法、协议和属性,但是不能声明实例变量。

2.扩展Extension(编译期)

  • 扩展跟分类定义很像,但是它只有声明部分没有实现部分,并且扩展中定义的方法得需要我们在该类的实现部分去实现。
  • 扩展可以定义实例方法、类方法、协议、属性和实例变量,因为它会在编译期将其中定义的数据加到该类的各种数据列表中。
  • 不能为系统类添加扩展,因为扩展中定义的方法的实现是要在该类的实现部分去书写代码的,但是系统类不会对我们开放的,所以我们就无法实现扩展方法,所以我们也就不能为系统类添加扩展了。

3.分类和扩展的区别

  • Extension 看起来很像一个匿名的 Category,但是 Extension 和有名字的category几乎完全是两个东西。 Extension 在编译期决议,它就是类的一部分,在编译期和头文件里的 @interface 以及实现文件里的 @implement 一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。Extension 一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加 Extension ,所以你无法为系统的类比如 NSString 添加 Extension。
  • 但是 Category 则完全不一样,它是在运行期决议的。就 Category 和 Extension 的区别来看,我们可以推导出一个明显的事实,Extension 可以添加实例变量,而 Category 是无法添加实例变量的(因为在运行时,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。

二、分类Category的实质

1.分类的结构

我们知道,所有的OC类和对象,在runtime层都是用struct结构体进行封装的,category也是如此,它被定义为category_t结构体,其数据结构如下:

typedef struct category_t *Category;
struct category_t {const char *name; // 分类名classref_t cls; // 类struct method_list_t *instanceMethods;  // 实例方法struct method_list_t *classMethods; // 类方法struct protocol_list_t *protocols;  // 协议struct property_list_t *instanceProperties; // 实例属性// Fields below this point are not always present on disk.struct property_list_t *_classProperties;   // 类属性method_list_t *methodsForMeta(bool isMeta) {if (isMeta) return classMethods;else return instanceMethods;}property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

从它的结构中就能知道它不能添加实例变量,因为其中没有实例变量的定义。

2.分类结构的源码解析

我们先创建一个新的分类,其中定义一些方法和属性,方便我们解析。

新建一个继承自NSObject的类的分类:

@interface TestNSObject (TestCategoryNSObject)<TestCategoryProtocol>@property (nonatomic, copy) NSString *personName;- (void)TestCategoryMethod;
+ (void)TestCategoryClassMethod;@end

该分类遵守的协议:

@protocol TestCategoryProtocol <NSObject>- (void)protocolMethod;+ (void)protocolClassMethod;@end

并实现其中的方法:

@implementation TestNSObject (TestCategoryNSObject)- (void)TestCategoryMethod {NSLog(@"TestCategoryMethod");
}+ (void)TestCategoryClassMethod {NSLog(@"TestCategoryClassMethod");
}- (void)protocolMethod {NSLog(@"protocolMethod");
}+ (void)protocolClassMethod {NSLog(@"protocolClassMethod");
}@end

2.1 分类的三大元素

使用clang -rewrite-objc NSObject+TestCategoryNSObject.m将其转为C++源码,可以看到它长这样:

// category结构体
struct _category_t {const char *name;struct _class_t *cls;const struct _method_list_t *instance_methods;const struct _method_list_t *class_methods;const struct _protocol_list_t *protocols;const struct _prop_list_t *properties;
};// category结构体的赋值语句
static struct _category_t _OBJC_$_CATEGORY_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) =
{"TestNSObject",0, // &OBJC_CLASS_$_TestNSObject,(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_TestNSObject_$_TestCategoryNSObject,(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_TestNSObject_$_TestCategoryNSObject,(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_TestNSObject_$_TestCategoryNSObject,(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_TestNSObject_$_TestCategoryNSObject,
};// category结构体数组
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {&_OBJC_$_CATEGORY_TestNSObject_$_TestCategoryNSObject,
};

我们能看到,这三个数据就是该类分类的重点!

  • category结构体
  • category结构体的赋值语句
  • category结构体数组
    • 其中每一个元素都是该类的category结构体,因为这里我们该类只定义了这一个分类,所以只有一个元素。

可能我们看到上面的category结构体的赋值语句其中那几个长串串数据人都傻了,别慌,下面我们慢慢来看:

2.2 分类的对象方法列表结构体

// 对象方法实现
static void _I_TestNSObject_TestCategoryNSObject_TestCategoryMethod(TestNSObject * self, SEL _cmd) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_7n_zhjyqbvs75z54mcf1v4rtctr0000gn_T_TestNSObject_TestCategoryNSObject_630503_mi_0);
}
static void _I_TestNSObject_TestCategoryNSObject_protocolMethod(TestNSObject * self, SEL _cmd) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_7n_zhjyqbvs75z54mcf1v4rtctr0000gn_T_TestNSObject_TestCategoryNSObject_630503_mi_2);
}// 对象方法列表结构体
static struct /*_method_list_t*/ {unsigned int entsize;  // sizeof(struct _objc_method)unsigned int method_count; // 数组长度struct _objc_method method_list[2]; // 用数组存储对象方法,每个元素的三个数据分别为:方法名、接收参数、以及函数指针
} _OBJC_$_CATEGORY_INSTANCE_METHODS_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_objc_method),2,{{(struct objc_selector *)"TestCategoryMethod", "v16@0:8", (void *)_I_TestNSObject_TestCategoryNSObject_TestCategoryMethod},{(struct objc_selector *)"protocolMethod", "v16@0:8", (void *)_I_TestNSObject_TestCategoryNSObject_protocolMethod}}
};
  • - (void)TestCategoryMethod;- (void)protocolMethod;方法的实现
  • 对象方法结构体列表结构体

我们可以看到,只要是在该分类中实现的对象方法(不管是协议中的还是自身的),都会添加到该分类的方法列表结构体_OBJC_$_CATEGORY_INSTANCE_METHODS_TestNSObject_$_TestCategoryNSObject中,如果我们仅仅是对方法定义而不实现,那么它就不会添加进来。

2.3 分类的类方法列表结构体

// 类方法的实现
static void _C_TestNSObject_TestCategoryNSObject_TestCategoryClassMethod(Class self, SEL _cmd) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_7n_zhjyqbvs75z54mcf1v4rtctr0000gn_T_TestNSObject_TestCategoryNSObject_630503_mi_1);
}
static void _C_TestNSObject_TestCategoryNSObject_protocolClassMethod(Class self, SEL _cmd) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_7n_zhjyqbvs75z54mcf1v4rtctr0000gn_T_TestNSObject_TestCategoryNSObject_630503_mi_3);
}// 类方法列表结构体
static struct /*_method_list_t*/ {unsigned int entsize;  // sizeof(struct _objc_method)unsigned int method_count; // 数组长度struct _objc_method method_list[2]; // 用数组存储类方法,每个元素的三个数据分别为:方法名、接收参数、以及函数指针
} _OBJC_$_CATEGORY_CLASS_METHODS_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_objc_method),2,{{(struct objc_selector *)"TestCategoryClassMethod", "v16@0:8", (void *)_C_TestNSObject_TestCategoryNSObject_TestCategoryClassMethod},{(struct objc_selector *)"protocolClassMethod", "v16@0:8", (void *)_C_TestNSObject_TestCategoryNSObject_protocolClassMethod}}
};
  • + (void)TestCategoryClassMethod;+ (void)protocolClassMethod;类方法的实现
  • 类方法列表结构体

与对象方法一样,但是它会添加到添加到类方法列表结构体_OBJC_$_CATEGORY_CLASS_METHODS_TestNSObject_$_TestCategoryNSObject中。

2.4 分类的协议列表结构体

// 协议结构体
struct _protocol_t {void * isa;  // NULLconst char *protocol_name; // 协议名const struct _protocol_list_t * protocol_list; // super protocolsconst struct method_list_t *instance_methods; // 对象方法const struct method_list_t *class_methods; // 类方法const struct method_list_t *optionalInstanceMethods;const struct method_list_t *optionalClassMethods;const struct _prop_list_t * properties; // 属性const unsigned int size;  // sizeof(struct _protocol_t)const unsigned int flags;  // = 0const char ** extendedMethodTypes;
};// 分类中添加的协议列表结构体
static struct /*_protocol_list_t*/ {long protocol_count;  // Note, this is 32/64 bitstruct _protocol_t *super_protocols[1]; // 因为我们在定义该协议的时候,有一个<NSObject>的操作,所以这里就会将其加进来,类似于继承吧,所以就有一个数据
} _OBJC_PROTOCOL_REFS_TestCategoryProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {1,&_OBJC_PROTOCOL_NSObject
};// 分类中添加的对象方法列表结构体
static struct /*_method_list_t*/ {unsigned int entsize;  // sizeof(struct _objc_method)unsigned int method_count;struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_TestCategoryProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_objc_method),1,{{(struct objc_selector *)"protocolMethod", "v16@0:8", 0}}
};// 分类中添加的类方法列表结构体
static struct /*_method_list_t*/ {unsigned int entsize;  // sizeof(struct _objc_method)unsigned int method_count;struct _objc_method method_list[1];
} _OBJC_PROTOCOL_CLASS_METHODS_TestCategoryProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_objc_method),1,{{(struct objc_selector *)"protocolClassMethod", "v16@0:8", 0}}
};// 协议结构体赋值,等等打包给分类
struct _protocol_t _OBJC_PROTOCOL_TestCategoryProtocol __attribute__ ((used)) = {0,"TestCategoryProtocol",(const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_TestCategoryProtocol,(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_TestCategoryProtocol,(const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_TestCategoryProtocol,0,0,0,sizeof(_protocol_t),0,(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_TestCategoryProtocol
};
// 将刚赋值完的结构体重命名
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_TestCategoryProtocol = &_OBJC_PROTOCOL_TestCategoryProtocol;// 将其再打包,封装成一个协议列表结构体,到时候赋值给该分类
static struct /*_protocol_list_t*/ {long protocol_count;  // Note, this is 32/64 bitstruct _protocol_t *super_protocols[1]; // 因为该分类只遵循了这一个协议,所以是1
} _OBJC_CATEGORY_PROTOCOLS_$_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {1,&_OBJC_PROTOCOL_TestCategoryProtocol //刚才打包封装的协议的结构体
};
  • 协议的封装逻辑还是挺清晰的,先是初始化赋值一个协议结构体,然后把这个结构体封装打包,然后再把打包的结构体赋给该分类中的协议列表。这样就算该分类中有多个协议,那么我们将其协议结构体初始化后,打包给该分类的协议列表就可以了。

2.5 分类的属性列表结构体

// 属性结构体
struct _prop_t {const char *name; // 属性名const char *attributes; // 属性的类型以及修饰符
};// 属性列表结构体
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_TestNSObject_$_TestCategoryNSObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_prop_t),1,{{"personName","T@\"NSString\",C,N"}} // 两个数据,一个属性名,一个属性类型及其修饰符
};
  • 从该源码可知,我们在分类中定义的属性,它会有一个属性列表来储存,但是分类中并没有成员变量结构体(_ivar_list_t结构体),它更不会自己生成其set/get方法。

这也说明了Category中不能添加成员变量这一事实。

分类结构总结

分类其实也是一个结构体,其中主要包含了以下内容:

  • _method_list_t 类型的【对象方法列表结构体】
  • _method_list_t 类型的【类方法列表结构体】
  • _protocol_list_t 类型的【协议列表结构体】
  • _prop_list_t 类型的【属性列表结构体】

注意:_category_t结构体中并不包含_ivar_list_t类型,也就是不包含【成员变量结构体】。

三、分类Category的源码分析

我们知道,Objective-C 的运行是依赖 Objective-C 的 Runtime 的,而 Objective-C 的 runtime 和其他系统库一样,是 OS X 和 iOS 通过 dyld 动态加载的。

那么分类到底是怎样将数据在运行期加载到本类上去呢,这就是我们接下来要看的了。

我们先从 runtime 的初始化函数_objc_init看起:

1._objc_init

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/void _objc_init(void)
{static bool initialized = false;if (initialized) return;initialized = true;// fixme defer initialization until an objc-using image is found?environ_init();tls_init();static_init();runtime_init();exception_init();
#if __OBJC2__cache_t::init();
#endif_imp_implementationWithBlock_init();_dyld_objc_notify_register(&map_images, load_images, unmap_image);#if __OBJC2__didCallDyldNotifyRegister = true;
#endif
}

其调用_dyld_objc_notify_register方法,传入map_images地址(方法地址或者函数地址),map_images读取资源(images代表资源模块):

2.map_images

void
map_images(unsigned count, const char * const paths[],const struct mach_header * const mhdrs[])
{bool takeEnforcementDisableFault;{mutex_locker_t lock(runtimeLock);map_images_nolock(count, paths, mhdrs, &takeEnforcementDisableFault);}if (takeEnforcementDisableFault) {#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATORbool objcModeNoFaults = DisableFaults|| DisableClassROFaults|| getpid() == 1|| is_root_ramdisk()|| !os_variant_has_internal_diagnostics("com.apple.obj-c");if (!objcModeNoFaults) {os_fault_with_payload(OS_REASON_LIBSYSTEM,OS_REASON_LIBSYSTEM_CODE_FAULT,NULL, 0,"class_ro_t enforcement disabled",0);}
#endif}
}

然后在其中调用map_images_nolock方法(这个方法太长了知道就行),在map_images_nolock方法中调用_read_images方法(镜像,加载一些模块),在_read_images函数中找到与分类相关的代码,加载分类信息(分类信息是个二维数组):

// Discover categories.
for (EACH_HEADER) {// 获取category列表category_t **catlist = _getObjc2CategoryList(hi, &count);bool hasClassProperties = hi->info()->hasCategoryClassProperties();// 便利category列表中的每一个categoryfor (i = 0; i < count; i++) {// 获得当前categorycategory_t *cat = catlist[i];// 获取该category的主类Class cls = remapClass(cat->cls);if (!cls) { // 没有主类,说明该分类有问题了catlist[i] = nil;if (PrintConnecting) {_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with ""missing weak-linked target class", cat->name, cat);}continue;}// 能到这就说明有主类bool classExists = NO;// 该category有 对象方法 || 协议 || 属性if (cat->instanceMethods ||  cat->protocols  ||  cat->instanceProperties) {// 将该分类中的 对象方法、协议、属性加到全局的分类列表中addUnattachedCategoryForClass(cat, cls, hi);if (cls->isRealized()) { // 主类中有实现// 将该分类中的 对象方法、协议、属性加到该类中的对应列表中remethodizeClass(cls);classExists = YES;}if (PrintConnecting) {_objc_inform("CLASS: found category -%s(%s) %s", cls->nameForLogging(), cat->name, classExists ? "on existing class" : "");}}// 该分类中有 类方法、协议、实现的类属性if (cat->classMethods  ||  cat->protocols  ||  (hasClassProperties && cat->_classProperties)) {// 将该分类中的 类方法、协议、实现的类属性加到全局的分类列表中addUnattachedCategoryForClass(cat, cls->ISA(), hi);if (cls->ISA()->isRealized()) { // 元类中有实现// 将该分类中的 类方法、协议、实现的类属性加到该类的元类中的对应列表中remethodizeClass(cls->ISA());}if (PrintConnecting) {_objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name);}}}
}
  • 1.获取category列表list
  • 2.遍历category list中的每一个category
  • 3.获取category的对应的主类cls,如果没有cls就跳过(continue)这个继续获取下一个
  • 4.如果其有对应的主类,并其有实例方法、协议、属性,则调用addUnattachedCategoryForClass,同时如果cls中有实现的话,进一步调用remethodizeClass方法
  • 5.如果其有对应的主类,并其有类方法、协议,则调用addUnattachedCategoryForClass,同时如果cls的元类有实现的话,就进一步调用remethodizeClass方法

第4、第5步主要是对类和元类对象相应方法的区分。

看一下其中调用的addUnattachedCategoryForClass的方法:

3.addUnattachedCategoryForClass

static void addUnattachedCategoryForClass(category_t *cat, Class cls, header_info *catHeader)
{runtimeLock.assertWriting();// DO NOT use cat->cls! cls may be cat->cls->isa instead// 获得一个全局的cats,是一个字典对象,key为cls,value为category_list *列表NXMapTable *cats = unattachedCategories();category_list *list;// 在全局的cats中查找有没有cls对应的category列表list = (category_list *)NXMapGet(cats, cls);if (!list) { // 如果没找到// 新开辟一块空间存储该category列表list = (category_list *)calloc(sizeof(*list) + sizeof(list->list[0]), 1);} else { // 找到了// 再多开辟一块空间,将传过来的category列表加入其中list = (category_list *)realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));}// 将cat和catHeader打包添加进来list->list[list->count++] = (locstamped_category_t){cat, catHeader};// 将我们传递过来的category以字典的方式存储,key为cls,value为传过来的分类列表NXMapInsert(cats, cls, list);
}// 返回一个全局的category字典
static NXMapTable *unattachedCategories(void)
{runtimeLock.assertWriting();//全局对象static NXMapTable *category_map = nil;if (category_map) return category_map;// fixme initial map sizecategory_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);return category_map;
}
  • 通过unattachedCategories()函数获得一个全局对象cats,key为cls,value为category_list *
  • 我们从这个全局字典中查找cls,获取一个category_list *list列表
  • 要是没有list列表,那么我们就生成一个category_list空间,将这组键(cls)值(cat、catHeader)对插入
  • 要是有list列表,我们就在该列表的基础上再分配出一个category_list大小的空间,将这组键(cls)值(cat、catHeader)对插入

等于说这个方法就是将我们分类中的方法添加到一个全局的map中,把类和category做一个关联映射,并且该map的keyclsvaluecategory_list *,即一个类对应一个分类列表,一个category_list *包含了很多分类category_t *,每个分类category_t *中又存储了该分类的各种数据(分类名、所属类、对象方法、类方法等等)。

4.remethodizeClass

static void remethodizeClass(Class cls)
{// 分类数组category_list *cats;bool isMeta;runtimeLock.assertWriting();// 该类是不是元类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);}
}

该方法只是得到该类的分类列表,然后将其给attachCategories方法。

5.attachCategories

static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{if (!cats) return;if (PrintReplacedMethods) printReplacements(cls, cats);bool isMeta = cls->isMetaClass();// 创建方法列表、属性列表、协议列表,用来存储分类列表中的所有方法、属性、协议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 firstint mcount = 0;           // 记录方法的数量int propcount = 0;        // 记录属性的数量int protocount = 0;       // 记录协议的数量int i = cats->count;      // 从分类数组最后开始遍历,保证先取的是最新的分类bool fromBundle = NO;     // 记录是否是从 bundle 中取的while (i--) { // 从后往前依次遍历auto& entry = cats->list[i];  // 取出当前分类// 取出分类中的方法列表。如果是元类,取得的是类方法列表;否则取得的是对象方法列表method_list_t *mlist = entry.cat->methodsForMeta(isMeta);if (mlist) { // 有方法列表mlists[mcount++] = mlist;            // 将方法列表放入 mlists 方法列表数组中fromBundle |= entry.hi->isBundle();  // 分类的头部信息中存储了是否是 bundle,将其记住}// 取出分类中的属性列表,如果是元类,取得的是 nilproperty_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi);if (proplist) { // 有属性列表proplists[propcount++] = proplist; // 将属性列表加到proplists属性列表数组中}// 取出分类中遵循的协议列表protocol_list_t *protolist = entry.cat->protocols;if (protolist) { // 有协议列表protolists[protocount++] = protolist; // 将协议列表加到protolists协议列表数组中}}// 上面的代码是将分类中存储的方法、属性、协议插入到各自对应的大数组中(方便等等插入对应的本类中的各种列表中),并且从后往前插,保证最终调用方法的时候会最先调用到最新的分类中的方法。// 取出当前类 cls 的 class_rw_t 数据auto rw = cls->data();// 将存储方法、属性、协议数组到本类的 rw 结构体中// 准备方法列表 mlists 中的方法,使用该方法实现本类中存储方法的序列化,因为方法的查找都是使用二分查找的,我们将其先排序再插入就到时候就不用麻烦了prepareMethodLists(cls, mlists, mcount, NO, fromBundle);// 将新方法列表添加到本类的 rw 的方法列表中rw->methods.attachLists(mlists, mcount);// 释放方法列表 mlistsfree(mlists);// 清除 cls 的缓存列表if (flush_caches  &&  mcount > 0) flushCaches(cls);// 将新属性列表添加到本类的 rw 的属性列表中rw->properties.attachLists(proplists, propcount);// 释放属性列表free(proplists);// 将新协议列表添加到本类的 rw 的协议列表中rw->protocols.attachLists(protolists, protocount);// 释放协议列表free(protolists);
}
  • 该方法主要就是将我们分类中的信息添加到本类的class_rw_t结构体中,就等于说是这个分类现在就没有作用了(也不能这样说,就是这个意思),分类中的方法、属性、协议都已经存储到本类中了,直接就能在本类调用了。

6. attachLists

attachLists方法保证新的数据添加到列表的前面:

void attachLists(List* const * addedLists, uint32_t addedCount) {if (addedCount == 0) return;if (hasArray()) { // 原列表中有很多数据,还要添加很多数据// many lists -> many listsuint32_t oldCount = array()->count; // 获取旧数据长度uint32_t newCount = oldCount + addedCount; // 得到更新后的数据长度setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); // 开辟新数据长度的空间array()->count = newCount; // 更新数据长度memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); // 将旧数据都往后移动新加数据的长度,保证旧数据在新数据的后面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 listsList* oldList = list; // 获取原列表uint32_t oldCount = oldList ? 1 : 0; // 如果原列表中有数据,其长度肯定为1,否则就为0uint32_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])); // 将新数据插在前面}
}

一图说明这个方法是在干什么:

那么它是怎么保证新数据一定在旧数据的前面呢?

// memmove :内存移动
void    *memmove(void *__dst, const void *__src, size_t __len);
/*__dst : 移动内存的目的地__src : 被移动的内存首地址__len : 被移动的内存长度将__src的内存移动__len块内存到__dst中
*/// memcpy :内存拷贝
void    *memcpy(void *__dst, const void *__src, size_t __n);
/*__dst : 拷贝内存的拷贝目的地__src : 被拷贝的内存首地址__n : 被移动的内存长度将__src的内存移动__n块内存到__dst中
*/

memmove之后:虽然本类的方法,属性,协议列表指针的地址会分别后移,但是本类的对应数组的指针依然指向原始位置:

memcpy后:原来指针并没有改变,至始至终指向开头的位置。并且经过memmove和memcpy方法之后,分类的方法,属性,协议列表被放在了类对象中原本存储的方法,属性,协议列表前面:

所以总体来说就是先将原方法移动到后面,再将分类方法拷贝进来。

  • array()->lists: 类对象原来的方法列表,属性列表,协议列表,比如Person中的那些方法等
  • addedLists:传入所有分类的方法列表,属性列表,协议列表,比如Person(Eat)、Person(Drink)中的那些方法等。

上面代码的作用就是通过memmove将原来的类找那个的方法、属性、协议列表分别进行后移,然后通过memcpy将传入的方法、属性、协议列表填充到开始的位置。

  • 分类的方法、属性、协议只是添加到原有类上,并没有将原有类的方法、属性、协议进行完全替换

    • 举个例子说明就是:假设原有类拥有 MethodA方法,分类也拥有 MethodA 方法,那> 么加载完分类之后,类的方法列表中会拥有两个 MethodA方法。
  • 分类的方法、属性、协议会添加到原有类的方法列表、属性列表、协议列表的最前面,而原有类的方法列表、属性列表、协议列表则被移动到了列表的后面
    • 因为运行时查找方法是顺着方法列表的顺序进行依次查找的,所以Category的方法会先被搜索到,然后直接指向,而原有类的方法则不被指向。这也是分类中的方法会覆盖掉原有类的方法最直接的原因。

总结一下分类是怎么实现方法添加的:

  • 首先通过分类的结构体存储各个分类中的方法、协议、属性
  • 然后在运行期时候调用相关方法,再将分类结构体中的内容都存入相关的全局category map中,以clskey,以category_list *列表为value
  • 最后再通过cls找到全局category map中对应的分类数据,把分类的数据序列化加入本类中的class_rw_t结构体中,并且保证新加的数据都是在旧数据的前面的

为什么分类不能添加成员变量?

这里需要说明一下,我们的分类为什么不能添加成员变量,这是因为分类结构中并没有成员变量列表的存储属性,分类在运行时被attach添加到类,是对rw的处理,在class_rw_t *rw中,并没有const ivar_list_t *ivars成员变量列表属性!!!这个成员变量列表属性是在class_ro_t *ro中处理的!所以我们不能在分类中添加成员变量,但是可以添加属性!!!

四、load方法和initialize方法

直接看结论吧,感觉没啥说的

1.load方法的调用顺序

load方法的调用时机,在runtime加载类、分类时由系统调用的。
每个类、分类的load在程序运行过程中只调用一次load,除非自己再手动调用load才会再次调用。

  • 先调用类的load方法

    • 按照编译的先后顺序调用(先编译的先调用)
    • 调用子类的load方法之前会先调用父类的load方法
  • 再调用分类的load方法
    • 按照编译的先后顺序调用(先编译的先调用)

系统调用load方法它是直接找到类的load方法的地址,然后调用load方法,然后再找到分类的load方法,再去调用。
而对于手动调用load,则通过msgSend方式,找到所属的类的元类对象,如果分类也实现了load,则调用分类的load,因为分类中的方法总是加载本类方法的前面。

对于“覆盖”的方法,会找到最后一个编译的方法,和我们上面理解的分类实现的方法一样。

2.initialize方法调用顺序

类第一次接收消息时,会调用initialize(初始化)。
通过msgSend调用。

  • 当父类没有initialize时,先调用父类的initialize,再调用当前类的initialize
  • 如果当前类没有实现initialize,则调用的是父类的initialize
  • 若多个子类未实现都未实现initialize方法,则父类的initialize方法会被调用多次
  • 当分类实现了initialize,会覆盖本类的initialize

并且我们在写initialize方法时,不需要调用父类的initialize方法,系统会自动调用,如果你在调用的话,就会多次调用分类的initialize方法了。

load和initialize区别

  • 调用时机
    load,当runtime加载类、分类时会调用。load方法总是在main函数之前调用,每个类、分类的load在运行时只调用一次
    initialize,在类第一次接收到消息时调用(先初始化父类,再初始化子类,每一个类只会被初始化一次)
  • 调用顺序
    load方法:先调用类的load,子类调用load方法之前会先调用父类的load,先编译的先调用;再调用分类的load方法,先编译的先调用
    initialize方法:先调用父类的initialize再调用当前类的initialize,如果子类没有实现initialize,则会调用父类的initialize;如果有分类,则调用最后编译的分类的initialize,就不调用本类的initialize了
  • 调用本质
    load,根据IMP地址直接调用(*load_method)(cls, SEL_load)
    initialize,通过objc_msgSend进行调用
  • 使用场景
    在load方法中实现方法交换(Method Swizzle)
    一般用于初始化全局变量或静态变量
  • 相同点
    两个方法会被自动调用,不需要手动调用他们
  • 区别
    load是通过直接函数地址调用,只会调用一次
    initialize是通过msgSend调用

    • 如果子类没有实现initialize,会调用父类的initialzie(所以父类的initialize会被调用很多次)
    • 分类如果实现了initialize,就会覆盖类本身的initailize

五、关联对象

上面也说了,分类中不可以添加实例变量,但是可以添加属性,但是属性不能自动生成set、get方法,那么我们要怎么实现在分类中添加属性呢,这就需要关联对象出马了!

1.关联对象的使用

关联对象也就只有三个接口方法,分别是objc_setAssociatedObjectobjc_getAssociatedObjectobjc_removeAssociatedObjects

1.1 objc_setAssociatedObject

OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy)OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

参数一:给哪个对象添加属性,如果要给自己添加属性,使用self就行了
参数二:设置关联对象的key,根据key获取关联对象的属性的值,就跟字典一样
参数三:关联的值,也就是我们想要存储的值
参数四:策略,属性以什么形式保存,类似于修饰符

策略有以下几种:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {OBJC_ASSOCIATION_ASSIGN = 0,           // 指定一个弱引用相关联对象OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关的对象的强引用,非原子性OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   // 指定相关的对象被复制,非原子性OBJC_ASSOCIATION_RETAIN = 01401,       // 指定相关的对象的强引用,原子性OBJC_ASSOCIATION_COPY = 01403          // 指定相关的对象被复制,原子性
};

1.2 objc_getAssociatedObject

OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

参数一:获取哪个对象里面的关联的属性
参数二:获取的是那个属性,字典的key,通过key去查找

1.3 objc_removeAssociatedObjects

OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

就一个参数,移除某个对象的全部关联对象。

举例:

我们给上面我们说的分类中的@property (nonatomic, copy) NSString *personName;添加关联对象:

- (void)setPersonName:(NSString *)personName {objc_setAssociatedObject(self, "personName", personName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}- (NSString *)personName {id temp = objc_getAssociatedObject(self, "personName");if (temp) {return temp;} else {return nil;}
}

我们通过personName作为该对象的key值,通过这个key值就可以存储和访问这个属性存储的值,这样我们也就实现了给分类中添加实例变量的功能了。

2.关联对象的底层结构

关联对象的核心技术有:

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

直接逐一分析:

2.1 AssociationsManager

class AssociationsManager {// associative references: object pointer -> PtrPtrHashMap.// AssociationsManager中只有一个变量AssociationsHashMapstatic AssociationsHashMap *_map; // 它才是核心,这个类只是对它的封装,它是个单例
public:// 构造函数中加锁AssociationsManager()   { AssociationsManagerLock.lock(); }// 析构函数中释放锁~AssociationsManager()  { AssociationsManagerLock.unlock(); }// 构造函数、析构函数中加锁、释放锁的操作,保证了AssociationsManager是线程安全的AssociationsHashMap &associations() {// AssociationsHashMap 的实现可以理解成单例对象if (_map == NULL)_map = new AssociationsHashMap();return *_map;}
};

2.2 AssociationsHashMap

class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {public:void *operator new(size_t n) { return ::malloc(n); }void operator delete(void *ptr) { ::free(ptr); }
};

我们发现这个玩意是继承自unordered_map,也就是说,他也是一个map,并且keydisguised_ptr_tvalueObjectAssociationMap *

2.3 ObjectAssociationMap

// ObjectAssociationMap是字典,key是从外面传过来的key,例如@selector(hello),value是关联对象,也就是
// ObjectAssociation
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {public:void *operator new(size_t n) { return ::malloc(n); }void operator delete(void *ptr) { ::free(ptr); }
};

我们发现这个也是一个map,其中keyvoid *valueObjcAssociation

2.4 ObjcAssociation

class ObjcAssociation {uintptr_t _policy;// 值id _value;
public:// 构造函数ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}// 默认构造函数,参数分别为0和nilObjcAssociation() : _policy(0), _value(nil) {}uintptr_t policy() const { return _policy; }id value() const { return _value; }bool hasValue() { return _value != nil; }
};

来了来了,终于找到值了,它才是真正存储关联对象值的地方,其中_value存储的是关联对象的值,_policy存储的是关联对象的策略。

那接下来我们使用图来总结一下关联对象的结构(这个图真的是太清晰了):

通过对上述结构的了解,我们发现:

  • 关联对象并不是存储在被关联对象本身内存中
  • 关联对象存储在全局的统一的一个AssociationsManager
  • object作为key,一个被关联对象的所有关联对象都存储在同一个ObjectAssociationMap
  • object被关联的对象不能为nil
  • 设置关联对象valuenil,就相当于是移除关联对象
  • 关联对象巧妙的机构:
    • 它是通过两层map来存储我们要关联的对象的
    • 第一层就是通过一个类obj作为key,获取这个类的所有关联对象

      如果有很多类都有关联对象,那么我们就可以通过不同的object来访问各个类中设置的关联对象。

    • 第二层就是这个类中,我们自己设置的字符串或者什么的作为key,获取这个对象的值

      如果该类有很多关联对象,那么我们又可以通过我们自己设置的不同的key值,来访问我们存储的值

分析完了关联对象的底层结构,我们在来看看它的三种方法:

3.关联对象的底层原理

3.1 objc_setAssociatedObject

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {_object_set_associative_reference(object, (void *)key, value, policy);
}

它调用了_object_set_associative_reference这个方法:

// 该方法完成了设置关联对象的操作
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {// retain the new value (if any) outside the lock.// 初始化空的ObjcAssociation(关联对象)ObjcAssociation old_association(0, nil);// 如果value存在,就将value和policy进行打包,方便后续的存储id new_value = value ? acquireValue(value, policy) : nil;{// 初始化一个managerAssociationsManager manager;// 获取其中的单例AssociationsHashMap,现在就有了全局的关联对象表AssociationsHashMap &associations(manager.associations());// 获取对象的DISGUISE值,作为AssociationsHashMap的keydisguised_ptr_t disguised_object = DISGUISE(object);if (new_value) { // 如果封装后的value有值,不为nil// break any existing association.// AssociationsHashMap::iterator 类型的迭代器// 以DISGUISE值为key,找这个类的关联对象mapAssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) { // 没到end,就说明找到了,即存在这个object的关联对象map// secondary table exists// 获取到ObjectAssociationMap(key是外部传来的key,value是关联对象类ObjcAssociation)ObjectAssociationMap *refs = i->second;// ObjectAssociationMap::iterator 类型的迭代器// 再以我们自己定义并传进来的key作为key,查找我们保存的对象值ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) { // 如果没到end,即找到了,就说明之前有存储过// 那么就将原关联对象的值存起来,并且赋新值old_association = j->second;j->second = ObjcAssociation(policy, new_value);} else { // 如果到end了,即没找到,就说明之前没有存储过// 无该key对应的关联对象,直接赋值保存即可// ObjcAssociation(policy, new_value)提供了这样的构造函数(*refs)[key] = ObjcAssociation(policy, new_value);}} else { // 即第一层遍历到end了,通过object找不到对应的关联对象map// create the new association (first time).// 执行到这里,说明该对象是第一次添加关联对象,直接新建添加就完事了// 初始化ObjectAssociationMapObjectAssociationMap *refs = new ObjectAssociationMap;// 第一层map赋值associations[disguised_object] = refs;// 第二层map赋值(*refs)[key] = ObjcAssociation(policy, new_value);// 设置该对象的有关联对象,调用的是setHasAssociatedObjects()方法object->setHasAssociatedObjects();}} else { // 如果封装后的value为nil// setting the association to nil breaks the association.// value无值,也就是释放一个key对应的关联对象// 通过迭代器找对应的map,找不到就说明原来也没有,也就不用管了AssociationsHashMap::iterator i = associations.find(disguised_object);if (i !=  associations.end()) { // 找到了// 获取第二层mapObjectAssociationMap *refs = i->second;// 通过key在第二层中找到对应的关联对象的值,没找到也就不管了,因为本来也就是要删除这个关联对象的ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) { // 找到了// 先将旧值保存下来old_association = j->second;// 调用erase()方法删除对应的关联对象refs->erase(j);}}}}// release the old value (outside of the lock).// 统一释放旧的关联对象if (old_association.hasValue()) ReleaseValue()(old_association);
}

首先根据我们传入的value经过acquireValue函数处理获取new_valueacquireValue函数内部其实时通过对策略的判断返回不同的值:

// 根据policy的值,对value进行retain或者copy
static id acquireValue(id value, uintptr_t policy) {switch (policy & 0xFF) {case OBJC_ASSOCIATION_SETTER_RETAIN:return objc_retain(value);case OBJC_ASSOCIATION_SETTER_COPY:return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);}return value;
}

之后创建AssociationsManager manager,以及拿到Manager内部的AssociationsHashMap,对应代码就是associations

AssociationsHashMap &associations() {// AssociationsHashMap 的实现可以理解成单例对象if (_map == NULL)_map = new AssociationsHashMap();return *_map;
}

之后我们看到传入的第一个参数objectobject经过DISGUISE函数被转换成了disguised_ptr_t类型的disguised_object

typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }

这里只对其做了简单的编码处理,直接取反了。

3.2 objc_getAssociatedObject

// 获取关联对象的方法
id objc_getAssociatedObject(id object, const void *key) {return _object_get_associative_reference(object, (void *)key);
}

其中调用了_object_get_associative_reference方法:

// 获取关联对象
id _object_get_associative_reference(id object, void *key) {id value = nil; // 用于保存等等获取的值uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; // 用于保存等等获取的策略{// 初始化一个AssociationsManagerAssociationsManager manager;// 获取到manager中的单例AssociationsHashMapAssociationsHashMap &associations(manager.associations());// 获取对象的DISGUISE值disguised_ptr_t disguised_object = DISGUISE(object);// 通过对象的DISGUISE值查找对象对应的mapAssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) { // 找到了// 获取ObjectAssociationMap,即获取第二层mapObjectAssociationMap *refs = i->second;// 在第二层map中通过key查找准确的值ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) { // 找到了// 获取到关联对象ObjcAssociationObjcAssociation &entry = j->second;// 获取到valuevalue = entry.value();// 获取到policypolicy = entry.policy();// 通过相应的策略返回相对应的修饰属性if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {objc_retain(value);}}}}// 通过相应的策略返回相对应的修饰属性if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {objc_autorelease(value);}// 返回关联对像的值return value;
}

很简单,就是通过传入的objectkey进行两层map的查找,找到了最终再根据策略返回就是了。

3.3 objc_removeAssociatedObjects

// 移除对象object的所有关联对象
void objc_removeAssociatedObjects(id object)
{// 判断对象是否有关联对象,有关联对象才会执行接下来的移除操作if (object && object->hasAssociatedObjects()) {_object_remove_assocations(object);}
}

它调用了_object_remove_assocations方法:

// 移除对象object的所有关联对象
void _object_remove_assocations(id object) {// 声明了一个vector,用于之后的便利删除,它也是两层结构vector<ObjcAssociation, ObjcAllocator<ObjcAssociation>> elements;{    // 初始化AssociationsManagerAssociationsManager manager;// 获取其中的单例mapAssociationsHashMap &associations(manager.associations());// 如果map size为空,直接返回if (associations.size() == 0) return;// 获取对象的DISGUISE值disguised_ptr_t disguised_object = DISGUISE(object);// 通过对象的DISGUISE进行查找AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) { // 找到了// copy all of the associations that need to be removed.// 获取第二层mapObjectAssociationMap *refs = i->second;// 将第二层map中的内容遍历加入到刚才定义的数组中for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {elements.push_back(j->second);}// remove the secondary table.// 删除这个第二层的mapdelete refs;// 释放第一层的对应键值对associations.erase(i);}}// the calls to releaseValue() happen outside of the lock.// 遍历删除for_each(elements.begin(), elements.end(), ReleaseValue());
}

到这里就结束了,重点还是那张图,把那张图看懂了,关联对象不轻轻松松学会了。

【iOS】——分类、扩展和关联对象相关推荐

  1. 【iOS】—— 分类、扩展和关联对象

    分类.扩展和关联对象 一.分类与扩展的区别 1.`category`类别(分类) 2.`extension`(类扩展) 二.关联对象的实现 1.关联对象的实现步骤: 2.关联对象分析: 三.关联对象- ...

  2. 【iOS】—— 分类,扩展和关联对象

    分类,扩展和关联对象 文章目录 分类,扩展和关联对象 分类和扩展 分类概念 扩展概念 两者区别 分类的实质 关联对象 通过关联对象给分类添加属性 关联对象的用处 关联对象的API 给声明的属性添加se ...

  3. iOS Runtime特性之关联对象

    前言 现在你准备用一个系统的类或者是你写的类,但是这个类并不能满足你的需求,你需要额外添加一个属性. 一般解决办法要么是extends(继承),要么使用category(类别). 而我并不推荐使用ex ...

  4. [iOS]-Category、Extension和关联对象

    目录: 参考的博客: 前言 一.Category分类 Extension扩展 Category的实质 Category结构体 将分类转成C++看起 对象方法列表结构体 类方法列表结构体 协议列表结构体 ...

  5. [OC学习笔记]分类和关联对象源码解析

    我们平时在开发的时候经常会使用分类来添加方法.协议.属性,但在添加属性的时候属性是不会自动生成成员变量的,这时候我们就需要关联对象来动态存储属性值. 分类 @interface NSObject(St ...

  6. 【iOS高级资深工程师面试篇】②、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 Objective-C语言特性部分1/2 分类-关联对象-扩展-代理

    iOS高级资深工程师面试篇系列 - 已更新3篇 UI部分1/3 -UITableView-事件传递&视图响应 UI部分2/3 -图像显示原理-UI卡顿&掉帧 UI部分3/3 -UIVi ...

  7. 【iOS开发】——Category底层原理、Extension、关联对象

    [iOS开发]--Category底层原理.Extension.关联对象 Category是什么?它可以用来干什么? Category特点 Category的实质以及实现过程 Category结构体 ...

  8. iOS runtime实战应用:关联对象

    在开始之前建议先阅读iOS runtime的基础理解篇:iOS内功篇:runtime 有筒子在面试的时候,遇到这样一个问题:"如何給NSArray添加一个属性(不能使用继承)",筒 ...

  9. iOS Category 添加属性实现原理 - 关联对象

    iOS Category 添加属性实现原理 - 关联对象 RunTime为Category动态关联对象 使用RunTime给系统的类添加属性,首先需要了解对象与属性的关系.对象一开始初始化的时候其属性 ...

最新文章

  1. spring beans源码解读之 ioc容器之始祖--DefaultListableBeanFactory
  2. Codeforces 1091E
  3. 企业微信H5_集成消息解密类,消息推送Get及Post回调处理
  4. spring整合hibernate初步
  5. Ettus Research USRP B200/B210 simple case
  6. Python 数据结构与算法 —— 链表
  7. 【07月04日】指数估值排名
  8. 如何在Outlook上正确设置雅虎邮箱
  9. 电脑文件误删除如何恢复?
  10. mysql查询高于平均_查询成绩高于平均分的成绩记录。
  11. 跟读 播放器 android,安卓手机英语学习利器 android 英语复读 跟读 练听力 练口语...
  12. 灰狼算法优化测试函数branin,测试函数的100种启发式算法求解方法之19
  13. 解决k8s中的长连接负载均衡问题
  14. [推荐系统] SVD、FunkSVD、BiasSVD和SVD++
  15. 组播PIM-原理介绍+报文分析+配置示例
  16. 移动UI 设计有哪些色彩级别
  17. 在清凉客厅,和飞利浦Fidelio B95家庭影院一起感受炙热好声音
  18. gsl科学计算库文档,翻译了索引,凑合看看。
  19. 与普通仓库相比,自动化立体库的优缺点
  20. ssd固态硬盘使用寿命怎么样?

热门文章

  1. 我手写了个SLAM算法!
  2. 流媒体技术基础-流媒体服务与框架
  3. Git 如何回到过去,然后 再 回到将来
  4. 嵩天-Python语言程序设计程序题--第五周:函数和代码复用
  5. 投资利润率、内部收益率和回收期案例
  6. 点到直线的距离计算原理及MATLAB程序
  7. 树莓派linux系统配置AODV协议,linux上模拟AODV路由协议 下面一些信息求各路大神解释!!...
  8. MTK6592平台 touchpanel驱动设备加载(一)
  9. Java | 第一章:Java环境搭建
  10. [附源码]计算机毕业设计Python+uniapp家政服务系统小程序7na26(程序+lw+远程部署)