iOS必备技能之Runtime,概念部分

Apple.jpg

Objective-C语言总是竭尽地将工作从编译链接时推迟到运营时。只要有或者,Objective-C总是利用动态的方式来缓解难点。那象征Objective-C语言不仅须求三个编译器,同时也亟需二个运作时系统来实施编译好的代码。Runtime扮演的角色类似于Objective-C语言的操作系统,Objective-C基于该系列来行事。

本位github地址
https://github.com/ICZhuang/Runtime

当向三个目的发送新闻,假诺格局被完结了,则一向在底层使用音信机制调整该措施,假诺措施没有被完毕,则响应链最前端是《iOS必备技能之Runtime(一)》中关系的动态方法化解方案,要是措施被动态拉长,那么那么些音讯会被对象吸收;若是音信不只怕被接收,则响应链会寻找有没有得以完成音讯转载的点子,让其余类去接受那么些消息;若是音讯转载也找不到相应的法门达成,那么程序才会报错(unrecognized
selector sent to instance)。

1、与Runtime的交互

Objective-C程序有三种途径和运作时系统互相:

  1. 通过Objective-C源代码;
  2. 通过Foundation框架中NSObject的方法;
  3. 经过一贯调用Runtime的函数。

小编们常见定义3个类的时候,会一直将艺术写到类中,给予一种调用改目的方法就自然会找到该对象的情势完成的错觉。其实不然。我们说调用二个对象的某八个目的方法,其实更准确的描述应该是向目的发送三个新闻,比如[receiver
message],应该描述成像receiver这几个目标发送二个message音信。

肆 、新闻转载

消息转载有二种,一种是对信息可定制的,一种是不行定制的。响应链优先响应不可定制的消息转载,假设没有完成就去响应可定制的消息转发。

1.1、通过Objective-C源代码

一大半情状下,你只需编写和编译Objective-C源代码,运维时系统在后台自动运维。

当编译Objective-C类和办法时,编译器为促成语言动态天性将电动制造一些数据结构和函数。那个数据结构包括类定义和磋商定义的消息(如objc_msgSend函数)。


归纳新闻转载(不可定制)

简言之的消息转载对转会的新闻不可以修改,怎么发过来的怎么转走。

- (id)forwardingTargetForSelector:(SEL)aSelector

其一办法予以已毕那个点子的类1个“传球”的力量,如若此类没有达成这些方法,那么forwardingTargetForSelector:回来多个任何类的对象,让其余类里面的办法代为兑现。如下示例,该类没有兑现method方法,可是转载给OtherClass来完毕那些格局。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(method)) {
        [[OtherClass alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

其一艺术只好让我们把音讯转载到另2个能处理那一个消息的目的,可是力不从心处理新闻的内容,比如参数和再次来到值。

1.2、通过NSObject的方法

Cocoa中绝大多数拣都以NSObject的子类,都继承了NSObject的艺术。

NSObject的少数方法可以从运维时系统中获取新闻,对目的开展自然水准的自身反省,如class再次来到对象的类;isKindOfClass:isMemberOfClass:自小编批评对象是或不是在指定的类继承系列中;respondsToSelector:检核对象是或不是响应指定的新闻;conformsToProtocol:检核查象是否贯彻了点名协议类的点子;methodForSelector:回到指定方法完毕的地址。

objc_msgSend

在Objective-C语言中,音信并从未跟艺术的兑现绑定的,直到运走势形下,才知道响应新闻时应当履行的办法。比如编译器会将上面表明式

[receiver message]

转换来音信发送方法 objc_msgSend
格局。这几个办法将新闻接受者和措施selector作为七个最重点的参数

objc_msgSend(receiver, selector)

其余新闻传递过来的参数都会以可变形参的款式拼接在selector后边

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

在信息发送方法中,就是在做动态绑定

  1. 第贰步查找selector的格局完结,相同的selector在差距的类有两样的兑现,但它会在
    receiver的class中找;
  2. 然后实施顺序,将receiver和其余参数作为实参传递过去;
  3. 最终将顺序的重临值作为友好的重回值重临

搜寻的key就存在类的构造中。类协会中有八个关键的因素:

  • superclass 指针
  • 分公布(dispatch
    table),那个分公布将selector和艺术的贯彻地点关联了四起

当多少个目的被创立,分配内存,并且给成员变量做初始化。第四个变量就是isa指针,指向它的class。通过class便可找到全部在继承链中的类。

图片 1

image_01

当给三个目标发送新闻,音信发送方法就会顺着对象的isa指针在类的分发布里找办法的selector,假设在类中并未找到,那么继续在superclass的分发布里找,直到查找到NSObject
class。一旦查找到,音讯发送方法就调起该办法完毕并把目的的数据结构传递过去。

为了增强查找速度,Runtime
会缓存那几个运用过的法门。各种class都有2个cache,它能够缓存继承而来的措施和自作者定义的艺术。在从分刊出里寻找从前,新闻查找体制会先从cache里查找。那些缓存会动态的增强以存放新的新闻指定执行过的法子。

完全消息转载(可定制)

时上边二种方式分别再次回到NO和nil时,完整的音讯转载forwardInvocation:不畏确保程序不报“unrecognized
selector sent to
instance”错误的最后一关了。在完全新闻转发其中,forwardInvocation:会对新闻进行对应,对象会创建三个NSInvocation对象,把与从不处理原始音讯和参数一起打包起来。

- (void)forwardInvocation:(NSInvocation *)anInvocation

为了了解转载的意图和限量,想象那样一个场景:要是在1个对象里你愿意可以响应negotiate方法,并且其余多少个差异类的目的也可以响应这些法子(那多少个类完结了negotiate方法),起先想到的法门应该是一直发送消息到那多少个类里面。
进一步考虑,如果您的那个目的对negotiate措施的应和的贯彻恰恰在任何的类里面,一种已毕格局就是让这一个目的的类去继承那么些类的章程,那样你就能够直接在在这么些类里面调用negotiate办法了。不过,既然他们被分成不一致的类,不属于同一继承体系,那也就代表大多数情景下您往往不可见如此做。
尽管你无法一而再negotiate方法,不过你可以因而把音讯一直发送给其他类的靶子的点子把那么些措施“借”过来。如下:

- (id)negotiate
{
    if ( [someOtherObject respondsTo:@selector(negotiate)] )
        return [someOtherObject negotiate];
    return self;
}

而是上边那种处理情势显得有点不灵活,特别是当您想把不止七个音讯传递给别的对象的时候——你不可以不为每多个想要借过来的主意提供和方面类似的兑现。其余,倘使这个音信本来就是基于runtime的,会随着新的艺术和类的改动而变更其落到实处,那么那种处理形式就变得捉襟见肘了。
forwardInvocation:就能很省心地缓解那个难点,它是这么工作的:当1个对象因为尚未新闻中对应方法名的艺术而不可以去响应音信的时候,runtime系统通报那一个目标发送forwardInvocation:音讯。每2个目标都从NSObject中持续取得forwardInvocation:办法,不过在NSObject的那个方法中只是简短调用了
doesNotRecognizeSelector:,那是个abstract方法(类似于C++的纯虚函数),当子类没有落到实处那么些艺术的时候,外部调用那几个形式就会抛出格外。

除非当音信的接收者没能调用任何1个此类已经存在的措施的时候,forwardInvocation:情势才可以被调用来拍卖新闻。比如,你想;要你的目的呢negotiate艺术转化到其余类的目的,那么它本身就无法有negotiate主意。假若有那几个法子,那么该类中就不会调用forwardInvocation:方法。

为了可以转载音信,全数的forwardInvocation:主意必须求做下边两件事:

  • 决定新闻要去何方
  • 带着固有的参数向目的进行发送

新闻可以用invokeWithTarget:来进展发送:

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

runtime系统第②会调用methodSignatureForSelector:措施来拿到艺术签名,方法签名记录了主意的参数和再次来到值的音讯。

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

倒车的信息的重临值是要回去到原有的发送者的,全数的能够回到的品类都能被传送,包涵id类型、结构体类型、双精度的浮点型数据等。

一个forwardInvocation:办法可以看成具有未被识其他新闻的“分配基本”,把音讯打包发送到不一样的目的。大概说它可以是一个“换乘站”,把拥有的音讯发送到同1个对象。它可以把音信举办转向,或然只是简短地“吞掉”它,那样就会没有答复也不曾报错。它仍是可以把几个新闻集成到3个响应中。总括起来就是一句话,那些法子能做什么取决去它的贯彻。

1.3、通过Runtime的函数

Runtime
系统是二个由一名目繁多函数和数据结构组成,具有公共接口的动态库,头文件存放在/usr/include/objc中。那一个函数支持用纯C的函数来兑现
Objective-C
中相同的效益。纵然有一部分措施结合了NSObject类的根底,可是我们在写 Objc
代码时一般不会直接用到这几个函数的。在Objective-C Runtime
Reference
中有对
Runtime 函数的详细文档。

隐式参数

当objc_msgSend找到了艺术的贯彻,它就会调起完结,并把音信传递的参数全体传递过去,其中囊括七个暗许就会传送的参数

  • 音信接收目的
  • method对应的selector

平常在写Objective-C代码的时候,平时会省去那三个参数,当被编译的时候,参会插入到艺术完结中。纵然在编码时不突显声明,但大家还是可以应用到它们。�self
则为消息接收目的, _cmd 则为信息的 selector。比如上边示例,_cmd
是strange的selector,self 则是吸纳strange新闻的接受者

- strange {
    id  target = getTheReceiver();
    SEL method = getTheMethod();

    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

中转和多继承的异同

虽说Objectiv-C不帮衬多延续,然而使用转载来模拟继承,可以让Objective-C完毕部分多屡次三番的性状。如下图所示,多个目标通过转载来响应音信就像从任何类里面“借“只怕说”继承“一个方法的落成均等。

forwarding.gif

在上图中,Warrior类把negotiate方法转化到Diplomat类的实例中,似乎Warrior类在完毕negotiate措施一致,它会对negotiate格局做出响应。
由此,转载和多继承有很多一般的风味,不过,它们有以下根本的不一致之处:

  1. 持续是把各种意义集中到了单个的目标中,它使类趋向于巨大化、全能化。相反地,转载是把职责举办了分歧,它把3个标题分成了好多小的难题分配给不一致的小目的,通过转载举行关联。
  2. respondsToSelector:instanceRespondToSelectorisKindOfClass:办法只看继承树,不看转载链,比如[aWarrior respondsToSelector:@selector(negotiate)]在此地为假,纵然它亦可吸收negotiate的音讯还要展开响应。�假诺利用了协商,那么conformsToProtocol:方式也在此列。然则你可以由此重写那个艺术让她在转载中公布和继承中千篇一律的法力:

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

其一技能只适合在并未此外可以采用的章程的状态下使用,它并不得以代表继承的功能。

2、Runtime术语

  • ##### SEL

SEL是炫耀到点子的C字符串,它差距于C语言中的函数指针,函数指针直接保存了措施的地方,但SEL只是格局编号。它的数据结构是如此的:

typedef struct objc_selector *SEL;

可以用 Objc 编译器命令@selector()或者 Runtime
系统的sel_registerName函数来收获三个SEL项目标办法选取器。不相同类中相同名字的主意所对应的主意拔取器是相同的,纵然方法名字一样而变量类型不同也会促成它们持有同等的艺术采纳器。

  • ##### id

总结来说,id是三个针对性实例的指针,定义如下:

typedef struct objc_object *id;

objc_object的定义是:

struct objc_object { Class isa; };
struct objc_class {
    Class isa;

#if !__OBJC2__
    ......
#endif
};

可以看来,id 是指向 objc_object 结构体的指针,而 objc_object 包涵1个Class 的结构体指针 isa。

不过isa指南针不一而再指向实例对象所属的类,不可以凭借它来规定项目,而应该用class措施来分明实例对象的类。因为KVO的兑现原理就是将被考察对象的isa指南针指向二个动态创制的中游类而不是真正的类,那是一种叫做
isa-swizzling
的技术,详见法定文档

  • ##### Class

Class是1个针对objc_class结构体的指针:

typedef struct objc_class *Class;

在 runtime.h 中可以看看

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_class结构体就是 Objective-C
的对象系统的木本,其中第三个字段isaobjc_class结构体指针,指向该目的所属的类型对象。实际上,在
Objective-C 中,类自身也是多少个目的,而二个类的 isa 指针指向它的元类(Meta
Class)。元类是1个类对象的类,元类中储存着类措施。

向3个对象发送音讯时,runtime会在那个目的所属的百般类的点子列表中搜寻。而向多个类发送消息时,runtime会在这几个类的元类的章程列表中查找。逐个类都会有三个单身的元类,因为各个类的类措施基本不容许完全相同。

那就是说元类是什么样?

和类一样,元类也是二个对象,它也有二个 isa
指针指向其所属的类。全数的元类都利用 NSObject
的元类作为它们的所属类。NSObject 的元类是它和谐。

与类一样,元类也有协调的父类。meta class 的 super class 是 super class
的 meta class。直到基类的 meta class,它的 super class
指向基类自身。关系如下:

图片 2

类关系图

  • ##### 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;
}                                           OBJC2_UNAVAILABLE;

主意名类型为SEL,后边提到过同样名字的艺术就是在不相同类中定义,它们的不二法门拔取器也同等。

艺术类型method_types是个 char
指针,其实存储着法子的参数类型和重回值类型。

method_imp本着了章程的完成,本质上是三个函数指针,后边会详细讲到。

  • ##### Ivar

Ivar是一种表示类中实例变量的项目。

typedef struct objc_ivar *Ivar;

可以依据实例查找其在类中的名字,也就是“反射”:

-(NSString *)nameWithInstance:(id)instance {
    unsigned int numIvars = 0;
    NSString *key=nil;
    Ivar * ivars = class_copyIvarList([self class], &numIvars);
    for(int i = 0; i < numIvars; i++) {
        Ivar thisIvar = ivars[i];
        const char *type = ivar_getTypeEncoding(thisIvar);
        NSString *stringType =  [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
        if (![stringType hasPrefix:@"@"]) {
            continue;
        }
        if ((object_getIvar(self, thisIvar) == instance)) {//此处若 crash 不要慌!
            key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
            break;
        }
    }
    free(ivars);
    return key;
}

class_copyIvarList
函数获取的不唯有实例变量,还有属性。但会在原本的习性名前加上2个下划线。

  • ##### IMP

IMP是2个函数指针,它的定义是:

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

当你发起1个 ObjC
音讯之后,末了它会举办的那段代码,就是由这几个函数指针指定的。而 IMP
这几个函数指针就对准了那些主意的落成。既然得到了执行有个别实例有些方法的进口,我们就可以绕开新闻传递阶段,直接实施办法,这在末端会波及。

  • ##### Cache

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

typedef struct objc_cache *Cache;

objc_class结构体中有两个struct objc_cache *cache,它终究是缓存啥的吗?

Cache为格局调用的性质举行优化,通俗地讲,每当实例对象吸收到多少个新闻时,它不会直接在isa本着的类的法子列表中遍历查找可以响应音讯的点子,因为这么功能太低了,而是优先在Cache中追寻。Runtime
系统会把被调用的措施存到Cache中(理论上讲一个措施如若被调用,那么它有大概今后还会被调用),下次摸索的时候成效更高。

  • ##### Property

@property标记了类中的属性,它是二个针对objc_property结构体的指针:

typedef struct objc_property *Property;

可以经过class_copyPropertyList
protocol_copyPropertyList主意来获取类和协商中的属性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

再次回到类型为指向指针的指针,因为属性列表是个数组,逐个成分内容都以1个objc_property_t指南针,而那多少个函数再次来到的值是指向这些数组的指针。

相对于class_copyIvarList函数,使用class_copyPropertyList
函数只可以取得类的习性,而不包涵成员变量。但此刻收获的属性名是不水肿划线的。

你能够用property_getAttributes函数来打通属性的称谓和@encode类型字符串:

  1. property_getAttributes 再次来到的字符串以字母 T 开首,接着是@encode
    编码和逗号。
  2. 若果属性有 readonly 修饰,则字符串中含有 中华V 和逗号。
  3. 假设属性有 copy 只怕 retain 修饰,则字符串分别包蕴 C
    或然&,然后是逗号。
  4. 假如属性定义有定制的 getter 和 setter 方法,则字符串中有 G 或许 S
    跟着相应的章程名以及逗号(例如,GcustomGetter,ScustomSetter:,,)。
  5. 若果属性是只读的,且有定制的 get 访问方法,则讲述到此为止。
  6. 字符串以 V 然后是性质的名字截至。

艺术地址

唯一可以规避动态绑定的方法就是赢得格局(完毕)的地址,直接调用。多少个情势被反复延续调用,而又想躲避每一回都要摸索的损耗,可以用这种措施逃避动态绑定。

由此NSObject class中宣示的 methodForSelector:
能够取得艺术的地方(IMP),就是三个函数指针。通过函数指针唤起它的贯彻。不过得留心函数指针与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);

面前五个参数是用来接纳 self 和 _cmd 的,转成函数形式要求加上。

瞩目 methodForSelector: 是 Cocoa runtime
提供的,并非Objective-C语言提供的。


给2个目的发送消息后,即使对应的措施没找到,则跻身下边步骤:

  1. 动态方法分析:唤起对象的resolveInstanceMethod: 大概resolveClassMethod:
    ,询问是还是不是能够处理新闻的selector,假如无法则进入下一步;
  2. 备援对象:唤起对象的forwardingTargetForSelector:,再次来到可以处理该selector的目的,尽管回去nil大概self,则跻身下一步;
  3. 消息转载:完结forwardInvocation:将音信转载给此外能处理该音信的靶子,该方法的参数会经过methodSignatureForSelector:创立,所以还应当落成methodSignatureForSelector:方法

参考:清楚音信传递机制
[Objective-C
音讯发送与转向机制原理
]


代理对象

转载不只只是模仿多一连,它尽只怕地发出了更轻量化的靶子去达成尽或者多的原本属于众多休戚相关对象的表征。代理对象作为任何类的替罪羊,把消息无疑传递过去。代理对象更关爱的是在向任何类的对象(远程对象)抓发新闻那一个进度的底细,比如确保在连年远程对象的进度中每种参数被正确传达等。可是它在这一个历程中并不是把远程对象做了副本,而是创制了2个一点青眼于远程对象的当地地址,通过这一个地点,远程对象在其余使用可以收到那一个转账的音讯。
还有一种情况,比如您有1个目标急需处理大批量的数额——创设二个复杂的图纸或许从本土磁盘中读取文件等,那种意况下利用代理对象也是很适合的。创造二个这样的对象是很耗时的,所以大家愿意唯有在它确实要求照旧在系统财富一时半刻闲置的时候才回去创造它,可是又要力保存在这几个目的的占位符使得其余对象关系到那么些目的的时候可以不荒谬运作。
在那种景况下,你能够在最先河的时候绝不成立三个全职能的目的,而是创制二个代理对象给它。那么些代理对象重要就是为即将成立的重型对象做3个占位,或是在时机成熟的时候,举办消息转载。当代理对象的forwardInvocation:方法第六次被调用的时候,它要确认代理的靶子是或不是存在了,要是还未曾存在,就去创制它。在那几个目的的其它职能被亟需前边,代理对象和那么些目的在意义上其实就是同样的。


参考:《Objective-C Runtime Programing
Guide》


链接:
《iOS必备技能之Runtime(一)》

3、消息

本段主要讲述如何将发新闻转换为objc_msgSend函数调用,怎么样通过名字来指定三个艺术,以及哪些行使objc_msgSend函数。

动态方法分析

些微情形你只怕希望动态的提供格局落成。比如申明了一个动态属性

@dynamic propertyName;

那表达与改属性相关的法子(setter&getter)要动态增进

可以兑现 resolveInstanceMethod: 和 resolveClassMethod:
动态的为对象可能类的selector添加兑现。

几个Objective-C的不二法门其实在底部就是1个简约的C函数,那函数带着至少多个参数,self

_cmd。通过class_addMethod添加贰个函数作为类的主意。因而,先添加二个函数

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}

接下来在 resolveInstanceMethod:
中动态的将它作为艺术(如resolveThisMethodDynamically)添加到class中

@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

音信转载和动态方法分析是逐一的,在进入音信转载机制以前,可以经过动态方法分析来拍卖措施。倘若respondsToSelector:
和 instancesRespondToSelector:
被唤起,通过动态方法处理为selector提供一个IMP。如若已毕了resolveInstanceMethod:
但又想让selector进入新闻转载机制,那么在遇见该selector时回来NO。

经试验,倘诺完成了resolveInstanceMethod:
并且在内部往类添加了selector关联的法门已毕,则不会再走备援对象和音讯转载机制。


3.一 、得到形式地址

使用NSObject类中的methodForSelector:措施,可以赢得二个对准方法达成的指针,通过该指针可以平素调用方法已毕。例:

void (*setter) (id, SEL, BOOL);
// methodForSelector:方法会返回一个函数指针
setter = (void (*)(id, SEL, BOOL))[self methodForSelector:@selector(setFilled:)];
for (int i = 0; i < 1000; i ++) {
    // 使用函数指针直接调函数,参数一:函数对象,参数二:函数签名,参数三:函数参数
    setter(self, @selector(setFilled:), NO);
}

注意,methodForSelector:是Runtime提供的功效,不是Objective-C语言本人的成效。

音讯转载

在出现崩溃以前,runtime 给目的发送 forwardInvocation:
音信,并将3个NSInvocation对象作为唯一的参数传递过去,那么些NSInvocation对象包罗了早先时期的新闻和参数。

您可以达成 forwardInvocation:
方法提供对音信的暗许处理,可能过滤音信防止出现错误。顾名思义,方法forwardInvocation:常常是用来将转速音讯给其它的目的。

为了驾驭音信转载的打算,想象那样一种情景:假设要求统筹三个目的足以响应negotiate新闻,并且期待它的响应时期可以牵动其余一种对象响应negotiate音信。已毕那需求很简短,可以在您的negotiate方法已毕元帅音信传递给其它三个目的。

更为假诺,你设计的对象对negotiate的响应实际上其实就是实施其它多少个类的落到实处,其中一种方法就是让你的类继承自该类。然则,那恐怕并不创造。原因在于,你的类和贯彻了negotiate的类只怕在差其他接续树分支。就是说它们或然不设有直接或直接的持续关系。

即便你无法一连negotiate方法,你可以回顾地完结negotiate,将negotiate信息传递给此外一个目的。如此一来你的靶子就响应了negotiate,就好像从别的对象”借用”到了negotiate。

- (id)negotiate {
    if ( [someOtherObject respondsTo:@selector(negotiate)] )
        return [someOtherObject negotiate];
    return self;
}

那种办法有个别昏头转向,尤其是当有多种信息要求传递给任何对象的气象。你要求在多个方法中覆盖全数其余对象应当响应的主意。其它,在写代码的时候,有局地新闻的倒车是不可预言的。那一个音信是取决于runtime,它们可能被新情势替代,或被新类重新完毕。

forwardInvocation:
提供的是一种动态消除方案。它工作的原理就想这样:当七个目的没有3个措施能够匹配音信中的selector,那个目的是不可以响应该消息的,那个时候,runtime
会给目的发送多少个 forwardInvocation:
新闻。全数的目的都从NSObject类中继承了改措施。但是,NSObject中的该方法只是归纳的调起doesNotRecognizeSelector:
。通过重写 forwardInvocation: 方法,将音讯转载给别的的靶子。

倒车3个音讯,须求在forwardInvocation:方法中:

  • 判定音信应该转发给谁,还有
  • 倒车的时候带上它原本的参数

音信可以透过 invokeWithTarget: 方法发送:

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

假定有重返值,则重临值会返给最初的发送者,包含别的项目 ids, structures,
and double-precision floating-point numbers.

forwardInvocation:
方法就像未知新闻的分发宗旨,将它们投递到指定的接受者。由可能是中转站,将新闻发送给同一目的。它可以将1个信息转换来另1个新闻,可能不难的就把新闻”吞下”,让音信得不到响应。forwardInvocation:
所干的事情就看它是怎么被达成的。

留意:forwardInvocation:
只有在新闻的接受者不可知响应的意况下才会去处理该音讯。举例来说,即使希望贰个目的转载negotiate音信给其余的靶子,那么那一个目的无法有negotiate方法,即使有,音信永远也不容许被forwardInvocation:处理。

3.2、objc_msgSend函数

Objective-C中,新闻是在运维的时候才和措施已毕绑定的。[receiver message]会被编译器转换到对objc_msgSend函数的调用。该函数有五个主要参数:receiverselector,再加上参数的话就是objc_msgSend(receiver, selector, arg1, arg2, ...)

音信发送的事无巨细步骤:

  1. 检测这么些 selector 是或不是要不经意的。比如 Mac OS X
    开发,有了垃圾堆回收就不理会 retain ,release 这个函数了。
  2. 检测那几个 target 是还是不是 nil 对象。ObjC 的特色是同意对五个 nil
    对象实施其它1个方法不会 Crash,因为会被忽略掉。
  3. 一旦地点七个都过了,那就初步查找这么些类的 IMP,先从 cache
    里面找,完了找得到就跳到对应的函数去实施。
  4. 设若 cache 找不到就找一下方法分发表。
  5. 假定分公布找不到就到超类的分发表去找,一贯找,直到找到NSObject类截止。
  6. 找到方法达成之后,然后将新闻接收者对象及方法中指定的参数传给找到的法门已毕,最终,将艺术完结的再次来到值作为该函数的再次回到值重回。
  7. 即便还找不到即将初阶进入动态方法分析了,后边会提到。

音信机制的关键在于编译器为类和目标生成的布局,逐个类的结构中最少含有七个着力成分:isa指南针和办法列表。

当目的被创造时,它会被分配内存,并初叶化实例变量。对象的首个实例变量是3个针对该对象的类结构体的指针,即isa。通过isa指南针可以访问它对应的类及相应的父类。方法列表存放方法名字和呼应的贯彻地点。

对象收裁撤息时,objc_msgSend先依据该目标的isa指南针找到该指标对应的类的方法表,从表中寻找对应的措施。即使找不到,objc_msgSend将持续在父类中搜索,直到NSObject类。一旦找到相应措施,objc_msgSend会以音信接收者对象为参数调用该形式。

为了加快音讯的处理进度,运行时系统平常会将使用过的法子放入缓存中。各个类都有一个独门的缓存,同时包含一而再的法门和在此类中定义的章程。objc_msgSend在查找办法时,会事先在缓存中找找。如若缓存中一度有了索要的办法,则音讯仅仅比函数调用慢一点点。

转载和多继承

一般来说图,1个Warrior实例对象转载一个negotiate音信给3个Diplomat实例对象。Warrior就恍如Diplomat一样能响应negotiate音信

图片 3

image-02

如此那般看来,转载音讯的目标就”继承”了来自七个持续树分支中的措施 —
本类所在继承分支和音信确实响应的目的所在的分段。按白话说,转载新闻的对象就想继续了多少个对象,2️而那个目的不存在直接或直接的持续关系。

新闻转载提供了多屡次三番中的许多特征。然而,他们两那有几许两样的是:多一连是将七个例外的表现合并到三个目的里面。它趋向更大,更增加的靶子的概念。相反,转载趋向将义务分割派给差别的对象。它将难题分解成不相同的目的,又将那一个目的以一种方法关联起来,但那种措施对音信发送者来说是晶莹的。

3.③ 、使用隐藏的参数

咱俩平日选择self来代表近日艺术的对象,但是怎么它能代表近日形式对象啊?实际上它是在代码编译时插入方法中的。

当objc_msgSend找到方法对应的兑现时,它会平素调用该措施,并将音信中的参数传递给艺术完结,同时,它还传递三个暗藏的参数:接收音讯的靶子(self)和情势选用器(_cmd)。

在点子中能够通过self来引用消息接收者对象,通过_cmd来引用方法本人。

转车和三番五次

虽说转发很像继承,但NSObject
class却能驾驭的分别它们。像respondsToSelector:和 isKindOfClass:
那样的艺术只会是从继承树种查找,并不会在转载链中找寻。举例来说,一个Warrior
object想知道自身是或不是响应negotiate信息

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
    ...

答案是NO,固然它能接受negotiate音讯,某种意义上,它只是转账消息给Diplomat
object。

只有,有这么一种情况,你希望它彰显出来的是近似继承了新闻转载的目标目的一样,你必要再度完结respondsToSelector:
和 isKindOfClass:

- (BOOL)respondsToSelector:(SEL)aSelector {
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
      /// 这里,检测 aSelector 消息是否可以被转发到另一个对象,或者另一个对象是否
      /// 是否可以响应 aSelector 消息, 如果可以retrun YES
    }
    return NO;
}

假诺二个对象足以转账音信给它的代理对象,你需求像下边一样已毕methodSignatureForSelector:方法

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

3.四 、动态方法分析

大家得以经过resolveInstanceMethod:resolveClassMethod:措施动态地增加实例方法和类措施的兑现。当Runtime系统在缓存和方法列表中找不到要推行的办法时,会调用resolveInstanceMethod:resolveClassMethod:艺术来给大家三回动态拉长方法落成的时机。例如,有如下的函数:

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}

可以透过resolveInstanceMethod:将它看成类方法
resolveThisMethodDynamically的实现:

@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

其中 “v@:” 表示重返值和参数,那一个符号涉及 Type
Encoding

动态方法解析会在音讯转发从前前履行。若是 respondsToSelector:
instancesRespondToSelector:方法被执行,动态方法解析器将会被第③给予二个提供该措施接纳器对应的IMP的时机。假设您兑现了resolveInstanceMethod:格局可是依然希望不奇怪举办信息转载,只需求回到NO就足以了。

④ 、信息转载

普通,给1个对象发送它不可以处理的音信会拿到出错提醒,但是,运行时系统在抛出错误从前,还有四次机会挽救程序:

  1. Method Resolution
  2. Fast Forwarding
  3. Normal Forwarding

图片 4

message forward

Method Resolution(动态方法分析)

Runtime系统在运行时会先调用resolveInstanceMethod:resolveClassMethod:方法,让大家添加方法的兑现。倘诺加上方法并回到YES,那系统就会重复开动五次音讯发送的进程。要是没有兑现或回到NO,会履行
法斯特 Forwarding 操作。

法斯特 Forwarding(火速转化)

即使目的对象落成forwardingTargetForSelector:办法,并且这一个办法再次来到的不是nil或self,也会重启音信发送的经过,把那音讯转载给指定对象来处理。否则,就会延续Normal Fowarding。

Normal Forwarding(“慢速”转发)

若果没有使用 法斯特 Forwarding 来转载音信,最终只能动用 Normal Forwarding
来拓展音信转载。它会调用methodSignatureForSelector:办法来得到函数的参数和再次来到值,如果回到为nil,程序会Crash掉,并抛出
unrecognized selector sent to instance
很是消息。即使回去1个函数签名,系统就会创设二个NSInvocation目标并调用forwardInvocation:措施开展新闻转载。

4.1、转发

一经二个对象收取一条不大概处理的新闻(如动态方法分析再次来到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:艺术似乎一个无法识其他音讯的散发焦点,将那么些音信转载给不一样接收目的。或许它也得以象3个运输站将装有的新闻都发送给同2个接到目的。它可以将二个音讯翻译成其余三个音信,可能简单的“吃掉”有个别音讯,因而没有响应也从不错误。forwardInvocation:艺术也得以对两样的音信提供平等的响应,这一切都在于方法的具体贯彻。该办法所提供是将差其余靶子链接到音讯链的力量。

注意:
forwardInvocation:方法唯有在信息接收目的中不能正常响应音讯时才会被调用。
所以,假诺你期望您的对象将贰个音信转发给其余对象,你的目的就不可以有其一点子。否则,forwardInvocation:将不会被调用。

4.② 、转载和文山会海继承

信息转载很像继承,并且可以用来在Objective-C程序中模拟多重继承。如下图所示,贰个目的通过转载来响应信息,看起来就像该指标从其他类那借来了依然”继承“了点子完毕平等。

图片 5

新闻转载和层层继承

在上图中,Warrior 类的贰个对象实例将 negotiate 音讯转载给 Diplomat
类的三个实例。看起来,Warrior 类如同和 Diplomat 类一样, 响应 negotiate
音信,并且作为和 Diplomat 一样,实际上是 Diplomat 类响应了该音讯。

音讯转载弥补了Objective-C不支持多重继承的质量,也防止了因为多连续导致单个类变得臃肿复杂。

4.三 、转载和类继承

即便音讯转载很像继承,但它不是后续。例如在 NSObject
类中,方法respondsToSelector:isKindOfClass:只会现身在继续链中,而不是音讯转载链中。例如,纵然向两个Warrior 类的目标询问它是不是响应 negotiate 消息:

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...

再次来到NO,尽管该对象可以接受和响应negotiate方法。

尽管你想要让它看起来的确像是继承了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:来回到准确的主意描述,这一个方法会最后响应被转接的新闻。比如3个目标能给它的替代者对象转载信息,它必要像下边这样完毕methodSignatureForSelector:

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

⑤ 、动态属性关联

在 OS X 10.6
之后,Runtime系统让Objc辅助向目的动态增进变量。涉及到的函数有以下三个:

void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy 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
内存管理的引用计数机制。你会发现此处边没有 weak 属性,关于怎么着关联 weak
属性,请参考《怎么着利用 Runtime 给现有的类添加 weak
属性》

6、Method Swizzling

Method Swizzling 就是格局沟通,紧要有三种采用情状:hook和面向切面编程。

hook一般在+load方式中采用:

- (void)replacementReceiveMessage:(id)arg1 {
    [self replacementReceiveMessage:arg1];
}
+ (void)load {
    SEL originalSelector = @selector(ReceiveMessage:);
    SEL overrideSelector = @selector(replacementReceiveMessage:);
    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method overrideMethod = class_getInstanceMethod(self, overrideSelector);
    if (class_addMethod(self, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
        class_replaceMethod(self, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, overrideMethod);
    }
}

APP需求举行数量埋点时,就要求面向切面编程了。借使须要总计按钮点击的意况,就足以把按钮点击的法子举办沟通,那样就足以最大限度地裁减代码修改和侵入。

要留心的是,在+load中应用 Method Swizzling
是一件很凶险的业务,因为它会影响工程中具有相同类的代码,大概会并发奇怪的Bug。

关于 Method Swizzling
有五个轻量级的库Aspects很值得阅读。