• Category的使用场合是什么?

    category是Objective-C 2.0之后添加的语言特性,category的主要作用是为已经存在的类添加方法。除此之外,apple还推荐了category的另外两个使用场景

    • 可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处,

    a)可以减少单个文件的体积
    b)可以把不同的功能组织到不同的category里
    c)可以由多个开发者共同完成一个类
    d)可以按需加载想要的category 等等。

    • 声明私有方法

    不过除了apple推荐的使用场景,广大开发者脑洞大开,还衍生出了category的其他几个使用场景:

    • 模拟多继承

    • 把framework的私有方法公开

    Objective-C的这个语言特性对于纯动态语言来说可能不算什么,比如javascript,你可以随时为一个“类”或者对象添加任意方法和实例变量。但是对于不是那么“动态”的语言而言,这确实是一个了不起的特性。

    • Category的实现原理
    1. //分类的内存结构,多个分类会对应的多个category_t。
    2. struct category_t {
    3. const char *name;
    4. classref_t cls;
    5. struct method_list_t *instanceMethods; //对象方法
    6. struct method_list_t *classMethods; //类方法
    7. struct protocol_list_t *protocols; //协议信息
    8. struct property_list_t *instanceProperties; //属性
    9. // Fields below this point are not always present on disk.
    10. struct property_list_t *_classProperties;
    11. method_list_t *methodsForMeta(bool isMeta) {
    12. if (isMeta) return classMethods;
    13. else return instanceMethods;
    14. }
    15. property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    16. };

    Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息

    在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

    • Category的加载处理过程
    1. /***********************************************************************
    2. * _read_images
    3. * Perform initial processing of the headers in the linked
    4. * list beginning with headerList.
    5. *
    6. * Called by: map_images_nolock
    7. *
    8. * Locking: runtimeLock acquired by map_images
    9. **********************************************************************/
    10. void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
    11. {
    12. ......
    13. // add by chenjie
    14. // Discover categories.
    15. for (EACH_HEADER) {
    16. //获取系统的category列表
    17. category_t **catlist =
    18. _getObjc2CategoryList(hi, &count);
    19. bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    20. for (i = 0; i < count; i++) {
    21. category_t *cat = catlist[i];
    22. Class cls = remapClass(cat->cls);
    23. if (!cls) {
    24. // Category's target class is missing (probably weak-linked).
    25. // Disavow any knowledge of this category.
    26. catlist[i] = nil;
    27. if (PrintConnecting) {
    28. _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
    29. "missing weak-linked target class",
    30. cat->name, cat);
    31. }
    32. continue;
    33. }
    34. // Process this category.
    35. // First, register the category with its target class.
    36. // Then, rebuild the class's method lists (etc) if
    37. // the class is realized.
    38. bool classExists = NO;
    39. if (cat->instanceMethods || cat->protocols
    40. || cat->instanceProperties)
    41. {
    42. addUnattachedCategoryForClass(cat, cls, hi);
    43. if (cls->isRealized()) {
    44. //重新组织了类对象的方法。
    45. remethodizeClass(cls);
    46. classExists = YES;
    47. }
    48. if (PrintConnecting) {
    49. _objc_inform("CLASS: found category -%s(%s) %s",
    50. cls->nameForLogging(), cat->name,
    51. classExists ? "on existing class" : "");
    52. }
    53. }
    54. if (cat->classMethods || cat->protocols
    55. || (hasClassProperties && cat->_classProperties))
    56. {
    57. addUnattachedCategoryForClass(cat, cls->ISA(), hi);
    58. if (cls->ISA()->isRealized()) {
    59. //重新组织了元类对象的方法。
    60. remethodizeClass(cls->ISA());
    61. }
    62. if (PrintConnecting) {
    63. _objc_inform("CLASS: found category +%s(%s)",
    64. cls->nameForLogging(), cat->name);
    65. }
    66. }
    67. }
    68. }
    69. }
    70. // Attach method lists and properties and protocols from categories to a class.
    71. // Assumes the categories in cats are all loaded and sorted by load order,
    72. // oldest categories first.
    73. static void
    74. attachCategories(Class cls, category_list *cats, bool flush_caches)
    75. {
    76. if (!cats) return;
    77. if (PrintReplacedMethods) printReplacements(cls, cats);
    78. bool isMeta = cls->isMetaClass();
    79. // fixme rearrange to remove these intermediate allocations
    80. // 方法数组
    81. /*
    82. [
    83. [method_t, method_t],
    84. [method_t, method_t]
    85. ]
    86. */
    87. method_list_t **mlists = (method_list_t **)
    88. malloc(cats->count * sizeof(*mlists));
    89. //属性数组
    90. property_list_t **proplists = (property_list_t **)
    91. malloc(cats->count * sizeof(*proplists));
    92. //协议数组
    93. protocol_list_t **protolists = (protocol_list_t **)
    94. malloc(cats->count * sizeof(*protolists));
    95. // Count backwards through cats to get newest categories first
    96. int mcount = 0;
    97. int propcount = 0;
    98. int protocount = 0;
    99. int i = cats->count;
    100. bool fromBundle = NO;
    101. while (i--) {
    102. //取出某得分类
    103. auto& entry = cats->list[i];
    104. //取出分类中的对象方法
    105. method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
    106. if (mlist) {
    107. mlists[mcount++] = mlist;
    108. fromBundle |= entry.hi->isBundle();
    109. }
    110. property_list_t *proplist =
    111. entry.cat->propertiesForMeta(isMeta, entry.hi);
    112. if (proplist) {
    113. proplists[propcount++] = proplist;
    114. }
    115. protocol_list_t *protolist = entry.cat->protocols;
    116. if (protolist) {
    117. protolists[protocount++] = protolist;
    118. }
    119. }
    120. //得到类对象里面的数组
    121. auto rw = cls->data();
    122. prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    123. // 将所有分类的对象方法,附加到类对象的方法列表中
    124. rw->methods.attachLists(mlists, mcount);
    125. free(mlists);
    126. if (flush_caches && mcount > 0) flushCaches(cls);
    127. // 将所有分类的属性,附加到类对象的属性列表中
    128. rw->properties.attachLists(proplists, propcount);
    129. free(proplists);
    130. // 将所有分类的协议,附加到类对象的协议列表中
    131. rw->protocols.attachLists(protolists, protocount);
    132. free(protolists);
    133. }
    134. void attachLists(List* const * addedLists, uint32_t addedCount) {
    135. if (addedCount == 0) return;
    136. if (hasArray()) {
    137. uint32_t oldCount = array()->count;
    138. uint32_t newCount = oldCount + addedCount;
    139. //重新开辟内存空间
    140. setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
    141. array()->count = newCount;
    142. //array()->lists 原来的方法列表
    143. memmove(array()->lists + addedCount, array()->lists,
    144. oldCount * sizeof(array()->lists[0]));
    145. //addedLists 新增的方法列表
    146. memcpy(array()->lists, addedLists,
    147. addedCount * sizeof(array()->lists[0]));
    148. }
    1. 通过Runtime加载某个类的所有Category数据

    2. 把所有Category的方法、属性、协议数据,合并到一个大数组中后面参与编译的Category数据,会在数组的前面

    3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

    • Category和Class Extension的区别是什么?

    Class Extension在编译的时候,它的数据就已经包含在类信息中

    Category是在运行时,才会将数据合并到类信息中

    • +load方法

    Category - 图1

    1. //加载load方法的步骤
    2. void prepare_load_methods(const headerType *mhdr)
    3. {
    4. size_t count, i;
    5. classref_t *classlist =
    6. _getObjc2NonlazyClassList(mhdr, &count);
    7. for (i = 0; i < count; i++) {
    8. schedule_class_load(remapClass(classlist[i]));
    9. }
    10. }
    11. static void schedule_class_load(Class cls)
    12. {
    13. if (!cls) return;
    14. assert(cls->isRealized()); // _read_images should realize
    15. if (cls->data()->flags & RW_LOADED) return;
    16. // Ensure superclass-first ordering
    17. schedule_class_load(cls->superclass);
    18. add_class_to_loadable_list(cls);
    19. cls->setInfo(RW_LOADED);
    20. }
    21. void add_class_to_loadable_list(Class cls)
    22. {
    23. IMP method;
    24. loadMethodLock.assertLocked();
    25. method = cls->getLoadMethod();
    26. if (!method) return; // Don't bother if cls has no +load method
    27. if (PrintLoading) {
    28. _objc_inform("LOAD: class '%s' scheduled for +load",
    29. cls->nameForLogging());
    30. }
    31. if (loadable_classes_used == loadable_classes_allocated) {
    32. loadable_classes_allocated = loadable_classes_allocated*2 + 16;
    33. loadable_classes = (struct loadable_class *)
    34. realloc(loadable_classes,
    35. loadable_classes_allocated *
    36. sizeof(struct loadable_class));
    37. }
    38. //loadable_classes是保存load方法的列表
    39. loadable_classes[loadable_classes_used].cls = cls;
    40. loadable_classes[loadable_classes_used].method = method;
    41. loadable_classes_used++;
    42. }
    43. //调用load方法
    44. void call_load_methods(void)
    45. {
    46. static bool loading = NO;
    47. bool more_categories;
    48. loadMethodLock.assertLocked();
    49. // Re-entrant calls do nothing; the outermost call will finish the job.
    50. if (loading) return;
    51. loading = YES;
    52. void *pool = objc_autoreleasePoolPush();
    53. do {
    54. // 1. Repeatedly call class +loads until there aren't any more
    55. while (loadable_classes_used > 0) {
    56. call_class_loads();
    57. }
    58. // 2. Call category +loads ONCE
    59. more_categories = call_category_loads();
    60. // 3. Run more +loads if there are classes OR more untried categories
    61. } while (loadable_classes_used > 0 || more_categories);
    62. objc_autoreleasePoolPop(pool);
    63. loading = NO;
    64. }
    65. static bool call_category_loads(void)
    66. {
    67. int i, shift;
    68. bool new_categories_added = NO;
    69. // Detach current loadable list.
    70. struct loadable_category *cats = loadable_categories;
    71. int used = loadable_categories_used;
    72. int allocated = loadable_categories_allocated;
    73. loadable_categories = nil;
    74. loadable_categories_allocated = 0;
    75. loadable_categories_used = 0;
    76. // Call all +loads for the detached list.
    77. for (i = 0; i < used; i++) {
    78. Category cat = cats[i].cat;
    79. load_method_t load_method = (load_method_t)cats[i].method;
    80. Class cls;
    81. if (!cat) continue;
    82. cls = _category_getClass(cat);
    83. if (cls && cls->isLoadable()) {
    84. if (PrintLoading) {
    85. _objc_inform("LOAD: +[%s(%s) load]\n",
    86. cls->nameForLogging(),
    87. _category_getName(cat));
    88. }
    89. (*load_method)(cls, SEL_load);
    90. cats[i].cat = nil;
    91. }
    92. }
    93. }

    应用场景

    在执行load方法时,整个应用程序都会阻塞等着所有类的load方法都执行完才继续,因此尽力减少在load函数中所做的操作。这个类真正的用途一般只用于调试程序,比如可以在分类中实现此方法,用来判断该分类是否已经正确载入系统。此外,一般在需要method swizzling时,一般是在load函数中实现,可保证程序一经加载方法就交换完成,且只执行一次。

    • +initialize方法

    Category - 图2

    1. Method class_getInstanceMethod(Class cls, SEL sel)
    2. {
    3. // Search method lists, try method resolver, etc.
    4. lookUpImpOrNil(cls, sel, nil,
    5. NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
    6. return _class_getMethod(cls, sel);
    7. }
    8. /***********************************************************************
    9. * class_initialize. Send the '+initialize' message on demand to any
    10. * uninitialized class. Force initialization of superclasses first.
    11. **********************************************************************/
    12. void _class_initialize(Class cls)
    13. {
    14. assert(!cls->isMetaClass());
    15. Class supercls;
    16. bool reallyInitialize = NO;
    17. // Make sure super is done initializing BEFORE beginning to initialize cls.
    18. // See note about deadlock above.
    19. //先调用父类的初始化方法
    20. supercls = cls->superclass;
    21. if (supercls && !supercls->isInitialized()) {
    22. _class_initialize(supercls);
    23. }
    24. // Try to atomically set CLS_INITIALIZING.
    25. {
    26. monitor_locker_t lock(classInitLock);
    27. if (!cls->isInitialized() && !cls->isInitializing()) {
    28. cls->setInitializing();
    29. reallyInitialize = YES;
    30. }
    31. }
    32. }
    • Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

    有load方法

    load方法在runtime加载类、分类的时候调用

    load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用

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

    1.调用方式
    1> load是根据函数地址直接调用
    2> initialize是通过objc_msgSend调用

    2.调用时刻
    1> load是runtime加载类、分类的时候调用(只会调用1次)
    2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)

    load、initialize的调用顺序?
    1.load
    1> 先调用类的load
    a) 先编译的类,优先调用load
    b) 调用子类的load之前,会先调用父类的load

    2> 再调用分类的load
    a) 先编译的分类,优先调用load

    2.initialize
    1> 先初始化父类
    2> 再初始化子类(可能最终调用的是父类的initialize方法)

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

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

    总结
    load和initialize方法都会在实例化对象之前调用,以main函数为分水岭,前者在main函数之前调用,后者在之后调用。这两个方法会被自动调用,不能手动调用它们。

    load和initialize方法都不用显示的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。

    load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。

    load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。