iOS NSRuntime机制

什么是Objective-C runtime?

简单来说,Objective-C runtime是一个实现Objective-C语言的C库。对象可以用C语言中的结构体表示,而方法(methods)可以用C函数实现。事实上,他们 差不多也是这么干了,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,Objective-C程序员可以在程序运行时创建,检 查,修改类,对象和它们的方法。

除了封装,Objective-C runtime库也负责找出方法的最终执行代码。当程序执行[object doSomething]时,不会直接找到方法并调用。相反,一条消息(message)会发送给对象(在这儿,我们通常叫它接收者)。runtime库 给次机会让对象根据消息决定该作出什么样的反应。Alan Kay反复强调消息传递(message-passing)是Smalltalk最重要的部分,而不是对象:

由于以前关于这个话题我创造了“对象”这个词,现在很多人都对这个概念趋之若鹜,这让我感到非常遗憾。

其实这里面更为重要的理念是“消息命令”(messaging),这才是Smalltalk的核心内容(现在尚有一些内容还没有全部完成)。日 语中有个简短的单词叫做“ma”,它用来表示两个物体之间的东西,在英语中和它最相近的单词也许是“interstitial”。制造一个庞大且可扩展系 统的关键是设计它各个模块之间的通信方式,而不是关注它的内部属性和行为。

实际上,在一篇介绍Smalltalk虚拟机的文章里,这门编程技术被叫做消息传递或者消息传送范式。“面向对象”通常用来描述内存管理系统。

在演讲和文章中都使用ObjC runtime这个词,看似只有一个,实际上存在很多runtime库。虽然它们都支持对象的自省检查和消息接收,但是它们却有不同的特性和实现方式(例 如,同样是发送消息,Apple的runtime用一步完成,而GNU runtime会先查询这些消息,然后执行查找到的函数分两步完成)。以下所有的讨论,都是基于Apple的最新runtime库(苹果公司在OSX 10.5和iOS发布时的版本)。

在那次演讲中,我决定研究runtime库某些领域的功能。我找了一些希望更透彻了解的东西,然后把它们做成问答的形式组成我的演讲。

动态创建类

如何实现Key-Value Observing?

KVO实现观察者模式的关键是它偷偷摸摸将被观察对象的类改变了,它子类化原来的类后,就能够自定义该对象的方法来调用KVO的回调方法。这些都是通过 objc_duplicateClass这个方法完成,但很遗憾,这个方法并不公开,我们无法私自调用。

条条大路通罗马,好在除了objc_duplicateClass,还有其他方法可以通过使用秘密子类化的方式实现观察者模式,比如创建和注册 “class pair”。那么什么是class pair呢?对于Objective-C的类来说,都有一对Class的对象来定义它:Class对象定义了这个类的实例方法,而metaclass定义 了这个类的类方法。所以每个class其实是它metaclass的单例。

这个代码展 示了观察者模式的工作原理。当你给对象增加观察者时,这个对象首先会检查自己是否可被观察,如果是,它会新创建一个类,用我们自己的-dealloc替代 原来类的方法,同样它也会把-class方法替换掉,类似于KVO被观察对象,当你访问被观察对象的类名时,返回的是它原来的类名,而不是新生成的类。

创建完类后,我们需要照着 Key-Value Coding为属性增加一个setter方法:这个setter方法会获取这个属性修改前的值和修改后的值,然后调用block形式的回调函数,将这两个值告诉观察者。代码中根据我们的意愿,这个block可以异步调用。

请注意, -addObserverForKey:withBlock:会使用s object_setClass() 将被观察对象的类替代为新组建的类。这样做最主要的目的是将消息转变为方法的方式改变,但是这需要非常小心,原来的类和新的类必须有相同的成员变量布局。 因为成员变量也是用过runtime访问,修改某个对象的类可能导致runtime无法找到对应的变量。

我们在存储观察者集合时遇到些麻烦,因为没地方去存它们。给ObserverPattern这个类增加成员变量不起作用,因为根本没有生成这个类的对象。被观察对象的成员变量是它原来类的,它并没有考虑过这些观察者。

Objective-C runtime通过引入associated objects帮助我们摆脱这个困境。在runtime里,理论上所有对象都可以拥有包含其他对象的字典。通过associated references,被观察对象可以存储和访问他们的观察者,而不需要额外的成员变量。

如果你运行多次后,你会发现ObserverPattern 还是有点小毛病的。由于观察者回调是异步调用的,观察者接

收到的变化事件也是乱序的。这意味着观察者其实无法区分被观察属性的最终状态是什么,回调中的新值可能早已被修改。我这样做的目的是为了说明在KVO中同步调用回调其实是个有用的特色,并非bug。

创建对象

那些额外的字节都是干啥用的?

当你创建一个 Objective-C对象时,runtime会在实例变量存储区域后面再分配一点额外的空间。这么做的目的是什么呢?你可以获取这块空间起始指针(用 object_getIndexedIvars),然后就可以索引实例变量(ivars)。好吧,下面我会使用自定义数组来说明一下索引ivars的用 处。

让我们创建一个数组!从这个SimpleArray中可以看到两件事情:最明显的一件是它使用了类簇模式。 当使用+alloc方法返回对象时,一般情况下已经为这个对象分配了所有的内存,但是在这个例子中,在+alloc时并不知道需要多大的内存空间。只有当 调用了 -initWithObjects:count:以后,才能根据数组内对象数量计算出这个数组需要多大的内存,所以+alloc只是返回一个占位符,只有 在初始化后才会分配和返回真正的数组对象。

或许你会问为什么我们要用类簇把事情搞那么复杂,使用 calloc()另外分配一块大小合适的缓存,然后把那些对象指针存到里面不就得了?答案是希望利用局部性原理提高访问性能。从数组的设计上我们可以看出,每次数组指针被访问时,之后会有很大几率访问到缓存指针,所以把它们肩并肩的放入内存意味着找到其中一个就是找到了另外一个。

消息派发

消息如何转发?

Objective-C其中一个强大特性是对象不需要实现某个方法,尽管它在编译时声明了该选择符(selector)。但它可以在运行时再决 定方法实现,或者将这些消息转发给其他对象,或者发出异常,亦或做一些其他事情。但是这个特性的某些方面曾经一直困扰我:消息转发(message forwarding)会调用 -forwardInvocation:,然后传入一个NSInvocation 对象。但是这个NSInvocation 类是在Foundation库中定义的,难道说runtime工作需要Foundation配合?

我试着挖掘其中的原因,发现答案并不是我想的那样,runtime不需要知道Foundation。runtime会让程序定义转发函数 (forwarding function),当 objc_msgSend()无法找到该selector的实现时,那个转发函数就会被调用。程序一启动,CoreFoundation就将 -forwardInvocation:定义成转发函数。

让我们来创建一个Ruby! 当然并不是真的实现完整的Ruby,Ruby有一个叫做#method_missing的函数,当对象收到一个它没有实现的消息时,这个函数就会被调到, 这和Smalltalk的做法比较相似。使用objc_setForwardHandler,我们也能在Objective-C的类中实现类似Ruby的 methodMissing:方法。

总结

Objective-C runtime可以有效的帮助我们为程序增加很多动态的行为。一些开发者除了使用method swizzling帮助调试程序,并不会在实际程序中使用它,但runtime编程的确有很多功能,它应该成为实际应用代码编写的重要工具。

原文地址:http://blog.securemacprogramming.com/2013/12/by-your-_cmd/

下面加一些本人的理解

由于OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法。利用runtime机制让我们可以在程序运行时动态修改类、对象中的所有属性、方法。

下面就介绍运行时一种很简单的使用方式,将字典对象转为模型。当然,你可能会问,我用KVO直接调用 setValuesForKeysWithDictionary:方法,传入一个字典一样可以快速将字典转模型啊,但是这种方法有它的弊端,只有遍历某个 模型中所有的成员变量,然后通过成员变量从字典中取出对应的值并赋值最为稳妥,否则,当模型中的属性数量与字典中的key的数量不一样时,就会报错。而 且,由于runtime是更底层的语言,我们编写的OC代码在运行时,编译器内部会先转为C和C++的代码,然后再执行,因而运用runtime机制,程 序的性能也会更好。说了这么多,下面就初步认识一下runtime的强大。

首先,我们定义一个类

@interface Person : NSObject{
    CGFloat height;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, assign) int no;

@end

然后,我们在其它文件中使用这个类,注意在使用之前,要包含 #import <objc/message.h>

下面通过一小段代码来获取到上面这个类中所有的成员变量

unsigned int outCount = 0;
    Ivar *vars = class_copyIvarList([Lender class], &outCount); // 获取到所有的成员变量列表

    // 遍历所有的成员变量
    for (int i = 0; i < outCount; i++) {
        Ivar ivar = vars[i]; // 取出第i个位置的成员变量

        const char *propertyName = ivar_getName(ivar); // 获取变量名
        const char *propertyType = ivar_getTypeEncoding(ivar); // 获取变量编码类型
        printf("---%s--%s\n", propertyName, propertyType);

    }

打印结果:

---height--f
[email protected]"NSString"
[email protected]"NSNumber"
---_no--i

可见,通过上面几句简单的代码就可以获取到某个类中所有变量的名称和类型,然后通过object_setIvar()方法为具体某个对象的某个成员变量赋值。
时间: 2024-10-27 01:37:39

iOS NSRuntime机制的相关文章

深入浅出iOS事件机制

深入浅出iOS事件机制 2015年 04月 12日 本文章将讲解有关iOS事件的传递机制,如有错误或者不同的见解,欢迎留言指出.转载自:http://zhoon.github.io/ios/2015/04/12/ios-event.html iOS的事件有好几种:Touch Events(触摸事件).Motion Events(运动事件,比如重力感应和摇一摇等).Remote Events(远程事件,比如用耳机上得按键来控制手机),其中最常用的应该就是Touch Events了,基本存在于每个a

IOS通知机制初解

消通知机制: 3个步骤: 1.通知的发布 2.通知的监听 3.通知的移除 需要了解的要点 1.通知中心:(NSNotificationCenter) 每一个应用程序都有一个通知中心(NSNotificationCenter)实例,专门负责协助不同对象之间的消息通信 任何一个对象都可以向通知中心发布通知(NSNotification),描述自己在做什么.其他感兴趣的对象(Observer)可以申请在某个特定通知发布时(或在某个特定的对象发布通知时)收到这个通知 2.通知:(NSNotificati

iOS事件机制(一)

iOS事件机制(一) DEC 7TH, 2013 运用的前提是掌握掌握的本质是理解 本篇内容将围绕iOS中事件及其传递机制进行学习和分析.在iOS中,事件分为三类: 触控事件(单点.多点触控以及各种手势操作) 传感器事件(重力.加速度传感器等) 远程控制事件(远程遥控iOS设备多媒体播放等) 这三类事件共同构成了iOS设备丰富的操作方式和使用体验,本次就首先来针对第一类事件:触控事件,进行学习和分析. Gesture Recognizers Gesture Recognizers是一类手势识别器

iOS事件机制(二)

iOS事件机制(二) DEC 29TH, 2013 本篇内容接上一篇iOS事件机制(一),本次主要介绍iOS事件中的多点触控事件和手势事件. 从上一篇的内容我们知道,在iOS中一个事件用一个UIEvent对象表示,UITouch用来表示一次对屏幕的操作动作,由多个UITouch对象构成了一个UIEvent对象.另外,UIResponder是所有响应者的父类,UIView.UIViewController.UIWindow.UIApplication都直接或间接的集成了UIResponder.关于

IOS消息机制应用实例--异常处理

IOS消息机制应用实例--异常处理 最近发现了一个在项目中常用的异常处的工具NullSafe,分析了它的实现原理,不小心发现了一个小Bug,现将其分享出来,关于这篇文章的Demo已经上传至GitHub,看完如有收获,欢迎Star,如有疑问欢迎issue,大家一起学习.在IOS开发中我们可能会遇到下面的情景:服务器给我们返回得某个字段是null,比如someValue:null,这个时候我们利用第三方工具转化之后会得到someValue = <null>,这个时候如果我们判断这个someValu

iOS核心笔记——iOS通知机制

1.iOS通知机制: 1-1.iOS通知发布者.通知中心.监听器之间的关系: 如下图所示: 2.通知(NSNotification)简介: 3.通知中心(NSNotificationCenter) 3-1.每个应用程序只有一个通知中心对象(单例对象): 4.发布通知: 4-1.对象在自己需要的某个时候使用通知类(NSNotification)创建通知对象后让通知中心帮忙发布通知: 1.可以在对象A需要使用通知的某个位置,直接让通知中心帮忙发送的时候将通知中的一系列信息通过通知中心提供的方法创建通

《转之微信移动团队微信公众号》iOS 事件处理机制与图像渲染过程

致歉声明: Peter在开发公众号功能时触发了一个bug,导致群发错误.对此我们深表歉意,并果断开除了Peter.以下交回给正文时间: iOS 事件处理机制与图像渲染过程 iOS RunLoop都干了什么 iOS 为什么必须在主线程中操作UI 事件响应 CALayer CADisplayLink 和 NSTimer iOS 渲染过程 渲染时机 CPU 和 GPU渲染 Core Animation Facebook Pop介绍 AsyncDisplay介绍 参考文章 iOS RunLoop都干了什

iOS 事件处理机制与图像渲染过程

iOS 事件处理机制与图像渲染过程 iOS RunLoop都干了什么 iOS 为什么必须在主线程中操作UI 事件响应 CALayer CADisplayLink 和 NSTimer iOS 渲染过程 渲染时机 CPU 和 GPU渲染 Core Animation Facebook Pop介绍 AsyncDisplay介绍 参考文章 iOS RunLoop都干了什么 RunLoop是一个接收处理异步消息事件的循环,一个循环中:等待事件发生,然后将这个事件送到能处理它的地方. 如图1-1所示,描述了

ios后台机制

最近在做关于界面信息保存的模块,查阅相关的资料,对ios的后台机制有一个系统的了解,现在总结出来: IOS是"伪后台",iOS 中所谓的"后台驻留"并不是指"执行中的程序",而是"最近使用过的程序". 关闭--------这个程序以彻底关闭或尚未被开启 待用--------程序处于开启状态,但是并未收到任何指令(例如:程序开启但用户锁定了机器) 使用中----正常使用中的程序 后台--------程序不在开启状态但仍然在后台运