ios浅谈runtime

老样子..先看文档 知道 runtime 到底是个什么东西呢.....

The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

runtime 使objc变成了真正的动态语言,是objc 的操作系统.objc 延迟了许多决定,直到运行时才做出.runtime 给了 objc 真正的动态性

runtime 运行时有两个版本,一般手机和64位机器的用的是现代版本,一般32位的机器用的是古老版本..我们接下来都是以现代版本进行解释的

和 objc-runtime 交互有三种方式

  1. 通过 objc 源代码 我们在写 objc 源代码的时候,在编译期间有的就已经被翻译优化了.背后变成了调用运行时的代码
  2. 就是通过 nsobject 中的一些方法 nsobject 不规定类的具体行为.只定义了它们的基本必须结构
  3. 最后的,自然就是通过runtime函数库直接调用

how the message expressions are converted into objc_msgSend function calls, and how you can refer to methods by name. It then explains how you can take advantage of objc_msgSend, and how—if you need to—you can circumvent dynamic binding.

如果你对objc消息机制掌握的很好,完全可以绕过动态绑定实现黑魔法
消息的最终接收者都是在运行时才确定的

[receiver message]
编译器转换为:
objc_msgSend(receiver, selector) //两个必须参数
objc_msgSend(receiver, selector, arg1, arg2, ...)//之后可选参数

The messaging function does everything necessary for dynamic binding:

It first finds the procedure (method implementation) that the selector refers to. Since the same method can be implemented differently by separate classes, the precise procedure that it finds depends on the class of the receiver.
It then calls the procedure, passing it the receiving object (a pointer to its data), along with any arguments that were specified for the method.
Finally, it passes on the return value of the procedure as its own return value.

作用:根据message 和 receiver的类的找到唯一的方法

我们先来看看类是什么 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 *` */

typedef struct objc_class *Class;

这是需要了解的东西 一个实例中都有一个 isa 指针,指向一个 Class(objc_class*)的结构体的指针,也就是实例指向自己的类,类中也有一个 Class(isa)指向元类.一切皆对象中,类也是对象

可以看到很清楚显示了实例和类之间的关系


When a message is sent to an object, the messaging function follows the object’s isa pointer to the class structure where it looks up the method selector in the dispatch table. If it can’t find the selector there, objc_msgSend follows the pointer to the superclass and tries to find the selector in its dispatch table. Successive failures cause objc_msgSend to climb the class hierarchy until it reaches the NSObject class. Once it locates the selector, the function calls the method entered in the table and passes it the receiving object’s data structure.

当一个消息被发送到一个对象时,函数调度机制根据这个对象的 isa 指针找到类的继承结构在类中的 dispatch_table 中找到这个方法,循环向上,找到之后,才开始进行执行 行话就是动态绑定.....
为了加速动态绑定的速度,每个类都有一个调度缓存,一些多次被使用的方法指针都在这里,在进行调用表查询之前,所有的方法都会先去查询这个 cache 中的方法.

isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类),

super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。

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

现在看看对象的定义 (所有的都是经过c包装后的)

/// Represents an instance of a class.
struct objc_object {
Class isa  OBJC_ISA_AVAILABILITY; //实例对象拥有指向类的指针
};

typedef struct objc_object *id;    

当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,NSObject类的alloc和allocWithZone:方法底层调用函数class_createInstance来创建objc_object数据结构.

常见的id,它是一个objc_object结构类型的指针。 代表任何对象

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

meta-class存储着一个类的所有类方法。每个类都会有一个单独的meta-class.所有meta-class类的isa 指向的都是 NSObject的meta-class类..也就是当我们调用类方法的时候,实际是去元类中查找.....


类与对象操作函数

runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。

When objc_msgSend finds the procedure that implements a method, it calls the procedure and passes it all the arguments in the message. It also passes the procedure two hidden arguments:

The receiving object
The selector for the method
These arguments give every method implementation explicit information about the two halves of the message expression that invoked it. They’re said to be “hidden” because they aren’t declared in the source code that defines the method. They’re inserted into the implementation when the code is compiled.

每底层objc_msgsend的方法在运行时都会有两个隐含的参数 在编译时被自动添加 一个是 self.指向receiver 一个是_cmd..代表的是方法本身
下面官方代码测试这两个参数

   - strange
 {
    id  target = getTheReceiver();
    SEL method = getTheMethod();
    
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
} 

self 很有用啊,我们不是经常通过 self 获得消息和成员变量嘛

每次执行方法的时候,都需要在调度表中查询找到函数的入口地址,这对于一般方法没问题.但是对于需要超高效率和执行次数多的方法,我们可以直接跳过动态绑定

sing methodForSelector: to circumvent dynamic binding saves most of the time required by messaging. However, the savings will be significant only where a particular message is repeated many times, as in the for loop shown above.

我们一般用 methodForSelector 来跳过动态绑定这]法是得到 selector 的实际函数

| SEL1 | SEL2 | SEL3 |

| IMP1 | IMP2 | IMP3 |

sel 只是一个方法的标志符,真正的执行代码地址是 imp 指向的地方 methodforselector可以的某个方法标识符的地址,我们也可以在程序中之直接使用

IMP imp =  [ NSObject methodForSelector:@selector(class)];
imp();


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

IMP is a C type referring to the implementation of a method, also known as an implementation pointer. It's a pointer to a function returning id, and with self and a method selector (available inside method definitions as the variable _cmd) as the first arguments

可以看到 id 是一种数据类型,imp 是一个指向返回数据类型是 id 的函数的指针,这个函数的第一个参数和第二个参数是self 和这个函数名_cmd;

objc 将许多需要编译和链接时确定的延迟到运行时确定,这样就发生了许多有趣的东西.比如.我们可以动态的给类添加属性使用 @dynamic propertyName;
动态的告诉编译器消息使用的属性

我们也可以动态的给类添加方法.(所有的这些和 java 中因为有虚拟机而有的反射有异曲同工之妙)

 void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}

class_addMethod 接收的参数:要添加的类 ,方法标识符 sel;方法执行入口地址;描述符

我们一定要有一个意识(runtime 是 一个函数库 帮助我们更好的处理 objc 和 java 虚拟机做的事情是很类似的 这是动态语言的一个大特性)

消息转发
当一个对象能接收一个消息时,就会走正常的方法调用流程。但如果一个对象无法接收指定消息时,又会发生什么事呢?默认情况下,如果是以 [object message]的方式调用方法,如果object无法响应message消息时,编译器会报错。但如果是以perform…的形式来调用,则需要等到运 行时才能确定object是否能接收message消息。如果不能,则程序崩溃。
所以我们不能确定一个对象是否可以接受一个消息的时候,会先进行判断

if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}

当一个对象无法接收某一消息时,就会启动所谓”消息转发(message forwarding)“机制,通过这一机制,我们可以告诉对象如何处理未知的消息。默认情况下,对象接收到未知的消息,会导致程序崩溃,报错

untime的强大之处在于它能在运行时创建类和对象。

动态创建类

动态创建类涉及到以下几个函数:

// 创建一个新类和元类

Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );

// 销毁一个类及其相关联的类

void objc_disposeClassPair ( Class cls );

// 在应用中注册由objc_allocateClassPair创建的类

void objc_registerClassPair ( Class cls );

和 java 中的反射有异曲同工之妙

息转发机制基本上分为三个步骤:

  1. 动态方法解析

  2. 备用接收者

  3. 完整转发

下面我们详细讨论一下这三个步骤。

动态方法解析

对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或 者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法”“。不过使用该方法的前提是我们已经 实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。如下代码所示:

void functionForMethod1(id self, SEL _cmd) {
   NSLog(@"%@, %p", self, _cmd);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

NSString *selectorString = NSStringFromSelector(sel);

if ([selectorString isEqualToString:@"method1"]) {
    class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
}

return [super resolveInstanceMethod:sel];
}

不过这种方案更多的是为了实现@dynamic属性。

备用接收者

如果在上一步无法处理消息,则Runtime会继续调以下方法:

  • (id)forwardingTargetForSelector:(SEL)aSelector
    如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。

使用这个方法通常是在对象内部,可能还有一系列其它对象能处理该消息,我们便可借这些对象来处理消息并返回,这样在对象外部看来,还是由该对象亲自处理了这一消息。如下代码所示:

@interface SUTRuntimeMethodHelper : NSObject

- (void)method2;

@end

@implementation SUTRuntimeMethodHelper

- (void)method2 {
    NSLog(@"%@, %p", self, _cmd);
}

@end

#pragma mark -

@interface SUTRuntimeMethod () {
    SUTRuntimeMethodHelper *_helper;
}

@end    

@implementation SUTRuntimeMethod

+ (instancetype)object {
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (self != nil) {
        _helper = [[SUTRuntimeMethodHelper alloc] init];
    }

    return self;
}

- (void)test {
    [self performSelector:@selector(method2)];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {

    NSLog(@"forwardingTargetForSelector");

    NSString *selectorString = NSStringFromSelector(aSelector);

// 将消息转发给_helper来处理
if ([selectorString isEqualToString:@"method2"]) {
    return _helper;
}

    return [super forwardingTargetForSelector:aSelector];
}

@end

这一步合适于我们只想将消息转发到另一个能处理该消息的对象上。但这一步无法对消息进行处理,如操作消息的参数和返回值。

完整消息转发

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。此时会调用以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息 有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation 方法中选择将消息转发给其它对象。

forwardInvocation:方法的实现有两个任务:

  1. 定位可以响应封装在anInvocation中的消息的对象。这个对象不需要能处理所有未知消息。

  2. 使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。

不过,在这个方法中我们可以实现一些更复杂的功能,我们可以对消息的内容进行修改,比如追回一个参数等,然后再去触发消息。另外,若发现某个消息不应由本类处理,则应调用父类的同名方法,以便继承体系中的每个类都有机会处理此调用请求。

还有一个很重要的问题,我们必须重写以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。

完整的示例如下所示:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

if (!signature) {
    if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
        signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
    }
}

return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([SUTRuntimeMethodHelper             instancesRespondToSelector:anInvocation.selector]) {
    [anInvocation invokeWithTarget:_helper];
}
}

NSObject的forwardInvocation:方法实现只是简单调用了doesNotRecognizeSelector:方法,它不会转发任何消息。这样,如果不在以上所述的三个步骤中处理未知消息,则会引发一个异常。

从某种意义上来讲,forwardInvocation:就像一个未知消息的分发中心,将这些未知的消息转发给其它对象。或者也可以像一个运输站一样将所有未知消息都发送给同一个接收对象。这取决于具体的实现。

消息转发与多重继承

回过头来看第二和第三步,通过这两个方法我们可以允许一个对象与其它对象建立关系,以处理某些未知消息,而表面上看仍然是该对象在处理消息。通过这 种关系,我们可以模拟“多重继承”的某些特性,让对象可以“继承”其它对象的特性来处理一些事情。不过,这两者间有一个重要的区别:多重继承将不同的功能 集成到一个对象中,它会让对象变得过大,涉及的东西过多;而消息转发将功能分解到独立的小的对象中,并通过某种方式将这些对象连接起来,并做相应的消息转 发。

不过消息转发虽然类似于继承,但NSObject的一些方法还是能区分两者。如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;  
}

这些东西我们只要了解就好,在开发中用到的不多

顺便在这里说一下 objc中前面文章提到的数据类型
typedef struct objc_category *Category;

struct objc_category {
    char *category_name                          OBJC2_UNAVAILABLE; // 分类名
    char *class_name                             OBJC2_UNAVAILABLE; // 分类所    属的类名
struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}

这是分类的结构体 和类是不是很相似啊?哈哈

我们再来看看协议的struct

typedef struct objc_object Protocol;

简单明了 大家一看,这不就是对象嘛

 struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY; //实例对象拥有指向类的指针
    };

class isa 指向了实现协议的类..(自然也将协议中的方法在实现的类中可以找到)

runtime.h 提供了很多接口让开发者灵活使用,有对类的,对对象的,对属性,方法的.(分类的东西已经包含在类中),对协议的

我们可以在程序中动态产生 继承 改变 注册 获得 销毁 各个我门想要操作

参考 apple runtime

最有趣又著名的莫过于sclector swizzing

这就是非常有用的的hook....
下一篇再讲

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,709评论 0 9
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,135评论 0 9
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,192评论 0 7
  • 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的...
    lylaut阅读 800评论 0 4
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 758评论 0 1