Effective Objective-C 2.0 总结(二)下

2017-06-05 黑夜来鸟

第 11 条:理解 objc_msgSend 的作用

1.调用对象方法,在Objective-C 中叫做 “传递消息”(pass a message),消息有 “名称”(name)或“选择子”(selector),可以接受参数,而且可能还有返回值。

2.objc_megSend 的原型:

 void objc_msgSend(id self,SEL cmd,...)

是一个 “参数个数可变的函数”,能够接受两个或两个以上的参数,第一个参数代表接收者,第二个参数代表选择子,后续参数就是参数。

3.objc_megSend 函数会依据接收者和选择子来调用适当的方法:

  • 在接收者所属的类搜寻其 “方法列表”

  • 找不到的话,就沿着继承体系继续向上查找

  • 最终还是找不到相符的方法就执行 “消息转发”

4.objc_msgSend 会将匹配结果缓存在 “快速映射表”(fast map)里面,每个类都有这样子的一块缓存,接下来还向该类发送一样的消息,那么执行起来就很快了。

5.这里有些特殊情况,需要由Objective-C 运行环境的另外一些函数来处理:

  • objc_msgSend_stret :如果待发送的消息要返回结构体,那么可以交由此函数处理。只有当CPU 寄存器能够容纳得下消息返回类型时,这个函数才能处理此消息。若是返回值无法容纳于CPU 寄存器(比如说返回的结构体太大了),那么就由另外一个函数执行派发。此时,那个函数会通过分配在栈上的某个变量来处理消息所返回的结构体。

  • objc_msgSend_fpret:如果消息返回的是浮点数,可以交由此函数处理。这个函数是为了处理x86 等架构CPU 中某些令人惊讶的奇怪状况。

  • objc_msgSendSuper:如果要给超类发消息,那么就交由此函数处理。

6.每个类里都有一张函数表,选择子的名称则是表的 “键”,对应的值都是指向函数的指针。objc_msgSend 等函数就是通过这个函数表来寻找应该执行的方法并执行跳转的。

7.如果某函数的最后一项操作是调用另外一个函数,那么就可以运用 “尾调用优化” 技术。编译器会生成跳转至另外一个函数所需的指令码,而且不会向调用栈推入新的 “栈帧”。

  • 消息由接收者、选择子及参数构成。给某对象 “发送消息” 也就是相当于在该对象上 “调用方法”。

  • 发给某对象的全部消息要由 “动态消息派发系统” 来处理,该系统会查出对应的方法,并执行其代码。

第 12 条:理解消息转发机制

1.当对象接收到无法解读的消息后,就会启动 “消息转发”(message forwarding)机制,程序员可经由此过程告诉对象应该如何处理未知消息。

2.消息转发分为两大阶段:

  • 第一阶段选征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个 “未知的选择子“(unknown seletor),这叫做 ”动态方法解析“(dynamic method resolution)。

  • 第二阶段涉及 ”完整的消息转发机制“(full forwarding mechanism)。如果运行期系统已经把第一阶段执行完了,那么接收者自己就无法再以动态新增方法的手段来响应包含该选择子的消息了。这里的第二阶段又分为下面两小步:

  • 首先,请接收者看看有没其他对象能处理这条消息;若有,则运行期系统会把消息转给那个对象,于是消息转发过程结束,一切正常。

* 若没有 ”备援的接收者“(replacement receiver),则启动完整的消息转发机制,运行期系统会把与消息有关的全部细节都封装到NSInvocation 对象中,再给接受者最后一次机会,令其设法解决当前还未处理的这条消息。

动态方法解析

对象在收到无法解读的消息后,首先将调用其所属类的下列类方法:

+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel

//表示这个类是否能新增一个方法来处理此选择子

备援接收者

当前接收者还有第二次机会处理未知的选择子,运行期系统会它:能不能把这条消息转发给其他接收者来处理:

- (id)forwardingTargetForSelector:(SEL)aSelector

我们无法操作经由这一步所转发的消息,若是想在发送给备援接收者之前先修改消息内容,那就得通过完整的消息转发机制。

完整的消息转发

将消息有关的信息全部丢到NSInvacation 对象中,把消息指派给目标对象

- (void)forwardInvocation:(NSInvocation *)anInvocation
  • 若对象无法响应某个选择子,则进入消息转发流程。

  • 通过运行期的动态方法解析功能,我们可以在需要用到的某个方法时再将其加入类中。

  • 对象可以把其无法解读的某些选择子转交给其他对象来处理。

  • 经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。

第 13 条:用 “方法调配技术” 调试 “黑盒方法”

1.不需要源代码,也不需要通过继承子类来覆写方法就能改变这个类本身的功能,新功能在本类的所有实例都生效,此方案称为 “方法调配”(method swizzling)。

2.每个类有个方法列表(函数指针 IMP),各自映射到自己的方法实现,只要我们能操作这个函数指针的指向,我们就可以动态的增加替换原有的方法。

3.互换两个已经写好的方法实现:

void method_exchangeImplementations(Method m1, Method m2) 
方法实现获取:
Method class_getInstanceMethod(Class cls, SEL name)

4.为已有方法增加新功能:

Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    BOOL didAddMethod =
    class_addMethod(class,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSelector,                                                   method_getImplementation(originalMethod),
                   method_getTypeEncoding(originalMethod));
    } else {
       method_exchangeImplementations(originalMethod, swizzledMethod);
    }
  • 在运行期,可以向类中新增或替换选择子所对应的方法实现。

  • 使用另一份实现来替换原有的方法实现,这道工序叫做 “方法调配”,开发者常用此技术向原有实现中添加新功能。

  • 一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。

第 14 条:理解 “类对象” 的用意

1.每个Objective-C 对象实例都是指向某块内存数据的指针。

2.Objective-C 对象所用的数据结构

struct objc_object {
    Class isa;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;

每个对象结构体首个成员是Class 类的变量,定义了对象所属的类,通常称为 “is a” 指针。

3.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;

Class 首个变量也是isa 指针,说明Class 本身也是Objective-C 对象,指向 “元类”(meta class)。

在类继承体系中查询类型信息

  1. “isMemberOfClass” 判断对象是否为某个特定类的实例

  2. “isKindOfClass” 判断出对象是否为某类或其派生类的实例

  • 每个实例都有一个指向Class 对象的指针,用以表明其类型,而这些Class 对象则构成了类的继承体系。

  • 如果对象类型无法在编译器确定,那么就应该使用类型信息查询方法来探知。

  • 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。


用户评论
开源开发学习小组列表