C的对象模型和runtime机制

来源:http://www.smjxgs.com 作者:王中王鉄算盘 人气:199 发布时间:2019-08-25
摘要:Objective-C的对象模型和runtime机制 学习runtime,主要参考的是@SOI的iOS runtime和runloop http://www.jianshu.com/p/ebc6e20b84cf 自己写一遍和阅读一遍还是有很大的区别。 也参考了@CornBallast的讲解,很风

Objective-C的对象模型和runtime机制

学习runtime,主要参考的是@SOI的iOS runtime和runloop
http://www.jianshu.com/p/ebc6e20b84cf
自己写一遍和阅读一遍还是有很大的区别。
也参考了@CornBallast的讲解,很风趣,讲的很明白。
http://www.jianshu.com/p/88d11bb12ba1

runtime

内容列表

对象模型(结构定义,类对象、元类和实例对象的关系) 消息传递和转发机制 runtime系统功能理解

Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言,它不仅需要一个编译器,还需要RunTime系统来动态创建类和对象,进行消息的发送和转发。
在使用runtime之前,我们需要引入头文件

运行时语言,实现Object-C的C语言库,将OC转换成C进行编译的过渡者。

作为一门动态编程语言,Objective-C 会尽可能的将编译和链接时要做的事情推迟到运行时。只要有可能,Objective-C 总是使用动态 的方式来解决问题。这意味着 Objective-C 语言不仅需要一个编译环境,同时也需要一个运行时系统来执行编译好的代码。运行时系统(runtime)扮演的角色类似于 Objective-C 语言的操作系统,Objective-C 基于该系统来工作。因此,runtime好比Objective-C的灵魂,很多东西都是在这个基础上出现的。所以它是值的你花功夫去理解的。

对象模型

#import "objc/objc-runtime.h"
实现

主要是用C语言实现,部分由汇编语言。实际上正是runtime将OC中面向对象的类转换成C语言中面向过程的结构体。无论是实例对象还是类对象,实际上对应的都是C中的结构体。

结构定义

对象(Object): OC中基本构造单元 (building block),用于存储和传递数据。

可以在objc.h的文件中查找到对象结构的定义,如下所示即对象结构为Class类型的isa,而Class是 objc_class结构类型指针。objc_class即我们理解的类对象结构,其也包含一个isa类对象结构指针。

类和对象的最终实现都是一种数据结构,(subclass is an instance of superclass)

对象结构

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

id类型定义

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

Class类型定义

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *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;

先看一个问题:
创建一个Person的类,然后创建一个Person对象。执行一下操作:

使用

iskindofclass和isMemberOfClass就是两个典型的运行时方法。
同时注意使用class_addMethod等方法时要引入#import <objc/runtime.h>头文件。

类(类对象)、元类(元类对象)和实例对象的关系

一个完整的类应该包括类方法、实例方法和成员变量(实例变量), 每个对象都包括一个isa(is a class)指针指向类对象(运行时方法发送给对象消息,才确定类别并调用相应的方法实现),类对象结构中记载了类的所有信息。

类对象的isa指向元类对象(meta class),类对象中的方法列表是实例方法(-, instance methods), 元类对象中的方法列表是类方法(+, class methods)

可以这么理解:

类包括类对象和元类对象,它们通过类对象结构定义,构成类的所有信息。在定义实例对象的时候,并不会进行任何存储空间(堆)分配,直到调用类方法alloc函数和实例方法init函数实现实例对象在堆中的结构存储分配,并将isa指向其类对象,父类成员变量和对应类对象成员变量初始化为0或nil

上述理解可以通过下面代码和对象变量结构分析来进行确认。 测试代码

#import 
#import 
@interface AClass : NSObject
{
    int a;
    char cA;
}

- (void)printA;

@end

@implementation AClass

- (void)printA
{
    NSLog(@I am class A~);
}

@end

@interface BClass : AClass
{
    int b;
    char cB;
}

- (void)printB;

@end

@implementation BClass

- (void)printB
{
    NSLog(@I am class B~);
}

@end


// ---------- main ----------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // ******对象模型初探******
        AClass *a = [[AClass alloc] init];
        BClass *b = [[BClass alloc] init];
        BClass *b1;
        [a printA];
        [b printB];
        }
        return 0;
 }

查看对象变量结构(通过设置断点进入Debug模式查看)

图片 1

类对象、元类和实例对象的isa指针调用图示< 喎?" target="_blank" class="keylink">vc3Ryb25nPqOoc3ViY2xhc3MgaXMgYSBpbnN0YW5jZSBvZiBzdXBlcmNsYXNzo6kNCjxwPigxKSBSb290IGNsYXNzIMrHTlNPYmplY3SjrCBOU09iamVjdMO709CzrMDgo6xzdXBlcmNsYXNzIKOtJmd0OyBuaWw8L3A DQo8cD4oMikgw7 49sDgttTP87a809DSu7j2aXNh1rjP8s6o0ru1xE1ldGEgY2xhc3M8L3A DQo8cD4oMykgw7 49tSqwOC21M/ztcQgaXNh1rjV67a81rjP8iBOU09iamVjdLXE1KrA4LbUz/M8YnIgLz4NCjxpbWcgYWx0PQ=="这里写图片描述" src="" title="" />

Person * person = [Person new];
NSLog(@"%p",[Person class]);
NSLog(@"%p",[person class]);
NSLog(@"%p",object_getClass(person));
NSLog(@"%p",object_getClass([person class]));
关于32位与64位

runtime分为早期版本和现行版本,32位使用早期,64位使用现行。32位已被淘汰。
64位与32位的区别在于,操作系统上64位用于3D动画等先进功能32位用于日常简单使用;处理器上64位比32位更宽处理能力更强;软件上基于32位和64位开发的不同;内存容量不同。现在必须支持arm64,代码中主要注意类型长度上的区别。ios推荐使用的NSInteger区别于int的地方也就是在于前者可以根据系统位数使用较大的长度。

消息传递和转发机制

消息传递(Messaging): 在对象之间传递数据并执行任务的过程

Objective-C基于C语言加入了面向对象特性和消息转发机制的动态语言,除编译器外还需要用Runtime系统来动态创建类和对象进行消息发送和转发。

不同语言有不同函数传递方法,C语言 - 函数指针,C - 函数调用(引用)类成员函数在编译时候就确定了其所属类别, Objective-C 通过选择器和block。

Objective-C强调消息传递而非方法调用。可以向一个对象传递消息,且不需要再编译期声明这些消息的处理方法。这些方法在运行时才确定。运行时(runtime)具体功能将在下面介绍。

[receiver message];
并不会马上执行 receiver 对象的 message方法的代码,而是向receiver发送一条message消息,该句话被编译器转化为:

id obj_msgSend(id self, SEL op, …);

PS: 消息调用函数还存在特殊情况,如其他函数
objc_msgSend_stret //待发送消息返回结构体
objc_msgSend_fpret //返回浮点数
objc_msgSendSuper //给超类发消息

SEL 表示方法选择器,结构如下: typedef struct objc_selector*SEL;, 可通过关键字@selector()获得

id 数据结构在第一部分:对象模型中已经有定义。

obj_msgSend 发消息流程:

根据receiver 对象的isa类对象指针获取对应的class(类对象); 优先在类对象的cache(fast map)查找message方法,Not find ->再到methodLists(类中的调度表,用于映射方法和实际内存地址,同时类中还包括指向父类的指针)查找; 如果没有在class象里找到,再到super_class查找; 如果找到message这个方法,执行它的实现IMP 如果找不到消息,则执行消息转发(message forwarding)

Method数据结构 runtime.h头文件中定义:

typedef struct objc_method *Method;
struct objc_method {
          SEL method_name; // 特殊的字符串,描述方法名, 可以通过关键字 @selector( ) 获取
          char *method_types;
          IMP method_imp;
}

PS:消息转发分为两大阶段即动态添加方法解析(dynamic method resolution)和完整的消息转发机制(full forward mechanism)

打印结果:

与传统静态语言C语言的区别

很常见的一个消息发送语句:

[receiver message]

会被编译器转化成

objc_msgSend(receiver, selector)

如果有参数则为

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

receiver:消息接受者(receiver),self,某个类
selector:方法名(message)
arg:参数
传统的静态语言,编译器会自上而下,在存在调用函数时会进入调用函数,逐句实现编译。
而OC在编译过程中并不会这样做,只是编辑成明确方法名调用者参数的形式,该函数被如何实现以及是否实现并不关心。只有在程序运行时才会根据方法名进入具体函数中,这也就是会在运行中找不到对应函数时会造成崩溃的原因。
OC实际上是使用消息传递机制代替传统C语言的函数调用。
_CMD,SEL类型,可以获取当前方法名的关键字。

- (void)message  
{  
    self.name = @"James";//通过self关键字给当前对象的属性赋值  
    SEL currentSel = _cmd;//通过_cmd关键字取到当前函数对应的SEL  
    NSLog(@"currentSel is :%s",(char *)currentSel);  
}

打印结果

ObjcRunTime[693:403] currentSel is :message

初步理解,其实objc_msgSend 所做的事情,就是通过我们传入的self 指针,找到class 的method_list 然后根据SEL 做比较,没有的话,就在super class 找,如此往复。直到找到匹配的SEL,然后,call imp。当然,runtime在其中又做了很多优化。

runtime系统功能理解

runtime : 程序运行后,提供相关支持的代码叫做OC运行期环境(OC runtime),它提供了对象间传递消息的重要函数(比如objc_msgSend),并且包含创建类实例所用的全部逻辑(即创建实例对象的存储结构和空间,包括isa指向“类对象”的指针)

runtime系统是一个用C语言编写动态链接库,核心是消息分发。Runtime机制包括对象模型,消息传递和转发,方法实现机制和其他运行时方法,可以实现动态创建修改类对象和对象等功能,消息传递和转发,方法动态实现,Method Swizzling等功能。

2017-07-07 10:22:11.228 Total[4157:410438] 0x10ccf2740
2017-07-07 10:22:11.228 Total[4157:410438] 0x10ccf2740
2017-07-07 10:22:11.229 Total[4157:410438] 0x10ccf2740
2017-07-07 10:22:11.229 Total[4157:410438] 0x10ccf2718
OC中的消息传递

OC中的消息传递与C中函数调用的最大区别在于OC中的消息传递在时运行时中进行的,而函数调用是在编译时就可以进行的。

id num = @123;
//输出123
NSLog(@"%@", num);
//程序崩溃,报错[__NSCFNumber appendString:]: unrecognized selector sent to instance 0x7b27
[num appendString:@"Hello World"];

上述代码没有任何问题,id可以使任何类型包括NSString,但是在运行过程中在NSNumber中找不到appendString这个方法,所以会报错。所以消息传递的强大之处在于可以在运行时添加新方法,缺点在于无法再编译时发现错误。

 

 

内容列表 对象模型(结构定义,类对象、元类和实例对象的关系) 消息传递和转发机制 runtime系统功能理解...

为什么打印的结果不一样呢?这就要分析类和对象了。

OC中的消息传递转化为C的函数调用

简单的创建Person对象

//为了方便查看转写后的C语言代码,将alloc和init分两步完成
Person *p = [Person alloc];
p = [p init];
p.name = @"Jiaming Chen";
[p showMyself];

使用clang -rewrite-objc main.m可以转化成C

Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_f5b408_mi_1);
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("showMyself"));

第一行代码简要表示为

Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));

第一步,获取Person类,第二步注册alloc方法,然后通过objc_msgSend将alloc消息发送给消息接受者Person。
第二行代码简要表示为

p = objc_msgSend(p, sel_registerName("init"));

由此可见,对于OC而言都是以消息传递实现,而正是runtime通过objc_msgSend将一个面向对象的消息传递转换成了一个面向过程的的函数调用。

Runtime数据结构

在Objective-C中,使用[receiver message]语法并不会马上执行receiver对象的message方法的代码,而是向receiver发送一条message消息,这条消息可能由receiver来处理,也可能由转发给其他对象来处理,也有可能假装没有接收到这条消息而没有处理。其实[receiver message]被编译器转化为:

idobjc_msgSend (idself, SEL op, ... );

这个可以在objc/message.h中找到。
下面从两个数据结构id和SEL来逐步分析和理解Runtime有哪些重要的数据结构。

消息转发

我们知道消息传递实际上做的事就是利用objc_msgSend,将消息传递给接受者,在接受者的结构体中的objc_method_list中查找方法。如果找到就可以直接调用,如果找不到就会通过super_class指针去它的父类中查找,直至基类NSObject。如果还是没有找到就会调用NSObject的doesNotRecognizeSelector报出unrecognized的错误。
但是在此之前,还有三次机会来处理这个消息,这就是所谓的消息转发。
具体在下文中。

SEL

SEL是函数objc_msgSend第二个参数的数据类型,表示方法选择器,按下面路径打开objc.h文件

图片 2

SEL数据结构

typedef struct objc_selector *SEL;
其实它就是映射到方法的C字符串,你可以通过Objc编译器命令@selector()或者Runtime系统的sel_registerName函数来获取一个SEL类型的方法选择器。

runtime源码学习
  • selector
    selector可以叫做选择器,其实指的就是对象的方法,也可以理解为C语言里面的函数指针,面向对象里面的对应概念。
    @selector(xxxx)的作用是找到名字为xxxx的方法。一般用于[a performSelector:@selector(b)];就是说去调用a对象的b方法,和[a b];的意思一样,但是这样更加动态一些。@selector(xxxx)返回的类型是SEL,看方法说明的时候如果参数类型是SEL,那么就是要接受@selector(xxxx)返回的值。
    在Objc中 SEL的定义是:
typedef struct objc_selector *SEL;  

我们注意到对于SEL的定义,实际上就是使用typedef将结构体objc_selector重新命名为*SEL。而SEL则是指向这个结构体的指针,所以SEL的对象不需要再加*。
在Mac OS X中SEL其实被映射为一个C字符串,可以看作是方法的名字(编号),它并不一个指向具体方法实现(IMP类型才是)。对于所有的类,只要方法名是相同的,产生的selector都是一样的。
简而言之,你可以理解 @selector()就是取类方法的编号,他的行为基本可以等同C语言的中函数指针,只不过C语言中,可以把函数名直接赋给一个函数指针,而Object-C的类不能直接应用函数指针,这样只能做一个@selector语法来取。
它的结果是一个SEL类型。这个类型本质是类方法的名字(编号)。

注意1. @selector是查找当前类的方法,而[object @selector(方法名:方法参数..) ] ;是取object对应类的相应方法.
注意2.查找类方法时,除了方法名,方法参数也查询条件之一.
注意3. 可以用字符串来找方法 SEL 变量名 = NSSelectorFromString(方法名字的字符串);
注意4. 可以运行中用SEL变量反向查出方法名字字符串
NSString *变量名 = NSStringFromSelector(SEL参数);

runtime 在实现selector时,实现了一个很大的Set,简单的说就是一个经过了优化过的hash表。而Set的特点就是唯一,也就是SEL是唯一的。那么对于字符串的比较仅仅需要比较他们的地址就可以了。 所以OC中不允许使用方法名一样参数不一样的函数,编译器会根据每个方法的方法名生成唯一的SEL。所以,在速度上是无与伦比的。
selector主要用于两个对象之间进行松耦合的通讯.这种方法很多开发环境用到。比如GTK,Delphi.基本上整个Cocoa库之间对象,控制之间通讯都是在这个基础构建的。
用户行为统计中的经典运用:

  (void)setUpAnalytics
{
   __weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     //读取配置文件,获取需要统计的事件列表
     for (NSString *classNameString in analyticsData.allKeys) {
         //使用运行时创建类对象
         const char *className = [classNameString UTF8String];
         //从一个字串返回一个类
         Class newClass = objc_getClass(className);
         NSArray *pageEventList = [[analyticsData objectForKey:classNameString] objectForKey:Event];
         for (NSDictionary *eventDict in pageEventList) {
             //事件方法名称
             NSString *eventMethodName = eventDict[MethodName];
             SEL seletor = NSSelectorFromString(eventMethodName);
             NSString *eventId = eventDict[EventId];
             [weakSelf trackEventWithClass:newClass selector:seletor event:eventId];
             [weakSelf uAnalyticstrackEventWithEventdata:eventDict];
         }
     }
   });
}
  • id
    id是通用类型指针,能够表示任何对象,换句话说,id 类型的变量可以存放任何数据类型的对象。在内部处理上,这种类型被定义为指向对象的指针,实际上是一个指向这种对象的实例变量的指针。
    查看到id数据结构如下:
// Represents an instance of a class.  
struct objc_object {  
    Class isa  OBJC_ISA_AVAILABILITY;  
};  
// A pointer to an instance of a class.  
typedef struct objc_object *id; 

实际上*id就是结构体objc_object的别名。而id则是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类。需要注意的是id 是一个指针,所以在使用id的时候不需要加星号。

NSString *str = [[NSString alloc] init];

这里我们定义的对象str,实际上同样是一个objc_object结构体指针。

  • Class
    isa指针的数据类型是Class,Class表示对象所属的类。
// An opaque type that represents an Objective-C class.  
typedef struct objc_class *Class;  

可以查看到Class其实就是一个objc_class结构体指针。class类型和id类型一样,本身继承于指针类型,用于储存类对象的指针,故在声明class类型变量时和id一样不需要*号。

NSString *str = [[NSString alloc] init];
Class c = [str Class];

这里我们定义的c,实际上同样是一个objc_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;  
/* Use `Class` instead of `struct objc_class *` */  

OC的类其实也是一个对象,即类对象,意思就是你可以向一个类发送消息。为了可以调用类方法,这个类的isa指针必须指向一个包含这些类方法的类结构体,也就是元类(meta-class)的概念。meta-class之所以重要,是因为它保存着创建类对象以及类方法所需的所有信息。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己所属的类。所有的meta-class使用基类的meta-class作为它们的父类,而基类的meta-class也是属于它自己,也就是说基类的meta-class的isa指针指向它自己。

图片 3

meta图解说明.png

上图实线是super_class指针,虚线是isa指针。有几个关键点需要解释以下:
1.Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。
2.每个Class都有一个isa指针指向唯一的Meta class
3.Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。
4.每个Meta class的isa指针都指向Root class (meta)。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        Class c1 = [p class];
        Class c2 = [Person class];
        //输出 1
        NSLog(@"%d", c1 == c2);
    }
    return 0;
}

由此可见,类对象是单例。对于Person这个类对象来说,只有一个,所有类对象均是如此。
介绍两个函数:

OBJC_EXPORT BOOL class_isMetaClass(Class cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

OBJC_EXPORT Class object_getClass(id obj) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

class_isMetaClass用于判断Class对象是否为元类,object_getClass用于获取对象的isa指针指向的对象。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        //输出1
        NSLog(@"%d", [p class] == object_getClass(p));
        //输出0
        NSLog(@"%d", class_isMetaClass(object_getClass(p)));
        //输出1
        NSLog(@"%d", class_isMetaClass(object_getClass([Person class])));
        //输出0
        NSLog(@"%d", object_getClass(p) == object_getClass([Person class]));
    }
    return 0;
}

由上看出,实例的isa指针指向类,类的isa指针指向元类。

super_class表示实例对象对应的父类;
name表示类名,我们可以在运行期,通过这个名称查找到该类(通过:id objc_getClass(const char *aClassName))或该类的 metaclass(id objc_getMetaClass(const char *aClassName));
ivars表示多个成员变量,它指向objc_ivar_list结构体。在runtime.h可以看到它的定义:

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

objc_ivar_list其实就是一个链表,存储多个objc_ivar,而objc_ivar结构体存储类的单个成员变量信息。
(* struct objc_ivar ivar_list[1] 这个称为结构体数组,顾名思义,结构数组是指能够存放多个结构体类型(objc_ivar)的一种数组(ivar_list)形式。
methodLists表示方法列表,它指向objc_method_list结构体的二级指针,可以动态修改
methodLists的值来添加成员方法,也是Category实现原理,同样也解释Category不能添加属性的原因。
Category只能在运行时中添加属性。代码如下:

///例如可能是这样的使用
  static const void *propertyKey = &propertyKey;
  /// 将value通过运行时绑定到self
  objc_setAssociatedObject(self, propertyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  /// 将value在运行时中通过propertyKey取出绑定的值
  id value = objc_getAssociatedObject(self, propertyKey);

在runtime.h可以看到它的定义

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

同理,objc_method_list也是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息。
cache用来缓存经常访问的方法,它指向objc_cache结构体,后面会重点讲到。
protocols表示类遵循哪些协议。

  • Method
    Method表示类中的某个方法,在runtime.h文件中找到它的定义:
/// An opaque type that represents a method in a class definition.  
typedef struct objc_method *Method;  
struct objc_method {  
    SEL method_name                                          OBJC2_UNAVAILABLE;  
    char *method_types                                       OBJC2_UNAVAILABLE;  
    IMP method_imp                                           OBJC2_UNAVAILABLE;  
}  

其实Method就是一个指向objc_method结构体指针,它存储了方法名(method_name)、方法类型(method_types)和方法实现(method_imp)等信息。而method_imp的数据类型是IMP,它是一个函数指针,后面会重点提及。

  • Ivar
    Ivar表示类中的实例变量,在runtime.h文件中找到它的定义:
/// An opaque type that represents an instance variable.  
typedef struct objc_ivar *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  
}  

Ivar其实就是一个指向objc_ivar结构体指针,它包含了变量名(ivar_name)、变量类型(ivar_type)等信息。

  • IMP
    在上面讲Method时就说过,IMP本质上就是一个函数指针,指向方法的实现,在objc.h找到它的定义:
/// A pointer to the function of a method implementation.   
#if !OBJC_OLD_DISPATCH_PROTOTYPES  
typedef void (*IMP)(void /* id, SEL, ... */ );   
#else  
typedef id (*IMP)(id, SEL, ...);   
#endif

当你向某个对象发送一条信息,可以由这个函数指针来指定方法的实现,它最终就会执行那段代码,这样可以绕开消息传递阶段而去执行另一个方法实现。

  • cache
    顾名思义,Cache主要用来缓存,那它缓存什么呢?我们先在runtime.h文件看看它的定义
typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;  
struct objc_cache {  
    unsigned int mask /* total = mask   1 */                 OBJC2_UNAVAILABLE;  
    unsigned int occupied                                    OBJC2_UNAVAILABLE;  
    Method buckets[1]                                        OBJC2_UNAVAILABLE;  
};  

Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。

id

接下来看objc_msgSend第一个参数的数据类型id,id是通用类型指针,能够表示任何对象。按下面路径打开objc.h文件

图片 4

id数据结构

/// Represents an instance of a class.
struct objc_object {
Class isa  OBJC_ISA_AVAILABILITY;
};

id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类
注意:根据Apple的官方文档Key-Value Observing Implementation Details提及,key-value observing是使用isa-swizzling的技术实现的,isa指针在运行时被修改,指向一个中间类而不是真正的类。所以,你不应该使用isa指针来确定类的关系,而是使用class方法来确定实例对象的类。

消息发送
  • objc_msgSend函数
    在前面已经提过,当某个对象使用语法[receiver message]来调用某个方法时,其实[receiver message]被编译器转化为:
id objc_msgSend ( id self, SEL op, ... );  

现在让我们看一下objc_msgSend它具体是如何发送消息:
1.首先根据receiver对象的isa指针获取它对应的class;
2.优先在class的cache查找message方法,如果找不到,再到methodLists查找;
3.如果没有在class找到,再到super_class查找;
4.一旦找到message这个方法,就执行它实现的IMP。

  • self与super
    为了让大家更好地理解self和super,借用sunnyxx博客的iOS程序员6级考试一道题目:下面的代码分别输出什么?
@implementation Son : Father  
-(id)init  
{  
    self = [super init];  
    if (self)  
    {  
        NSLog(@"%@", NSStringFromClass([self class]));  
        NSLog(@"%@", NSStringFromClass([super class]));  
    }  
    return self;  
}  
@end  

self表示当前这个类的对象,而super是一个编译器标示符,和self指向同一个消息接受者(son)。在本例中,无论是[self class]还是[super class],接受消息者都是Son对象,但super与self不同的是,self调用class方法时,是在子类Son中查找方法,而super调用class方法时,是在父类Father中查找方法。
当调用[self class]方法时,会转化为objc_msgSend函数,这个函数定义如下:

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

这时会从当前Son类的方法列表中查找,如果没有,就到Father类查找,还是没有,最后在NSObject类查找到。我们可以从NSObject.mm文件中看到- (Class)class的实现:

-(Class)class {  
    return object_getClass(self);  
}  

所以NSLog(@"%@", NSStringFromClass([self class]));会输出Son。
当调用[super class]方法时,会转化为objc_msgSendSuper,这个函数定义如下:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...) 

objc_msgSendSuper函数第一个参数super的数据类型是一个指向objc_super的结构体,从message.h文件中查看它的定义:

/// Specifies the superclass of an instance.   
struct objc_super {  
    /// Specifies an instance of a class.  
    __unsafe_unretained id receiver;  

    /// Specifies the particular superclass of the instance to message.   
#if !defined(__cplusplus)  &&  !__OBJC2__  
    /* For compatibility with old objc-runtime.h header */  
    __unsafe_unretained Class class;  
#else  
    __unsafe_unretained Class super_class;  
#endif  
    /* super_class is the first class to search */  
};  
#endif 

结构体包含两个成员,第一个是receiver,表示某个类的实例。第二个是super_class表示当前类的父类。
这时首先会构造出objc_super结构体,这个结构体第一个成员是self,第二个成员是(id)class_getSuperclass(objc_getClass("Son")),实际上该函数会输出Father。然后在Father类查找class方法,查找不到,最后在NSObject查到。此时,内部使用objc_msgSend(objc_super->receiver, @selector(class))去调用,与[self class]调用相同,所以结果还是Son。
Method Resolution
Fast Forwarding
Normal Forwarding
我的理解:objc_super结构体中receiver是son,superclass是father,而objc_msgSend(objc_super->receiver, @selector(class))内部是使用objc_super中的receiver作为消息发送者,也就是son调用NSObject中的class方法,所以结果是son。

Class

isa指针的数据类型是Class,Class表示对象所属的类,按下面路径打开objc.h文件

图片 5

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;

注意:OBJC2_UNAVAILABLE是一个Apple对Objc系统运行版本进行约束的宏定义,主要为了兼容非Objective-C 2.0的遗留版本,但我们仍能从中获取一些有用信息。
让我们分析一些重要的成员变量表示什么意思和对应使用哪些数据结构。
isa表示一个Class对象的Class,也就是Meta Class(元类)。在面向对象的设计中,一切都是对象,Class在设计中本身也是一个对象。我们会在objc-runtime-new.h文件找到证据,发现objc-class有一下定义:

struct objc_class : objc_object {// Class ISA;Class
superclass;cache_tcache;// formerly cache pointer and
vtableclass_data_bits_tbits;// class_rw_t * plus custom rr/alloc
flags......}

由此可见,结构体objc_class也是继承objc_object,说明Class在设计中本身也是一个对象
其实Meta Class也是一个Class,那么它也跟其他Class一样有自己的isa和super_class指针,关系如下:

图片 6

Class isa and superclass relationship from Google

上图实线是super_class指针,虚线是isa指针。有几个关键点需要解释以下:
Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。
每个Class都有一个isa指针指向唯一的Meta class
Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。
每个Meta class的isa指针都指向Root class (meta)。

方法解析与消息转发

[receiver message]调用方法时,如果在message方法在receiver对象的类继承体系中没有找到方法,那怎么办?一般情况下,程序在运行时就会Crash掉,抛出unrecognized selector sent to…类似这样的异常信息。但在抛出异常之前,还有三次机会按以下顺序让你拯救程序。
1.Method Resolution 即所属类动态方法解析
2.Fast Forwarding 即备援接受者
3.Normal Forwarding

  • Method Resolution首先Objective-C在运行时调用 resolveInstanceMethod:或 resolveClassMethod:方法,让你添加方法的实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送的过程。
    举一个简单例子,定义一个类Message,它主要定义一个方法sendMessage,下面就是它的设计与实现:
@interface Message : NSObject  
-(void)sendMessage:(NSString *)word;  
@end  

@implementation Message  
-(void)sendMessage:(NSString *)word  
{  
    NSLog(@"normal way : send message = %@", word);  
}  
@end  

如果我在viewDidLoad方法中创建Message对象并调用sendMessage方法:

-(void)viewDidLoad {  
    [super viewDidLoad];  
    Message *message = [Message new];  
    [message sendMessage:@"Sam Lau"];  
}  

控制台会打印以下信息:

normal way : send message = Sam Lau

但现在我将原来sendMessage方法实现给注释掉,覆盖resolveInstanceMethod方法:

#pragma mark - Method Resolution  
/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation  
 (BOOL)resolveInstanceMethod:(SEL)sel  
{  
    if (sel == @selector(sendMessage:)) {  
        class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {  
            NSLog(@"method resolution way : send message = %@", word);  
        }), "v@*");  
    }  
    return YES;  
} 

控制台就会打印以下信息:

method resolution way : send message = Sam Lau

该方法主要原型为
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
方法名为给class添加方法,返回一个Bool类型返回值。第一个参数为需要添加方法的类,第二个参数为实例方法的名字也就是 (BOOL)resolveInstanceMethod:(SEL)sel方法中的参数sel,第三个参数为IMP类型的变量也就是函数的实现。需要传一个C函数,该函数至少要有两个变量,一个是id self,一个是SEL _cmd,也可以传字符串。
另一种写法。

void dynamicAdditionMethodIMP(id self, SEL _cmd) {
    //实现
    NSLog(@"dynamicAdditionMethodIMP");
}
class_addMethod([self class], name, (IMP)dynamicAdditionMethodIMP, "v@:");

第四个参数传入一个字符串"v@*,它表示方法的参数和返回值,详情请参考Type Encodings。
如果resolveInstanceMethod方法返回NO,运行时就跳转到下一步:消息转发(Message Forwarding)。

  • Fast Forwarding
    当对象所属类不能动态添加方法后,runtime就会询问当前的接受者是否有其他对象可以处理这个未知的selector。方法就是:
- (id)forwardingTargetForSelector:(SEL)aSelector;

如果目标对象实现- forwardingTargetForSelector:方法,系统就会在运行时调用这个方法,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理。否则,就会继续Normal Fowarding。
继续上面Message类的例子,将sendMessage和resolveInstanceMethod方法注释掉,然后添加forwardingTargetForSelector方法的实现:

#pragma mark - Fast Forwarding  
-(id)forwardingTargetForSelector:(SEL)aSelector  
{  
    if (aSelector == @selector(sendMessage:)) {  
        return [MessageForwarding new];  
    }  
    return nil;  
}  

此时还缺一个转发消息的类MessageForwarding,这个类的设计与实现如下:

@interface MessageForwarding : NSObject  
-(void)sendMessage:(NSString *)word;  
@end  

@implementation MessageForwarding  
-(void)sendMessage:(NSString *)word  
{  
    NSLog(@"fast forwarding way : send message = %@", word);  
}  
@end  

此时,控制台会打印以下信息:

fast forwarding way : send message = Sam Lau

这里叫Fast,是因为这一步不会创建NSInvocation对象,但Normal Forwarding会创建它,所以相对于更快点。

  • Normal Forwarding
    如果没有使用Fast Forwarding来消息转发,最后只有使用Normal Forwarding来进行消息转发。它首先调用methodSignatureForSelector:方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用-forwardInvocation:方法。
    继续前面的例子,将forwardingTargetForSelector方法注释掉,添加methodSignatureForSelector和forwardInvocation方法的实现:
#pragma mark - Normal Forwarding  
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector  
{  
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];  
    if (!methodSignature) {  
        methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];  
    }  
    return methodSignature;  
}  
-(void)forwardInvocation:(NSInvocation *)anInvocation  
{  
    MessageForwarding *messageForwarding = [MessageForwarding new];  
    if ([messageForwarding respondsToSelector:anInvocation.selector]) {  
        [anInvocation invokeWithTarget:messageForwarding];  
    }  
}  

super_class

表示实例对象对应的父类

三种方法的选择

Runtime提供三种方式来将原来的方法实现代替掉,那该怎样选择它们呢?

  • Method Resolution
    由于Method Resolution不能像消息转发那样可以交给其他对象来处理,所以只适用于在原来的类中代替掉。
  • Fast Forwarding
    它可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。
  • Normal Forwarding
    它跟Fast Forwarding一样可以消息转发,但它能通过NSInvocation对象获取更多消息发送的信息,例如:target、selector、arguments和返回值等信息。

name

表示类名

Associated Objects

如果我们想对系统的类添加方法时,可以使用扩展,但是添加属性时,只能使用继承。如果不想使用继承,则可以用runtime来关联对象,这与我们自定义类的添加属性不同。本质上是使用类别进行扩展,通过添加get方法和set方法从而在使用时可以使用点方法使用。与普通使用方法一致。
同时当使用Category对某个类进行扩展时,有时需要存储属性,Category是不支持的,这时需要使用Associated Objects来给已存在的类Category添加自定义的属性。Associated Objects提供三个C语言API来向对象添加、获取和删除关联值:

  • 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 )

其中objc_AssociationPolicy是个枚举类型,它可以指定Objc内存管理的引用计数机制。

  typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {  
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */  
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.  
                                            *   The association is not made atomically. */  
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.  
                                            *   The association is not made atomically. */  
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object. 
                                            *   The association is made atomically. */  
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied. 
                                            *   The association is made atomically. */  
};  

下面有个关于NSObject AssociatedObject Category添加属性associatedObject的示例代码:
NSObject AssociatedObject.h

@interface NSObject (AssociatedObject)  
@property (strong, nonatomic) id associatedObject;  
@end 

NSObject AssociatedObject.m

@implementation NSObject (AssociatedObject)  
- (void)setAssociatedObject:(id)associatedObject  
{  
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);  
}  
- (id)associatedObject  
{  
    return objc_getAssociatedObject(self, _cmd);  
}  
@end 

Associated Objects的key要求是唯一并且是常量,而SEL是满足这个要求的,所以上面的采用隐藏参数_cmd作为key。
一个给scrollview添加refreshView的实际用例:

@interface UIScrollView (Refresh)  
@property (nonatomic) RefreshView * refreshView;  
@end  

#import <objc/runtime.h>  
static char kRefreshView;  
@implementation UIScrollView (Refresh)  
@dynamic refreshView;  

- (void)setRefreshView:(RefreshView *)aView {  

    objc_setAssociatedObject(self, &kRefreshView, aView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);  
}  


- (RefreshView *)refreshView {  

    return objc_getAssociatedObject(self, &kRefreshView);  
}  

@end  

ivars

表示多个成员变量,它指向objc_ivar_list结构体。在runtime.h可以看到它的定义:

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

objc_ivar_list其实就是一个链表,存储多个objc_ivar,而objc_ivar结构体存储类的单个成员变量信息。

GetClass

得到一个实例的类。代码如下:

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

@interface ViewController ()  

@end  

@implementation ViewController  

- (void)viewDidLoad {  
    [super viewDidLoad];  
    // Do any additional setup after loading the view, typically from a nib.  


    Person * p1 = [[Person alloc] init];  

    Class c1 = object_getClass(p1);  
    NSLog(@"%@", c1);  
    Person * p2 = [[[c1 class] alloc] init];  
    NSLog(@"%@", p2.name);  

} 

methodLists

表示方法列表,它指向objc_method_list结构体的二级指针,可以动态修改methodLists的值来添加成员方法,也是Category实现原理,同样也解释Category不能添加实例变量*的原因。在runtime.h可以看到它的定义:

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

同理,objc_method_list也是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息。

isKindOfClass和isMemberOfClass

先看看isKindOfClass和isMemberOfClass在Object.mm中的实现:

- (BOOL)isKindOf:aClass
{
     Class cls;
     for (cls = isa; cls; cls = cls->superclass)
          if (cls == (Class)aClass)
               return YES;
     return NO;
}

- (BOOL)isMemberOf:aClass
{
     return isa == (Class)aClass;
}

可以看到isKindOfClass是取自身指针isa与参数指针做对比,如果相等返回YES,如果不等则取其superClass指针,直到superClass为空为止,循环结束返回空。

int main(int argc, const char * argv[]) {
     @autoreleasepool {
          BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
          BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
          BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
          BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
          NSLog(@"%d %d %d %d", res1, res2, res3, res4);
     }
     return 0;
}

//输出
2014-11-05 14:45:08.474 Test[9412:721945] 1 0 0 0

res1中NSObject的isa指针指向NSOject的meta-class,继续循环,meta-class的superClass是NSObject所以为ture;
res3中Sark的isa指针指向sark的meta-class,接下来指向NSObject的meta-class,再接下来指向NSObject;
res2中NSObject的isa指针指向NSOject的meta-class,与NSObject不一致;
res4中Sark的isa指针指向sark的meta-class,与Sark不一致。
注意上面是类与类相比。下面是类的实例与类相比:

Person *person = [[Person alloc] init];  
Teacher *teacher = [[Teacher alloc] init];  

//YES   
if ([teacher isKindOfClass:[Teacher class]]) {  
    NSLog(@"teacher 是 Teacher类或Teacher的子类");  
}  
//YES   
if ([teacher isKindOfClass:[Person class]]) {  
    NSLog(@"teacher 是 Person类或Person的子类");  
}  
//YES   
if ([teacher isKindOfClass:[NSObject class]]) {  
    NSLog(@"teacher 是 NSObject类或NSObject的子类");  
} 

// YES   
if ( [teacher respondsToSelector: @selector( setName: )] == YES ) {  
    NSLog(@"teacher responds to setSize: method" );  
}  

// NO   
if ( [teacher respondsToSelector: @selector( abcde )] == YES ) {  
    NSLog(@"teacher responds to nonExistant method" );  
}  

// YES   
if ( [Teacher respondsToSelector: @selector( alloc )] == YES ) {  
    NSLog(@"teacher class responds to alloc methodn" );  
}  

cache

顾名思义,Cache主要用来缓存,那它缓存什么呢?我们先在runtime.h文件看看它的定义:

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

#define CACHE_BUCKET_NAME(B)  ((B)->method_name)
#define CACHE_BUCKET_IMP(B)   ((B)->method_imp)
#define CACHE_BUCKET_VALID(B) (B)
#ifndef __LP64__
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
#else
#define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
#endif
struct objc_cache {
    unsigned int mask /* total = mask   1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。

Method Swizzling

Method Swizzling就是在运行时将一个方法的实现代替为另一个方法的实现。由于Foundation等框架都是闭源的,我们没有办法直接修改代码,通常情况下只能通过继承,类别,关联属性等手段添加属性和实例方法,较为繁琐。而Method Swizzling可以通过将一个方法的实现代替另一个方法的实现,从而达到修改闭源代码的目的。
例如一个想要统计每个页面出现的次数,如果在所有页面的viewWillAppear中都添加统计代码,则太过繁琐。那我们使用一个类别,并自定义一个方法替代系统的viewWillAppear,在其中做统计,并在统计结束后调用系统的viewWillAppear即可。

@interface UIViewController (MyUIViewController)

@end

@implementation UIViewController(MyUIViewController)
  (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        SEL originalSelector = @selector(viewWillAppear:);
        Method originalMethod = class_getInstanceMethod([self class], originalSelector);

        SEL exchangeSelector = @selector(myViewWillAppear:);
        Method exchangeMethod = class_getInstanceMethod([self class], exchangeSelector);

        method_exchangeImplementations(originalMethod, exchangeMethod);
    });
}

- (void)myViewWillAppear:(BOOL)animated {
    //这里实际上是调用系统的viewWillAppear,不会递归调用。
    [self myViewWillAppear:animated];
    NSLog(@"MyViewWillAppear %@", [self class]);
}

使用load实现预加载。使用GCD的dispatch_once_t保证只会交换一次不会重复交换。使用method_exchangeImplementations来实现交换。
将上述代码放入工程中就可以打印所有viewcontroller的加载情况。

protocols

表示类遵循哪些协议

Aspect-Oriented Programming(AOP)

类似记录日志、身份验证、缓存等事务非常琐碎,与业务逻辑无关,很多地方都有,又很难抽象出一个模块,这种程序设计问题,业界给它们起了一个名字叫横向关注点(Cross-cutting concern),AOP作用就是分离横向关注点(Cross-cutting concern)来提高模块复用性,它可以在既有的代码添加一些额外的行为(记录日志、身份验证、缓存)而无需修改代码。

Method

Method表示类中的某个方法,在runtime.h文件中找到它的定义:

/// An opaque type that represents a method in a class definition.
struct objc_method {
SEL method_name                                          OBJC2_UNAVAILABLE;
char *method_types                                      OBJC2_UNAVAILABLE;
IMP method_imp                                          OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

其实Method就是一个指向objc_method结构体指针,它存储了方法名(method_name)、方法类型(method_types)和方法实现(method_imp)等信息。而method_imp的数据类型是IMP,它是一个函数指针,后面会重点提及。

runtime如何实现weak置为nil

runtime对注册的类会进行布局,对于weak修饰的对象会放入一个hash表中,用weak指向的对象内存地址作为key。当此对象的引用计数为0的时候会dealloc,假如weak指向的对象内存地址是a,那么就会以a为键在这个weak表中搜索,找到所有以a为键的weak对象,从而设置为nil。

NSString *name = [[NSString alloc] initWithString: @"Jiaming Chen"];
__weak NSString *weakStr = name;

当为weakStr这一weak类型的对象赋值时,编译器会根据name的地址为key去查找weak哈希表,该表项的值为一个数组,将weakStr对象的地址加入到数组中,当name变量超出变量作用域或引用计数为0时,会执行dealloc函数,在执行该函数时,编译器会以name变量的地址去查找weak哈希表的值,并将数组里所有 weak对象全部赋值为nil。

Ivar

Ivar表示类中的实例变量,在runtime.h文件中找到它的定义:

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

Ivar其实就是一个指向objc_ivar结构体指针,它包含了变量名(ivar_name)、变量类型(ivar_type)等信息。

总结

虽然在平时项目不是经常用到Objective-C的Runtime特性,但当你阅读一些iOS开源项目时,你就会发现很多时候都会用到。所以深入理解Objective-C的Runtime数据结构、消息转发机制有助于你更容易地阅读和学习开源项目。

IMP

在上面讲Method时就说过,IMP本质上就是一个函数指针,指向方法的实现,在objc.h找到它的定义:

/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif

当你向某个对象发送一条信息,可以由这个函数指针来指定方法的实现,它最终就会执行那段代码,这样可以绕开消息传递阶段而去执行另一个方法实现。

消息发送

前面从objc_msgSend作为入口,逐步深入分析Runtime的数据结构,了解每个数据结构的作用和它们之间关系后,我们正式转入消息发送这个正题。

objc_msgSend函数

在前面已经提过,当某个对象使用语法[receiver message]来调用某个方法时,其实[receiver message]被编译器转化为:

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

现在让我们看一下objc_msgSend它具体是如何发送消息:

首先根据receiver对象的isa指针获取它对应的class

优先在class的cache查找message方法,如果找不到,再到methodLists查找

如果没有在class找到,再到super_class查找

一旦找到message这个方法,就执行它实现的IMP。

图片 7

Objc Message

self与super

为了让大家更好地理解self和super,借用sunnyxx博客的ios程序员6级考试一道题目:下面的代码分别输出什么?

@implementation Son : Father
- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

self表示当前这个类的对象,而super是一个编译器标示符,和self指向同一个消息接受者。在本例中,无论是[self class]还是[super class],接受消息者都是Son对象,但super与self不同的是,self调用class方法时,是在子类Son中查找方法,而super调用class方法时,是在父类Father中查找方法。
当调用[self class]方法时,会转化为objc_msgSend函数,这个函数定义如下:

id objc_msgSend(idself, SEL op, ...)

这时会从当前Son类的方法列表中查找,如果没有,就到Father类查找,还是没有,最后在NSObject类查找到。我们可以从NSObject.mm文件中看到- (Class)class的实现:

- (Class)class{
        return object_getClass(self);
}

所以NSLog(@"%@", NSStringFromClass([self class]));会输出Son。
当调用[super class]方法时,会转化为objc_msgSendSuper,这个函数定义如下:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

objc_msgSendSuper函数第一个参数super的数据类型是一个指向objc_super的结构体,从message.h文件中查看它的定义:

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};
#endif

结构体包含两个成员,第一个是receiver,表示某个类的实例。第二个是super_class表示当前类的父类。

这时首先会构造出objc_super结构体,这个结构体第一个成员是self,第二个成员是(id)class_getSuperclass(objc_getClass("Son")),实际上该函数会输出Father。然后在Father类查找class方法,查找不到,最后在NSObject查到。此时,内部使用objc_msgSend(objc_super->receiver, @selector(class))去调用,与[self class]调用相同,所以结果还是Son。

未完待续

本文由4887王中王鉄算盘奖结果发布于王中王鉄算盘,转载请注明出处:C的对象模型和runtime机制

关键词:

上一篇:简单的登陆验证4887王中王鉄算盘奖结果

下一篇:没有了

最火资讯