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 <objc/runtime.h>

@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章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,853评论 0 9
  • 面试笔试都是必考语法知识的。请认真复习和深入研究OC。 Objective-C 方法和选择器有何不同?(Diffe...
    b485c88ab697阅读 5,764评论 0 35
  • 前言:面试笔试都是必考语法知识点。请认真复习和深入研究OC。 目录:iOS-面试题-OC基础篇 (1) - (84...
    麦穗0615阅读 4,291评论 0 33
  • 柚子小姐今年二十五,她说“我失恋了”的时候,就像喝了口白开水那样自然。 可是,听她讲出这句话的所有人,都像吃了口芥...
    西凉姑娘阅读 546评论 1 1
  • 复盘计划:10️下旬重点 计划和复盘的完成✅ 红色标记每天完成一点点 10点 上课 1点到4点开会 5点面谈2个学...
    从心印心阅读 416评论 0 0