IOS Runtime

来源:互联网 发布:java订票系统 编辑:程序博客网 时间:2024/05/29 19:25

什么是runtime(运行时机制)

  Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言,这意味着它不仅需要一个编译器,还需要Runtime系统来动态创建类和对象,进行消息发送和转发。

    1.runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。

    2.平时编写的OC代码, 在程序运行过程中, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者

runtime的作用

  runtime是属于OC的底层, 可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)。

    1.在程序运行过程中, 动态创建一个类(比如KVO的底层实现)

    2.在程序运行过程中, 动态地为某个类添加属性\方法, 修改属性值\方法

    3.遍历一个类的所有成员变量(属性)\所有方法

runtime相关应用

  * NSCoding(归档和解档, 利用runtime遍历模型对象的所有属性)

  * 字典 --> 模型 (利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上)

Objective-C 消息转发

在 Objective-C 中向一个不知道如何响应这个方法的对象发送消息是完全合法的(甚至可能是一种潜在的设计决定)。苹果的文档中给出的一个原因是模拟多继 承,Objective-C 不是原生支持的,或者你可能只是想抽象你的设计并且隐藏幕后处理这些消息的其他对象/类。这一点是 runtime 非常需要的。它是这样做的 1. Runtime 检查了你的类和所有父类的 class cache 和分发表,但是没找到指定的方法。2. Objective_C 的 Runtime  会在你的类上调用 + (BOOL) resolveInstanceMethod:(SEL)aSEL。 这就给了你一个机会去提供一个方法实现并且告诉 runtime 你已经解析了这个方法,如果它开始查找,这回就会找到这个方法。你可以像这样实现…定义一个函数…

1
2
3
4
void fooMethod(id obj, SEL _cmd)
    NSLog(@"Doing Foo");
}

然后你可以像这样使用 class_addMethod() 解析它…

1
2
3
4
5
6
7
8
9
+(BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if(aSEL == @selector(doFoo:))
    {
            class_addMethod([self class],aSEL,(IMP)fooMethod,"v@:");
            return YES;
    }
    return [super resolveInstanceMethod];
}

在 class_addMethod() 最后一部分的 "v@:" 是方法的返回和参数类型。你可以在 Runtime Guide 的 Type Encoding 章节看到完整介绍。 3. Runtime 然后调用 – (id)forwardingTargetForSelector:(SEL)aSelector。这样做是为了给你一次机会(因为我们不能解析这个方法 (参见上面的 #2))引导 Objective-C runtime 到另一个可以响应这个消息的对象上,在花费昂贵的处理过程调用  – (void)forwardInvocation:(NSInvocation *)anInvocation 之前调用这个方法也是更好的。你可以像这样实现

1
2
3
4
5
6
7
8
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:))
    {        
        return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

显然你不想从这个方法直接返回 self,否则可能会产生一个死循环。 4. Runtime 最后一次会尝试在目标对象上调用 – (void)forwardInvocation:(NSInvocation *)anInvocation。如果你从没看过 NSInvocation,它是 Objective-C 消息的对象形式。一旦你有了一个 NSInvocation 你可以改变这个消息的一切,包括目标对象,selector 和参数。所以你可以这样做…  

1
2
3
4
5
6
7
8
9
-(void)forwardInvocation:(NSInvocation *)invocation
{  
    SEL invSEL = invocation.selector;    
    if([altObject respondsToSelector:invSEL]) {        
        [invocation invokeWithTarget:altObject];    
    else {        
        [self doesNotRecognizeSelector:invSEL];    
    }
}

如果你继承自 NSObject,默认它的 – (void)forwardInvocation:(NSInvocation *)anInvocation 实现只是简单的调用 -doesNotRecognizeSelector:,你可以在最后一次机会里覆盖这个方法去做一些事情。(译注:对这块内容有兴趣的同学可以参见:http://www.cnblogs.com/biosli/p/NSObjectinherit2.html)

Non Fragile ivars(Modern Runtime)(非脆弱的 ivar)

我们最近在 Modern Runtime 里得到的是 Non Fragile ivars 的概念。当编译你的类时,编译器生成了一个 ivar 布局,显示了在你的类中从哪可以访问你的 ivars,获取指向你的对象的指针,查看 ivar 与对象起始字节的偏移关系,和获取读入的变量类型的总共字节大小等一些底层的细节。所以你的 ivar 布局可能看起来像这样,左侧的数字是字节偏移量。

2014031602.png

我们有了 NSObject 的 ivar 布局,然后我们继承自 NSObject 去扩展它并且添加了我们自己的 ivars。在苹果发布更新前这都工作的很好,但是 Mac OS X 10.6 发布后,就成了这样

2014031603.png

你的自定义对象被剔除了因为我们有了一个重叠的父类。唯一可以防止这个的办法是如果苹果坚持之前的布局,如果他们这么做了,那么他们的框架就不能改进,因 为他们的 ivar 布局被冻住了。在 fragile ivar 下你不得不重新编译你继承自苹果类的类来恢复兼容性。所以在非 fragile ivar 时,会发生生么?

2014031604.png

使用非 fragile ivars 时,编译器生成和 fragile ivars 相同的 ivar 布局。然而当 runtime 检测到一个重叠的超类时,它调整你在这个类中新增的 ivar 的偏移量,这样在子类中新增加的那部分就显示出来了。


  * KVO(利用runtime动态产生一个类)

  * 用于封装框架(想怎么改就怎么改)

Runtime数据结构

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

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

下面简单介绍runtime中重要的数据结构:

SEL

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

id

id是通用类型指针,能够表示任何对象。id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类。

Class

Class表示对象所属的类。可以查看到Class其实就是一个objc_class结构体指针。在面向对象设计中,一切都是对象,Class在设计中本身也是一个对象。

Method

Method表示类中的某个方法。其实Method就是一个指向objc_method结构体指针,它存储了方法名(method_name)、方法类型(method_types)和方法实现(method_imp)等信息。

Ivar

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

IMP

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

Cache

Cache主要用来缓存。Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。

0 0
原创粉丝点击