iOS Category使用及其源碼分析

Category基本使用

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

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

語法:Category聲明

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

@interface 類名(分類名)

屬性的聲明

方法的聲明

@end

語法:Category實現

@implementation 類名 (分類名)

屬性的setter定義

屬性的getter定義

方法的定義

@end

分類接口部分遵循原則:

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

2、分類實現部分必須引用對應的接口文件;

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

給已存在類追加分類

追加新方法

無論是自定義還是系統的類,皆可爲已存在類追加新方法。

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

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

例:通過爲系統的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

覆蓋現有方法

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

關聯引用

通過分類,我們可以爲一個類追加新的方法,但是不能追加實例變量。但是,藉助Objective-C 的運行時功能,可以爲已經存在的實例對象增加實例變量。通過這種方式和分類合起來使用,不創建子類,也可以對類進行動態的擴展。關聯引用在運行時中不僅可根據需要爲對象添加關聯,也可以對已添加關聯進行移除。

添加關聯

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

object:所屬類,即要增加關聯的對象;

key:關鍵字,用來區分關聯引用,必須使用確定的、不再改變的地址作爲鍵值;

value :引用對象;

policy:用來指定關聯引用的存儲策略。

此方法通過設置value 爲nil,可以刪除key 的關聯。

檢索關聯

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

通過關鍵字key來檢索關聯對象object,如果沒有關聯到任何對象,則返回值爲nil。

移除關聯

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

已存在代碼可能已經使用了關聯,因此不建議使用此方法,可使用objc_setAssociatedObject,設置參數爲nil,以此來分別移除關聯。

void objc_removeAssociatedObjects(id _Nonnull object);

關聯策略

OBJC_ASSOCIATION_ASSIGN

內存管理時,不給關聯對象發送retain消息,僅僅通過賦值進行關聯。弱引用。

OBJC_ASSOCIATION_RETAIN_NONATOMIC

內存管理時,會給關聯對象發送retain消息並持有,如果同樣的key已經關聯了其他對象,則會給其他對象發送release消息。釋放關聯對象的所有者時,會給所有的關聯對象發送release消息。強引用。

OBJC_ASSOCIATION_COPY_NONATOMIC:

在進行對象關聯引用時候會複製一份原對象,並用新複製的對象進行關聯操作。

OBJC_ASSOCIATION_RETAIN:

在對象持有方面和 OBJC_ASSOCIATION_RETAIN_NONATOMIC一樣,唯一區別是OBJC_ASSOCIATION_RETAIN是多線程安全的,支持排他性的關聯操作。objc_getAssociatedObject的操作和OBJC_ASSOCIATION_RETAIN一樣。

OBJC_ASSOCIATION_COPY:

在對象持有方面和 OBJC_ASSOCIATION_COPY_NONATOMIC一樣,唯一區別是OBJC_ASSOCIATION_COPY是多線程安全的,支持排他性的關聯操作。

例: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);//添加關聯
//    objc_setAssociatedObject(self, @"email",nil, OBJC_ASSOCIATION_COPY);//移除關聯
}

- (NSString *)email{
    //檢索關聯
    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

底層結構

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

打開重寫後生成的.cpp 文件,查找_category_t,在文件末尾找到如下結構體。

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;//屬性列表
};

對象方法列表結構體,存儲分類中的所有對象方法

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:", "[email protected]:[email protected]", (void *)_I_MyModel_Test_setEmail_},
    {(struct objc_selector *)"email", "@[email protected]:8", (void *)_I_MyModel_Test_email},
    {(struct objc_selector *)"walk", "[email protected]:8", (void *)_I_MyModel_Test_walk},
    {(struct objc_selector *)"myModelProtocolAction", "[email protected]:8", (void *)_I_MyModel_Test_myModelProtocolAction}}
};

類方法結構體,存儲着類的方法

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", "[email protected]:8", (void *)_C_MyModel_Test_load}}
};

協議列表結構體和屬性列表結構體,存儲着協議和屬性,值得注意的是,在編譯後的文件中,並沒有找到像類文件編譯後那樣的_ivar_list_t成員變量結構體和settergetter方法,這也證明了分類不可以添加成員變量。而屬性的settergetter 方法,編譯後的是走的我們手動添加的相關的關聯引用的方法。

//協議
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","[email protected]\"NSString\",C,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);

}

//檢索關聯引用
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的分類的結構體賦值,依次分析各個變量對應的賦值。

第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個協議列表,_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結構體,並給結構內定義的變量進行了賦值。_category_t結構體裏面存儲着分類的對象方法、類方法、協議、和屬性,但是沒有成員變量。這就是_category_t重寫後的底層結構。

源碼分析

Category運行時加載過程

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

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

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

在這個過程中,才正式合併分類。需要注意的是,load_images只有在map_images執行結束後,纔會執行,會執行多次。但是load_images內的loadAllCategories方法,只在分類還沒初始化並且_dyld_objc_notify_register 的調用已經完成時,即didInitialAttachCategories 爲false, didCallDyldNotifyRegister爲true,此時才啓用,此時就不再走realizeClassWithoutSwift->methodizeClass->attachToClass這一部分了,因爲類對象已經存在了,因此改換成loadAllCategories後面的過程了。

_objc_init

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

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

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

unmap_image:取消內存映射。

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方法中。程序啓動後只執行一次。

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

作用:對所有的鏡像列表執行addHeader,主要過濾重複的鏡像。 該方法內調用了加載所有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方法調用過程中,最終會引用到 objc-runtime-new.mm 文件內的_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);
        }
    }
    /**
     *當其他線程調用新分類代碼之前,此線程完成其修復。分類搜索必須延遲以避免潛在的競爭。
     */
    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 執行第一次初始化,不含swift端初始化
            realizeClassWithoutSwift(cls, nil);
        }
    }
}

realizeClassWithoutSwift

作用:實現ClassWithoutSwift,對類 cls 執行第一次初始化,包括分配其讀寫數據。不執行任何 Swift 端初始化。

返回類的真實類結構。鎖定:runtimeLock 讀寫鎖必須被調用者上寫鎖,保證線程安全。


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 的方法列表、協議列表和屬性列表。將 cls 類的所有沒有被 attach 的分類 attach 到 cls 上。即將分類中的方法、屬性、協議添加到 methods、 properties 和 protocols 中。鎖定:runtimeLock 讀寫鎖必須被調用者上寫鎖,保證線程安全。

static void methodizeClass(Class cls, Class previously)
{
    /**
    *省略了部分代碼,只摘取重要代碼
    */
 
    runtimeLock.assertLocked();
  
    // Install methods and properties that the class implements itself.
  //添加類自身實現的方法和屬性
    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方法進行交換,即給根元類發送 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

作用: 當鏡像的狀態變化時,會回調load_images方法。只在map_images 結束後,分類沒初始化時候啓用。會多次調用。

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

作用:加載分類的之前的區別判斷。

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類已實現
                    if (cls->isRealized()) {
                        //合併分類的相關信息到類中
                        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

作用:從分類列表中添加方法列表、屬性和協議到 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];
       //方法數組 ,如果是元類,返回的是類方法,如果是類,則返回是實例對象方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
              //準備 mlists 中的方法列表集合,mcount:列表個數,NO:排除基本方法,fromBundle:是否來自bundle
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
              // 將新mlist列表添加到 rwe 中的方法列表數組中
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
       //屬性數組
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
              // 將新proplist列表添加到 rwe 中的屬性列表數組中
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }
        //協議數組
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
              // 將新protolist列表添加到 rwe 中的協議列表數組中
                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數據與原有的list 數據進行合併。

  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、先編譯的類,優先調用load

2、先調用父類的load,再調用子類的load

3、先調用類load,再調用分類load,然後先編譯的分類先調用load

4、調用時機最靠前,在main函數運行之前,load 方法就會調用,適合方法交換。

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

Category之 initialize

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

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

3、和 load 不同,initialize 方法調用時,所有的類都load到了內存中;

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

總結:

Category運行時的加載過程,是通過動態的先初始化類和元類的相關信息,然後再將分類的實例方法、類方法、屬性以及協議,合併到類對象和元類對象中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章