#import <Foundation/Foundation.h> #import "XMGPerson.h" #import <objc/runtime.h> int main(int argc, const char * argv[]) { @autoreleasepool { // 成员变量的数量 unsigned int outCount = 0; // 得到全部的成员变量 // ivars是一个指向成员变量的指针 // ivars默认指向第0个成员变量(最前面) Ivar *ivars = class_copyIvarList([XMGPerson class], &outCount); // 遍历全部的成员变量 for (int i = 0; i<outCount; i++) { // 取出i位置对应的成员变量 // Ivar ivar = *(ivars + i); Ivar ivar = ivars[i]; // 得到成员变量的名字 NSLog(@"%s", ivar_getName(ivar)); } // 若是函数名中包含了copy\new\retain\create等字眼,那么这个函数返回的数据就须要手动释放 free(ivars); // Ivar ivar = *ivars; // Ivar ivar2 = *(ivars + 1); // NSLog(@"%s %s", ivar_getName(ivar), ivar_getName(ivar2)); // 一个Ivar就表明一个成员变量 // int *p; 指向int类型的变量 // Ivar *ivars; 指向Ivar类型的变量 } return 0; }
// 成员变量的数量 unsigned int outCount = 0; // 得到全部的成员变量 Ivar *ivars = class_copyIvarList([UITextField class], &outCount); // 遍历全部的成员变量 for (int i = 0; i<outCount; i++) { // 取出i位置对应的成员变量 Ivar ivar = ivars[i]; // 得到成员变量的名字 NSLog(@"%s", ivar_getName(ivar)); } // 若是函数名中包含了copy\new\retain\create等字眼,那么这个函数返回的数据就须要手动释放 free(ivars);
[array insertObject:foo atIndex:5]; objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
typedef struct objc_object { Class isa; } *id;
咱们知道了运行时会发消息给对象。咱们也知道一个对象的class保存了方法列表。那么这些消息是如何映射到方法的,这些方法又是如何被执行的呢?less
第一个问题的答案很简单。class的方法列表实际上是一个字典,key为selectors,IMPs为value。一个IMP是指向方法在内存中的实现。很重要的一点是,selector和IMP之间的关系是在运行时才决定的,而不是编译时。这样咱们就能玩出些花样。函数
IMP一般是指向方法的指针,第一个参数是self,类型为id,第二个参数是_cmd,类型为SEL,余下的是方法的参数。这也是self和_cmd被定义的地方。下面演示了Method和IMP优化
- (id)doSomethingWithInt:(int)aInt{} id doSomethingWithInt(id self, SEL _cmd, int aInt){}
做用:ui
以前已经提过消息分发,不过这只是一小部分功能。全部的运行时方法都有特定的前缀。下面是一些有意思的方法:atom
class_addIvar, class_addMethod, class_addProperty和class_addProtocol容许重建classes。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList
class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty
class_conformsToProtocol, class_respondsToSelector, class_getSuperclass
class_createInstance来建立一个object
method_getName, method_getImplementation, method_getReturnType等等
method_setImplementation和method_exchangeImplementations
比较基础的一个动态特性是经过String来生成Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起来很简单:spa
Class stringclass = NSClassFromString(@"NSString")
因而咱们就获得了一个string class。接下来:代理
NSString *myString = [stringclass stringWithString:@"Hello World"];
为何要这么作呢?
直接使用Class不是更方便?一般状况下是,但有些场景下这个方法会颇有用。首先,能够得知是否存在某个class,NSClassFromString 会返回nil,若是运行时不存在该class的话。指针
另外一个使用场景是根据不一样的输入返回不一样的class或method。好比你在解析一些数据,每一个数据项都有要解析的字符串以及自身的类型(String,Number,Array)。你能够在一个方法里搞定这些,也可使用多个方法。其中一个方法是获取type,而后使用if来调用匹配的方法。另外一种是根据type来生成一个selector,而后调用之。如下是两种实现方式:code
- (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 {}
以前咱们讲过,方法由两个部分组成。Selector至关于一个方法的id;IMP是方法的实现。这样分开的一个便利之处是selector和IMP之间的对应关系能够被改变。好比一个 IMP 能够有多个 selectors 指向它。orm
而 Method Swizzling 能够交换两个方法的实现。或许你会问“什么状况下会须要这个呢?”。咱们先来看下Objective-C中,两种扩展class的途径。首先是 subclassing。你能够重写某个方法,调用父类的实现,这也意味着你必须使用这个subclass的实例,但若是继承了某个Cocoa class,而Cocoa又返回了原先的class(好比 NSArray)。这种状况下,你会想添加一个方法到NSArray,也就是使用Category。99%的状况下这是OK的,但若是你重写了某个方法,就没有机会再调用原先的实现了。
Method Swizzling 能够搞定这个问题。你能够重写某个方法而不用继承,同时还能够调用原先的实现。一般的作法是在category中添加一个方法(固然也能够是一个全新的class)。能够经过method_exchangeImplementations这个运行时方法来交换实现。来看一个demo,这个demo演示了如何重写addObject:方法来纪录每个新添加的对象。
#import <objc/runtime.h> @interface NSMutableArray (LoggingAddObject) - (void)logAddObject:(id)aObject; @end @implementation NSMutableArray (LoggingAddObject) + (void)load { Method addobject = class_getInstanceMethod(self, @selector(addObject:)); Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:)); method_exchangeImplementations(addObject, logAddObject); } - (void)logAddObject:(id)aobject { [self logAddObject:aObject]; NSLog(@"Added object %@ to array %@", aObject, self); } @end
咱们能够在运行时建立新的class,这个特性用得很少,但其实它仍是很强大的。你能经过它建立新的子类,并添加新的方法。
但这样的一个子类有什么用呢?别忘了Objective-C的一个关键点:object内部有一个叫作isa的变量指向它的class。这个变量能够被改变,而不须要从新建立。而后就能够添加新的ivar和方法了。能够经过如下命令来修改一个object的class.
object_setClass(myObject, [MySubclass class]);
这能够用在Key Value Observing。当你开始observing an object时,Cocoa会建立这个object的class的subclass,而后将这个object的isa指向新建立的subclass。
目前为止,咱们讨论了方法交换,以及已有方法的处理。那么当你发送了一个object没法处理的消息时会发生什么呢?很明显,"it breaks"。大多数状况下确实如此,但Cocoa和runtime也提供了一些应对方法。
首先是动态方法处理。一般来讲,处理一个方法,运行时寻找匹配的selector而后执行之。有时,你只想在运行时才建立某个方法,好比有些信息只有在运行时才能获得。要实现这个效果,你须要重写+resolveInstanceMethod: 和/或 +resolveClassMethod:。若是确实增长了一个方法,记得返回YES。
+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
if (aSelector == @selector(myDynamicMethod)) { class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSelector]; }
若是 resolve method 返回NO,运行时就进入下一步骤:消息转发。有两种常见用例。1) 将消息转发到另外一个能够处理该消息的object。2) 将多个消息转发到同一个方法。
消息转发分两步。首先,运行时调用-forwardingTargetForSelector:,若是只是想把消息发送到另外一个object,那么就使用这个方法,由于更高效。若是想要修改消息,那么就要使用-forwardInvocation:,运行时将消息打包成NSInvocation,而后返回给你处理。处理完以后,调用invokeWithTarget:。
Cocoa有几处地方用到了消息转发,主要的两个地方是代理(Proxies)和响应链(Responder Chain)。NSProxy是一个轻量级的class,它的做用就是转发消息到另外一个object。若是想要惰性加载object的某个属性会颇有用。NSUndoManager也有用到,不过是截取消息,以后再执行,而不是转发到其余的地方。
响应链是关于Cocoa如何处理与发送事件与行为到对应的对象。好比说,使用Cmd+C执行了copy命令,会发送-copy:到响应链。首先是First Responder,一般是当前的UI。若是没有处理该消息,则转发到下一个-nextResponder。这么一直下去直到找到可以处理该消息的object,或者没有找到,报错。
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) { NSLog(@"Hello %@", string); }); class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");