1,系统分配了16个字节给NSObject对象,通过malloc_size函数获得,但对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)。
2,instance对象的isa指针指向class对象; class对象的isa指向meta-class对象;meta-class对象的isa指向基类的meta-class对象。
3,OC类信息:
>>>对象方法、属性、成员变量、协议信息,存放在class对象中。
>>>类方法存放在meta-class对象中。
>>>成员变量的具体值,存放在instance对象
4,KVO实现的本质:利用Runtime API动态生成一个子类,并且让instance对象的isa指向一个全新的子类,当此对象属性key被修改时,会调用Foundation的_NSSet(key属性名)ValueAndNotify函数,一系列方法如下:willChangeValueForKey:--->父类原来的setter--->didChangeValueForKey:--->内部会触发监听器(observe)的监听方法(observeValueForKeyPath:ofObject:change:context)
如果我们要手动触发KVO,要手动调用willChangeValueForKey:和didChangeValueForKey:
***直接修改成员变量,是不会触发KVO的,因为是直接通过变量指针地址的方式修改
5,setValue:forKey:原理
当我们通过KVC调用setValue:forKey:时,会按照setKey:和_setKey:的顺序查找方法,(如果找到方法会传参调用)如果没有找到就查看accessInstanceVariablesDirectly方法的返回值A,如果A = YES,就会按照_key---> _isKey---> key ---> isKey顺序查找成员变量,如果找到成员变量就会直接赋值。 如果没有找到以及上一步骤A=NO时,都会调用setValue:forUndefinedKey:方法,如果没有实现此方法,就会抛出异常NSUnknownKeyException。
valueForKey:的原理:
当我们通过valueForKey:获取对应属性值时,路径同上述赋值。按照getKey,key,isKey,_key顺序查找方法,(如果找到方法直接调用结束)如果没找到方法,会查看accessInstanceVariableDirectly方法的返回值isA,如果isA=YES,按照_key--->_isKey--->key--->isKey的顺序查找成员变量,如果找到成员变量直接赋值。如果没有找到了成员变量以及上一步骤isA=NO,就会调用valueForUndefinedKey:方法,如果没有实现此方法,就会抛出异常NSUnknownKeyException
6,+load方法会在runtime加载类和分类时调用; 每个类、分类的+load,在程序运行过程中只调用一次;调用顺序:1,先调用类的+load,按照编译先后顺序调用(先编译先调用),调用子类的+load之前,会先调用的+load; 2,再调用分类的+load,按照编译先后顺序调用(同样先编译先调用)
objc4源码解读:_objc_init--->load_images--->prepare_load_methods(--->schedule_class_load--->add_class_to_loadable_list--->add_category_to_loadable_list)--->call_load_methods(--->call_class_loads--->call_category_loads--->(*load_method)(cls, SEL_load))
***+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
7,Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息。在程序运行的时候,runtime会将Category的数据合并到类信息(类对象,元类对象)中。
Category和Class Extension的区别是:Class Extension在编译的时候,它的数据就已经包含在类信息中;而Category是在运行时,才会将数据合并到类信息中。
8,+initialize方法会在类第一次接收到消息时调用,当然也会先调用父类的+initialize。objc4源码解读过程:objc_msgSend--->class_getInstanceMethod--->lookUpImpOrNil--->_class_initialize--->callInitialize--->objc_msgSend(cls, SEL_initialize)
9, +initialize和+load的很大区别是:+initialize是通过objc_msgSend进行调用的,所以有以下特点:a,如果子类没有实现+initialize,就会调用父类的+initialize(所以父类的+initialize可能会被调用多次)b,如果分类实现了+initialize,就覆盖类本身的+initialize调用。
>>> load是根据函数地址直接调用,runtime加载类、分类的时候调用(只会调用1次)
10,Class内部结构有一个方法缓存(cache_t), 用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。
11,method_t 是对方法\函数的封装,struct method_t {SEL name; //函数名 const char *types; //返回值类型、参数类型 IMP imp;// 指向函数的指针}
SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似;可以通过@selector()和sel_registerName()获得,可以通过sel_getName和NSStringFromSelector()转成字符串;
不同类中相同名字的方法,所对应的方法选择器是相同的。
typedef struct objc_selector *SEL;
12 ,@dynamic 是提醒编译器不要自动生成setter和getter的实现,不要自动生成成员变量
13,利用runtime性实现方法交换,避免数组的越界和字典中key为nil导致的崩溃
网友评论