天天看點

Runtime02KVO和Category原理

周遊監聽對象前後的執行個體方法的變化:

界面效果就是一個空白界面:

Runtime02KVO和Category原理

上代碼:

Person類:

@interface Person : NSObject
{
@public
    int _age;
}
//系統會預設為age生成set和get方法
@property (nonatomic, assign) int age;
@end
//---------分割線,分隔一個類的.h檔案和.m檔案-------
#import "Person.h"
@implementation Person
@end

           

ViewController類:

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
//---------分割線,分隔一個類的.h檔案和.m檔案-------
#import "ViewController.h"
#import "Person.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (strong, nonatomic) Person *person1;
@property (strong, nonatomic) Person *person2;
@end

@implementation ViewController
- (void)dealloc {
    [self.person1 removeObserver:self forKeyPath:@"age"];
}

/*
 既然下面的viewDidLoad()已經添加了對person1的age屬性的監聽。是以runtime機制會在運作時生成Person的子類,該子類名為NSKVONotifying_Person,是以此時就不要在你的項目裡面再建立名為NSKVONotifying_Person的類了!如果你手動在工程添加了名為NSKVONotifying_Person的類,那麼運作時會報 KVO failed to allocate class pair for name NSKVONotifying_Person, automatic key-value observing will not work for this class的警告,即runtime機制不會在運作時自動建立NSKVONotifying_Person類了,進而導緻person1的age屬性的監聽會失效。
 
 */
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[Person alloc] init];
    self.person1.age = 1;
    
    self.person2 = [[Person alloc] init];
    self.person2.age = 2;

    // 給person1對象添加對Person的age屬性的監聽
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];

	NSLog(@"object_getClass(self.person1) = %@, [person1 class = %@]", object_getClass(self.person1),
          [self.person1 class]);
    NSLog(@"object_getClass(self.person2) = %@, [person2 class = %@]", object_getClass(self.person2),
          [self.person2 class]);
    
    
    NSLog(@"--------self.person1的方法有:");
    [self printMethodNamesOfClass:object_getClass(self.person1)];
    NSLog(@"--------self.person2的方法有:");
    [self printMethodNamesOfClass:object_getClass(self.person2)];
    
}

- (void)printMethodNamesOfClass:(Class)cls {  //擷取的是在Person.m檔案或者系統自動為屬性生成的get和set方法
    unsigned int count;
    //獲得方法數組
    Method *methodList = class_copyMethodList(cls, &count);
    //存儲方法名
    NSMutableString *methodNames = [NSMutableString string];
    //周遊所有的方法
    for (int i = 0; i < count; i++) {
        //獲得方法
        Method method = methodList[i];
        //獲得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        //拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    //釋放記憶體
    free(methodList);
    //列印方法名
    NSLog(@"class = %@,方法有: %@", cls, methodNames);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
//    self.person1.age = 11; //會調用KVO,即會調用person1的setAge()
    
//    self.person1->_age = 11; //報錯:'Person' does not have a member named '_age'
    
//    [self.person1 setValue:@123 forKey:@"_age"]; //不會調用KVO,即不會調用person1的setAge(),是因為直接指派給成員變量_age
    
    [self.person1 setValue:@1234 forKey:@"age"]; //會調用KVO,即會調用person1的setAge()
    
//    [self.person1 willChangeValueForKey:@"age"];
//    [self.person1 didChangeValueForKey:@"age"]; //這一行代碼裡面最終會調用Observer(本例中指ViewController)的observeValueForKeyPath方法
    
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"監聽到的%@ 的 %@屬性值改變了,存放新舊值的字典為%@, context = %@ ", object, keyPath, change, context);
}

@end
           

在螢幕上滑動一下的調用棧如下

Runtime02KVO和Category原理

上圖的列印結果分析

添加對Person類的age屬性的監聽後,runtime機制會在運作時生成一個名為NSKVONitifying_Person的類,并實作(重寫)了setAge()、class()、dealloc()、_isKVOA()方法。其中,重寫的setAge()的實作僞代碼如下:

- (void)setAge:(int)age {
    _NSSetIntValueAndNotify(); //僞代碼
}

//僞代碼
void _NSSetIntValueAndNotify() {
    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey:@"age"];
}

//僞代碼
- (void)didChangeValueForKey:(NSString *)key {
    //通知監聽器,某某屬性值發送了改變
    [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
           

重寫的class()的實作的僞代碼如下:

Class class {
	return [super class];
}
           

分類(Category)

先看代碼

main.m代碼

#import <Foundation/Foundation.h>
#import "Person+Eat.h"
#import "Person+Test.h"
#import "Person.h"
/**
 每一個類的分類在編譯時都會由一個_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;
     };
 
 在編譯時,就已經确定了每一個類的分類都是_category_t結構體的一個執行個體。
 */
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        [person run];
        
        objc_msgSend(person, @selector(run));
        
        [person test];
        
        [person eat];
        
        objc_msgSend(person, @selector(eat));
        
        //通過runtime機制動态的将分類的方法合并到類對象、元類對象中
    }
    return 0;
}
           

Person類:

@interface Person : NSObject
- (void)run;
@end
//---------分割線,分隔一個類的.h檔案和.m檔案-------
#import "Person.h"
@interface Person() {
    int _abc;
}
@property (nonatomic, assign)int age;
- (void)abc;

@end

@implementation Person

- (void)abc {
    
}

- (void)run {
    NSLog(@"Person run");
}

+ (void)run2 { 
}

@end

           

Person+Test 分類的代碼如下:

@interface Person (Test)
- (void)test;
@end
//---------分割線,分隔一個類的.h檔案和.m檔案-------
#import "Person+Test.h"

@implementation Person (Test)

- (void)run {
    NSLog(@"Person (Test) - run");
}

- (void)test {
    NSLog(@"Person (Test) test");
}

+ (void)test2 {
}

@end
           

Person+Eat 分類的代碼如下

#import "Person.h"

@interface Person (Eat)<NSCopying, NSCoding>
- (void)eat;
@property (assign, nonatomic) int weight;
@property (assign, nonatomic) int height;
@end
//---------分割線,分隔一個類的.h檔案和.m檔案-------
#import "Person+Eat.h"

@implementation Person  (Eat)

- (void)run {
    NSLog(@"Person (Eat) - run");
}

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

+ (void)eat2
{
}

@end

           

上述代碼可能因為objc_msgSend()而導緻編譯不過,此時需要關閉對objc_msgSend的檢查,具體操作如下:

Runtime02KVO和Category原理

調整.m檔案編譯的順序,如下圖所示

Runtime02KVO和Category原理

點選運作,列印結果如下:

Runtime02KVO和Category原理

列印結果分析

因為Person+Test

前置知識

Category裡面的對象方法(- 号開頭的方法)隻有在運作時(runtime)才會被添加到類的方法清單中,Category裡面的類方法(+ 号開頭的方法)隻有在運作時(runtime)才會被添加到類的元類的方法清單中,而不是在編譯時被添加。整個編譯過程中,編譯器先将oc代碼轉為C++代碼(即把.m檔案轉成.cpp檔案,通過執行

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxxxx.m

指令實作轉化),然後編譯成可執行代碼,然後運作,在運作的過程中(runtime),系統才會把分類所實作的執行個體方法和類方法分别加入到類對象的執行個體方法清單和類方法清單中。

以上面的例子為例,在Person+Eat.m檔案的目錄下,執行

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Eat.m

,系統就會生成

Person+Eat.cpp

。該Person+Eat.cpp檔案的部分關鍵代碼如下:

struct _prop_t { //代表屬性的結構體
	const char *name;
	const char *attributes;
};

struct _protocol_t;
struct _objc_method {  //代表方法(Method)的結構體
	struct objc_selector * _cmd;
	const char *method_type;
	void  *_imp;
};

struct _protocol_t {  //代表協定的結構體
	void * isa;  // NULL
	const char *protocol_name;
	const struct _protocol_list_t * protocol_list; // super protocols
	const 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;  // = 0
	const char ** extendedMethodTypes;
};

struct _ivar_t {  //代表成員變量的結構體
	unsigned long int *offset;  // pointer to ivar offset location
	const char *name;
	const char *type;
	unsigned int alignment;
	unsigned int  size;
};

struct _class_ro_t { //class_readOnly_table的縮寫
	unsigned int flags;
	unsigned int instanceStart;
	unsigned int instanceSize;
	const unsigned char *ivarLayout;
	const char *name;  //類名
	const struct _method_list_t *baseMethods; 
	const struct _objc_protocol_list *baseProtocols;
	const struct _ivar_list_t *ivars;  //成員變量清單,類的成員變量都放在這裡
	const unsigned char *weakIvarLayout;
	const struct _prop_list_t *properties;  //屬性清單
};

struct _class_t {  //表示一個類的結構體
	struct _class_t *isa;
	struct _class_t *superclass;
	void *cache;
	void *vtable;
	struct _class_ro_t *ro;
};

struct _category_t {  //每一個類的分類在編譯時都會由一個_category_t結構體表示,即每一個分類都是_category_t結構體的一個執行個體。不同的分類的資訊肯定不同,是以不同的_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; //屬性清單
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

static struct /*_method_list_t*/ {  //Person+Eat分類的執行個體方法清單
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2]; //Person+Eat分類的執行個體方法有2個,分别為run()和eat()方法
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"run", "[email protected]:8", (void *)_I_Person_Eat_run},
	{(struct objc_selector *)"eat", "[email protected]:8", (void *)_I_Person_Eat_eat}}
};

static struct /*_method_list_t*/ { //Person+Eat分類的類方法清單
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];  //Person+Eat分類的類方法有1個,即eat2()方法
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"eat2", "[email protected]:8", (void *)_C_Person_Eat_eat2}}
};

static const char *_OBJC_PROTOCOL_METHOD_TYPES_NSCopying [] __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"@[email protected]:8^{_NSZone=}16"
};

static struct /*_method_list_t*/ {   //Person+Eat分類遵守的的NSCopying協定的執行個體方法清單
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1]; //執行個體方法清單有1個copyWithZone()方法
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"copyWithZone:", "@[email protected]:8^{_NSZone=}16", 0}}
};

struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
	0,
	"NSCopying",
	0,
	(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
	0,
	0,
	0,
	0,
	sizeof(_protocol_t),
	0,
	(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;

static const char *_OBJC_PROTOCOL_METHOD_TYPES_NSCoding [] __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"[email protected]:[email protected]\"NSCoder\"16",
	"@[email protected]:[email protected]\"NSCoder\"16"
};

static struct /*_method_list_t*/ { //Person+Eat分類遵守的的NSCoding協定的執行個體方法清單,有2個執行個體方法
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCoding __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"encodeWithCoder:", "[email protected]:[email protected]", 0},
	{(struct objc_selector *)"initWithCoder:", "@[email protected]:[email protected]", 0}}
};

struct _protocol_t _OBJC_PROTOCOL_NSCoding __attribute__ ((used)) = {
	0,
	"NSCoding",
	0,
	(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCoding,
	0,
	0,
	0,
	0,
	sizeof(_protocol_t),
	0,
	(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCoding
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCoding = &_OBJC_PROTOCOL_NSCoding;

static struct /*_protocol_list_t*/ {  //Person+Eat分類所遵守的協定方法清單
	long protocol_count;  // Note, this is 32/64 bit
	struct _protocol_t *super_protocols[2];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	2,
	&_OBJC_PROTOCOL_NSCopying,
	&_OBJC_PROTOCOL_NSCoding
};

static struct /*_prop_list_t*/ {  //Person+Eat分類的屬性方法清單
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	2,
	{{"weight","Ti,N"},
	{"height","Ti,N"}}
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Person;

//每一個分類都是_category_t結構體的一個執行個體,下面的_category_t結構體的執行個體存儲的是Person+Eat分類的資訊。
static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"Person", //Person+Eat分類的類名
	0, // &OBJC_CLASS_$_Person, 
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat, //Person+Eat分類的執行個體方法清單
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat, //Person+Eat分類的類方法清單
	(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat, //Person+Eat分類的協定清單
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Eat, //Person+Eat分類的屬性清單
};
           

在Person+Test.m檔案的目錄下,執行

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Test.m

,系統就會生成

Person+Test.cpp

。該Person+Test.cpp檔案的部分關鍵代碼如下:

struct _category_t {  //每一個類的分類在編譯時都會由一個_category_t結構體表示,即每一個分類都是_category_t結構體的一個執行個體。不同的分類的資訊肯定不同,是以不同的_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;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

static struct /*_method_list_t*/ { //Person+Test分類的執行個體方法清單
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"run", "[email protected]:8", (void *)_I_Person_Test_run},
	{(struct objc_selector *)"test", "[email protected]:8", (void *)_I_Person_Test_test}}
};

static struct /*_method_list_t*/ { //Person+Test分類的類方法清單
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"test2", "[email protected]:8", (void *)_C_Person_Test_test2}}
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Person;
//每一個分類都是_category_t結構體的一個執行個體,下面的_category_t結構體的執行個體存儲的是Person+Test分類的資訊。
static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"Person", //Person+Test分類所屬的類的類名
	0, // &OBJC_CLASS_$_Person,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test, //Person+Test分類的執行個體方法清單
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test, //Person+Test分類的類方法清單
	0,
	0,
};
           

接下來分析runtime源碼,runtime源碼中定義的 category_t 結構體 和 上面将分類的.m檔案轉化成.cpp檔案中定義的 _category_t 結構體差别不大。

runtime裡面定義的category_t 的定義如下:

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);
};
           

當一個程式被加載進記憶體并開始運作時,方法的調用順序的時序圖如下

Runtime02KVO和Category原理

上圖的runtime源碼的方法調用如下

程式被加載進記憶體并開始運作時,調用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();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image); //這行是關鍵,map_images是一個函數名
}
           

map_images()函數

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
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); //看這行,images指的是鏡像、子產品,該方法會周遊類的所有分類(每個分類的資訊都由一個category_t結構體的執行個體存儲)
}

           

map_images_nolock()函數部分代碼如下

/***********************************************************************
* map_images_nolock
* Process the given images which are being mapped in by dyld.
* All class registration and fixups are performed (or deferred pending
* discovery of missing superclasses etc), and +load methods are called.
*
* info[] is in bottom-up order i.e. libobjc will be earlier in the 
* array than any library that links to libobjc.
*
* Locking: loadMethodLock(old) or runtimeLock(new) acquired by map_images.
**********************************************************************/
#if __OBJC2__
#include "objc-file.h"
#else
#include "objc-file-old.h"
#endif

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()函數如下

/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked 
* list beginning with headerList. 
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;


#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    // Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);  //一個類的所有分類
        bool hasClassProperties = hi->info()->hasCategoryClassProperties(); //目前分類是否有類屬性

        for (i = 0; i < count; i++) { //周遊一個類的所有分類
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
           
            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);  //把執行個體方法、執行個體屬性和協定都分别添加到類方法清單、屬性清單和協定清單中
                    classExists = YES;
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());  //把類方法、類屬性和協定都分别添加到類的元類的方法清單、屬性清單和協定清單中
                }
            }
        }
    }
#undef EACH_HEADER
}
           

remethodizeClass()代碼如下:

/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
    category_list *cats;
    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {

        attachCategories(cls, cats, true /*flush caches*/);        //把分類的資訊添加到到所屬的類中
        free(cats);
    }
}
           
//看注釋
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    bool isMeta = cls->isMetaClass(); //cls是否是元類

    // fixme rearrange to remove these intermediate allocations
    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 first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) { //從後往前周遊類的所有分類
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data(); //rw是class_rw_t類型,class_rw_t存儲了類的方法清單、屬性清單、協定清單、第一個子類的位址、一個class_ro_t類型的指針。class_ro_t存儲了執行個體對象的占用的記憶體空間大小、類名、成員變量清單

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);  //把分類的執行個體方法清單(或者類方法清單)添加到類的方法清單中(或者元類的方法清單中)
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);//把分類的執行個體屬性清單(或者類屬性清單)添加到類的屬性清單中(或者元類的屬性清單中)
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);//把分類遵守的協定清單添加到類的協定清單中(或者元類的協定清單中)
    free(protolists);
}
           

attachLists()函數如下:

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;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); //重新配置設定記憶體
            array()->count = newCount;
            //array()->lists存的是類的原來的方法
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0])); //把類的方法清單(說白了就是個數組)中原有的方法往後移addedCount個位置,其中addedCount指的是要添加的方法個數。
           
            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 lists
            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;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
}
           
Runtime02KVO和Category原理
Runtime02KVO和Category原理
Runtime02KVO和Category原理