转:Cocoa Runtime系统知识整理

本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具有了灵活的动态特性,使这门古老的语言焕发生机。主要内容以下:程序员

  • 引言objective-c

  • 简介算法

  • 与Runtime交互编程

  • Runtime术语数组

  • 消息缓存

  • 动态方法解析数据结构

  • 消息转发框架

  • 健壮的实例变量(Non Fragile ivars)函数

  • Objective-C Associated Objects布局

  • 总结

引言

曾经以为Objc特别方便上手,面对着 Cocoa 中大量 API,只知道简单的查文档和调用。还记得初学 Objective-C 时把[receiver message]当成简单的方法调用,而无视了“发送消息”这句话的深入含义。因而[receiver message]会被编译器转化为:

objc_msgSend(receiver, selector)

若是消息含有参数,则为:

objc_msgSend(receiver, selector, arg1, arg2, ...)

若是消息的接收者可以找到对应的selector,那么就至关于直接执行了接收者这个对象的特定方法;不然,消息要么被转发,或是临时向接收者动态添加这个selector对应的实现内容,要么就干脆玩完崩溃掉。

如今能够看出[receiver message]真的不是一个简简单单的方法调用。由于这只是在编译阶段肯定了要向接收者发送message这条消息,而receive将要如何响应这条消息,那就要看运行时发生的状况来决定了。

Objective-C 的 Runtime 铸就了它动态语言的特性,这些深层次的知识虽然平时写代码用的少一些,可是倒是每一个 Objc 程序员须要了解的。

简介

由于Objc是一门动态语言,因此它老是想办法把一些决定工做从编译链接推迟到运行时。也就是说只有编译器是不够的,还须要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。

Runtime其实有两个版本:“modern”和 “legacy”。咱们如今用的 Objective-C 2.0 采用的是现行(Modern)版的Runtime系统,只能运行在 iOS 和 OS X 10.5 以后的64位程序中。而OS X较老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你须要从新编译它的子类,而现行版就不须要。

Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而做出的努力。你能够在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。

与Runtime交互

Objc 从三种不一样的层级上与 Runtime 系统进行交互,分别是经过 Objective-C 源代码,经过 Foundation 框架的NSObject类定义的方法,经过对 runtime 函数的直接调用。

Objective-C源代码

大部分状况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳做着。

还记得引言中举的例子吧,消息的执行会使用到一些编译器为实现动态语言特性而建立的数据结构和函数,Objc中的类、方法和协议等在 runtime 中都由一些数据结构来定义,这些内容在后面会讲到。(好比objc_msgSend函数及其参数列表中的id和SEL都是啥)

NSObject的方法

Cocoa 中大多数类都继承于NSObject类,也就天然继承了它的方法。最特殊的例外是NSProxy,它是个抽象超类,它实现了一些消息转发有关的方法,能够经过继承它来实现一个其余类的替身类或是虚拟出一个不存在的类,说白了就是领导把本身展示给你们风光无限,可是把活儿都交给幕后小弟去干。

有的NSObject中的方法起到了抽象接口的做用,好比description方法须要你重载它并为你定义的类提供描述内容。NSObject还有些方法能在运行时得到类的信息,并检查一些特性,好比class返回对象的类;isKindOfClass:isMemberOfClass:则检查对象是否在指定的类继承体系中;respondsToSelector:检查对象可否响应指定的消息;conformsToProtocol:检查对象是否实现了指定协议类的方法;methodForSelector:则返回指定方法实现的地址。

Runtime的函数

Runtime 系统是一个由一系列函数和数据结构组成,具备公共接口的动态共享库。头文件存放于/usr/include/objc目录下。许多函数容许你用纯C代码来重复实现 Objc 中一样的功能。虽然有一些方法构成了NSObject类的基础,可是你在写 Objc 代码时通常不会直接用到这些函数的,除非是写一些 Objc 与其余语言的桥接或是底层的debug工做。在Objective-C Runtime Reference中有对 Runtime 函数的详细文档。

Runtime术语

还记得引言中的objc_msgSend:方法吧,它的真身是这样的:

id objc_msgSend ( id self, SEL op, ... );

下面将会逐渐展开介绍一些术语,其实它们都对应着数据结构。

SEL

objc_msgSend函数第二个参数类型为SEL,它是selector在Objc中的表示类型(Swift中是Selector类)。selector是方法选择器,能够理解为区分方法的 ID,而这个 ID 的数据结构是SEL:

typedef struct objc_selector *SEL;

其实它就是个映射到方法的C字符串,你能够用 Objc 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来得到一个SEL类型的方法选择器。

不一样类中相同名字的方法所对应的方法选择器是相同的,即便方法名字相同而变量类型不一样也会致使它们具备相同的方法选择器,因而 Objc 中方法命名有时会带上参数类型(NSNumber一堆抽象工厂方法拿走不谢),Cocoa 中有好多长长的方法哦。

id

objc_msgSend第一个参数类型为id,你们对它都不陌生,它是一个指向类实例的指针:

typedef struct objc_object *id;

objc_object又是啥呢:

struct objc_object { Class isa; };

objc_object结构体包含一个isa指针,根据isa指针就能够顺藤摸瓜找到对象所属的类。

Class

之因此说isa是指针是由于Class实际上是一个指向objc_class结构体的指针:

typedef struct objc_class *Class;

objc_class就是咱们摸到的那个瓜,里面的东西多着呢:

struct objc_class {
 Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
 Class super_class                                     OBJC2_UNAVAILABLE;
 const char *name                                   OBJC2_UNAVAILABLE;
 long version                                              OBJC2_UNAVAILABLE;
 long info                                                      OBJC2_UNAVAILABLE;
 long instance_size                                     OBJC2_UNAVAILABLE;
 struct objc_ivar_list *ivars                          OBJC2_UNAVAILABLE;
 struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;
 struct objc_cache *cache                      OBJC2_UNAVAILABLE;
 struct objc_protocol_list *protocols        OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

能够看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。
其中objc_ivar_listobjc_method_list分别是成员变量列表和方法列表:

struct objc_ivar_list {
 int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
 int space                                                  OBJC2_UNAVAILABLE;
#endif
 /* variable length structure */
 struct objc_ivar ivar_list[1]                      OBJC2_UNAVAILABLE;
}

struct objc_method_list {
 struct objc_method_list *obsolete        OBJC2_UNAVAILABLE;

 int method_count                                    OBJC2_UNAVAILABLE;
#ifdef __LP64__
 int space                                                    OBJC2_UNAVAILABLE;
#endif
 /* variable length structure */
 struct objc_method method_list[1]       OBJC2_UNAVAILABLE;
}

若是你C语言不是特别好,能够直接理解为objc_ivar_list结构体存储着objc_ivar数组列表,而objc_ivar结构体存储了类的单个成员变量的信息;同理objc_method_list结构体存储着objc_method数组列表,而objc_method结构体存储了类的某个方法的信息。

最后要提到的还有一个objc_cache,顾名思义它是缓存,它在objc_class的做用很重要,在后面会讲到。

一个 ObjC 类同时也是一个对象,为了处理类和对象的关系,runtime 库建立了一种叫作元类 (Meta Class) 的东西。当你发出一个相似[NSObject alloc]的消息时,你事实上是把这个消息发给了一个类对象 (Class Object) ,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。你会说 NSObject 的子类时,你的类就会指向 NSObject 作为其超类。可是全部的元类最终都指向根元类为其超类。全部的元类的方法列表都有可以响应消息的类方法。因此当 [NSObject alloc] 这条消息发给类对象的时候,objc_msgSend()会去它的元类里面去查找可以响应消息的方法,若是找到了,而后对这个类对象执行方法调用。

Method

Method是一种表明类中的某个方法的类型。

typedef struct objc_method *Method;

objc_method在上面的方法列表中提到过,它存储了方法名,方法类型和方法实现:

struct objc_method {
 SEL method_name                             OBJC2_UNAVAILABLE;
 char *method_types                          OBJC2_UNAVAILABLE;
 IMP method_imp                                OBJC2_UNAVAILABLE;
}
  1. 方法名类型为SEL,前面提到过相同名字的方法即便在不一样类中定义,它们的方法选择器也相同。

  2. 方法类型method_types是个char指针,其实存储着方法的参数类型和返回值类型。

  3. method_imp指向了方法的实现,本质上是一个函数指针,后面会详细讲到。

Ivar

Ivar是一种表明类中实例变量的类型。

typedef struct objc_ivar *Ivar;

而objc_ivar在上面的成员变量列表中也提到过:

struct objc_ivar {
 char *ivar_name                              OBJC2_UNAVAILABLE;
 char *ivar_type                                 OBJC2_UNAVAILABLE;
 int ivar_offset                                     OBJC2_UNAVAILABLE;
#ifdef __LP64__
 int space                                            OBJC2_UNAVAILABLE;
#endif
}

PS:OBJC2_UNAVAILABLE之类的宏定义是苹果在 Objc 中对系统运行版本进行约束的黑魔法,有兴趣的能够查看源代码。

IMP

IMP在objc.h中的定义是:

typedef id (*IMP)(id, SEL, ...);

它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息以后,最终它会执行的那段代码,就是由这个函数指针指定的。而IMP这个函数指针就指向了这个方法的实现。既然获得了执行某个实例某个方法的入口,咱们就能够绕开消息传递阶段,直接执行方法,这在后面会提到。
你会发现IMP指向的方法与objc_msgSend函数类型相同,参数都包含id和SEL类型。每一个方法名都对应一个SEL类型的方法选择器,而每一个实例对象中的SEL对应的方法实现确定是惟一的,经过一组id和SEL参数就能肯定惟一的方法实现地址;反之亦然。

Cache

在runtime.h中Cache的定义以下:

typedef struct objc_cache *Cache

还记得以前objcclass结构体中有一个struct objc_cache *cache吧,它究竟是缓存啥的呢,先看看objccache的实现:

struct objc_cache {
 unsigned int mask /* total = mask + 1 */       OBJC2_UNAVAILABLE;
 unsigned int occupied                                    OBJC2_UNAVAILABLE;
 Method buckets[1]                                          OBJC2_UNAVAILABLE;
};

Cache为方法调用的性能进行优化,通俗地讲,每当实例对象接收到一个消息时,它不会直接在isa指向的类的方法列表中遍历查找可以响应消息的方法,由于这样效率过低了,而是优先在Cache中查找。Runtime 系统会把被调用的方法存到Cache中(理论上讲一个方法若是被调用,那么它有可能从此还会被调用),下次查找的时候效率更高。这根计算机组成原理中学过的 CPU 绕过主存先访问Cache的道理挺像,而我猜苹果为提升Cache命中率应该也作了努力吧。

消息

前面作了这么多铺垫,如今终于说到了消息了。Objc 中发送消息是用中括号([])把接收者和消息括起来,而直到运行时才会把消息与方法实现绑定。

objc_msgSend函数

在引言中已经对objc_msgSend进行了一点介绍,看起来像是objc_msgSend返回了数据,其实objc_msgSend从不返回数据而是你的方法被调用后返回了数据。下面详细叙述下消息发送步骤:

  1. 检测这个 selector 是否是要忽略的。好比 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数了。

  2. 检测这个 target 是否是 nil 对象。ObjC 的特性是容许对一个 nil 对象执行任何一个方法不会 Crash,由于会被忽略掉。

  3. 若是上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找获得就跳到对应的函数去执行。

  4. 若是 cache 找不到就找一下方法分发表。

  5. 若是分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。

  6. 若是还找不到就要开始进入动态方法解析了,后面会提到。
    PS:这里说的分发表其实就是Class中的方法列表,它将方法选择器和方法实现地质联系起来。
    其实编译器会根据状况在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或objc_msgSendSuper_stret四个方法中选择一个来调用。若是消息是传递给超类,那么会调用名字带有”Super”的函数;若是消息返回值是数据结构而不是简单值时,那么会调用名字带有”stret”的函数。排列组合正好四个方法。

方法中的隐藏参数

咱们常常在方法中使用self关键字来引用实例自己,但从没有想过为何self就能取到调用当前方法的对象吧。其实self的内容是在方法运行时被偷偷的动态传入的。

objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,并将消息中全部的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:

– 接收消息的对象(也就是self指向的内容) – 方法选择器(_cmd指向的内容)

之因此说它们是隐藏的是由于在源代码方法的定义中并无声明这两个参数。它们是在代码被编译时被插入实现中的。尽管这些参数没有被明确声明,在源代码中咱们仍然能够引用它们。在下面的例子中,self引用了接收者对象,而_cmd引用了方法自己的选择器:

- strange
{
 id  target = getTheReceiver();
 SEL method = getTheMethod();
 if ( target == self || method == _cmd )
     return nil;
 return [target performSelector:method];
}

在这两个参数中,self 更有用。实际上,它是在方法实现中访问消息接收者对象的实例变量的途径。
而当方法中的super关键字接收到消息时,编译器会建立一个objc_super结构体:

struct objc_super { id receiver; Class class; };

这个结构体指明了消息应该被传递给特定超类的定义。但receiver仍然是self自己,这点须要注意,由于当咱们想经过[super class]获取超类时,编译器只是将指向self的id指针和class的SEL传递给了objc_msgSendSuper函数,由于只有在NSObject类找到class方法,而后class方法调用object_getClass(),接着调用objc_msgSend(objc_super->receiver, @selector(class)),传入的第一个参数是指向self的id指针,与调用[self class]相同,因此咱们获得的永远都是self的类型。

获取方法地址

在IMP那节提到过能够避开消息绑定而直接获取方法的地址并调用方法。这种作法不多用,除非是须要持续大量重复调用某方法的极端状况,避开消息发送泛滥而直接调用该方法会更高效。

NSObject类中有个methodForSelector:实例方法,你能够用它来获取某个方法选择器对应的IMP,举个栗子:

void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target
 methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
 setter(targetList[i], @selector(setFilled:), YES);

当方法被当作函数调用时,上节提到的两个隐藏参数就须要咱们明确给出了。上面的例子调用了1000次函数,你能够试试直接给target发送1000次setFilled:消息会花多久。

PS:methodForSelector:方法是由 Cocoa 的 Runtime 系统提供的,而不是 Objc 自身的特性。

动态方法解析

你能够动态地提供一个方法的实现。例如咱们能够用@dynamic关键字在类的实现文件中修饰一个属性:

@dynamic propertyName;

这代表咱们会为这个属性动态提供存取方法,也就是说编译器不会再默认为咱们生成setPropertyName:propertyName:方法,而须要咱们动态提供。咱们能够经过分别重载resolveInstanceMethod:resolveClassMethod:方法分别添加实例方法实现和类方法实现。由于当 Runtime 系统在Cache和方法分发表中(包括超类)找不到要执行的方法时,Runtime会调用resolveInstanceMethod:resolveClassMethod:来给程序员一次动态添加方法实现的机会。咱们须要用class_addMethod函数完成向特定类添加特定方法实现的操做:

void dynamicMethodIMP(id self, SEL _cmd) {
 // implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
 if (aSEL == @selector(resolveThisMethodDynamically)) {
       class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
       return YES;
 }
 return [super resolveInstanceMethod:aSEL];
}
@end

上面的例子为resolveThisMethodDynamically方法添加了实现内容,也就是dynamicMethodIMP方法中的代码。其中 “v@:” 表示返回值和参数,这个符号涉及 Type Encoding
PS:动态方法解析会在消息转发机制浸入前执行。若是 respondsToSelector:instancesRespondToSelector:方法被执行,动态方法解析器将会被首先给予一个提供该方法选择器对应的IMP的机会。若是你想让该方法选择器被传送到转发机制,那么就让resolveInstanceMethod:返回NO。

消息转发

重定向

在消息转发机制执行前,Runtime 系统会再给咱们一次偷梁换柱的机会,即经过重载- (id)forwardingTargetForSelector:(SEL)aSelector方法替换消息的接受者为其余对象:

- (id)forwardingTargetForSelector:(SEL)aSelector
{
 if(aSelector == @selector(mysteriousMethod:)){
     return alternateObject;
 }
 return [super forwardingTargetForSelector:aSelector];
}

毕竟消息转发要耗费更多时间,抓住此次机会将消息重定向给别人是个不错的选择,不过千万别返回self,由于那样会死循环。

转发

当动态方法解析不做处理返回NO时,消息转发机制会被触发。在这时forwardInvocation:方法会被执行,咱们能够重写这个方法来定义咱们的转发逻辑:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
 if ([someOtherObject respondsToSelector:
         [anInvocation selector]])
     [anInvocation invokeWithTarget:someOtherObject];
 else
     [super forwardInvocation:anInvocation];
}

该消息的惟一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。咱们能够实现forwardInvocation:`方法来对不能处理的消息作一些默认的处理,也能够将消息转发给其余对象来处理,而不抛出错误。

这里须要注意的是参数anInvocation是从哪的来的呢?其实在forwardInvocation:消息发送前,Runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。因此咱们在重写forwardInvocation:的同时也要重写methodSignatureForSelector:方法,不然会抛异常。

当一个对象因为没有相应的方法实现而没法响应某消息时,运行时系统将经过forwardInvocation:消息通知该对象。每一个对象都从NSObject类中继承了forwardInvocation:方法。然而,NSObject中的方法实现只是简单地调用了doesNotRecognizeSelector:。经过实现咱们本身的forwardInvocation:方法,咱们能够在该方法实现中将消息转发给其它对象。

forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不一样接收对象。或者它也能够象一个运输站将全部的消息都发送给同一个接收对象。它能够将一个消息翻译成另一个消息,或者简单的”吃掉“某些消息,所以没有响应也没有错误。forwardInvocation:方法也能够对不一样的消息提供一样的响应,这一切都取决于方法的具体实现。该方法所提供是将不一样的对象连接到消息链的能力。

注意: forwardInvocation:方法只有在消息接收对象中没法正常响应消息时才会被调用。 因此,若是咱们但愿一个对象将negotiate消息转发给其它对象,则这个对象不能有negotiate方法。不然,forwardInvocation:将不可能会被调用。

转发和多继承

转发和继承类似,能够用于为Objc编程添加一些多继承的效果。一个对象把消息转发出去,就好似它把另外一个对象中的方法借过来或是“继承”过来同样。
这使得不一样继承体系分支下的两个类能够“继承”对方的方法,

替代者对象(Surrogate Objects)

转发不只能模拟多继承,也能使轻量级对象表明重量级对象。弱小的女人背后是强大的男人,毕竟女人遇到难题都把它们转发给男人来作了。这里有一些适用案例,能够参看官方文档。

转发与继承

尽管转发很像继承,可是NSObject类不会将二者混淆。像respondsToSelector: isKindOfClass:这类方法只会考虑继承体系,不会考虑转发链。
若是你为了某些意图偏要“弄虚做假”让别人觉得Warrior继承到了Diplomat的negotiate方法,你得从新实现 respondsToSelector: 和 isKindOfClass:来加入你的转发算法:

- (BOOL)respondsToSelector:(SEL)aSelector
{
 if ( [super respondsToSelector:aSelector] )
     return YES;
 else {
     /* Here, test whether the aSelector message can     *
      * be forwarded to another object and whether that  *
      * object can respond to it. Return YES if it can.  */
 }
 return NO;
}

除了respondsToSelector:isKindOfClass:以外,instancesRespondToSelector:中也应该写一份转发算法。若是使用了协议,conformsToProtocol:一样也要加入到这一行列中。相似地,若是一个对象转发它接受的任何远程消息,它得给出一个methodSignatureForSelector:来返回准确的方法描述,这个方法会最终响应被转发的消息。好比一个对象能给它的替代者对象转发消息,它须要像下面这样实现methodSignatureForSelector:

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
 NSMethodSignature* signature = [super methodSignatureForSelector:selector];
 if (!signature) {
    signature = [surrogate methodSignatureForSelector:selector];
 }
 return signature;
}

健壮的实例变量(Non Fragile ivars)

在 Runtime 的现行版本中,最大的特色就是健壮的实例变量。当一个类被编译时,实例变量的布局也就造成了,它代表访问类的实例变量的位置。从对象头部开始,实例变量依次根据本身所占空间而产生位移:
超类后面加上咱们本身类的实例变量,看起来不错。但试想若是那天苹果更新了NSObject类,发布新版本的系统的话,那就悲剧了:
咱们自定义的类被划了两道线,那是由于那块区域跟超类重叠了。惟有苹果将超类改成之前的布局才能拯救咱们,但这样也致使它们不能再拓展它们的框架了,由于成员变量布局被死死地固定了。在脆弱的实例变量(Fragile ivars) 环境下咱们须要从新编译继承自 Apple 的类来恢复兼容性。那么在健壮的实例变量下回发生什么呢?
在健壮的实例变量下编译器生成的实例变量布局跟之前同样,可是当 runtime 系统检测到与超类有部分重叠时它会调整你新添加的实例变量的位移,那样你在子类中新添加的成员就被保护起来了。
须要注意的是在健壮的实例变量下,不要使用sizeof(SomeClass),而是用class_getInstanceSize([SomeClass class])代替;也不要使用offsetof(SomeClass, SomeIvar),而要用ivar_getOffset(class_getInstanceVariable([SomeClass class], "SomeIvar"))来代替。

Objective-C Associated Objects

在 OS X 10.6 以后,Runtime系统让Objc支持向对象动态添加变量。涉及到的函数有如下三个:

void objcsetAssociatedObject ( id object, const void *key, id value, objcAssociationPolicy policy );
id objc_getAssociatedObject ( id object, const void *key );
void objc_removeAssociatedObjects ( id object );

这些方法以键值对的形式动态地向对象添加、获取或删除关联值。其中关联政策是一组枚举常量:

enum {
OBJC_ASSOCIATION_ASSIGN  = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC  = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC  = 3,
OBJC_ASSOCIATION_RETAIN  = 01401,
OBJC_ASSOCIATION_COPY  = 01403
};

这些常量对应着引用关联值的政策,也就是 Objc 内存管理的引用计数机制。

总结

咱们之因此让本身的类继承NSObject不只仅由于苹果帮咱们完成了复杂的内存分配问题,更是由于这使得咱们可以用上 Runtime 系统带来的便利。可能咱们平时写代码时可能不多会考虑一句简单的[receiver message]背后发生了什么,而只是当作方法或函数调用。深刻理解 Runtime 系统的细节更有利于咱们利用消息机制写出功能更强大的代码,好比 Method Swizzling 等。

原文出处: 杨萧玉的博客
原文连接: http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/

相关文章
相关标签/搜索