经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS底层原理(三)Category
来源:cnblogs  作者:FunkyRay  时间:2021/4/12 9:50:13  对本文有异议

Category的本质

Category的底层结构

1.我们先给Person增加一个Person+Eat的分类

  1. @interface Person (Eat) <NSCopying, NSCoding>
  2. - (void)eat;
  3. @property (assign, nonatomic) int weight;
  4. @property (assign, nonatomic) double height;
  5. @end
  6. @implementation Person (Eat)
  7. - (void)eat
  8. {
  9. NSLog(@"eat");
  10. }
  11. - (void)eat1
  12. {
  13. NSLog(@"eat1");
  14. }
  15. + (void)eat2
  16. {
  17. }
  18. + (void)eat3
  19. {
  20. }
  21. @end

2.然后通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Eat.m转换成Person+Eat.cpp文件,发现内部会生成一个_category_t类型的结构体

  1. struct _category_t {
  2. const char *name; // 类名
  3. struct _class_t *cls;
  4. const struct _method_list_t *instance_methods; // 对象方法
  5. const struct _method_list_t *class_methods; // 类方法
  6. const struct _protocol_list_t *protocols; // 协议列表
  7. const struct _prop_list_t *properties; // 属性列表
  8. };

3.我们还发现会生成一个_category_t结构体类型的变量,这个变量对应着该分类文件是Person+Eat,并且里面记录着所有的分类信息

  1. // 变量名对应着分类文件名
  2. static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
  3. {
  4. "Person", // 类名
  5. 0, // cls
  6. (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat, // 对象方法
  7. (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat, // 类方法
  8. (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat, // 协议列表
  9. (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Eat, // 属性列表
  10. };

4._OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat这个变量里面记录着分类的对象方法eateat1

  1. static struct /*_method_list_t*/ {
  2. unsigned int entsize; // sizeof(struct _objc_method)
  3. unsigned int method_count;
  4. struct _objc_method method_list[2];
  5. } _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  6. sizeof(_objc_method),
  7. 2,
  8. {{(struct objc_selector *)"eat", "v16@0:8", (void *)_I_Person_Eat_eat},
  9. {(struct objc_selector *)"eat1", "v16@0:8", (void *)_I_Person_Eat_eat1}}
  10. };

5._OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat这个变量里面记录着分类的类方法eat2eat3

  1. static struct /*_method_list_t*/ {
  2. unsigned int entsize; // sizeof(struct _objc_method)
  3. unsigned int method_count;
  4. struct _objc_method method_list[2];
  5. } _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  6. sizeof(_objc_method),
  7. 2,
  8. {{(struct objc_selector *)"eat2", "v16@0:8", (void *)_C_Person_Eat_eat2},
  9. {(struct objc_selector *)"eat3", "v16@0:8", (void *)_C_Person_Eat_eat3}}
  10. };

6. _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat这个变量里面记录着NSCopyingNSCoding两个协议

  1. static struct /*_protocol_list_t*/ {
  2. long protocol_count; // Note, this is 32/64 bit
  3. struct _protocol_t *super_protocols[2];
  4. } _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  5. 2,
  6. &_OBJC_PROTOCOL_NSCopying,
  7. &_OBJC_PROTOCOL_NSCoding
  8. };

7._OBJC_$_PROP_LIST_Person_$_Eat这个变量里面记录着属性weightheight

  1. static struct /*_prop_list_t*/ {
  2. unsigned int entsize; // sizeof(struct _prop_t)
  3. unsigned int count_of_properties;
  4. struct _prop_t prop_list[2];
  5. } _OBJC_$_PROP_LIST_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  6. sizeof(_prop_t),
  7. 2,
  8. {{"weight","Ti,N"},
  9. {"height","Td,N"}}
  10. // Ti,N和Td,N对应着int和double两个类型
  11. };

Category的加载处理过程

1.通过分析查找到objc-rumtime-new.mm文件里的attachCategories函数,将分类文件里的数据信息都附加到对应的类对象或者元类对象里,详细代码如下

  1. // 附加上分类的核心操作
  2. // cls:类对象或者元类对象,cats_list:分类列表
  3. static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
  4. int flags)
  5. {
  6. if (slowpath(PrintReplacedMethods)) {
  7. printReplacements(cls, cats_list, cats_count);
  8. }
  9. if (slowpath(PrintConnecting)) {
  10. _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
  11. cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
  12. cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
  13. }
  14. // 先分配固定内存空间来存放方法列表、属性列表和协议列表
  15. constexpr uint32_t ATTACH_BUFSIZ = 64;
  16. method_list_t *mlists[ATTACH_BUFSIZ];
  17. property_list_t *proplists[ATTACH_BUFSIZ];
  18. protocol_list_t *protolists[ATTACH_BUFSIZ];
  19. uint32_t mcount = 0;
  20. uint32_t propcount = 0;
  21. uint32_t protocount = 0;
  22. bool fromBundle = NO;
  23. // 判断是否为元类
  24. bool isMeta = (flags & ATTACH_METACLASS);
  25. auto rwe = cls->data()->extAllocIfNeeded();
  26. for (uint32_t i = 0; i < cats_count; i++) {
  27. // 取出某个分类
  28. auto& entry = cats_list[i];
  29. // entry.cat就是category_t *cat
  30. // 根据isMeta属性取出每一个分类的类方法列表或者对象方法列表
  31. method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
  32. // 如果有方法则添加mlist数组到mlists这个大的方法数组中
  33. // mlists是一个二维数组:[[method_t, method_t, ....], [method_t, method_t, ....]]
  34. if (mlist) {
  35. if (mcount == ATTACH_BUFSIZ) {
  36. prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
  37. rwe->methods.attachLists(mlists, mcount);
  38. mcount = 0;
  39. }
  40. // 将分类列表里先取出来的分类方法列表放到大数组mlists的最后面(ATTACH_BUFSIZ - ++mcount),所以最后编译的分类方法列表会放在整个方法列表大数组的最前面
  41. mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
  42. fromBundle |= entry.hi->isBundle();
  43. }
  44. // 同上面一样取出的是分类中的属性列表proplist加到大数组proplists中
  45. // proplists是一个二维数组:[[property_t, property_t, ....], [property_t, property_t, ....]]
  46. property_list_t *proplist =
  47. entry.cat->propertiesForMeta(isMeta, entry.hi);
  48. if (proplist) {
  49. if (propcount == ATTACH_BUFSIZ) {
  50. rwe->properties.attachLists(proplists, propcount);
  51. propcount = 0;
  52. }
  53. proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
  54. }
  55. // 同上面一样取出的是分类中的协议列表protolist加到大数组protolists中
  56. // protolists是一个二维数组:[[protocol_ref_t, protocol_ref_t, ....], [protocol_ref_t, protocol_ref_t, ....]]
  57. protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
  58. if (protolist) {
  59. if (protocount == ATTACH_BUFSIZ) {
  60. rwe->protocols.attachLists(protolists, protocount);
  61. protocount = 0;
  62. }
  63. protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
  64. }
  65. }
  66. if (mcount > 0) {
  67. prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
  68. NO, fromBundle, __func__);
  69. // 将分类的所有对象方法或者类方法,都附加到类对象或者元类对象的方法列表中
  70. rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
  71. if (flags & ATTACH_EXISTING) {
  72. flushCaches(cls, __func__, [](Class c){
  73. return !c->cache.isConstantOptimizedCache();
  74. });
  75. }
  76. }
  77. // 将分类的所有属性附加到类对象的属性列表中
  78. rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
  79. // 将分类的所有协议附加到类对象的协议列表中
  80. rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
  81. }

2.上述的每步操作都会调用attachLists方法来进行元素分配,详细代码如下

  1. void attachLists(List* const * addedLists, uint32_t addedCount) {
  2. if (addedCount == 0) return;
  3. if (hasArray()) {
  4. // 获取原本的个数
  5. uint32_t oldCount = array()->count;
  6. // 最新的个数 = 原本的个数 + 新添加的个数
  7. uint32_t newCount = oldCount + addedCount;
  8. // 重新分配内存
  9. array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
  10. // 将新数组的个数改为最新的个数
  11. newArray->count = newCount;
  12. // 将旧数组的个数改为最新的个数
  13. array()->count = newCount;
  14. // 递减遍历,将旧数组里的元素从后往前的依次放到新数组里
  15. for (int i = oldCount - 1; i >= 0; i--)
  16. newArray->lists[i + addedCount] = array()->lists[i];
  17. // 将新增加的元素从前往后的依次放到新数组里
  18. for (unsigned i = 0; i < addedCount; i++)
  19. newArray->lists[i] = addedLists[i];
  20. // 释放旧数组数据
  21. free(array());
  22. // 赋值新数组数据
  23. setArray(newArray);
  24. validate();
  25. }
  26. else if (!list && addedCount == 1) {
  27. // 0 lists -> 1 list
  28. list = addedLists[0];
  29. validate();
  30. }
  31. else { .... }
  32. }

总结

  • 编译时
    • 每一个Category都会生成一个_category_t结构体对象,记录着所有的属性、方法和协议信息
  • 运行时
    • 通过Runtime加载某个类的所有Category数据
    • 把所有Category的对象方法、类方法、属性、协议数据,分别合并到一个二维数组中,并且后面参与编译的Category数据,会在数组的前面
    • 将合并后的Category数据(方法、属性、协议),插入到类原来数据的前面

面试题

1.如果几个分类中都有同样的方法,会调用哪个,调用顺序是什么

  • 有分类会先调用分类的方法,如果多个分类都有相同的方法,那么会根据编译顺序来决定执行哪个分类的方法,后参与编译的分类方法会放到整个方法列表数组的最前面,到时调用会遍历所有的方法列表数组,先找到的分类方法列表先执行。
  • 在Xcode中查看编译顺序:Build Phases->Compile Sources
  • 分类里面相同的方法会覆盖类原本的方法这种说法是错误的,根本就没有覆盖,只是最先遍历找到哪个就执行哪个,不存在覆盖的概念

2. Class Extension和Category的实现是一样的吗

  • 不一样。
  • 类扩展只是将.h文件中的声明放到.m中作为私有来使用,编译时就已经合并到该类中了。
  • 分类中的声明都是公开的,而且是利用运行时机制在程序运行时将分类里的数据合并到类中

3.Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

  • load方法
  • load方法Runtime加载类、分类的时候调用,而且只调用一次
  • load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用

4.load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?

load方法

  • load方法会在Runtime加载类、分类时调用- 每个类、分类的load,在程序运行过程中只调用一次
源码分析

1.在objc-runtime-new.mmload_images方法可以发现准备处理load方法和调用load方法的函数

  1. void load_images(const char *path __unused, const struct mach_header *mh) {
  2. ....
  3. {
  4. mutex_locker_t lock2(runtimeLock);
  5. // 准备load方法
  6. prepare_load_methods((const headerType *)mh);
  7. }
  8. // 调用load方法
  9. call_load_methods();
  10. }

2.在prepare_load_methods中发现,调用load方法前的类的处理和分类的处理

  1. void prepare_load_methods(const headerType *mhdr) {
  2. size_t count, i;
  3. runtimeLock.assertLocked();
  4. // 按编译顺序拿到所有的类的list
  5. classref_t const *classlist =
  6. _getObjc2NonlazyClassList(mhdr, &count);
  7. // 按添加进数组的顺序遍历处理类的列表
  8. for (i = 0; i < count; i++) {
  9. schedule_class_load(remapClass(classlist[i]));
  10. }
  11. // 按编译顺序拿到所有的分类列表
  12. category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
  13. for (i = 0; i < count; i++) {
  14. category_t *cat = categorylist[i];
  15. Class cls = remapClass(cat->cls);
  16. ....
  17. // 按顺序直接添加
  18. add_category_to_loadable_list(cat);
  19. }
  20. }

3.递归调用schedule_class_load方法来优先添加父类放到列表中,然后再添加当前类,所以执行调用时肯定先执行父类的load方法

  1. static void schedule_class_load(Class cls) {
  2. if (!cls) return;
  3. ASSERT(cls->isRealized()); // _read_images should realize
  4. if (cls->data()->flags & RW_LOADED) return;
  5. // 递归调用,找到传进来的类的父类添加到列表中
  6. schedule_class_load(cls->getSuperclass());
  7. // 然后再调用当前传进来的类添加到列表中,所以父类肯定是在前面
  8. add_class_to_loadable_list(cls);
  9. cls->setInfo(RW_LOADED);
  10. }

4.在objc-loadmethod.mmcall_load_methods方法可以发现,程序运行时会先调用类的load方法,然后调用分类的load方法,详细代码如下

  1. void call_load_methods(void)
  2. {
  3. static bool loading = NO;
  4. bool more_categories;
  5. loadMethodLock.assertLocked();
  6. // Re-entrant calls do nothing; the outermost call will finish the job.
  7. if (loading) return;
  8. loading = YES;
  9. void *po ol = objc_autoreleasePoolPush();
  10. do {
  11. // 1. 优先调用类的load方法
  12. while (loadable_classes_used > 0) {
  13. call_class_loads();
  14. }
  15. // 2. 只调用一次分类的load方法
  16. more_categories = call_category_loads();
  17. // 3. Run more +loads if there are classes OR more untried categories
  18. } while (loadable_classes_used > 0 || more_categories);
  19. objc_autoreleasePoolPop(pool);
  20. loading = NO;
  21. }

5.找到每个类的load方法的内存地址,然后直接调用

  1. static void call_class_loads(void) {
  2. int i;
  3. // Detach current loadable list.
  4. struct loadable_class *classes = loadable_classes;
  5. int used = loadable_classes_used;
  6. loadable_classes = nil;
  7. loadable_classes_allocated = 0;
  8. loadable_classes_used = 0;
  9. // Call all +loads for the detached list.
  10. for (i = 0; i < used; i++) {
  11. Class cls = classes[i].cls;
  12. // 取出类里面的load方法
  13. // load_method_t:指向函数地址的指针
  14. // 这里的method对应的结构体为loadable_class
  15. load_method_t load_method = (load_method_t)classes[i].method;
  16. if (!cls) continue;
  17. if (PrintLoading) {
  18. _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
  19. }
  20. // 直接调用load方法
  21. (*load_method)(cls, @selector(load));
  22. }
  23. // Destroy the detached list.
  24. if (classes) free(classes);
  25. }
  26. // 找到的method对应的结构体
  27. struct loadable_class {
  28. Class cls; // may be nil
  29. IMP method; // 这个就是load方法
  30. };

6.找到每个分类的load方法的内存地址,然后直接调用

  1. static bool call_category_loads(void) {
  2. int i, shift;
  3. bool new_categories_added = NO;
  4. // Detach current loadable list.
  5. struct loadable_category *cats = loadable_categories;
  6. int used = loadable_categories_used;
  7. int allocated = loadable_categories_allocated;
  8. loadable_categories = nil;
  9. loadable_categories_allocated = 0;
  10. loadable_categories_used = 0;
  11. // Call all +loads for the detached list.
  12. for (i = 0; i < used; i++) {
  13. Category cat = cats[i].cat;
  14. // 取出每一个分类里的load方法
  15. // 这里的method对应的结构体为loadable_category
  16. load_method_t load_method = (load_method_t)cats[i].method;
  17. Class cls;
  18. if (!cat) continue;
  19. cls = _category_getClass(cat);
  20. if (cls && cls->isLoadable()) {
  21. if (PrintLoading) {
  22. _objc_inform("LOAD: +[%s(%s) load]\n",
  23. cls->nameForLogging(),
  24. _category_getName(cat));
  25. }
  26. // 直接调用load方法
  27. (*load_method)(cls, @selector(load));
  28. cats[i].cat = nil;
  29. }
  30. }
  31. ....
  32. }
  33. // 找到的method对应的结构体
  34. struct loadable_category {
  35. Category cat; // may be nil
  36. IMP method; // 这个方法就是load方法
  37. };
调用顺序
  • 先调用类的load
    • 按照编译先后顺序调用(先编译,先调用)
    • 调用子类的load之前会先调用父类的load
  • 再调用分类的load
    • 按照编译先后顺序调用(先编译,先调用)
load方法系统调用和主动调用的区别
  • 系统调用load方法调用是直接找到类和分类中的方法的内存地址直接调用
  • 主动调用load方法是通过消息机制来发送消息的,会在对应的消息列表里按顺序遍历一层层查找,找到就调用

initialize方法

initialize方法会在类第一次接收到消息时调用

源码分析

1.由于在调用到这个类的时候才会执行initialize方法,那么说明是在发消息过程中来执行的,我们在objc-runtime-new.mm中调用class_getInstanceMethod或者class_getClassMethod方法,详细代码如下

  1. Method class_getClassMethod(Class cls, SEL sel)
  2. {
  3. if (!cls || !sel) return nil;
  4. return class_getInstanceMethod(cls->getMeta(), sel);
  5. }
  6. Method class_getInstanceMethod(Class cls, SEL sel)
  7. {
  8. if (!cls || !sel) return nil;
  9. lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
  10. return _class_getMethod(cls, sel);
  11. }

2.一步步调用最后找到initializeNonMetaClass函数,先递归找到父类调用initialize方法,然后当前类调用initialize方法。可以在callInitialize里发现本质都是通过Runtime的消息机制进行的发送

  1. void initializeNonMetaClass(Class cls) {
  2. ASSERT(!cls->isMetaClass());
  3. Class supercls;
  4. bool reallyInitialize = NO;
  5. // 如果存在父类,并且没有初始化父类,就去初始化父类
  6. supercls = cls->getSuperclass();
  7. if (supercls && !supercls->isInitialized()) {
  8. initializeNonMetaClass(supercls);
  9. }
  10. SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
  11. {
  12. monitor_locker_t lock(classInitLock);
  13. if (!cls->isInitialized() && !cls->isInitializing()) {
  14. // 未初始化的类通过setInitializing做了标记,下次就不会再调用了
  15. cls->setInitializing();
  16. reallyInitialize = YES;
  17. // Grab a copy of the will-initialize funcs with the lock held.
  18. localWillInitializeFuncs.initFrom(willInitializeFuncs);
  19. }
  20. }
  21. ....
  22. {
  23. // 调用初始化
  24. callInitialize(cls);
  25. ....
  26. }
  27. ....
  28. }
  29. // 调用initialize的函数
  30. void callInitialize(Class cls) {
  31. // 通过runtime消息机制发送initialize消息
  32. ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
  33. asm("");
  34. }
调用顺序
  • 先调用父类的initialize,再调用子类的initialize
  • (先初始化父类,再初始化子类,每个类只会初始化1次)
initialize和load的区别
  • initialize是通过objc_msgSend进行调用的,而load找到函数地址直接调用的
  • 如果子类没有实现initialize,会调用父类的initialize
    • 所以父类的initialize可能会被调用多次,第一次是系统通过消息发送机制调用的父类initialize,后面多次的调用都是因为子类没有实现initialize,而通过superclass找到父类再次调用的
  • 如果分类实现了initialize,就覆盖类本身的initialize调用

5.Category能否添加成员变量?如果可以,如何给Category添加成员变量?

不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果

实现方案

通过objc_setAssociatedObject设置关联对象来实现

  1. @interface Person (Test)
  2. @property (copy, nonatomic) NSString *name;
  3. @property (assign, nonatomic) int weight;
  4. @end
  5. @implementation Person (Test)
  6. - (void)setName:(NSString *)name
  7. {
  8. /*
  9. * object:关联的对象
  10. * value:关联的值
  11. * objc_AssociationPolicy:关联策略
  12. */
  13. objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
  14. }
  15. - (NSString *)name
  16. {
  17. // 隐式参数
  18. // _cmd == @selector(name)
  19. return objc_getAssociatedObject(self, _cmd);
  20. }
  21. - (void)setWeight:(int)weight
  22. {
  23. objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  24. }
  25. - (int)weight
  26. {
  27. // _cmd == @selector(weight)
  28. return [objc_getAssociatedObject(self, _cmd) intValue];
  29. }
  30. @end
实现源码分析

1.在objc-references.mm中我们可以看到objc_setAssociatedObject的实现代码如下

  1. void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) {
  2. if (!object && !value) return;
  3. if (object->getIsa()->forbidsAssociatedObjects())
  4. _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
  5. // 通过传进来的object生成一个key
  6. DisguisedPtr<objc_object> disguised{(objc_object *)object};
  7. ObjcAssociation association{policy, value};
  8. // retain the new value (if any) outside the lock.
  9. association.acquireValue();
  10. bool isFirstAssociation = false;
  11. {
  12. AssociationsManager manager;
  13. // 取出AssociationsManager里的AssociationsHashMap这个属性
  14. AssociationsHashMap &associations(manager.get());
  15. // 如果value有值
  16. if (value) {
  17. // 根据传进来的object key的值disguised取出ObjectAssociationMap
  18. auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
  19. if (refs_result.second) {
  20. /* it's the first association we make */
  21. isFirstAssociation = true;
  22. }
  23. // 根据refs_result的key存放进association
  24. // association就是ObjcAssociation
  25. // 总之就是对应的每一个key一层层的赋值
  26. auto &refs = refs_result.first->second;
  27. auto result = refs.try_emplace(key, std::move(association));
  28. if (!result.second) {
  29. association.swap(result.first->second);
  30. }
  31. } else { // 如果value为空
  32. auto refs_it = associations.find(disguised);
  33. if (refs_it != associations.end()) {
  34. auto &refs = refs_it->second;
  35. auto it = refs.find(key);
  36. if (it != refs.end()) {
  37. association.swap(it->second);
  38. refs.erase(it);
  39. if (refs.size() == 0) {
  40. // 也会找到associations进行擦除
  41. associations.erase(refs_it);
  42. }
  43. }
  44. }
  45. }
  46. }
  47. ....
  48. }

2.关联对象的取值函数objc_getAssociatedObject的实现代码如下

  1. void _object_get_associative_reference(id object, const void *key) {
  2. ObjcAssociation association{};
  3. {
  4. AssociationsManager manager;
  5. // associations是manager里面所有的AssociationsHashMap的list
  6. AssociationsHashMap &associations(manager.get());
  7. // 根据object在list里找到对应的那个AssociationsHashMap类型的i
  8. AssociationsHashMap::iterator i = associations.find((objc_object *)object);
  9. if (i != associations.end()) {
  10. // 再取出所有的ObjectAssociationMap的list
  11. ObjectAssociationMap &refs = i->second;
  12. // 根据key在list里找到对应的ObjectAssociationMap类型的j
  13. ObjectAssociationMap::iterator j = refs.find(key);
  14. if (j != refs.end()) {
  15. // 取出ObjectAssociation类型的值association
  16. association = j->second;
  17. // 取出association里的value和策略
  18. association.retainReturnedValue();
  19. }
  20. }
  21. }
  22. return association.autoreleaseReturnedValue();
  23. }

3.关联对象的几个类的实现关系可以用下图表示

总结:
  • 关联对象并不是存储在被关联对象本身内存中
  • 关联对象存储在全局的统一的一个AssociationsManager
  • 设置关联对象为nil,就相当于是移除关联对象
  • object对象被释放,关联对象的值也会对应的从内存中移除(内存管理自动做了处理)

原文链接:http://www.cnblogs.com/funkyRay/p/ios-di-ceng-yuan-li-sancategory.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号