EffectiveOC的学习-3

这里是EffectiveOC的 第 13,14条

2016-06-06 | 阅读

13. 使用Method Swizzling

Objective-C中,类的方法列表中将selector的名称映射到相关的方法实现上,即这个方法列表中对应了方法名与函数指针的关系,使得 动态消息派发系统能够据此找到应该调用的方法.而这种函数指针,被叫做IMP,对于IMP的定义一般为:

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

所以,OC中的类的函数,实际是以C的函数来实现的,而这个函数的第一个参数是id,一般为self,第二个参数为SEL,然后才是函数定义的参数.

在说具体实现Method swizzling之前,要了解几个runtime的函数 :

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) : 为类添加函数,可以override父类中的函数,但是不能替换已经存在的函数. 最后一个参数typesruntime中的type encoding类型. 返回值表示函数是否添加成功,如果当前类中已有要添加的函数名对应的函数,则添加失败.

IMP method_setImplementation( Method method, IMP imp) : 为类设置函数实现, method代表了函数的声明,而IMP来表示具体函数指针,也就是具体的函数实现. 返回值类型是一个IMP,表示设置之前的函数实现.

void method_exchangeImplementations( Method m1, Method m2) : 交换函数实现,一般的Method swizzling就是通过该方法来实现,这个方法实际上调用了执行了以下操作:

IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);

IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) : 这个函数如果从名字上理解其功能,会觉得就与method_exchangeImplementations很相似,但是两者不做相同的操作 , class_replaceMethod只更换一个函数的具体实现,而不是将两个函数的实现继续交行. class_replaceMethod , 如果指定的sel当前已经存在,则执行method_setImplementation,即将这个sel对应的函数的实现替换成对应的IMP;而如果指定的SEL当前类内没有具体实现,则执行class_addMethod ,添加一个新的函数.

了解了runtime中对于函数的操作后,很容易想到method swizzling的具体实现.

首先,我们要先添加一个方法到指定的类上,添加方法,我们有两种方式,一种通过category直接为指定的类添加指定的方法,这种情况一般是我们知道具体要为指定的类和指定的方法进行替换.一种方式是通过class_addMethod来动态添加函数,多用于我们不确定要为哪些类进行method swizzling的时候,一般使用imp_implementationWithBlock来将一个block转换为IMP指针,如下:

SEL swizzledSelector = NSSelectorFromString(@"Beacon_tableView:didSelectRowAtIndexPath:");
Method originMethod = class_getInstanceMethod([delegate class], @selector(tableView:didSelectRowAtIndexPath:));
        IMP swizzleIMP = imp_implementationWithBlock(^(id obj,UITableView *tableView,NSIndexPath *indexPath) {
            NSLog(@"click");
            [obj performSelector:swizzledSelector withObject:tableView withObject:indexPath];
        });
        BOOL didAddMethod = class_addMethod([delegate class], swizzledSelector, swizzleIMP, method_getTypeEncoding(originMethod));
        if (didAddMethod) {
            Method swizzleMethod = class_getInstanceMethod([delegate class], swizzledSelector);
            method_exchangeImplementations(originMethod,swizzleMethod);
        }

添加方法后,就可以使用method_exchangeImplementations来交换两个函数的实现了. 这里一般使用method_exchangeImplementations来交换一个类中两个函数的实现,而不是去通过class_replaceMethod来将一个函数单独的进行替换成我们想要的实现,原因是,很多时候我们还需要用到原函数的功能,所以要在类中保留原有函数的实现.

需要注意的一点,在上面代码中,我们在替换的函数实现中,调用了

[obj performSelector:swizzledSelector withObject:tableView withObject:indexPath];

而这个swizzledSelector是我们添加的函数的SEL,即Beacon_tableView:didSelectRowAtIndexPath:. 需要注意的就是,进行Method swizzling后, 所有的Beacon_tableView:didSelectRowAtIndexPath:都指向了替换前的函数tableView:didSelectRowAtIndexPath:的实现,而所有的tableView:didSelectRowAtIndexPath:的函数调用,实际上都是在调用添加的这个block的实现.

Method swizzling的特点在于,可以通过替换方法来实现一个钩子,可以在不被人察觉的情况进行特别的功能. 一般用于用户行为的收集,如上述,通过Method swizzling,对所有的TableViewDeletage添加方法,以在点击事件发生时,截获并记录事件的发生.

Method swizzling是一个危险而又有用的工具,危险在于你不了解它,如果你理解了它的实现,了解需要注意的地方,那它就只是一个有用的工具,而不再危险了.

14.理解”类对象”的含义

要理解Objective-C对象的本质,类对象是一个指向某块内存地址的指针.一般用id表示,而id如此定义:

struct objc_object {
    Class isa;
};

typedef struct objc_object *id;

id是一个objc_object的结构体的指针,这个结构体只有一个Class对象. 也就表示了任何一个类的实例,在内存上地址的第一块,是一个isa指针,指向的是实现的类对象.而 Class对象是这样的定义的:

typedef struct objc_class *Class;

而这个objc_class具体定义如下:

struct objc_class {
    Class isa; // isa指针,指出类的类型
    Class super_class ; // 父类
    const char *name; // 类名
    long version ; // 版本信息,不重要
    long info ; 
    long instance_size; // 类的实例变量的大小
    struct objc_ivar_list *ivars; // 成员变量数组
    struct objc_method_list **methodLists; // 方法数组
    struct objc_cache *cache; // 方法的缓存,提高方法调用速率
    struct objc_protocol_list *protocols; // 协议的数组.
}

通过这个定义,我们可以看到OC是如何使用C来实现一个类的. 其中需要重点讨论的是isa指针, 任何一个id对象都有一个isa指针,指向了对象的类,而这个类还有一个isa指针,这个指针指向了 另外一个类 metaClass,metaclass中放置了类对象本身所具有的元数据,即类方法定义在metaclass中. 所以一个对象的类 实际上由两个objc_class结构组成,一个objc_class存放了实例方法和实例变量,一个objc_class称为metaclass存放了类方法.结合继承的结构,如下图所示 :

类是单例的,即对于一个类,只有 一个类和一个metaclass.

NSObject协议(NSObject类并不是整个OC所有对象的积累)中有两个方法 与 类对象相关 :

// 检测对象是否是这个类或者派生类的实例
- (BOOL)isKindOfClass:(Class)aClass;
// 检测对象是否是这个类的实例.
- (BOOL)isMemberOfClass:(Class)aClass;

检测对象的类是否相同,也通过 :

if([objectA class] == [objectB class])

来进行判断.