iOS RunTime运行时(1):类与对象

Objective-C语言是一门动态语言,他将很多静态语言在编译和链接期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码更具有灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一下方法的实现等。

这种特性意味着OC不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于OC来说,这个运行时系统就像一个操作系统一样:他让所有的工作可以正常的运行,这个运行时系统就是Objc RunTime。objc RunTime 其实是一个RunTime库,他基本上是用C语言和汇编写的。这个库使得C语言具有了面向对象的能力。

RunTime库主要做以下几件事:

1:封装:在这个库中 对象可以使用C语言中的结构体来表示,而方法可以用C函数来实现,另外再加上了一些额外的特性,这些结构体和函数被RunTime函数封装后,我们就可以在程序运行时创建 检查 修改类 对象和他们的方法了。

2:找出方法的最终执行代码  当程序执行[obj doSomething]时 会向消息接收者(obj)发送一条消息(doSomething) RunTime会根据接收消息者是否能响应此消息而做出不同反应。

OC RunTime有两个版本:Modern runtime 和 legacy runtime  Modern Runtime 覆盖了64位的Mac OS X Apps,还有 iOS Apps,Legacy Runtime 是早期用来给32位 Mac OS X Apps 用的,也就是可以不用管就是了。

在这一系列文章中,我们将介绍runtime的基本工作原理,以及如何利用它让我们的程序变得更加灵活。在本文中,我们先来介绍一下类与对象,这是面向对象的基础,我们看看在Runtime中,类是如何实现的。

类与对象基础数据结构

Class

Objective-C类是由Class类型来表示的 它实际上是一个指向objc_class结构体的指针 他的定义如下:

typedef struct objc_class *Class

查看objc/runtime.h 中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;

在这个几个定义中 下面几个字段是我们感兴趣的

1> isa: 需要注意的是在OC中 所有的类自身也是i一个对象 这个对象的Class里面 也有一个isa指针 它指向metaClass(元类) 我们会在后面介绍他。

实例对象的isa指针指向类,类的isa指针指向其元类(metaClass)。对象就是一个含isa指针的结构体。类存储实例对象的方法列表,元类存储类的方法列表,元类也是类对象。

2> super_class 指向该类的父类 如果该类已经是最顶层的根类(比如NSObject或者NSProxy) 则super_class为NULL

3> cache 用于缓存最近使用的方法,一个接收者接收到对象的时候 他会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的 很多方法实际上很少用到或者根本用不上。在这种情况下 如果每次来消息时 我们都是methodLists中遍历一遍 性能会很差。这个时候,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会缓存到cache中,下次调用的时候runtime会优先寻找cache 如果cache中没有,采取methodLists中去找 这样对于经常调用到的方法 就会提高很多效率。

4> version 我们可以使用这个字段来提供类的版本信息 这对于对象的序列化非常有用 它可是让我们识别出不同类定义版本中实例变量布局的改变。

针对cache 我们可以用下面的例子来说明其执行过程:

NSArray *array = [[NSArray alloc] init];

其流程是:

1.[NSArray alloc]先被执行。因为NSArray没有+alloc方法,于是去父类NSObject去查找。
2.检测NSObject是否响应+alloc方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间,然后把isa指针指向NSArray类。同时,+alloc也被加进cache列表里面。
3.接着,执行-init方法,如果NSArray响应该方法,则直接将其加入cache;如果不响应,则去父类查找。
4.在后期的操作中,如果再以[[NSArray alloc] init]这种方式来创建数组,则会直接从cache中取出相应的方法,直接调用。

objc_object 与 id

objc_object是表示一个类的实例的结构体,他的定义如下(objc/objc.h):

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

可以看到,这个结构体中只有一个字体,即指向其类的isa指针。这样,当我们向一个Objective-C对象发送消息的时候,运行时库会根据实例对象的isa指针找到实例对象所属的类。RunTime库会在类的方法列表及父类的方法列表中寻找与消息对应的selector之乡的方法。找到后就运行这个方法。

当创建一个特定类的实例对象时 分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。在NSObject类的alloc 和 allocWithZone:方法是用函数class__createInstance来创建objc_object数据结构。

另外还有我们常见的id,它是一个objc_object结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。

objc_cache

上面提到了objc_class结构体中的cache字段,他用于缓存调用过的方法。这个字段是一个指向objc_cache结构体的指针。其定义如下:

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

1:mask 一个整数,指定分配的缓存bucket的总数。在方法查找的过程中,Objective-C runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index=(mask & selector))这可以做一个简单的hash散列算法。

2.occupied 一个整数 指定实际占用的缓存bucket的总数

3.buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。

元类 (Meta Class)

在上面我们提到 所有类自身也是一个对象 我们可以向这个对象发送消息(调用类方法)

    
NSArray *array = [NSArray array];

这个例子中,+array消息发送给了NSArray类 而这个NSArray也是一个对象 既然是对象 那么他也是一个objc_object指针 包含一个指向其类的isa指针。那么 有这么一个疑问,这个isa指针指向了什么? 为了调用+array方法 这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体,这就引出了meta_class的概念。

meta-class是一个类对象的类。

当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。

meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

再深入一下,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。

通过上面的描述,再加上对objc_class结构体中super_class指针的分析,我们就可以描绘出类及相应meta-class类的一个继承体系了,如下图所示:

对于NSObject继承体系来说,其实例方法对体系中的所有实例、类和meta-class都是有效的;而类方法对于体系内的所有类和meta-class都是有效的。

在正式的学习RunTime之前 我们先来了解几个概念

SEL 类成员方法指针 但不同于C语言中的函数指针 函数指针直接保存了方法的地址 但是SEL只是方法编号

IMP 一个函数指针 保存了方法的地址

Method 方法的结构体 其中保存了方法的名字 实现 和 类型的描述字符串

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types  //方法返回值,和各个参数类型等的字符串描述;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}

//根据函数获取函数的SEL
@selector()
//获取函数指针SEL的函数名字符串
NSString *NSStringFromSelector(SEL aSelector);
//根据函数名获取函数指针
SEL NSSelectorFromString(NSString *aSelectorName);
//获取类Class的字符串描述
NSString *NSStringFromClass(Class aClass);
//根据类的字符串描述获取类Class
Class _Nullable NSClassFromString(NSString *aClassName);
//获取协议的字符串描述(协议名字)
NSString *NSStringFromProtocol(Protocol *proto)
//根据协议名字获取协议对象
Protocol * _Nullable NSProtocolFromString(NSString *namestr)

类与对象操作函数

runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。下面我们将根据这些方法的用途来分类讨论这些方法的使用。

类相关的操作函数

我们可以回过头去看看objc_class的定义,runtime提供的操作类的方法主要就是针对这个结构体中的各个字段的。下面我们分别介绍这一些的函数。并在最后以实例来演示这些函数的具体用法。

类名(name)

类名的操作函数主要有:

const char *class_getName(Class cls) 

对于 class_getName(Class cls)函数 如果传入的cls为Nil 则返回一个nil。

父类(super_class)和元类(meta-class)

//获取父类名
Class  class_getSuperclass(Class cls)
//判断给定的class是不是一个元类
BOOL class_isMetaClass(Class cls)

class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。
class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。

示例代码:

#import "ViewController.h"
#import <objc/runtime.h>
#import "Person.h"


@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p = [[Person alloc] init];
    //获取类名
    const char *className = class_getName([p class]);
    //变成oc中的String
    NSString *ocName = [NSString stringWithUTF8String:className];
    //获取父类的名称
    Class superClass = class_getSuperclass([p class]);
    const char *superName = class_getName(superClass);
    NSString *superOcName = [NSString stringWithUTF8String:superName];
    
    //判断是否为元类
    if (class_isMetaClass([p class])) {
        NSLog(@"我是元类");
    }else {
        NSLog(@"我不是元类");
    }
    
    NSLog(@"获取到的类名:%@ 它父类的名称是%@",ocName,superOcName);
    
}

打印结果:

2016-12-19 15:51:42.750 RunTimeDemo[1239:93957] 我不是元类
2016-12-19 15:51:42.752 RunTimeDemo[1239:93957] 获取到的类名:Person 它父类的名称是NSObject

 

实例变量的大小(instance_size)

    
// 获取实例大小
size_t class_getInstanceSize ( Class cls );

代码示例:

//获取实例变量的大小
    size_t size = class_getInstanceSize([p class]);
    NSLog(@"获取实例变量的大小%zu",size);
//结果
2016-12-19 15:59:20.350 RunTimeDemo[1257:96333] 获取实例变量的大小8

获取一个类的成员变量 主要使用一下函数

// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
 
// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
 
// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
 
// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );

class_getInstanceVariable函数,他返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(IVar)

class_getClassVariable函数,目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。

Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以使用class_addIvar函数了。不过需要注意的是,这个方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<<alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))。

class_copyIvarList函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。

示例代码:

- (void)getClassIvarDemo {
    UInt32 count;
    //动态获取某个类中的成员变量
    Ivar *ivars = class_copyIvarList([Person class], &count);
    //遍历属性 列表
    for (UInt32 i = 0; i < count; i ++) {
        Ivar ivar = ivars[i];
        
        const char *ivarName = ivar_getName(ivar);
        NSString *ocName = [NSString stringWithUTF8String:ivarName];
        //获取成员变量的类型
        const char * typeName = ivar_getTypeEncoding(ivar);
        NSLog(@"成员变量:%@ 成员变量的类型%@",ocName,[NSString stringWithUTF8String:typeName]);
    }
    free(ivars);
    Person *p = [[Person alloc] init];
    p.name = @"tian";
    //获取Person类中一个指定的类的信息
    Ivar ivar = class_getInstanceVariable([Person class], "_name");
    //获取该属性的值
    id name = object_getIvar(p, ivar);
    NSString *newName = @"wang";
    if ( name != newName) {
        //为成员变量赋值
        object_setIvar(p, ivar, newName);
    }
    
    NSLog(@"%@ 成员变量新值%@",[NSString stringWithUTF8String:ivar_getName(ivar)],p.name);
    
}

结果:
2016-12-19 17:07:10.333 RunTimeDemo[1437:122637] 成员变量:_age 成员变量的类型i
2016-12-19 17:07:10.334 RunTimeDemo[1437:122637] 成员变量:_name 成员变量的类型@"NSString"
2016-12-19 17:07:10.334 RunTimeDemo[1437:122637] 成员变量:_ID 成员变量的类型@"NSString"
2016-12-19 17:07:10.334 RunTimeDemo[1437:122637] _name 成员变量新

添加成员变量 将在会面的章节介绍。

2.属性操作函数

// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );
 
// 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
 
// 为类添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
 
// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

示例代码:

#import <Foundation/Foundation.h>

@interface Person : NSObject {
    NSString *weight;
}

@property (nonatomic,copy) NSString *name;

@property (nonatomic,assign) int age;

@property (nonatomic,copy) NSString *ID;

@end


- (void)getClassPropertyListDemo {
    uint count;
    
    //获取属性列表
    objc_property_t *propertys = class_copyPropertyList([Person class], &count);
    for (uint i = 0; i < count; i ++) {
        objc_property_t property = propertys[i];
        //属性的名字
        const char * propertyName = property_getName(property);
        //属性名称
        NSString *ocName = [NSString stringWithUTF8String:propertyName];
        
        //获取属性的类型
        const char *propertyType = property_getAttributes(property);
        NSString *ocType = [NSString stringWithUTF8String:propertyType];
        
        NSLog(@"属性的名字%@ ----- 属性的属性%@",ocName,ocType);
    }
    free(propertys);
    
    Person *p = [[Person alloc] init];
    p.name = @"tianlianfeng";
    //获取特定的属性
    objc_property_t property = class_getProperty([Person class], "name");
    //获取特定属性的值
    [p setValue:@"tianxiaoer" forKey:[NSString stringWithUTF8String: property_getName(property)]];
    NSLog(@"属性的值%@",p.name);
}

结果:
2016-12-19 17:42:10.847 RunTimeDemo[1520:134330] 属性的名字name ----- 属性的属性T@"NSString",C,N,V_name
2016-12-19 17:42:10.848 RunTimeDemo[1520:134330] 属性的名字age ----- 属性的属性Ti,N,V_age
2016-12-19 17:42:10.848 RunTimeDemo[1520:134330] 属性的名字ID ----- 属性的属性T@"NSString",C,N,V_ID
2016-12-19 17:42:10.849 RunTimeDemo[1520:134330] 属性的值tianxiaoer

可以看到打印的属性 并没有 weight 因为weight没有被 @property修饰。

方法(methodLists)

在讲方法前 我们需要了解一些东西。

首先看一下方法的定义 Method 就是一个objc_method结构体

objc_method是类的一个方法的描述。

定义如下:

typedef struct objc_method *Method;  
  
struct objc_method {  
    SEL method_name;        // 方法名称  
    charchar *method_typesE;    // 参数和返回类型的描述字串  
    IMP method_imp;         // 方法的具体的实现的指针  
}  

那么方法的实现主要有以下函数:

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
 
// 获取类中的指定实例方法
Method class_getInstanceMethod ( Class cls, SEL name );
 
// 获取类中的指定类方法
Method class_getClassMethod ( Class cls, SEL name );
 
// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
 
// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
 
// 返回方法的具体实现的指针
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
 
// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

代码示例:

先看一下 为了实验配置的Person类

#import <Foundation/Foundation.h>

@interface Person : NSObject {
    NSString *weight;
}

@property (nonatomic,copy) NSString *name;

@property (nonatomic,assign) int age;

@property (nonatomic,copy) NSString *ID;


- (void)method1;

- (void)method2;

+ (void)classMethod;

@end


#import "Person.h"

@interface Person ()

- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;

@end

@implementation Person



- (void)method1 {
    NSLog(@"我是method1的实现");
}


-(void)method2 {
    NSLog(@"我是method2的实现");
}

+ (void)classMethod {
    NSLog(@"我是类方法");
}

- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
    NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}

@end
//获取一个类中的所有方法列表
- (void) getClassMethodList {
    uint outCount;
    //获取所有的方法  但是不知道为什么 类方法 没有打印出来
    
    Method *methods = class_copyMethodList([Person class], &outCount);
    for (uint i = 0; i < outCount; i ++) {
        Method method = methods[i];
        //获取方法的名字
        SEL selector = method_getName(method);
        
        NSLog(@"%@",NSStringFromSelector(selector));
    }
    free(methods);
    
}

//获取类中的指定方法

- (void)getClassMethod {
    //获取类中指定的实例方法
    Method method = class_getInstanceMethod([Person class], @selector(method1));
    
    if (method) {
        //获取该方法的实现指针
        IMP imp = class_getMethodImplementation([Person class], @selector(method1));
        NSLog(@"方法的实现指针%p",imp);
    }
    
    //获取类中指定的类方法
    Method classMethod = class_getClassMethod([Person class], @selector(classMethod));
    if (classMethod) {
        //获取该方法的实现指针
        IMP imp = class_getMethodImplementation_stret([Person class], @selector(classMethod));
        NSLog(@"方法的实现指针%p",imp);
    }
    
}

替代方法的实现 好像实例方法不能喝类方法交换 试了两次 程序运行崩溃

- (void)getClassMethod {
    //获取类中指定的实例方法
    Method method = class_getInstanceMethod([Person class], @selector(method1));
    //获取该方法的实现指针
    IMP imp = class_getMethodImplementation([Person class], @selector(method1));
    Method method2 = class_getInstanceMethod([Person class], @selector(method2));
    //方法二的实现指针
    IMP method2Imp = class_getMethodImplementation([Person class], @selector(method2));
    
    //交换方法的实现 方法一
    
    //替代方法的实现 将方法一的实现 改为方法二的实现 现在调用方法一 实现的是方法二
    class_replaceMethod([Person class], @selector(method1), method2Imp, method_getTypeEncoding(method2));
    //将method2 方法的实现指针 指向 method1 调用method2实现的是method1
    class_replaceMethod([Person class], @selector(method2), imp, method_getTypeEncoding(method));
    
    Person *p = [[Person alloc] init];
    [p method1];
    //执行结果:我是method2的实现
    [p method2];
    //执行结果:我是method1的实现
    
    //交换方法的实现 方法二
    method_exchangeImplementations(method, method2);
    
    //交换方法的实现 方法三
    method_setImplementation(method, method2Imp);
    method_setImplementation(method2, imp);
    
    [p method1];
    //执行结果:我是method2的实现
    [p method2];
    //执行结果:我是method1的实现
}

通常来讲 交换或者拦截系统的方法 是我们关心的 因为交换我们自己写的方法 其实没什么意义。

- (void)method3 {
    NSLog(@"我是方法三");
    //拦截了系统方法 但是我们又想让他响应
    [self method3];
}

- (void)addMethodForSelf {
    //获取方法
    Method viewwill = class_getInstanceMethod([self class], @selector(viewWillAppear:));
    Method method = class_getInstanceMethod([self class], @selector(method3));
    BOOL success = class_addMethod([self class], @selector(viewWillAppear:), class_getMethodImplementation([self class], @selector(method3)), method_getTypeEncoding(method))
    ;
    if (success) {
        class_replaceMethod([self class],@selector(method3) , class_getMethodImplementation([self class], @selector(viewWillAppear:)),method_getTypeEncoding(viewwill));
    }else {
        method_exchangeImplementations(viewwill, method);
    }

    
}

交换方法有几种途径 周全起见,有两种情况要考虑一下。第一种情况是要复写的方法(overridden)并没有在目标类中实现(notimplemented),而是在其父类中实现了。第二种情况是这个方法已经存在于目标类中(does existin the class itself)。这两种情况要区别对待。 (译注: 这个地方有点要明确一下,它的目的是为了使用一个重写的方法替换掉原来的方法。但重写的方法可能是在父类中重写的,也可能是在子类中重写的。) 对于第一种情况,应当先在目标类增加一个新的实现方法(override),然后将复写的方法替换为原先(的实现(original one)。 对于第二情况(在目标类重写的方法)。这时可以通过method_exchangeImplementations来完成交换

class_getInstanceMethod、class_getClassMethod函数,与class_copyMethodList不同的是,这两个函数都会去搜索父类的实现。
class_copyMethodList函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)(一个类的实例方法是定义在元类里面)。该列表不包含父类实现的方法。outCount参数返回方法的个数。在获取到列表后,我们需要使用free()方法来释放它。
class_replaceMethod函数,该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现。
class_getMethodImplementation函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。
class_respondsToSelector函数,我们通常使用NSObject类的respondsToSelector:或instancesRespondToSelector:方法来达到相同目的。

协议(objc_protocol_list)

协议相关的操作包含以下函数:

// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );
 
// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
 
// 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

示例代码

 // 协议
        Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &outCount);
        Protocol * protocol;
        for (int i = 0; i < outCount; i++) {
            protocol = protocols[i];
            NSLog(@"protocol name: %s", protocol_getName(protocol));
        }
 
        NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"" : @" not", protocol_getName(protocol));
 

拦截调用

上面的方法调用是在正常情况下逐级上寻的,如果到最后也没有找到要调用的方法,就会转向拦截调用。

那么什么是拦截调用呢

拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理

+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后两个方法需要转发其他类的处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;

第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理返回YES;

第二个方法和第一个方法类似 只不过处理的是 实例方法

第三个方法是将你调用的不存在的方法重新定位到一个其他声明了这个方法的类,只需要你返回这个方法的target

第四个方法是将你调用的这个不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用调用invokeWithTarget:方法让某个target触发这个方法。

参考博客:http://blog.jobbole.com/79566/

原文地址:https://www.cnblogs.com/huanying2000/p/6198443.html