Objective-C 动态实现

在 Objective-C 中,我们偶尔会看到一个属性被声明为 @dynamic, 其实这个是在向编译器保证,虽然现在这个属性找不到 setter 和 getter 方法,但是在运行时会有可用的实现,你编译器不要自动帮我合成 ivar 了。我们可以使用 resolveinstancemethod: 方法 和 resolveClassMethod: 方法来动态提供方法实现,那么 @dynamic 合成属性的方法是怎么样的呢? 我们这里使用一个 Demo 来说明这个过程。
这个 Demo 使用动态实现来为 NSMutableDictionary 中存储的属性动态创建获取方法和设置方法。

创建一个 Person 对象,该对象有 firstName 和 lastName 这两个属性。

// Person.h
@interface Person : NSObject
@property (nonatomic,copy) NSString *firstName;
@property (nonatomic,copy) NSString *lastName;
@end

接下来看 Person.m 文件

#import "Person.h"
#import 

@interface Person ()
@property (nonatomic,strong) NSMutableDictionary *properties;
@end

@implementation Person
// @dynamic, 其实这个是在向编译器保证,虽然现在这个属性找不到 setter 和 getter 方法,
// 但是在运行时会有可用的实现,你编译器不要自动帮我合成 ivar 了
@dynamic firstName,lastName;

- (id)init{
    if (self = [super init]) {
        _properties = [[NSMutableDictionary alloc] init];
    }
    return self;
}

// getter 方法
static id propertyIMP(id self, SEL _cmd){
    return [[self properties] valueForKey:NSStringFromSelector(_cmd)];
}

// setter 方法
static void setPropertyIMP(id self,SEL _cmd, id aValue){
    id value = [aValue copy];
    // 使用 mutableCopy 而不是 copy
    NSMutableString *key = [NSStringFromSelector(_cmd) mutableCopy];
    // 删除 “set”
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    // 删除 “:”
    [key deleteCharactersInRange:NSMakeRange([key length] -1, 1)];
    // 将第一个字母变为小写
    NSString *firstC = [key substringToIndex:1];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstC lowercaseString]];
    // 保存对应属性的值
    [[self properties] setValue:value forKey:key];
}

// 这里假设所有不能识别的方法都是 setter 方法或者 getter 方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if ([NSStringFromSelector(sel) hasPrefix:@"set"]) {
        // 第一个字符 v 表明返回值是一个 void。
        // 接下来的二个字符 @: 表明该方法接受一个 id 和一个 SEL
        // 最后一个字符是方法的显式参数 @ 表示是一个 id
        class_addMethod([self class], sel, (IMP)setPropertyIMP, "v@:@");
    }else{
        // 第一个字符 @ 表明返回值是一个 id。
        // 对于消息传递系统来说,所以的 Objective-C 对象都是 id 类型。
        // 接下来的二个字符 @: 表明该方法接受一个 id 和一个 SEL
        class_addMethod([self class], sel, (IMP)propertyIMP, "@:@");
    }
    return YES;
}
@end

从代码中可以看到,我们将 firstName,lastName 这2个属性设置成 @dynamic 。然后提供了 static void setPropertyIMP(id self,SEL _cmd, id aValue) 这个 setter 方法,也提供了 static id propertyIMP(id self, SEL _cmd) 这个 getter 方法。在 setter 和 getter 中使用了 NSMutableDictionary 来存储和读取属性。

当 Person 无法识别一些方法的时候,Objective-C 的消息分派就会调用 Person 类的 + (BOOL)resolveInstanceMethod:(SEL)sel 方法。那么我们在这个方法里面 为 Person 这个类动态的添加属性的 setter 和 getter 方法。这样 Person 就可以正确的响应属性的 setter 和 getter。这个就是 Objective-C 的动态实现,在运行过程中提供方法实现。

参考

本文是《iOS 编程实战》的读书笔记,对阅读的内容进行总结。当我们看懂了之后,不一定懂;我们跟着书上代码敲了一遍之后,还是不一定懂;只有我们能够把自己理解的内容写下来或者通过其它方式表达出来的时候,这个才是真的懂了;

Runtime Type Encoding
《iOS编程实战》第24章

你可能感兴趣的:(Objective-C 动态实现)