Objective-C具有相当多的动态特性,基本的,也是经常被提到和用到的有动态类型(Dynamic typing),动态绑定(Dynamic binding)和动态加载(Dynamic loading)。

这些动态特性都是在Cocoa程序开发时非常常用的语言特性,而在这之后,OC在底层也提供了相当丰富的运行时的特性,比如枚举类属性方法、获取方法实现等等。虽然在平常的Cocoa开发中这些较底层的运行特性基本用不着,但是在某些情况下如果你知道这些特性并合理加以运用的话,往往能事半功倍~

动态特性基础

1、动态类型

即运行时再决定对象的类型。这类动态特性在日常应用中非常常见,简单说就是id类型。id类型即通用的对象类,任何对象都可以被id指针所指,而在实际使用中,往往使用introspection来确定该对象的实际所属类:

id obj = someInstance;
if ([obj isKindOfClass:someClass])
{
someClass *classSpecifiedInstance = (someClass *)obj;
// Do Something to classSpecifiedInstance which now is an instance of someClass
//...
}

-isKindOfClass:是NSObject的方法,用意确定某个NSObject对象是否是某个类的成员。与之相似的为-isMemberOfClass:,可以用以确定某个对象是否是某个类或其子类的成员。这两个方法为典型的introspection方法。在确定对象为某类成员后,可以安全地进行强制转换,继续之后的工作。

2、动态绑定

  基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和响应的消息也被完全确定,这就是动态绑定。在继续之前,需要明确Objective-C中消息的概念。由于OC的动态特性,在OC中其实很少提及“函数”的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了,而在OC中最常使用的是消息机制。调用一个实例的方法,所做的是向该实例的指针发送消息,实例在收到消息后,从自身的实现中寻找响应这条消息的方法。

  动态绑定所做的,即是在实例所属类确定后,将某些属性和相应的方法绑定到实例上。这里所指的属性和方法当然包括了原来没有在类中实现的,而是在运行时才需要的新加入的实现。在Cocoa层,我们一般向一个NSObject对象发送-respondsToSelector:或者-instancesRespondToSelector:等来确定对象是否可以对某个SEL做出响应,而在OC消息转发机制被触发之前,对应的类的+resolveClassMethod:和+resolveInstanceMethod:将会被调用,在此时有机会动态地向类或者实例添加新的方法,也即类的实现是可以动态绑定的。一个例子:

void dynamicMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
//该方法在OC消息转发生效前被调用
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{ if (aSEL == @selector(resolveThisMethodDynamically))
{
//向[self class]中新加入返回为void的实现,SEL名字为aSEL,实现的具体内容为dynamicMethodIMP
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, “v@:”);
return YES;
}
return [super resolveInstanceMethod:aSel];
}

当然也可以在任意需要的地方调用class_addMethod或者method_setImplementation(前者添加实现,后者替换实现),来完成动态绑定的需求。

3、动态加载

根据需求加载所需要的资源,这点很容易理解,对于iOS开发来说,基本就是根据不同的机型做适配。最经典的例子就是在Retina设备上加载@2x的图片,而在老一些的普通屏设备上加载原图。随着Retina iPad的推出,和之后可能的Retina Mac的出现,这个特性相信会被越来越多地使用。


深入运行时特性

  基本的动态特性在常规的Cocoa开发中非常常用,特别是动态类型和动态绑定。由于Cocoa程序大量地使用Protocol-Delegate的设计模式,因此绝大部分的delegate指针类型必须是id,以满足运行时delegate的动态替换(在Java里这个设计模式被叫做Strategy?不是很懂Java,求纠正)。而Objective-C还有一些高级或者说更底层的运行时特性,在一般的Cocoa开发中较为少见,基本被运用与编写OC和其他语言的接口上。但是如果有所了解并使用得当的话,在Cocoa开发中往往可以轻易解决一些棘手问题。

  这类运行时特性大多由/usr/lib/libobjc.A.dylib这个动态库提供,里面包括了对于类、实例成员、成员方法和消息发送的很多API,包括获取类实例变量列表,替换类中的方法,为类成员添加变量,动态改变方法实现等,十分强大。完整的API列表和手册可以在这里找到。虽然文档开头表明是对于Mac OS X Objective-C 2.0适用,但是由于这些是OC的底层方法,因此对于iOS开发来说也是完全相同的。

  一个简单的例子,比如在开发Universal应用或者游戏时,如果使用IB构建了大量的自定义的UI,那么在由iPhone版转向iPad版的过程中所面临的一个重要问题就是如何从不同的nib中加载界面。在iOS5之前,所有的UIViewController在使用默认的界面加载时(init或者initWithNibName:bundle:),都会走-loadNibNamed:owner:options:。而因为我们无法拿到-loadNibNamed:owner:options的实现,因此对其重载是比较困难而且存在风险的。因此在做iPad版本的nib时,一个简单的办法是将所有的nib的命名方式统一,然后使用自己实现的新的类似-loadNibNamed:owner:options的方法将原方法替换掉,同时保证非iPad的设备还走原来的loadNibNamed:owner:options方法。使用OC运行时特性可以较简单地完成这一任务。

  

+(BOOL)swizze {
Method oldMethod = class_getInstanceMethod(self, @selector(loadNibNamed:owner:options:));
if (!oldMethod) {
return NO;
}
Method newMethod = class_getInstanceMethod(self, @selector(loadPadNibNamed:owner:options:));
if (!newMethod) {
return NO;
} method_exchangeImplementations(oldMethod, newMethod);
return YES;
}

loadPadNibNamed:owner:options的实现如下,注意在其中的loadPadNibNamed:owner:options由于之前已经进行了交换,因此实际会发送为系统的loadNibNamed:owner:options。以此完成了对不同资源的加载。

-(NSArray *)loadPadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options {
NSString *newName = [name stringByReplacingOccurrencesOfString:@"@pad" withString:@""];
newName = [newName stringByAppendingFormat:@"@pad"];
//判断是否存在
NSFileManager *fm = [NSFileManager defaultManager];
NSString* filepath = [[NSBundle mainBundle] pathForResource:newName ofType:@”nib”]; //这里调用的loadPadNibNamed:owner:options:实际为为交换后的方法,即loadNibNamed:owner:options:
if ([fm fileExistsAtPath:filepath]) {
return [self loadPadNibNamed:newName owner:owner options:options];
} else {
return [self loadPadNibNamed:name owner:owner options:options];
}
}

  当然这只是一个简单的例子,而且这个功能也可以通过别的方法来实现。比如添加UIViewController的类别来重载init,但是这样的重载会比较危险,因为你UIViewController的实现你无法完全知道,因此可能会由于重载导致某些本来应有的init代码没有覆盖,从而出现不可预测的错误。当然在上面这个例子中重载VC的init的话没有什么问题(因为对于VC,init的方法会被自动转发为loadNibNamed:owner:options,因此init方法并没有做其他更复杂的事情,因此只要初始化VC时使用的都是init的话就没有问题)。但是并不能保证这样的重载对于其他类也是可行的。因此对于实现未知的系统方法,使用上面的运行时交换方法会是一个不错的选择~

代码如下,在程序运行时调用+swizze,交换自己实现的loadPadNibNamed:owner:options和系统的loadNibNamed:owner:options,之后所有的loadNibNamed:owner:options消息都将会发为loadPadNibNamed:owner:options,由自己的代码进行处理。

深入Objective-C的动态特性 Runtime的更多相关文章

  1. iOS 动态特性和RunTime

    过去的几年中涌现了大量的Objective-C开发者.有些是从动态语言转过来的,比如Ruby或Python,有些是从强类型语言转过来的,如Java或C#,当然也有直接以Objective-C作为入门语 ...

  2. iOS开发——高级特性&Runtime运行时特性详解

    Runtime运行时特性详解 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 ...

  3. Objective C运行时(runtime)

    #import <objc/runtime.h> void setBeingRemoved(id __self, SEL _cmd) { NSLog(@"------------ ...

  4. ios Objective-C的动态特性

    这是一篇译文,原文在此,上一篇文章就是受这篇文章启发,这次干脆都翻译过来. 过去的几年中涌现了大量的Objective-C开发者.有些是从动态语言转过来的,比如Ruby或Python,有些是从强类型语 ...

  5. Objective-C的动态特性

    最近几年中涌现了大量的Objective-C开发者.有些是从动态语言转过来的,比如Ruby或Python,有些是从强类型语言转过来的,如Java或C#,当然也有直接以Objective-C作为入门语言 ...

  6. (译)Objective-C的动态特性

    这是一篇译文,原文在此,上一篇文章就是受这篇文章启发,这次干脆都翻译过来. 过去的几年中涌现了大量的Objective-C开发者.有些是从动态语言转过来的,比如Ruby或Python,有些是从强类型 ...

  7. iOS 深入Objective-C的动态特性

    深入Objective-C的动态特性 Objective-C具有相当多的动态特性,基本的,也是经常被提到和用到的有动态类型(Dynamic typing),动态绑定(Dynamic binding)和 ...

  8. OC动态特性

    今天是2.15周日,快要过年了,我以一个实习生的身份在公司工作了快要两个月了吧,什么是北漂,北漂就是感觉生活节奏变了,以前困了可以上课睡觉,累了可以回家休息数周,人际交往乏了,可以躲起来看着窗外的雨或 ...

  9. JS动态特性

    var arr=[]; arr['js']='jquery'; arr['css']='oocss'; var obj={}; for(var i in arr) { obj[i]=arr[i]; } ...

随机推荐

  1. IIS安装asp组件:JMail 邮件收发组件

    JMail简介 jmail是一种服务器端的邮件发送组件,和个人用的客户端邮件软件不一样的.jmail是在服务器上给程序用来发邮件用的,除了软件编程人员,其他人一般平常用不上. jmail是一个第三方邮 ...

  2. Tomcat通过配置一个虚拟路径管理web工程

    关于虚拟路径.学问javaweb训练课程,如今,鉴于这种情况下老师. 当我们的项目,当在不同的文件夹项目.我们如何使用tomcat去管理web工程. 教师提出的解决方案是 使用虚拟路径方式,并按照实施 ...

  3. TCMalloc 安装与使用

    TCMalloc 安装和使用 Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn.net/chen19870707 Da ...

  4. UVa 353 - Pesky Palindromes

    称号:字符串统计回文子的数量. 分析:dp,暴力.因为数据是小,直接暴力可以解决. 说明:(UVa最终评出800该). #include <iostream> #include <c ...

  5. SQL -- 是否或推断线相交以在其内部的平面

    SQL如下面: update mapping_nj_roads set municipality='227' from mapping_geodata_boundary a where a.suppo ...

  6. 【转】Android 收集已发布程序的崩溃信息

    我们写程序的时候都希望能写出一个没有任何Bug的程序,期望在任何情况下都不会发生程序崩溃.不过理想是丰满的,现实是骨感的.没有一个程序员能保证自己写的程序绝对不会出现异常崩溃.特别是针对用户数达到几十 ...

  7. Windows PHone 8 获取硬件信息

    /// <summary> /// 获取系统信息 /// </summary> private string GetDeviceInfo() { StringBuilder s ...

  8. latex如何输入正确的 双引号

    latex当输入双引号,假设直接用双引号键在键盘上.玩过顺-handed. 引述左输入法是正确的:按两次"Tab在之上,数字1左边的键".至于后面行情,该方法是一样的老,这是两次单 ...

  9. 左右v$datafile和v$tempfile中间file#

    v$datafile关于存储在文件中的数据视图的信息,v$tempfile查看存储在一个临时文件中的信息. 有两种观点file#现场,首先来看看官方文件的定义: V$DATAFILE This vie ...

  10. 《Javascript语言精粹》 读书笔记

    1 6种值被当做假:false.null.undefined.空字符串''.数字0.数字NaN,其他所有值都为真,包括"false" 2 typeof有6种值,分别是'number ...