Objective-C Runtime

来源:互联网 发布:apache ant.jar maven 编辑:程序博客网 时间:2024/06/13 23:52

作为一种动态编程语言,Objective-C 拥有一个运行时系统来支持动态创建类,添加方法、进行消息传递和转发。利用 Objective-C 的 Runtime 可以实现一些很棒的功能。本篇文章会简单介绍一下消动态方法解析,并使用它实现一个容易扩展和序列化的实体类。 本文仅简单介绍相关概念,更详尽的说明请参考苹果官方文档Objective-C Runtime Programming Guide。

消息传递(Messaging)

在很多语言,比如 C ,调用一个方法其实就是跳到内存中的某一点并开始执行一段代码。没有任何动态的特性,因为这在编译时就已经确定了。而在 Objective-C 中,执行 [object foo] 语句并不会立即执行 foo 这个方法的代码。它是在运行时给 object 发送一条叫 foo 的消息。这个消息,也许会由 object 来处理,也许会被转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。

事实上,在编译时你写的 Objective-C 函数调用的语法都会被翻译成一个 C 的函数调用 - objc_msgSend()。比如,下面两行代码就是等价的:

[object foo];objc_msgSend(object, @selector(foo));

消息传递过程: 首先,找到 object 的 class; 通过 class 找到 foo 对应的方法实现; 如果 class 中没到 foo,继续往它的 superclass 中找; 一旦找到 foo 这个函数,就去执行它的实现.

假如,最终没找到 foo 的方法实现,会发生什么呢?让我们看一个类:

@interface SomeClass : NSObject- (void)foo;- (void)crash;@end@implementation SomeClass-(void)foo {   NSLog(@"method foo was called on %@", [self class]);}@end

SomeClass 这个类声明了一个方法 foo,和一个方法 crash, 我们实现了 foo 方法,但是没有实现 crash 方法。现在分别调用这两个方法,会发生什么?

SomeClass *someClass = [[SomeClass alloc] init];[someClass foo];[someClass crash];

运行这段代码,可以看到下面的输出: ```[removed] method foo was called on SomeClass : -[SomeClass crash]: unrecognized selector sent to instance 0x7ff67ac377f0 : Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SomeClass crash]: unrecognized selector sent to instance 0x7ff67ac377f0' First throw call stack: (     0   CoreFoundation                      0x0000000101380e65 __exceptionPreprocess + 165     1   libobjc.A.dylib                     0x0000000100a70deb objc_exception_throw + 48     2   CoreFoundation                      0x000000010138948d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205     ...

程序执行了 foo 方法,并打印出日志。然后程序崩溃了,在执行 crash 方法时就抛出了一个异常,因为 crash 方法没有对应的实现。但在异常抛出前,Objective-C 的运行时会给你三次拯救程序的机会:- Method resolution- Fast forwarding- Normal forwarding#Method Resolution首先,Objective-C 运行时会调用 +resolveInstanceMethod: 或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。还是以 foo 为例,你可以这么实现:```javascriptvoid fooMethod(id obj, SEL _cmd) {    NSLog(@"Doing foo");}+ (BOOL)resolveInstanceMethod:(SEL)aSEL {    if(aSEL == @selector(foo:)){        class_addMethod([self class], aSEL, (IMP)fooMethod, "v@:");        return YES;    }    return [super resolveInstanceMethod];}

Core Data 有用到这个方法。NSManagedObjects 中 properties 的 getter 和 setter 就是在运行时动态添加的。 如果 resolveInstanceMethod: 方法返回 NO,运行时就会进行下一步:消息转发(Message Forwarding)。 

实现一个容易扩展和序列化的实体类

这里,就使用上述的 Normal forwarding 来创建一个容易扩展和序列化的类。 通常我们会这样定义一个实体类:在类中定义许多属性,然后通过属性的 setter 和 getter 方法来存取值。

@interface MyModel : NSObject@property (nonatomic, strong) NSString *prop1;@property (nonatomic, strong) NSString *prop2;// ...@end

现在我们需要把上面的实体类对象导出成一个 JSON,可能就需要下面 toDictionary: 这样的方法:

@interface MyModel : NSObject@property (nonatomic, strong) NSString *prop1;@property (nonatomic, strong) NSString *prop2;// ...- (NSDictionary *)toDictionary;@end@implementation MyModel- (NSDictionary *)toDictionary {    NSMutableDictionary *dict = [NSMutableDictionary dictionary];    if (self.prop1) dict[@"prop1"] = self.prop1;    if (self.prop2) dict[@"prop2"] = self.prop2;    return [dict copy];}@end

假如 MyModel 有很多个属性,这样写就比较繁琐。那么,既然要导出为 JSON 对象,中间肯定需要构建一个字典对象,能不能再保存值的时候就直接保存到一个字典中呢?于是,对上面的类改造一下:

@interface MyModel : NSObject@property (nonatomic, strong) NSString *prop1;// ...@property (nonatomic, strong) NSMutableDictionary *dictionary;@end@implementation MyModel- (NSMutableDictionary *)dictionary {    if (!_dictionary) {        _dictionary = [NSMutableDictionary dictionary];    }    return _dictionary;}- (void)setProp1:(NSString *)prop1 {    if (prop1) {        self.dictionary[@"prop1"] = prop1;    } else {        [self.dictionary removeObjectForKey:@"prop1"];    }}- (NSString *)prop1 {    return self.dictionary[@"prop1"];}@end

我们在 MyModel 中加了一个属性 dictionary,在保存值的时候直接保存到这个字典里面,导出 JSON 的时候就简单许多。但是要对每一个属性写一个 setter 一个 getter,这样也不合适。

通过观察这些 setter 和 getter,我发现他们非常相似,而且通过这些方法名可以解析出属性名。那么,我们能不能在运行时再决定把值存在那个 key 下面呢?结合动态方法解析,然后就有了下面这个雏形:

@implementation MyModel- (NSMutableDictionary *)dictionary {    if (!_dictionary) {        _dictionary = [NSMutableDictionary dictionary];    }    return _dictionary;}+ (BOOL)resolveInstanceMethod:(SEL)sel {    if (isGetter) {        // 如果 sel 是一个 Getter,动态添加一个 Getter 实现        // Getter 的实现需要从 dictionary 中取出对应的值        return YES;    }    if (isSetter) {        // 如果 sel 是一个 Setter,就动态添加一个 Setter 实现        // Setter 实现中需要把值保存到 dictionary 中        return YES;    }    return NO;}@end- (void)setProp1:(NSString *)prop1 {    if (prop1) {        self.dictionary[@"prop1"] = prop1;    } else {        [self.dictionary removeObjectForKey:@"prop1"];    }}- (NSString *)prop1 {    return self.dictionary[@"prop1"];}@end

为了实现上面的功能,要做下面几个事情:

  • 需要让 setter 和 getter 在运行时决定
  • 运行时要判断需要解析的 selector 是不是 setter 或者 getter。
  • 实现一个通用的 setter 和 getter

编译器默认会为每个属性创建 setter 和 getter 方法,可以使用 @dynamic 关键词告诉编译器不要为某个属性创建 setter 和 getter 方法。

@implementation MyModel// 编译器不再自动实现 setProp1: 和 prop1 方法// 在运行时就可以为 prop1 属性动态添加 setter 和 getter@dynamic prop1;@end

最终实现的 MyModel 类如下:

@interface MyModel : NSObject@property (nonatomic, strong) NSString *prop1;@property (nonatomic, strong) NSString *prop2;// ...@property (nonatomic, strong) NSMutableDictionary *dictionary;+ (objc_property_t)parseSelector:(SEL)selector isGetter:(BOOL *)isGetter isSetter:(BOOL *)isSetter;@end// 针对 id 类型属性 getter 的实现void dynamicSetter(MyModel *obj, SEL sel, id value) {    objc_property_t prop = [[obj class] parseSelector:sel isGetter:NULL isSetter:NULL];    NSString *propName = [NSString stringWithFormat:@"%s", property_getName(prop)];    if (value) {        obj.dictionary[propName] = value;    } else {        [obj.dictionary removeObjectForKey:propName];    }}// 针对 id 类型属性 setter 的实现id dynamicGetter(MyModel *obj, SEL sel) {    objc_property_t prop = [[obj class] parseSelector:sel isGetter:NULL isSetter:NULL];    NSString *propName = [NSString stringWithFormat:@"%s", property_getName(prop)];    return obj.dictionary[propName];}@implementation MyModel// 声明这两个属性的 setter 和 getter 是动态创建的@dynamic prop1, prop2;- (NSMutableDictionary *)dictionary {    if (!_dictionary) {        _dictionary = [NSMutableDictionary dictionary];    }    return _dictionary;}// 判断是否是 setter 或 getter,返回属性名+ (objc_property_t)parseSelector:(SEL)selector isGetter:(BOOL *)isGetter isSetter:(BOOL *)isSetter {    NSString *selStr = NSStringFromSelector(selector);    // 首先根据 setter 和 getter 的特点推断出属性名    char propName[selStr.length +1];    memset(propName, 0, selStr.length +1);    if ([selStr hasPrefix:@"set"]) {        strncpy(propName, selStr.UTF8String +3, selStr.length -4); // drop 'set' and ':'        propName[0] += ('a' - 'A'); // lowercase first letter        if (isSetter!=NULL) *isSetter = YES;    } else {        strncpy(propName, selStr.UTF8String, selStr.length);        if (isGetter!=NULL) *isGetter = YES;    }    // 然后使用推断出的属性名反查属性,如果没找到,说明这个 selector 既不是某个属性的 setter 也不是 getter    objc_property_t prop = class_getProperty([self class], propName);    if (!prop) {        if (isSetter!=NULL) *isSetter = NO;        if (isGetter!=NULL) *isGetter = NO;    }    return prop;}+ (BOOL)resolveInstanceMethod:(SEL)sel {    BOOL isGetter, isSetter;    objc_property_t prop = [self parseSelector:sel isGetter:&isGetter isSetter:&isSetter];    const char *typeEncoding = property_copyAttributeValue(prop, "T");    if (typeEncoding != NULL) {        if (typeEncoding[0] == '@') {            if (isGetter) {                class_addMethod([self class], sel, (IMP)dynamicGetter, "@@:");                return YES;            }            if (isSetter) {                class_addMethod([self class], sel, (IMP)dynamicSetter, "v@:@");                return YES;            }        } else {            // 这里可以添加一些 setter 和 getter 实现以支持 int, float 等基本类型的属性        }    }    return NO;}@end

有关上面提到的属性类型 typeEncoding 可以查看苹果文档

注意:上面的实现仅支持 OC 对象类型的属性,对于 int, float 和结构体等类型的属性,需要实现特别的 setter 和 getter。

现在,可以为 MyModel 添加许多属性,而不用在写 toDictionary 或者手动实现从 dictionary 中存取值的方法了。也可以继承 MyModel,添加许多属性:

@interface MyModelSub : MyModel@property (nonatomic, strong) NSString *name;@property (nonatomic, strong) NSString *nickname;@end// 类实现中不需要添加许多代码@implementation MyModelSub@dynamic name, nickname;@end

MyModel 和它子类的对象可以快速转化成一个 NSDictionary:

MyModelSub *model = [[MyModelSub alloc] init];model.prop1 = @"pro1value";model.prop2 = @"pro2value";model.name = @"Alex";model.nickname = @"alex";NSLog(@"model.dictionary = %@, \n model.prop1=%@", model.dictionary, model.prop1);

执行后,可以看到下面的输出:

model.dictionary = {    name = Alex;    nickname = alex;    prop1 = pro1value;    prop2 = pro2value;},  model.prop1=pro1value

我们可以很方便的把 NSDictionary 转化成一个 MyModel 对象:

MyModelSub *model = [[MyModelSub alloc] init];model.dictionary = [@{@"name":@"Alex", @"nickname":@"alex"} mutableCopy];

执行后,可以看到下面的输出:

model.name = Alex,model.nickname = alex

利用 Objective-C 的 runtime 特性,我们可以自己来对语言进行扩展,解决项目开发中的一些设计和技术问题。后续文章里,我会介绍消息转发以及使用消息转发实现 MyModel 这样一个类。    

0 0