iOS Category使用及其源碼分析

Category基本使用

分類和類一樣都是在接口內(nèi)聲明,在類文件內(nèi)實現(xiàn)。但是分類不可聲明實例變量,只可聲明屬性和方法,并且分類的實現(xiàn)部分不能包含@synthesize。分類的接口中含有屬性聲明時,實現(xiàn)部分就要手動定義屬性的訪問方法。這樣是為了防止隨意訪問同一個類的不同文件中定義的實例變量。

方法可以是實例方法也可為類方法。

語法:Category聲明

注意:類名必須是已存在的類,不可定義未存在的類。

@interface 類名(分類名)

屬性的聲明

方法的聲明

@end

語法:Category實現(xiàn)

@implementation 類名 (分類名)

屬性的setter定義

屬性的getter定義

方法的定義

@end

分類接口部分遵循原則:

1、分類的接口部分必須引用所屬類的接口文件;

2、分類實現(xiàn)部分必須引用對應(yīng)的接口文件;

3、使用分類中的方法時候,必須引用此方法所在的頭文件;

例:MyModel.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MyModel : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)NSInteger age;
@property (nonatomic,assign)CGFloat height;
- (void)work;
@end

NS_ASSUME_NONNULL_END  

MyModel.m

#import "MyModel.h"

@implementation MyModel

- (void)work{
    NSLog(@"-------MyModel work---------");
}

@end

MyModel+Test.h

#import "MyModel.h"

NS_ASSUME_NONNULL_BEGIN

@interface MyModel (Test)
- (void)walk;
@end

NS_ASSUME_NONNULL_END

MyModel+Test.m


#import "MyModel+Test.h"
@implementation MyModel (Test)
  
- (void)walk{
    NSLog(@"----Test walk--------");
}
@end

給已存在類追加分類

追加新方法

無論是自定義還是系統(tǒng)的類,皆可為已存在類追加新方法。

雖然分類不可用追加實例變量,但是新追加的方法可以訪問類中屬性和方法。借此,我們可以為已有類增加新功能。

當我們在使用繼承來為已有類增加新功能感覺麻煩時候,可以通過分類來為正在使用的類增加新功能。

例:通過為系統(tǒng)的NSDate類來增加新方法,獲取當前時間并返回一個指定格式的時間字符串。

NSDate+Format.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSDate (Format)
+(NSString *)getCurrentDayTimeDetail;
@end

NS_ASSUME_NONNULL_END

NSDate+Format.m


#import "NSDate+Format.h"

@implementation NSDate (Format)

+(NSString *)getCurrentDayTimeDetail {
    NSDate *now = [self date];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyy年MM月dd日 HH:mm:ss"];
    [formatter setLocale:[[NSLocale alloc]initWithLocaleIdentifier:@"zh_CN"]];
    NSTimeZone *timeZone = [[NSTimeZone alloc]initWithName:@"Asia/Shanghai"];
    [formatter setTimeZone:timeZone];
    NSString *current = [formatter stringFromDate:now];
    return current;
}

@end

覆蓋現(xiàn)有方法

新定義分類中的方法如果和原有方法重名,新定義的方法會覆蓋老的方法。通過此種方式,可以不使用繼承來實現(xiàn)方法覆蓋。但是如果不留意覆蓋了原有方法,也是會引起不可預(yù)測的問題,尤其是覆蓋了比較重要的方法,極有可能發(fā)生嚴重的問題。如果多個重復(fù)名稱方法,就無法知道到底執(zhí)行了哪個方法。覆蓋并不提示警告,因此在使用過程中,一定要注意避免方法被覆蓋掉。

關(guān)聯(lián)引用

通過分類,我們可以為一個類追加新的方法,但是不能追加實例變量。但是,借助Objective-C 的運行時功能,可以為已經(jīng)存在的實例對象增加實例變量。通過這種方式和分類合起來使用,不創(chuàng)建子類,也可以對類進行動態(tài)的擴展。關(guān)聯(lián)引用在運行時中不僅可根據(jù)需要為對象添加關(guān)聯(lián),也可以對已添加關(guān)聯(lián)進行移除。

添加關(guān)聯(lián)

 void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy);

object:所屬類,即要增加關(guān)聯(lián)的對象;

key:關(guān)鍵字,用來區(qū)分關(guān)聯(lián)引用,必須使用確定的、不再改變的地址作為鍵值;

value :引用對象;

policy:用來指定關(guān)聯(lián)引用的存儲策略。

此方法通過設(shè)置value 為nil,可以刪除key 的關(guān)聯(lián)。

檢索關(guān)聯(lián)

 id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);

通過關(guān)鍵字key來檢索關(guān)聯(lián)對象object,如果沒有關(guān)聯(lián)到任何對象,則返回值為nil。

移除關(guān)聯(lián)

Objective-C 運行時移除關(guān)聯(lián)的方法如下,這個方法會移除object對象的所有關(guān)聯(lián),謹慎使用。

已存在代碼可能已經(jīng)使用了關(guān)聯(lián),因此不建議使用此方法,可使用objc_setAssociatedObject,設(shè)置參數(shù)為nil,以此來分別移除關(guān)聯(lián)。

void objc_removeAssociatedObjects(id _Nonnull object);

關(guān)聯(lián)策略

OBJC_ASSOCIATION_ASSIGN

內(nèi)存管理時,不給關(guān)聯(lián)對象發(fā)送retain消息,僅僅通過賦值進行關(guān)聯(lián)。弱引用。

OBJC_ASSOCIATION_RETAIN_NONATOMIC

內(nèi)存管理時,會給關(guān)聯(lián)對象發(fā)送retain消息并持有,如果同樣的key已經(jīng)關(guān)聯(lián)了其他對象,則會給其他對象發(fā)送release消息。釋放關(guān)聯(lián)對象的所有者時,會給所有的關(guān)聯(lián)對象發(fā)送release消息。強引用。

OBJC_ASSOCIATION_COPY_NONATOMIC:

在進行對象關(guān)聯(lián)引用時候會復(fù)制一份原對象,并用新復(fù)制的對象進行關(guān)聯(lián)操作。

OBJC_ASSOCIATION_RETAIN:

在對象持有方面和 OBJC_ASSOCIATION_RETAIN_NONATOMIC一樣,唯一區(qū)別是OBJC_ASSOCIATION_RETAIN是多線程安全的,支持排他性的關(guān)聯(lián)操作。objc_getAssociatedObject的操作和OBJC_ASSOCIATION_RETAIN一樣。

OBJC_ASSOCIATION_COPY:

在對象持有方面和 OBJC_ASSOCIATION_COPY_NONATOMIC一樣,唯一區(qū)別是OBJC_ASSOCIATION_COPY是多線程安全的,支持排他性的關(guān)聯(lián)操作。

例:MyModel+Test.h

#import "MyModel.h"

NS_ASSUME_NONNULL_BEGIN
@protocol MyModelProtocol <NSObject>

- (void)myModelProtocolAction;

@end
  
@interface MyModel (Test)<MyModelProtocol>
  //不會自動生成實例變量,在這里添加屬性,其實是添加的setter和getter方法。
@property (nonatomic,copy)NSString *email;
- (void)walk;
@end

NS_ASSUME_NONNULL_END

MyModel+Test.m

#import "MyModel+Test.h"
#import <objc/runtime.h>
@implementation MyModel (Test)

- (void)setEmail:(NSString *)email{
    objc_setAssociatedObject(self, @"email",email, OBJC_ASSOCIATION_COPY);//添加關(guān)聯(lián)
//    objc_setAssociatedObject(self, @"email",nil, OBJC_ASSOCIATION_COPY);//移除關(guān)聯(lián)
}

- (NSString *)email{
    //檢索關(guān)聯(lián)
    return  objc_getAssociatedObject(self, @"email");
}

+(void)load{
    NSLog(@"----Test load-------");
}

//+ (void)initialize{
//    NSLog(@"----Test initialize-------");
//}
//

- (void)walk{
    NSLog(@"----Test walk-------%@",self.email);
}

- (void)myModelProtocolAction{
    NSLog(@"----myModelProtocolAction-------");
}


@end

底層結(jié)構(gòu)

通過終端命令:clang -rewrite-objc MyModel+Test.m 生成.cpp格式的文件。

打開重寫后生成的.cpp 文件,查找_category_t,在文件末尾找到如下結(jié)構(gòu)體。

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;//協(xié)議列表
    const struct _prop_list_t *properties;//屬性列表
};

對象方法列表結(jié)構(gòu)體,存儲分類中的所有對象方法

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[4];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    4,
    {{(struct objc_selector *)"setEmail:", "v24@0:8@16", (void *)_I_MyModel_Test_setEmail_},
    {(struct objc_selector *)"email", "@16@0:8", (void *)_I_MyModel_Test_email},
    {(struct objc_selector *)"walk", "v16@0:8", (void *)_I_MyModel_Test_walk},
    {(struct objc_selector *)"myModelProtocolAction", "v16@0:8", (void *)_I_MyModel_Test_myModelProtocolAction}}
};

類方法結(jié)構(gòu)體,存儲著類的方法

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_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"load", "v16@0:8", (void *)_C_MyModel_Test_load}}
};

協(xié)議列表結(jié)構(gòu)體和屬性列表結(jié)構(gòu)體,存儲著協(xié)議和屬性,值得注意的是,在編譯后的文件中,并沒有找到像類文件編譯后那樣的_ivar_list_t成員變量結(jié)構(gòu)體和settergetter方法,這也證明了分類不可以添加成員變量。而屬性的settergetter 方法,編譯后的是走的我們手動添加的相關(guān)的關(guān)聯(lián)引用的方法。

//協(xié)議
static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_MyModelProtocol
};

//屬性
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_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"email","T@\"NSString\",C,N"}}
};

//添加關(guān)聯(lián)引用
static void _I_MyModel_Test_setEmail_(MyModel * self, SEL _cmd, NSString * _Nonnull email) {
    objc_setAssociatedObject(self, (NSString *)&__NSConstantStringImpl__var_folders_lc_jzfd703x2r1czsk56zjspmw40000gn_T_MyModel_Test_00509d_mi_0,email, OBJC_ASSOCIATION_COPY);

}

//檢索關(guān)聯(lián)引用
static NSString * _Nonnull _I_MyModel_Test_email(MyModel * self, SEL _cmd) {

    return objc_getAssociatedObject(self, (NSString *)&__NSConstantStringImpl__var_folders_lc_jzfd703x2r1czsk56zjspmw40000gn_T_MyModel_Test_00509d_mi_1);
}


下面是MyModel的分類的結(jié)構(gòu)體賦值,依次分析各個變量對應(yīng)的賦值。

第1個是類名,將MyModel 賦值給了name;

第2個cls,將0賦值給cls;

第3個是對象列表,將_OBJC$CATEGORY_INSTANCE_METHODS_MyModel$_Test變量賦值給對象方法列表_instance_methods;

第4個是類方法列表,將*_OBJC$CATEGORY_CLASS_METHODS_MyModel$_Test變量賦值給類方法列表class_methods;

第5個協(xié)議列表,_OBJC_CATEGORY_PROTOCOLS$MyModel$_Test變量賦值給了protocols;

最后是屬性列表賦值,將_OBJC_$_PROP_LIST_MyModel_$_Test 變量賦值給了properties;

static struct _category_t _OBJC_$_CATEGORY_MyModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "MyModel",
    0, // &OBJC_CLASS_$_MyModel,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyModel_$_Test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MyModel_$_Test,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_MyModel_$_Test,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyModel_$_Test,
};

從上面編譯后的代碼分析,我們可以看出,編譯器生成了一個_category_t結(jié)構(gòu)體,并給結(jié)構(gòu)內(nèi)定義的變量進行了賦值。_category_t結(jié)構(gòu)體里面存儲著分類的對象方法、類方法、協(xié)議、和屬性,但是沒有成員變量。這就是_category_t重寫后的底層結(jié)構(gòu)。

源碼分析

Category運行時加載過程

_objc_init->map_images->map_images_nolock->_read_images->realizeClassWithoutSwift->methodizeClass->attachToClass

在上面的執(zhí)行過程中,map_images方法只執(zhí)行一次,這個過程加載并緩存所有Mach-O鏡像文件,并沒有進行合并分類的相關(guān)操作。

load_images->loadAllCategories->load_categories_nolock->attachCategories->attachLists

在這個過程中,才正式合并分類。需要注意的是,load_images只有在map_images執(zhí)行結(jié)束后,才會執(zhí)行,會執(zhí)行多次。但是load_images內(nèi)的loadAllCategories方法,只在分類還沒初始化并且_dyld_objc_notify_register 的調(diào)用已經(jīng)完成時,即didInitialAttachCategories 為false, didCallDyldNotifyRegister為true,此時才啟用,此時就不再走realizeClassWithoutSwift->methodizeClass->attachToClass這一部分了,因為類對象已經(jīng)存在了,因此改換成loadAllCategories后面的過程了。

_objc_init

首先在objc(objc4-818.2)開源庫中, objc-os.mm類文件內(nèi),查找_objc_init 方法,在此方法內(nèi)的_dyld_objc_notify_register函數(shù)注冊了3個方法;我們只關(guān)注前面兩個重要的函數(shù)參數(shù);

map_images:加載并緩存所有Mach-O鏡像文件;

load_images:初始化類和分類時需要;

unmap_image:取消內(nèi)存映射。

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
}

map_images

作用:加載并緩存所有Mach-O鏡像文件,主要操作在map_images_nolock方法中。程序啟動后只執(zhí)行一次。

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

map_images_nolock

作用:對所有的鏡像列表執(zhí)行addHeader,主要過濾重復(fù)的鏡像。 該方法內(nèi)調(diào)用了加載所有Mach-O鏡像文件的_read_images方法。

void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
     /**
     *代碼過長,省略部分代碼
     */

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
}

_read_images

map_images方法調(diào)用過程中,最終會引用到 objc-runtime-new.mm 文件內(nèi)的_read_images方法,_read_images方法的作用是從Mach-O鏡像文件中讀取所有類信息、方法信息、分類信息。

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    /**
    *省略了部分代碼,只摘取重要代碼
    */
 
    //搜索分類
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }
    /**
     *當其他線程調(diào)用新分類代碼之前,此線程完成其修復(fù)。分類搜索必須延遲以避免潛在的競爭。
     */
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
            }
          ////對類 cls 執(zhí)行第一次初始化,不含swift端初始化
            realizeClassWithoutSwift(cls, nil);
        }
    }
}

realizeClassWithoutSwift

作用:實現(xiàn)ClassWithoutSwift,對類 cls 執(zhí)行第一次初始化,包括分配其讀寫數(shù)據(jù)。不執(zhí)行任何 Swift 端初始化。

返回類的真實類結(jié)構(gòu)。鎖定:runtimeLock 讀寫鎖必須被調(diào)用者上寫鎖,保證線程安全。


static Class realizeClassWithoutSwift(Class cls, Class previously)
{
   runtimeLock.assertLocked();
    /**
    *省略了部分代碼,只摘取重要代碼
    */
  // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }
     //附加分類
    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}

methodizeClass

作用: 修正 cls 的方法列表、協(xié)議列表和屬性列表。將 cls 類的所有沒有被 attach 的分類 attach 到 cls 上。即將分類中的方法、屬性、協(xié)議添加到 methods、 properties 和 protocols 中。鎖定:runtimeLock 讀寫鎖必須被調(diào)用者上寫鎖,保證線程安全。

static void methodizeClass(Class cls, Class previously)
{
    /**
    *省略了部分代碼,只摘取重要代碼
    */
 
    runtimeLock.assertLocked();
  
    // Install methods and properties that the class implements itself.
  //添加類自身實現(xiàn)的方法和屬性
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
  //// 如果是根元類
    if (cls->isRootMetaclass()) {
        // root metaclass
     // 對根元類的initialize方法進行交換,即給根元類發(fā)送 SEL_initialize 消息,但是走的是 objc_noop_imp方法,里面不做任何操作
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    //給 cls 類附加分類
    if (previously) {//如果已存在類
        if (isMeta) {//如果是元類,給cls附加分類,并返回cls 類的沒有被附加的類
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
          //當一個類重新定位時,帶有類方法的分類,可以在類自身而不是類之上的元類注冊。通過attachToClass來添加這些。
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

}

load_images

作用: 當鏡像的狀態(tài)變化時,會回調(diào)load_images方法。只在map_images 結(jié)束后,分類沒初始化時候啟用。會多次調(diào)用。

extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

loadAllCategories

作用:加載所有分類的入口

static void loadAllCategories() {
    mutex_locker_t lock(runtimeLock);

    for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        load_categories_nolock(hi);
    }
}

load_categories_nolock

作用:加載分類的之前的區(qū)別判斷。

static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            locstamped_category_t lc{cat, hi};

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Ignore the category.
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category.
            if (cls->isStubClass()) {
                // Stub classes are never realized. Stub classes
                // don't know their metaclass until they're
                // initialized, so we have to add categories with
                // class methods or properties to the stub itself.
                // methodizeClass() will find them and add them to
                // the metaclass as appropriate.
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // First, register the category with its target class.
                // Then, rebuild the class's method lists (etc) if
                // the class is realized.
                if (cat->instanceMethods ||  cat->protocols
                    ||  cat->instanceProperties)
                {
                    //cls類已實現(xiàn)
                    if (cls->isRealized()) {
                        //合并分類的相關(guān)信息到類中
                        attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                }

                if (cat->classMethods  ||  cat->protocols
                    ||  (hasClassProperties && cat->_classProperties))
                {
                    if (cls->ISA()->isRealized()) {
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
        }
    };

    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

attachCategories

作用:從分類列表中添加方法列表、屬性和協(xié)議到 cls 類中, attachCategories 要求分類列表中是排好序的,新的在前面。

static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,int flags)
{
  /**
    *省略了部分代碼,只摘取重要代碼
    */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    //判斷是否為元類
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();

    for (uint32_t i = 0; i < cats_count; i++) {
        //取出某個分類
        auto& entry = cats_list[i];
       //方法數(shù)組 ,如果是元類,返回的是類方法,如果是類,則返回是實例對象方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
              //準備 mlists 中的方法列表集合,mcount:列表個數(shù),NO:排除基本方法,fromBundle:是否來自bundle
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
              // 將新mlist列表添加到 rwe 中的方法列表數(shù)組中
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
       //屬性數(shù)組
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
              // 將新proplist列表添加到 rwe 中的屬性列表數(shù)組中
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }
        //協(xié)議數(shù)組
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
              // 將新protolist列表添加到 rwe 中的協(xié)議列表數(shù)組中
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}


attachLists

作用:將新的list數(shù)據(jù)與原有的list 數(shù)據(jù)進行合并。

  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;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<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;
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
        }
    }

    void tryFree() {
        if (hasArray()) {
            for (uint32_t i = 0; i < array()->count; i++) {
                try_free(array()->lists[i]);
            }
            try_free(array());
        }
        else if (list) {
            try_free(list);
        }
    }

    template<typename Other>
    void duplicateInto(Other &other) {
        if (hasArray()) {
            array_t *a = array();
            other.setArray((array_t *)memdup(a, a->byteSize()));
            for (uint32_t i = 0; i < a->count; i++) {
                other.array()->lists[i] = a->lists[i]->duplicate();
            }
        } else if (list) {
            other.list = list->duplicate();
        } else {
            other.list = nil;
        }
    }
};

Category之 load

1、先編譯的類,優(yōu)先調(diào)用load;

2、先調(diào)用父類的load,再調(diào)用子類的load;

3、先調(diào)用類load,再調(diào)用分類load,然后先編譯的分類先調(diào)用load;

4、調(diào)用時機最靠前,在main函數(shù)運行之前,load 方法就會調(diào)用,適合方法交換。

5、不是懶加載,只會在程序調(diào)用期間調(diào)用一次,謹記,若在類與分類中都實現(xiàn)了 load 方法,兩者都會被調(diào)用,方法不會被覆蓋,但是順序不確定。

Category之 initialize

1、若是子類,會先走父類initialize,再走子類initialize,如果子類沒有重寫這個方法,父類里這個方法也會被調(diào)用;

2、initialize 是類第一次接收到消息的時候調(diào)用,每一個類只會initialize一次,父類可能不止調(diào)用一次;

3、和 load 不同,initialize 方法調(diào)用時,所有的類都load到了內(nèi)存中;

4、initialize 的運行線程安全,一般只在 initialize中進行常量的初始化 。

總結(jié):

Category運行時的加載過程,是通過動態(tài)的先初始化類和元類的相關(guān)信息,然后再將分類的實例方法、類方法、屬性以及協(xié)議,合并到類對象和元類對象中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容