刨根问底Objective-C Runtime.doc_第1页
刨根问底Objective-C Runtime.doc_第2页
刨根问底Objective-C Runtime.doc_第3页
刨根问底Objective-C Runtime.doc_第4页
刨根问底Objective-C Runtime.doc_第5页
已阅读5页,还剩40页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

前言关于Objective-C Runtime一篇好的文档 : Understanding the Objective-C Runtime译文地址为: /blog/2014/10/06/yi-li-jieobjective-cruntime/Objective-C Runtime源码是开源的,下载地址为: /tarballs/objc4/习题内容唐巧_boy在微博上分享了他们技术讨论会关于objc runtime的讨论习题内容,习题来自 sunnyxx(博客)。以下是习题内容(图片转自唐巧_boy微博):自己做完这些题之后,也顺便复习了一些ObjectiveC Runtime的知识,现在整理一下,分享给大家。该笔记分为四篇:刨根问底ObjectiveC Runtime(1) Self & Super刨根问底ObjectiveC Runtime(2) Object & Class & Meta Class刨根问底ObjectiveC Runtime(3) 消息和Category刨根问底ObjectiveC Runtime(4)- 成员变量与属性刨根问底ObjectiveC Runtime(1) Self & Super下面的代码输出什么?implementationSon:Father-(id)initself=superinit;if(self)NSLog(”%”,NSStringFromClass(selfclass);NSLog(”%”,NSStringFromClass(superclass);returnself;end答案:都输出 Son2014-11-0511:06:18.060Test8566:568584NSStringFromClass(selfclass)=Son2014-11-0511:06:18.061Test8566:568584NSStringFromClass(superclass)=Son解惑:这个题目主要是考察关于objc中对 self 和 super 的理解。self 是类的隐藏参数,指向当前调用方法的这个类的实例。而 super 是一个 Magic Keyword, 它本质是一个编译器标示符,和 self 是指向的同一个消息接受者。上面的例子不管调用self class还是super class,接受消息的对象都是当前 Son xxx 这个对象。而不同的是,super是告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。真的是这样吗?继续看:使用clang重写命令:$clang-rewrite-objctest.m发现上述代码被转化为:NSLog(NSString*)&_NSConstantStringImpl_var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0,NSStringFromClass(Class(*)(id,SEL)(void*)objc_msgSend)(id)self,sel_registerName(“class”);NSLog(NSString*)&_NSConstantStringImpl_var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1,NSStringFromClass(Class(*)(_rw_objc_super*,SEL)(void*)objc_msgSendSuper)(_rw_objc_super)(id)self,(id)class_getSuperclass(objc_getClass(“Son”),sel_registerName(“class”);从上面的代码中,我们可以发现在调用 self class 时,会转化成 objc_msgSend函数。看下函数定义:idobjc_msgSend(idself,SELop,.)我们把 self 做为第一个参数传递进去。而在调用 super class时,会转化成 objc_msgSendSuper函数。看下函数定义:idobjc_msgSendSuper(structobjc_super*super,SELop,.)第一个参数是 objc_super 这样一个结构体,其定义如下:structobjc_super_unsafe_unretainedidreceiver;_unsafe_unretainedClasssuper_class;结构体有两个成员,第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self 。第二个成员是记录当前类的父类是什么。所以,当调用 self class 时,实际先调用的是 objc_msgSend函数,第一个参数是 Son当前的这个实例,然后在 Son 这个类里面去找 - (Class)class这个方法,没有,去父类 Father里找,也没有,最后在 NSObject类中发现这个方法。而 - (Class)class的实现就是返回self的类别,故上述输出结果为 Son。objc Runtime开源代码对- (Class)class方法的实现:-(Class)classreturnobject_getClass(self);而当调用 super class时,会转换成objc_msgSendSuper函数。第一步先构造 objc_super 结构体,结构体第一个成员就是 self 。第二个成员是 (id)class_getSuperclass(objc_getClass(“Son”) , 实际该函数输出结果为 Father。第二步是去 Father这个类里去找- (Class)class,没有,然后去NSObject类去找,找到了。最后内部是使用 objc_msgSend(objc_super-receiver, selector(class)去调用,此时已经和self class调用相同了,故上述输出结果仍然返回 Son。刨根问底ObjectiveC Runtime(2) Object & Class & Meta Clas本篇笔记主要是讲述objc runtime中关于Object & Class & Meta Class的细节。习题内容下面代码的运行结果是?interfaceSark:NSObjectendimplementationSarkendintmain(intargc,constchar*argv)autoreleasepoolBOOLres1=(id)NSObjectclassisKindOfClass:NSObjectclass;BOOLres2=(id)NSObjectclassisMemberOfClass:NSObjectclass;BOOLres3=(id)SarkclassisKindOfClass:Sarkclass;BOOLres4=(id)SarkclassisMemberOfClass:Sarkclass;NSLog(”%d%d%d%d”,res1,res2,res3,res4);return0;运行结果为:2014-11-0514:45:08.474Test9412:7219451000这里先看几个概念什么是 idid 在 objc.h 中定义如下:/Apointertoaninstanceofaclass.typedefstructobjc_object*id;就像注释中所说的这样 id 是指向一个 objc_object 结构体的指针。id 这个struct的定义本身就带了一个 *, 所以我们在使用其他NSObject类型的实例时需要在前面加上 *, 而使用 id 时却不用。那么objc_object又是什么呢objc_object 在 objc.h 中定义如下:/Representsaninstanceofaclass.structobjc_objectClassisa;这个时候我们知道Objective-C中的object在最后会被转换成C的结构体,而在这个struct中有一个 isa 指针,指向它的类别 Class。那么什么是Class呢在 objc.h 中定义如下:/AnopaquetypethatrepresentsanObjective-Cclass.typedefstructobjc_class*Class;我们可以看到 Class本身指向的也是一个C的struct objc_class。继续看在runtime.h中objc_class定义如下:structobjc_classClassisaOBJC_ISA_AVAILABILITY;#if!_OBJC2_Classsuper_classOBJC2_UNAVAILABLE;constchar*nameOBJC2_UNAVAILABLE;longversionOBJC2_UNAVAILABLE;longinfoOBJC2_UNAVAILABLE;longinstance_sizeOBJC2_UNAVAILABLE;structobjc_ivar_list*ivarsOBJC2_UNAVAILABLE;structobjc_method_list*methodListsOBJC2_UNAVAILABLE;structobjc_cache*cacheOBJC2_UNAVAILABLE;structobjc_protocol_list*protocolsOBJC2_UNAVAILABLE;#endifOBJC2_UNAVAILABLE;该结构体中,isa 指向所属Class, super_class指向父类别。继续看下载objc源代码,在 objc-runtime-new.h 中,我们发现 objc_class有如下定义:structobjc_class:objc_object/ClassISA;Classsuperclass;.豁然开朗,我们看到在Objective-C的设计哲学中,一切都是对象。Class在设计中本身也是一个对象。而这个Class对象的对应的类,我们叫它 Meta Class。即Class结构体中的 isa 指向的就是它的 Meta Class。Meta Class根据上面的描述,我们可以把Meta Class理解为 一个Class对象的Class。简单的说:当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类的方法列表里查找当我们发送一个消息给一个类时,这条消息会在类的MetaClass的方法列表里查找而 Meta Class本身也是一个Class,它跟其他Class一样也有自己的 isa 和 super_class 指针。看下图:每个Class都有一个isa指针指向一个唯一的Meta Class每一个Meta Class的isa指针都指向最上层的Meta Class(图中的NSObject的Meta Class)最上层的Meta Class的isa指针指向自己,形成一个回路每一个Meta Class的super class指针指向它原本Class的 Super Class的Meta Class。但是最上层的Meta Class的 Super Class指向NSObject Class本身最上层的NSObject Class的super class指向 nil解惑为了更加清楚的知道整个函数调用过程,我们使用clang -rewrite-objc main.m重写,可获得如下代码:BOOLres1=(BOOL(*)(id,SEL,Class)(void*)objc_msgSend)(id)(Class(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“NSObject”),sel_registerName(“class”),sel_registerName(“isKindOfClass:”),(Class(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“NSObject”),sel_registerName(“class”);BOOLres2=(BOOL(*)(id,SEL,Class)(void*)objc_msgSend)(id)(Class(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“NSObject”),sel_registerName(“class”),sel_registerName(“isMemberOfClass:”),(Class(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“NSObject”),sel_registerName(“class”);BOOLres3=(BOOL(*)(id,SEL,Class)(void*)objc_msgSend)(id)(Class(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“Sark”),sel_registerName(“class”),sel_registerName(“isMemberOfClass:”),(Class(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“NSObject”),sel_registerName(“class”);BOOLres4=(BOOL(*)(id,SEL,Class)(void*)objc_msgSend)(id)(Class(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“Sark”),sel_registerName(“class”),sel_registerName(“isMemberOfClass:”),(Class(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“NSObject”),sel_registerName(“class”);先看前两个调用:最外层是 objc_msgSend函数,转发消息。函数第一个参数是 (id)(Class (*)(id, SEL)(void *)objc_msgSend)(id)objc_getClass(“NSObject”), sel_registerName(“class”)函数第二个参数是转发的selector函数第三个参数是 (Class (*)(id, SEL)(void *)objc_msgSend)(id)objc_getClass(“NSObject”), sel_registerName(“class”)我们注意到第一个参数和第三个参数对应重写的是NSObject class,即使用objc_msgSend向 NSObject Class 发送 selector(class) 这个消息打开objc源代码,在 Object.mm 中发现+ (Class)class实现如下:+(Class)classreturnself;所以即返回Class类的对象本身。看如下输出:NSLog(”%p”,NSObjectclass);NSLog(”%p”,NSObjectclass);2014-11-0518:48:30.939Test11682:8659880x7fff768d40f02014-11-0518:48:30.940Test11682:8659880x7fff768d40f0继续打开objc源代码,在 Object.mm 中,我们发现 isKindOfClass的实现如下:-(BOOL)isKindOf:aClassClasscls;for(cls=isa;cls;cls=cls-superclass)if(cls=(Class)aClass)returnYES;returnNO;对着上面Meta Class的图和实现,我们可以看出当 NSObject Class对象第一次进行比较时,得到它的isa为 NSObject的Meta Class, 这个时候 NSObject Meta Class 和 NSObject Class不相等。然后取NSObject 的Meta Class 的Super class,这个时候又变成了 NSObject Class, 所以返回相等。所以上述第一个输出结果是 YES 。我们在看下 isMemberOfClass的实现:-(BOOL)isMemberOf:aClassreturnisa=(Class)aClass;综上所述,当前的 isa 指向 NSObject 的 Meta Class, 所以和 NSObject Class不相等。所以上述第二个输出结果为 NO 。继续看后面两个调用:Sark Class 的isa指向的是 Sark的Meta Class,和Sark Class不相等Sark Meta Class的super class 指向的是 NSObject Meta Class, 和 Sark Class不相等NSObject Meta Class的 super class 指向 NSObject Class,和 Sark Class 不相等NSObject Class 的super class 指向 nil, 和 Sark Class不相等所以后面两个调用的结果都输出为 NO 。刨根问底ObjectiveC Runtime(3) 消息 和 Category本篇笔记主要是讲述objc runtime的 消息和Category。习题内容下面的代码会?Compile Error / Runtime Crash / NSLog?interfaceNSObject(Sark)+(void)foo;endimplementationNSObject(Sark)-(void)fooNSLog(”IMP:-NSObject(Sark)foo”);endintmain(intargc,constchar*argv)autoreleasepoolNSObjectfoo;NSObjectnewfoo;return0;答案:代码正常输出,输出结果如下:2014-11-0613:11:46.694Test14872:1110786IMP:-NSObject(Sark)foo2014-11-0613:11:46.695Test14872:1110786IMP:-NSObject(Sark)foo使用clang -rewrite-objc main.m重写,我们可以发现 main 函数中两个方法调用被转换成如下代码:(void(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“NSObject”),sel_registerName(“foo”);(void(*)(id,SEL)(void*)objc_msgSend)(id)(NSObject*(*)(id,SEL)(void*)objc_msgSend)(id)objc_getClass(“NSObject”),sel_registerName(“new”),sel_registerName(“foo”);我们发现上述两个方法最终转换成使用 objc_msgSend 函数传递消息。这里先看几个概念objc_msgSend函数定义如下:idobjc_msgSend(idself,SELop,.)关于 id 的解释请看objc runtime系列第二篇博文: objc runtime中Object & Class & Meta Class的细节什么是 SEL打开objc.h文件,看下SEL的定义如下:typedefstructobjc_selector*SEL;SEL是一个指向objc_selector结构体的指针。而 objc_selector 的定义并没有在runtime.h中给出定义。我们可以尝试运行如下代码:SELsel=selector(foo);NSLog(”%s”,(char*)sel);NSLog(”%p”,sel);constchar*selName=”foo”UTF8String;SELsel2=sel_registerName(selName);NSLog(”%s”,(char*)sel2);NSLog(”%p”,sel2);输出如下:2014-11-0613:46:08.058Test15053:1132268foo2014-11-0613:46:08.058Test15053:11322680x7fff8fde51142014-11-0613:46:08.058Test15053:1132268foo2014-11-0613:46:08.058Test15053:11322680x7fff8fde5114Objective-C在编译时,会根据方法的名字生成一个用来区分这个方法的唯一的一个ID。只要方法名称相同,那么它们的ID就是相同的。两个类之间,不管它们是父类与子类的关系,还是之间没有这种关系,只要方法名相同,那么它的SEL就是一样的。每一个方法都对应着一个SEL。编译器会根据每个方法的方法名为那个方法生成唯一的SEL。这些SEL组成了一个Set集合,当我们在这个集合中查找某个方法时,只需要去找这个方法对应的SEL即可。而SEL本质是一个字符串,所以直接比较它们的地址即可。当然,不同的类可以拥有相同的selector。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP。那么什么是IMP呢继续看定义:typedefid(*IMP)(id,SEL,.);IMP本质就是一个函数指针,这个被指向的函数包含一个接收消息的对象id,调用方法的SEL,以及一些方法参数,并返回一个id。因此我们可以通过SEL获得它所对应的IMP,在取得了函数指针之后,也就意味着我们取得了需要执行方法的代码入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。那么 objc_msgSend 到底是怎么工作的呢?在Objective-C中,消息直到运行时才会绑定到方法的实现上。编译器会把代码中target doSth转换成 objc_msgSend消息函数,这个函数完成了动态绑定的所有事情。它的运行流程如下:检查selector是否需要忽略。(ps: Mac开发中开启GC就会忽略retain,release方法。)检查target是否为nil。如果为nil,直接cleanup,然后return。(这就是我们可以向nil发送消息的原因。)然后在target的Class中根据Selector去找IMP寻找IMP的过程:先从当前class的cache方法列表(cache methodLists)里去找找到了,跳到对应函数实现没找到,就从class的方法列表(methodLists)里找还找不到,就到super class的方法列表里找,直到找到基类(NSObject)为止最后再找不到,就会进入动态方法解析和消息转发的机制。(这部分知识,下次再细谈)那么什么是方法列表呢?上一篇博文中提到了objc_class结构体定义,如下:structobjc_classClassisaOBJC_ISA_AVAILABILITY;#if!_OBJC2_Classsuper_classOBJC2_UNAVAILABLE;constchar*nameOBJC2_UNAVAILABLE;longversionOBJC2_UNAVAILABLE;longinfoOBJC2_UNAVAILABLE;longinstance_sizeOBJC2_UNAVAILABLE;structobjc_ivar_list*ivarsOBJC2_UNAVAILABLE;structobjc_method_list*methodListsOBJC2_UNAVAILABLE;structobjc_cache*cacheOBJC2_UNAVAILABLE;structobjc_protocol_list*protocolsOBJC2_UNAVAILABLE;#endifOBJC2_UNAVAILABLE;structobjc_method_liststructobjc_method_list*obsoleteOBJC2_UNAVAILABLE;intmethod_countOBJC2_UNAVAILABLE;#ifdef_LP64_intspaceOBJC2_UNAVAILABLE;#endif/*variablelengthstructure*/structobjc_methodmethod_list1OBJC2_UNAVAILABLE;1) objc_method_list 就是用来存储当前类的方法链表,objc_method存储了类的某个方法的信息。Methodtypedefstructobjc_method*Method;Method 是用来代表类中某个方法的类型,它实际就指向objc_method结构体,如下:structobjc_methodSELmethod_nameOBJC2_UNAVAILABLE;char*method_typesOBJC2_UNAVAILABLE;IMPmethod_impOBJC2_UNAVAILABLE;OBJC2_UNAVAILABLE;method_types是个char指针,存储着方法的参数类型和返回值类型。SEL 和 IMP 就是我们上文提到的,所以我们可以理解为objc_class中 method list保存了一组SELIMP的映射。2)objc_cache 用来缓存用过的方法,提高性能。Cachetypedefstructobjc_cache*CacheOBJC2_UNAVAILABLE;实际指向objc_cache结构体,如下:structobjc_cacheunsignedintmask/*total=mask+1*/OBJC2_UNAVAILABLE;unsignedintoccupiedOBJC2_UNAVAILABLE;Methodbuckets1OBJC2_UNAVAILABLE;mask: 指定分配cache buckets的总数。在方法查找中,Runtime使用这个字段确定数组的索引位置occupied: 实际占用cache buckets的总数buckets: 指定Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。objc_msgSend每调用一次方法后,就会把该方法缓存到cache列表中,下次的时候,就直接优先从cache列表中寻找,如果cache没有,才从methodLists中查找方法。说完了 objc_msgSend, 那么题目中的Category又是怎么工作的呢?继续看概念我们知道Catagory可以动态地为已经存在的类添加新的方法。这样可以保证类的原始设计规模较小,功能增加时再逐步扩展。在runtime.h中查看定义:typedefstructobjc_category*Category;同样也是指向一个 objc_category 的C 结构体,定义如下:structobjc_categorychar*category_nameOBJC2_UNAVAILABLE;char*class_nameOBJC2_UNAVAILABLE;structobjc_method_list*instance_methodsOBJC2_UNAVAILABLE;structobjc_method_list*class_methodsOBJC2_UNAVAILABLE;structobjc_protocol_list*protocolsOBJC2_UNAVAILABLE;OBJC2_UNAVAILABLE;通过上面的结构体,大家可以很清楚的看出存储的内容。我们继续往下看,打开objc源代码,在 objc-runtime-new.h中我们可以发现如下定义:structcategory_tconstchar*name;classref_tcls;structmethod_list_t*instanceMethods;structmethod_list_t*classMethods;structprotocol_list_t*protocols;structproperty_list_t*instanceProperties;上面的定义需要提到的地方有三点:name 是指 class_name 而不是 category_namecls是要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对应到对应的类对象instanceProperties表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的为了验证上述内容,我们使用clang -rewrite-objc main.m重写,题目中的Category被编译器转换成了这样:/interfaceNSObject(Sark)/+(void)foo;/*end*/implementationNSObject(Sark)staticvoid_I_NSObject_Sark_foo(NSObject*self,SEL_cmd)NSLog(NSString*)&_NSConstantStringImpl_var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_dd1ee3_mi_0);/endstaticstruct_category_t_OBJC_$_CATEGORY_NSObject_$_Sark_attribute_(used,section(“_DATA,_objc_const”)=“NSObject”,0,/&OBJC_CLASS_$_NSObject,(conststruct_method_list_t*)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_Sark,0,0,0,;staticstruct_category_t*L_OBJC_LABEL_CATEGORY_$1_attribute_(used,section(“_DATA,_objc_catlist,regular,no_dead_strip”)=&_OBJC_$_CATEGORY_NSObject_$_Sark,;_OBJC_$_CATEGORY_NSObject_$_Sark是按规则生成的字符串,我们可以清楚的看到是NSObject类,且Sark是NSObject类的Category_category_t结构体第二项 classref_t 没有数据,验证了我们上面的说法由于题目中只有 - (void)foo方法,所以结构体中存储的list只有第三项instanceMethods被填充。_I_NSObject_Sark_foo代表了Category的foo方法,I表示实例方法最后这个类的Category生成了一个数组,存在了_objc_catlist里,目前数组的内容只有一个&_OBJC_$_CATEGORY_NSObject_$_Sark最终这些Category里面的方法是如何被加载的呢?1.打开objc源代码,找到 objc-os.mm, 函数_objc_init为runtime的加载入口,由libSystem调用,进行初始化操作。2.之后调用objc-runtime-new.mm - map_images加载map到内存3.之后调用objc-runtime-new.mm-_read_images初始化内存中的map, 这个时候将会load所有的类,协议还有Category。NSOBject的+load方法就是这个时候调用的这里贴上Category被加载的代码:/Discovercategories.for(EACH_HEADER)category_t*catlist=_getObjc2CategoryList(hi,&count);for(i=0;icls);if(!cls)/Categorystargetclassismissing(probablyweak-linked)./Disavowanyknowledgeofthiscategory.catlisti=nil;if(PrintConnecting)_objc_inform(“CLASS:IGNORINGcategory?(%s)%pwith”“missingweak-linkedtargetclass”,cat-name,cat);continue;/Processthiscategory./First,registerthecategorywithitstargetclass./Then,rebuildtheclasssmethodlists(etc)if/theclassisrealized.BOOLclassExists=NO;if(cat-instanceMethods|cat-protocols|cat-instanceProperties)addUnattachedCategoryForClass(cat,cls,hi);if(cls-isRealized()remethodizeClass(cls);classExists=YES;if(PrintConnecting)_objc_inform(“CLASS:foundcategory-%s(%s)%s”,cls-nameForLogging(),cat-name,classExists?”onexistingclass”:”“);if(cat-classMethods|cat-protocols/*|cat-classProperties*/)addUnattachedCategoryForClass(cat,cls-ISA(),hi);if(cls-ISA()-isRealized()remethodizeClass(cls-ISA();if(PrintConnecting)_objc_inform(“CLASS:foundcategory+%s(%s)”,cls-nameForLogging(),cat-name);1) 循环调用了 _getObjc2CategoryList方法,这个方法的实现是:GETSECT(_getObjc2CategoryList,category_t*,”_objc_catlist”);方法中最后一个参数_objc_catlist就是编译器刚刚生成的category数组2) load完所有的categories之后,开始对Category进行处理。从上面的代码中我们可以发现:实例方法被加入到了当前的类对象中, 类方法被加入到了当前类的Meta Class中 (cls-ISA)Step 1. 调用addUnattachedCategoryForClass方法Step 2. 调用remethodizeClass方法, 在remethodizeClass的实现里调用attachCategoryMethodsstaticvoidattachCategoryMethods(Classcls,category_list*cats,boolflushCaches)if(!cats)return;if(PrintReplacedMethods)printReplacements(cls,cats);boolisMeta=cls-isMetaClass();method_list_t*mlists=(method_list_t*)_malloc_internal(cats-count*sizeof(*mlists);/Countbackwardsthroughcatstogetnewestcategoriesfirstintmcount=0;inti=cats-count;BOOLfromBundle=NO;while(i-)method_list_t*mlist=cat_method_list(cats-listi.cat,isMeta);if(mlist)mlistsmcount+=mlist;fromBundle|=cats-listi.fromBundle;attachMethodLists(cls,mlists,mcount,NO,fromBundle,flushCaches);_free_internal(mlists);这里把一个类的category_list的所有方法取出来生成了method list。这里是倒序添加的,也就是说,新生成的category的方法会先于旧的category的方法插入。之后调用attachMethodLists将所有方法前序添加进类的method list中,如果原来类的方法列表是a,b,Category的方法列表是c,d。那么插入之后的方法列表将会是c,d,a,b。小发现看上面被编译器转换的代码,我们发现Category头文件被注释掉了,结合上面category的加载过程。这就是我们即使没有import category的头文件,都能够成功调用到Category方法的原因。runtime加载完成后,Category的原始信息在类结构中将不会存在。解惑根据上面提到的知识,我们对题目中的代码进行分析。1) objc runtime加载完后,NSObject的Sark Category被加载。而NSObject的Sark Category的头文件 + (void)foo 并没有实质参与到工作中,只是给编译器进行静态检查,所有我们编译上述代码会出现警告,提示我们没有实现 + (void)foo 方法。而在代码编译中,它已经被注释掉了。2) 实际被加入到Class的method list的方法是 - (void)foo,它是一个实例方法,所以加入到当前类对象NSObject的方法列表中,而不是NSObject Meta class的方法列表中。3) 当执行 NSObject foo时,我们看下整个objc_msgSend的过程:结合上一篇Meta Class的知识:objc_msgSend 第一个参数是 “(id)objc_getClass(“NSObject”)”,获得NSObject Class的对象。类方法在Meta Class的方法列表中找,我们在load Category方法时加入的是- (void)foo实例方法,所以并不在NSOBject Meta Class的方法列表中继续往 super class中找,在上一篇博客中我们知道,NSObject Meta Class的super class是NSObject本身。所以,这个时候我们能够找到- (void)foo 这个方法。所以正常输出结果4) 当执行NSObject new foo,我们看下整个objc_msgSend的过程:NSObject new生成一个NSObject对象。直接在该对象的类(NSObject)的方法列表里找。能够找到,所以正常输出结果。刨根问底ObjectiveC Runtime(4) 成员变量与属性本篇笔记主要是讲述objc runtime的 成员变量和属性。习题内容下面代码会? Compile Error / Runtime Crash / NSLog?interfaceSark:NSObjectproperty(nonatomic,copy)NSString*name;endimplementationSark-(void)speakNSLog(”mynameis%”,);endinterfaceTest:NSObjectendimplementationTest-(instancetype)initself=superinit;if(self)idcls=Sarkclass;void*obj=&cls;(_bridgeid)objspeak;returnself;endintmain(intargc,constchar*argv)autoreleasepoo

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论