objc语言的运行时处理

转自:http://www.cnblogs.com/xwang/p/3542515.html

转自:http://blog.csdn.net/kesalin/article/details/7178871

在Objective-C中,消息是通过objc_msgSend()这个runtime方法及相近的方法来实现的。这个方法需要一个target,selector,还有一些参数。理论上来说,编译器只是把消息分发变成objc_msgSend来执行。比如下面这两行代码是等价的。

1 [array insertObject:foo atIndex:5];  
2 objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

class的方法列表其实是一个字典,key为selectors,IMPs为value。一个IMP是指向方法在内存中的实现。很重要的一点是,selector和IMP之间的关系是在运行时才决定的,而不是编译时。这样我们就能玩出些花样。

IMP通常是指向方法的指针,第一个参数是self,类型为id,第二个参数是_cmd,类型为SEL,余下的是方法的参数。这也是self_cmd被定义的地方。下面演示了Method和IMP

1 - (id)doSomethingWithInt:(int)aInt{} 
2 id doSomethingWithInt(id self, SEL _cmd, int aInt){}

 

objc中存在一些用于修改和自省的方法,这些方法差不多都是以特定的前缀开头,如

class_addIvar, class_addMethod,class_addProperty和class_addProtocol

允许重建classes,

1
class_copyIvarList,class_copyMethodList, class_copyProtocolList和class_copyPropertyList

能拿到一个class的所有内容,而

1
class_getClassMethod, class_getClassVariable, class_getInstanceMethod,class_getInstanceVariable, class_getMethodImplementation和class_getProperty

返回单个内容。也有些用于自省的方法,如

1
class_conformsToProtocol, class_respondsToSelector,class_getSuperclass。

最后,你可以使用class_createInstance来创建一个object。

比较基础的一个动态特性是通过String来生成Classes和Selectors。Cocoa提供了NSClassFromStringNSSelectorFromString方法,使用起来很简单

1 Class stringclass = NSClassFromString(@"NSString");
2 NSString *myString = [stringclass stringWithString:@"Hello World"];

为什么要这么做呢,直接使用class不是更方便,通常情况下,但有些场景有会很能有用,首先可以得知某个class是否存在,因为不存在的话NSClassFromString会返回nil,用于检查。另一个常见的场景是根据不同的输入返回不同的class跟method。在解析数据的时候,下面是一个例子

复制代码
- (void)parseObject:(id)object {
    for (id data in object) {
        if ([[data type] isEqualToString:@"String"]) {
            [self parseString:[data value]]; 
        } else if ([[data type] isEqualToString:@"Number"]) {
            [self parseNumber:[data value]];
        } else if ([[data type] isEqualToString:@"Array"]) {
            [self parseArray:[data value]];
        }
    }
}
- (void)parseObjectDynamic:(id)object {
    for (id data in object) {
        [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"parse%@:", [data type]]) withObject:[data value]];
    }
}
- (void)parseString:(NSString *)aString {}
- (void)parseNumber:(NSString *)aNumber {}
- (void)parseArray:(NSString *)aArray {}
复制代码

可以看到用了后者的话可以把代码行数降下来,将来如果有新的类型,只需要增加实现方法即可,而不用去添加新的else if.

在objc中,方法由两部分组成,selector相当于一个方法的id,IMP是方法的实现,这样分开的一个便利之处就是selector和IMP之间的对应关系可以被改变。这就是Method Swizzling的存在处,交换两个方法的实现,下面是代码实现:

复制代码
 1 void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel){
 2     Method orig_method = nil, alt_method = nil;
 3     // First, look for the methods
 4     orig_method = class_getInstanceMethod(aClass, orig_sel);
 5     alt_method = class_getInstanceMethod(aClass, alt_sel);
 6     // If both are found, swizzle them
 7     if ((orig_method != nil) && (alt_method != nil)){
 8         char *temp1;
 9         IMP temp2;
10         temp1 = orig_method->method_types;
11         orig_method->method_types = alt_method->method_types;
12         alt_method->method_types = temp1;
13         temp2 = orig_method->method_imp;
14         orig_method->method_imp = alt_method->method_imp;
15         alt_method->method_imp = temp2;
16     }
17 }
复制代码

当然,上面的第八行开始到十五行之间的代码可以用如下进行替换。

method_exchangeImplementations(orig_method,alt_method)

上面我们谈到了方法交换,但是当你发送了一个object无法处理的消息时会发生什么呢?这里首先会是动态方法处理

1 resolveInstanceMethod && resolveClassMethod

在这两个重写的地方运用class_addMethod,同时记得返回YES,下面是一个例子

复制代码
1 + (BOOL)resolveInstanceMethod:(SEL)aSelector {
2     if (aSelector == @selector(myDynamicMethod)) {
3         class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:");
4         return YES;
5     }
6     return [super resolveInstanceMethod:aSelector];
7 }
复制代码

如果resolve method返回了NO,那么运行时就进入下一个步骤--消息转发。首先会调用-forwardingTargetForSelector:,如果只是把消息发送到另一个object,那么就用这个方法,但是如果你想修改消息,那么就要使用-forwardInvocation:,将消息打包成NSInvocation,调用invokeWithTarget:

整个文章下来,可以看到objc表面看起来跟c#,java等语言在方法调用上没什么区别,但最关键的是objc的运行时消息处理,我们可以在消息处理上添加更多的自由,其优势在于在不扩展语言本身的情况下做很多事,比如KVO,提供了优雅的API来与已有的代码进行无疑结合。

下面就结合运行时来谈谈KVO的内部真正实现。当你第一次观察某个object时,runtime会创建一个新的继承原先class的subclass。在这个新的class中,它重写了所有被观察的key,然后将object的isa指针指向新创建的class(这个指针告诉Objective-C运行时某个object到底是哪种类型的object)。所以object神奇地变成了新的子类的实例。


下面转自: Cocoa]深入浅出Cocoa之 Method Swizzling

代码就不用多解释了,下面我们来看如何使用。先看辅助类Foo:
Foo.h
  1. //  
  2. //  Foo.h  
  3. //  MethodSwizzling  
  4. //  
  5. //  Created by LuoZhaohui on 1/5/12.  
  6. //  Copyright (c) 2012 http://blog.csdn.net/kesalin/. All rights reserved.  
  7. //  
  8.   
  9. #import <Foundation/Foundation.h>  
  10.   
  11. // Foo  
  12. //  
  13. @interface Foo : NSObject  
  14.   
  15. - (void) testMethod;  
  16. - (void) baseMethod;  
  17. - (void) recursionMethod;  
  18.   
  19. @end  
  20.   
  21. // Bar  
  22. //  
  23. @interface Bar : Foo  
  24.   
  25. - (void) testMethod;  
  26.   
  27. @end  
  28.   
  29. // Bar(BarCategory)  
  30. //  
  31. @interface Bar(BarCategory)  
  32.   
  33. - (void) altTestMethod;  
  34. - (void) altBaseMethod;  
  35. - (void) altRecursionMethod;  
  36.   
  37. @end  

Foo.m
  1. //  
  2. //  Foo.m  
  3. //  MethodSwizzling  
  4. //  
  5. //  Created by LuoZhaohui on 1/5/12.  
  6. //  Copyright (c) 2012 http://blog.csdn.net/kesalin/. All rights reserved.  
  7. //  
  8.   
  9. #import "Foo.h"  
  10.   
  11. // Foo  
  12. //  
  13. @implementation Foo  
  14.   
  15. - (void) testMethod  
  16. {  
  17.     NSLog(@" >> Foo testMethod");  
  18. }  
  19.   
  20. - (void) baseMethod  
  21. {  
  22.     NSLog(@" >> Foo baseMethod");  
  23. }  
  24.   
  25. - (void) recursionMethod  
  26. {  
  27.     NSLog(@" >> Foo recursionMethod");  
  28. }  
  29.   
  30. @end  
  31.   
  32. // Bar  
  33. //  
  34. @implementation Bar  
  35.   
  36. - (void) testMethod  
  37. {  
  38.     NSLog(@" >> Bar testMethod");  
  39. }  
  40.   
  41. @end  
  42.   
  43. // Bar(BarCategory)  
  44. //  
  45. @implementation Bar(BarCategory)  
  46.   
  47. - (void) altTestMethod  
  48. {  
  49.     NSLog(@" >> Bar(BarCategory) altTestMethod");  
  50. }  
  51.   
  52. - (void) altBaseMethod  
  53. {  
  54.     NSLog(@" >> Bar(BarCategory) altBaseMethod");  
  55. }  
  56.   
  57. - (void) altRecursionMethod  
  58. {  
  59.     NSLog(@" >> Bar(BarCategory) recursionMethod");  
  60.     [self altRecursionMethod];  
  61. }  
  62.   
  63. @end  

下面是具体的使用示例:

  1. int main (int argc, const char * argv[])  
  2. {  
  3.     @autoreleasepool  
  4.     {  
  5.         Foo * foo = [[[Foo alloc] init] autorelease];  
  6.         Bar * bar = [[[Bar alloc] init] autorelease];  
  7.           
  8.         NSLog(@"========= Method Swizzling test 1 =========");  
  9.           
  10.         NSLog(@" Step 1");  
  11.         [foo testMethod];  
  12.         [bar testMethod];  
  13.         [bar altTestMethod];  
  14.           
  15.         NSLog(@" Step 2");  
  16.         [Bar swizzleMethod:@selector(testMethod) withMethod:@selector(altTestMethod)];  
  17.         [foo testMethod];  
  18.         [bar testMethod];  
  19.         [bar altTestMethod];  
  20.           
  21.         NSLog(@"========= Method Swizzling test 2 =========");  
  22.         NSLog(@" Step 1");  
  23.         [foo baseMethod];  
  24.         [bar baseMethod];  
  25.         [bar altBaseMethod];  
  26.           
  27.         NSLog(@" Step 2");  
  28.         [Bar swizzleMethod:@selector(baseMethod) withMethod:@selector(altBaseMethod)];  
  29.         [foo baseMethod];  
  30.         [bar baseMethod];  
  31.         [bar altBaseMethod];  
  32.           
  33.         NSLog(@"========= Method Swizzling test 3 =========");  
  34.         [Bar swizzleMethod:@selector(recursionMethod) withMethod:@selector(altRecursionMethod)];  
  35.         [bar recursionMethod];  
  36.     }  
  37.   
  38.     return 0;  
  39. }  

输出结果为:注意,test 3 中调用了递归调用自己的方法,你能理解为什么没有出现死循环么?


========= Method Swizzling test 1 =========
Step 1
>> Foo testMethod
>> Bar testMethod
>> Bar(BarCategory) altTestMethod
Step 2
>> Foo testMethod
>> Bar(BarCategory) altTestMethod
>> Bar testMethod
========= Method Swizzling test 2 =========
Step 1
>> Foo baseMethod
>> Foo baseMethod
>> Bar(BarCategory) altBaseMethod
Step 2
>> Foo baseMethod
>> Bar(BarCategory) altBaseMethod
>> Foo baseMethod
========= Method Swizzling test 3 =========
>> Bar(BarCategory) recursionMethod
>> Foo recursionMethod


test3 解释:在函数体 {} 之间的部分是真正的 IMP,而在这之前的是 SEL。通常情况下,SEL 是与 IMP 匹配的,但在 swizzling 之后,情况就不同了。下图就是调用的时序图。


rentzsch 写了一个完善的开源类 jrswizzle 来处理 Method Swizzling,如果你在工程中使用到 Method Swizzling手法,应该优先使用这个类库,:)。


Refference

MethodSwizzlinghttp://www.cocoadev.com/index.pl?ExtendingClasses

jrswizzlehttps://github.com/rentzsch/jrswizzle


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值