首页 > 代码库 > 深入Objective-C的动态特性
深入Objective-C的动态特性
Objective-C有相当多的动态特性,基本上也是最常用的有动态类型(Dynamic typing)、动态绑定(Dynamic binding)和动态加载(Dynamic loading),这些都是在Cocoa程序开发中非常常用的语言特性,在此之后OC底层也提供了相当丰富的运行时特性,比如枚举类属性方法、获取方法实现等等。虽然在平常的Cocoa开发中这些底层的运行特性基本用不着,但是在某些情况下如果你知道这些特性并合理加以运用的话,往往能事半功倍。
动态语言基础:
1.动态类型
也就是运行时再决定对象的类型。这类动态特性在日常应用中非常常见,简单的说就是id类型。id类型即通用的对象类,任何对象都可以被id指针指向,而在实际应用中往往使用introspection(内省)来确定该对象实际所属类:
id obj = someInstance;//id指针指向了obj对象 if ([obj isKindOfClass:someClass])//采用内省来判断这个obj对象的确切类型也可以使用isMemberOfClass方法(NSObject方法) { someClass *classSpecifiedInstance = (someClass *)obj;//对obj进行安全的强制类型转换,并且用一个确定类型的指针来指向它 }
2.动态绑定
基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和相应消息也被完全确定了,这就是动态绑定的实质。在继续之前,需要明确Objective-C中的消息机制。由于OC的动态特性,OC中很少提及"函数"的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了。而在OC中最常使用的是消息机制。调用一个实例的方法,所做的是向该实例的指针发送消息,实例在接收到消息之后,从自身的实现中寻找响应这条消息的方法。
动态绑定所做的,即是在实例所属的类确定后,将某些属性和相应的方法绑定到实例上。这里所指的属性和方法当然包括了原来没有在类中实现的,而是在运行时才需要的新加入的实现。在Cocoa层次上,我们一般会向一个NSObject对象发送-respondToSelector:或者-instancesRespondToSelector:来确定对象是否可以对某个SEL做出相应,而在OC转发消息机制被触发之前,对应的类的+resolveClassMethod:和+resolveInstanceMethod:将会被调用,在此时有机会动态地向类或者实例添加新的方法,也就是类的实现是可以动态绑定的。
3.动态加载
根据需求加载所需的资源,对于iOS开发来说基本就是根据不同的机型来适配。最经典的例子就是在Retina设备上加载@2x的图片,而在老一些的普通屏设备上加载原图。
深入运行时
基本的动态特性在常规的Cocoa开发中非常常用,特别是动态类型和动态绑定。由于Cocoa程序大量地使用Protocol-Delegate的设计模式,因此大部分的delegate指针类型必须是id,以满足运行时delegate的动态替换。而OC中还有一些高级或者说更加底层的运行时特性,在一般的Cocoa开发中较为少见,基本被运用在OC和其他语言的接口上。但是如果有所了解并且使用得当的话,在Cocoa中往往可以轻易解决棘手的问题。
这类运行时特性大多由/usr/lib/libobjc.A.dylib这个动态库提供,里面包括对类、实例成员、成员方法和消息发送的很多的API,包括获取类实例变量列表,替换类中方法,为类成员添加变量,动态改变方法实现等等,十分强大。虽然文档开头表明是对于Mac OS X Objective-C 2.0适用,但是由于这些是OC的底层方法,因此对于iOS开发来说也是完全相同的。
举一个简单的例子。比如在进行Universal应用或者游戏时,如果使用IB构建大量的自定义的UI,那么iPhone版本转向iPad版本的过程中面临的一个重要问题就是如何从不同的nib中加载界面。在iOS5以前,所有的UIViewController在使用默认的页面加载时(init 或者initWithNibName:),都会走-loadNibNamed:owner:options:。而因为我们无法拿到-loadNibNamed:owner:options:的实现,因此对其重载是比较困难而且存在风险的。因此在做iPad版本的nib时,一个简单的办法是将所有的nib的命名方式统一,然后使用自己实现的新的类似-loadNibNamed:owner:options:的方法将原方法替换掉,同时保证非iPad的设备还走原来的loadNibNamed:owner:options:方法。使用OC运行时特性可以较简单地完成这一任务。
代码如下,在程序运行时调用+swizze,交换自己实现的loadNibNamed:owner:options:和系统的loadNibNamed:owner:options:,之后所有的loadNibNamed:owner:options:消息都将会发为loadNibNamed:owner:options:,由自己的代码进行处理。
1 2 3 4 5 6 | +( BOOL )swizze { Method oldMethod = class_getInstanceMethod( self , @selector (loadNibNamed:owner:options:)); //<span style="font-family: 仿宋; font-size: 15px;">取出系统的实现方法,存储为oldMethod</span> if (!oldMethod) { //<span style="font-family: 仿宋;">如果没有这个方法直接返回NO</span> return NO ; } Method newMethod = class_getInstanceMethod( self , @selector (loadPadNibNamed:owner:options:));<span style= "font-family: 仿宋;" > //取出自定义的实现方法,存储为newMethod</span> |
1 | if (!newMethod) { return NO ; } method_exchangeImplementations(oldMethod, newMethod); return YES ; } //<span style="font-family: 仿宋; font-size: 15px;">用自定义的方法实现来取代系统的方法实现</span> |
loadNibNamed:owner:options的实现如下,注意在其中的loadNibNamed:owner:options由于之前已经进行了交换,因此实际会发送为系统的loadNibNamed:own
1 2 3 4 5 6 7 8 9 10 11 12 13 | -( 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]; } } |
原文参考:http://onevcat.com/2012/04/objective-c-runtime/