iOS Runtime Programing

来源:互联网 发布:网络诈骗有哪些形式 编辑:程序博客网 时间:2024/06/05 11:50
    Objective-c语言将许多决定从编译链接时推迟到运行时。无论何时,只要有可能,它会动态地做事。这意味着该语言不仅需要一个编译器,同时也需要一个运行时的系统去运行编译代码。运行时系统充当一个Objective-c语言的系统,它决定objective-c如何工作。
    本文着眼于NSObject类以及Objective-c程序如何与运行时系统交互。特别的,它在运行时动态地为新的类检查范式,并且转发消息到其他类中。它同时在你的程序运行时提供你如何找到关于类的信息。
    你读本文会了解Objective-C运行时系统如何工作并且如何去掌握它的优势。通常,虽然,几乎没有对你来说没有理由去了解和知道这些去写一个Cocoa应用。

本文的结构

本文拥有以下章节:
运行时版本和平台
与运行时系统交互
消息
动态方法解析
消息传递
类型编码

声明属性


运行版本和平台

在不同的平台有不同版本的Objective-C 运行时系统
传统和现在版本
有两种版本的Objective-C runtime ———现在 和 传统。现在的版本在Objective-C2.0被引入,包括一系列新特性。传统版本的编程接口Objective-C 1.0 runtime Reference描述,现在版本的runtime编程接口在Objective-C Runtime Reference中描述。

最值得注意的特性是实例变量在现在版本的runtime不再脆弱。
传统版本的runtime,如果你在一个类中改变一个实例变量的布局,你必须重新编译类的子类。
现在版本的runtime,如果你类中实例变量的布局,你不需要重新编译它的子类。
另外,现在的runtime支持实例变量同步声明属性

平台

iPhone应用程序,并在OS X v10.5的64位程序,后来使用了现代版的运行库。
其他程序(OS X桌面上的32位程序)使用运行时的旧版本。


与Runtime交互


Objective-C 编程与Runtime系统在三个不同的层面:通过Objective-C源码,通过NSObject类中的Foundation framework定义的方法,通过直接调用Runtime方法。

NSObject 方法

      Cocoa中大多数类是NSObject的子类,所以大多数类继承它的方法。(著名的例外是NSProxy class,查看消息转发章节)。它的方法所以建立了一系列的行为被其他示例或者类对象继承。然而,在少数情况下,NSObject方法仅仅定义了需要被做事的一个模板,他不提供所有必须得代码。      

     比如,NSObject 类定义了一个description实例方法返回一段描述类内容的字符串。这主要被用于调试。NSObject的这个方法的实现类包含什么,所以它返回一段字符串包含名字和类的地址。NSObject的子类可以实现这个方法返回更多细节。例如,Foundation类NSArray返回一串它包含的类的描述。

     一些NSObject方法仅仅只查询runtime系统的信息。这些方法同一类执行内省。此类方法是类方法。仅仅询问对象去标示一个类,isKindOfClass,isMemberOfClass,测试一个对象是否处于继承层次,respondsToSelector,确认一个对象是否接受特定的消息,conformsToProtocol,确认一个对象是否声明实现一个特定协议的方法,methodForSelector,提供一个方法实现的地址。这些方法给予对象自省的能力。

Runtime函数

       runtime系统是一个动态公共库,公共接口包含一系列函数和数据结构在目录/usr/include/objc中。这些函数大多允许你使用纯C去实现当你写Objective-C时编译器做的事。其他形式的基本的函数输出通过NSObject 类导出。这些函数使人们开发其他接口的运行系统,并产生增强的开发环境工具。在使用Objective-C编程时,它们可能是没有用的。然而,少数runtime函数可能会在写Objective-C程序时有用。所有这些函数在Objective-C runtime reference中。

信息转发

       这章描述消息表达式是如果转化为objc_msgSend函数调用的,如何通过名字指向消息。之后解释你如果借助objc——msgSend的优势,需要的话,如何通过避开动态绑定。

objc_msgSend 函数

       在Objective-C中,消息直到运行时绑定到方法实现。编译器将消息表达式[receiver message]转变成一条消息传递函数调用。objc_msgSend.这个函数取得接受者和消息中提到的方法的名字(消息选择器selector),两个关键的参数:

objc_msgSend(receiver,selector)

任何消息传递的参数也被传递到消息objc_msgSend中

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

消息传递函数会完成动态绑定所有需要的步骤:

首先,先找到消息选择器指向的程序(方法实现),因为相同的方法可以对于不同的类可以有不同的实现,根据接受者的类找到精确的程序。
然后调用程序,传递它到接受的类(指向数据的指针),随着方法具体的说明。
最后,返回程序的返回值作为它的返回值。
注释:编译器产生对消息传送函数的调用。你不应该在你的代码中直接调用它。


消息传递的关键在于编译器为每个类和对象创建的结构。每个类结构包括两种重要的元素:
指向父类的指针。
类调度表。表中含有与方法选择器有关的入口与特定标示方法的地址。setOrigin::选择器的方法与setOrigin::的地址关联(实现的程序),display方法的选择器与display的地址关联,以此类推。

当一个新对象创建,内存申请,它的示例变量被初始化。首先在对象变量中是一个指向它自身类的结构的指针。这个指针叫做isa,给予对象访问它的类的子类的入口。

注释:虽然严格上来说不是语言的一部分,一个对象在Objective—C Runtime系统中是必须的。一个对象必须是相当于一个struct objc_object(在objc/objc.h定义)中任何字段的结构定义。然而,你很少需要创建自己的根对象,从NSObject和NSProxy继承的对象自动具有isa指针。

类中元素和对象的结构如图所示:



当一个消息发送给一个对象时,消息传递函数根据对象的isa指针找到类结构体之后寻找调度表中的方法选择器。如果不能找到选择器selector,objc_msgSend会根据指向父类的指针,尝试在父类结构体中的调度表寻找方法选择器.成功地失败引起objc_msgSend一直向上寻找知道NSObject类,只要它定位到选择器,函数调用调度表中得方法,将它传递给对象的数据结构。
这是方法实现如何在运行时被选择,在面向对象的行话中,方法被动态绑定到消息里。

为了加速消息传递过程,runtime系统会在使用后缓存选择器和方法地址。每一个类都有一个缓存,它可以包含被继承的方法和类中的方法的选择器。在搜索调度表之前,消息传递路径第一次检查接收对象的缓存(理论上一个被使用过的方法可能被再次使用)。如果方法选择器在缓存中,消息转发仅仅比函数调用慢一点。程序一旦运行足够长时间热身它的缓存,几乎所有的消息都可以找到缓存的方法。程序运行时,缓存动态增长去容纳新的消息。


使用隐藏的参数

当objc_msgSend找到方法实现的程序时,它会调用程序并且将所有消息中的参数传递。同时会将两个隐藏的参数传递给程序:

接收的对象

方法的选择器

这些参数给予每个方法实现有关两个半条信息表达式调用它的精确的信息。它们据说被隐藏因为它们没有在定义方法的源码中声明。它们会在代码编译时被插入实现中。

尽管这些参数没有被精确的声明,源码依旧可以指向它们(就像它指向接受者对象的实例参数中一样)。一个指向接受者对象的方法,它自己的选择器_cmd。在实例中,_cmd是一个选择器,指向strange的方法,自身接收strange的消息。
- strange
{
    id    target = getTheReceiver();
    SEL  method = getTheMethod();
    if(target == self || method==_cmd)
        return nil;
    return [target performSelector:method];
}


得到方法地址

规避动态绑定的唯一方法是得到一个方法的地址,直接调用它,好像它是一个函数。这可能是适当的在极少数情况下,当一个特定的方法将被执行多次在继承和你想避免消息传递的执行方式,每次的开销。

随着NSObject类,methodForSelector中定义的方法:,你可以要求一个指向实现方法的程序,然后使用指针来调用该程序。退货必须仔细强制转换为适当的功能类型:即methodForSelector指针。都返回和参数类型应包括中投。

下面的例子演示了如何实现该setFilled的过程:方法可以如下形式调用:
void (*setter)(id,SEL,BOOL);
int i;
setter = (void(*)(id,SEL,BOOL))[target methodForSelector:@selector(setFilled:)];
for(i = 0;i < 100;i++)
setter(targetList[i],@selector(setFilled:),YES);

前两个参数传入程序的参数是接受者对象(self)和消息选择器(_cmd)。这些参数被隐藏在方法语法中,同时必须被精确的制作。
使用selectorForSelector:对于规避动态绑定节约了大量消息传递必须得时间。然而,节省是显而易见的只有当一个特殊的消息被重复使用很多次。如上面的循环展示的那样。
注释:methodForSelector:在Cocoa Runtime系统中提供,并不是一个Objective-C语言自身的特性。


动态绑定解析

本章描述你如何动态地提供一个方法的实现。

动态方法解析
有一些场景你想要动态地提供一个方法的实现。例如,Objective-C声明属性特性包括@dynamic:
@dynamic propertyName;
告诉编译器属性联系的方法会被动态地提供。
你可以实现方法resolveInstanceMethod:和resolveClassMethod:去分别动态地为实例和类方法提供一个已知选择器的实现。
一个Objective-C方法仅仅是一个C函数有两个参数self和_cmd.你可以添加一个函数到一个类中作为方法使用函数class_addMethod.给出如下函数:
void dynamicMethodIMP(id self,SEL,_cmd){
    //implementation…
}
你可以动态地添加一个方法到一个类中(called resolveThisMethodDynamically)using resolveInstanceMethod:如下:
@implementation MyClass
+ (BOOL)resolveInstaceMethod:(SEL)aSel
{
    if(aSEL == @selector(resolveThisMethodDynamically)){
        class_addMethod([self class],aSEL,(IMP)denamicMethodIMP,“v@:”);
        return YES;
    }
}
传递方法和动态方法解析庞大的正交的。一个类拥有在消息转发机制缴付之前动态解决一个方法。如果respondsToSelector或者instancesRespondToSelector:被援引,动态的方法采掘器被首先给予机会提供一个IMP for selector.如果你实现resolveInstanceMethod:方法,,但是又想提供一个特殊的选择器传递消息通过传递机制,你返回NO。

动态加载

一个Objective-C 程序加载并链接classes和categories当程序运行时。新的代码编入程序和相同的处理,以在开始时加载的class和category。
动态加载可以被用来做很多不同的事。例如,在系统参数应用中得各个模块是动态加载的。

在Cocoa环境中,动态加载通常被用来运行应用进行定制。还可以写得模块是你的程序在运行时加载,就像Interface Buider中加载调色板和OS X system预制应用加载自定义喜好的模块。可加载模块扩展了你的程序的功能。它们有助于运行你想做但做不了并自己定义的方式。您所提供的框架,但是其他人代码。

虽然是一个运行时函数来执行的Mach-O的文件Objective-C的模块(objc_loadModules,在objc/ objc-load.h定义)的动态加载,Cocoa的NSBundle类提供了一个显著更方便的接口,用于动态加载,一个的对象取向并结合相关服务。看到一个NSBundle类规范中有关一个NSBundle类及其使用的信息基础架构的参考。看到OS X ABI的Mach-O文件格式参考有关的Mach-O文件的信息。

消息转发
发送一条不能处理的消息到一个对象是一个错误。然而,声明错误,Runtime系统给了接收对象第二次机会去处理消息。


转发
如果你发送一条不能处理的消息到一个对象在声明错误时,Rumtime发送给对象一条forwardInvocation:消息和Nsinvocation对象作为参数,NSInvocation对象包含原始的消息和我们传入的参数。

你可以实现一个forwordInvocation:方法去给予一个默认的回应到消息中,以其他方式避免错误。就像它名字指出的,forwoardInvocation:是一个通常用来传递消息到其他对象的。

为了解传递的范围和意图,想象以下场景:或许,首先,你设计了一个对象可以回应一条消息叫做negotiate,并且你想它的回应包括其他种类类的回应。你可以轻松的完成它以传递一条negotiate消息到其他对象negotiate方法的实现中。

更进一步,想象一下你希望你的对象对于negotiate消息的回应精确地在其他类中实现。一条解决它的方法就是使你的类继承其他类的方法。然而这也许不是准备 事可能的方法。也许有很多可能你的类和类实现的negotiate在继承层次的不同分支。
尽管你的类不能继承negotiate方法,你依然可能借助它去实现一个版本的方法,借助其他类的实例。
- (id)negotiate
{
    if([someOtherOjbect respondsTo:@selector(negotiate)])
        return [someOtherObject negotiate];
    return self;
}
消息返回值被传递到最初的发送者。所有类型的返回值都会被传递到发送者,包括ids,structure,double,float
forwardInvocation:方法对于不认识的消息扮演发送中心,将它们打包至不同的接受者。或者作为一个发送站,发送所有消息到不同的目的地。可以发送一条消息到其他对象,或者仅仅吞了一些消息,没有任何错误和回应。forwardInvocation:方法同样可以将几种消息统一到一种回应。forwardInvocation:做的是根据实现者。然而,它提供的机会是将对象链接到一个传递链中,放大程序设计的可能。

注释:forwardInvocation:方法处理一个消息只有在他们不能在名义上的接受者援引一个已存在的方法。例如,如果你想你的对象传递negotiate消息到其他对象,它自己本身不能拥有negotiate方法。如果有的话,消息不会被传递到forwardInvocation:。

传递和多继承

转发模拟继承,在Objective-C中被用来实现多继承的效果。如图所示一个对象响应一个消息由传递它就像借或者继承一个其他类方法的实现。

在这个插图中,一个Warrior类的实例传递一条negotiate消息到Diplomat类的实例中。Warrior实现negotiate就像Diplomat一样。它就像响应negotiate消息一样,它似乎对negotiate消息做出响应,并为所有实际目的做出响应(尽管是一个Diplomat做的事);

转法消息的对象从而“继承”继承层次中两个分支中的方法——它自己的分支中得和响应消息对象的。在例子中,Warrior类似乎继承Diplomat就像它自己的父类一样。

转发消息提供了大多数,你想要从多继承获得的特性。然而,两者之间有重要的不同:多继承组合不同的能力到一个类中。它的趋势是大型的,多方面的对象。转发,在另一个方面,分配不同责任,不同的对象,他的问题分解成更小的对象,但是联系这些对象通过一种方法,对于消息发送者是透明的。

替代对象

转发不仅模仿多继承,同时使得它可以开发轻量级对象相当于更多有价值的对象。替代者代替其他对象接受传递的消息。

在Objective-C编程语言中“远程通信”中代理就是这样一个替代者。代理负责转发消息的管理信息到远程接收器,确定参数值被复制并且通过连接检索,以此类推。但它并不是图做其他的,他不会复制远程对象的功能。但是只给远程对象一个本地地址,让它可以接受另一个应用程序的消息。

其他种类的替代对象也是可能的。想象一下,例如,你拥有一个对象操作着大量数据,也许它会创建一个复杂的影像或读取磁盘上的文件的内容。这个对象设置起来会非常耗时,所以你宁愿懒洋洋地做它(懒加载?),在真正需要,或者系统资源限制时。同时,你至少需要一个占位符,以使其他对象正常的工作。

在这种情况下,你可以开始创造,而不完全成熟的对象,但一个轻量级替代它。这个对象自身可以做一些事情,如有关数据响应,但大多只会持有为巨大对象提供内存,当需要时,转发消息给它。当代替的forwardInvocation:方法第一次接收到发送给其他对象的消息,这将确保改对象的存在,将创建它,如果它没有。所有较大对象经过替代,对于程序其他有关的部分,替代和较大的物体是相同的。

转发和继承
虽然转发模拟继承,但NSObject类从来没有混淆它们。方法responseToSelector:和isKindOfClass:看起来只在继承层次结构中,从来没有在转发链。如果,例如,一个Warrior对象被询问它是否响应一个negotiate消息。
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
答案是否定的,尽管它可以接收negotiate消息没错,并且马上回应,在一定意义上,传递它们到Diplomat。
在许多情况下,NO是正确地答案。但它可能不是。如果你使用转发去设置一个替代的对象或者扩展类的功能,转发机制也许应该是透明的继承。如果你希望你的对象,作为它们的对象是否真的继承对象的行为,你需要重新实现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;
}

除了respondsToSelector:和isKindOfClass:,该instancesRespondToSelector:方法也应反映转发算法。如果协议被使用时,conformsToProtocol:方法同样应该被添加到列表中。同样,如果一个对象转发接收到任何远程的消息,它应该有一个版本methodSignatureForSelector:即可以返回,最终以转发的消息作出响应的方法,准确的描述;例如,如果一个对象是能够将消息转发给其代理人,您将实现methodSignatureForSelector:如下:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}
您可能会考虑把转发算法某处私有代码,并包括所有这些方法,forwardInvocation:,调用它。



注意:这是一种先进的技术,只适宜没有其他的解决方案是可能的。它不打算作为替代产业。如果你必须使用这种技术,确保您完全理解类做转发和你转发的类的行为。
在本节中提到的方法都在NSObject类规范Foundation框架参考。有关invokeWithTarget信息:,看到NSInvocation类规范的基础架构参考。

0 0
原创粉丝点击