IOS Object-C 中Runtime详解及实例代码

2020-01-18 21:31:31王冬梅

我们知道OC是动态的,也就是说很多东西它不是在编译时去判断,而是在运行时去处理的,这其实带来了很大的灵活性,比如这里我们对于一些不存在的方法的调用,就可以动态去添加上一个方法来代替我们要调用的方法。

比如我们添加一个Button,点击事件我们关联到一个没有具体实现的实例方法,这种情况下如果不做任何处理那么点击Button后一定会崩溃,但是如果我们拦截并动态添加一个方法来报警,就不会崩溃了:


@interface ViewController ()
@property (nonatomic, strong) UIButton *logBtn;

- (void)notHas;// 要调用的实例方法,没有具体实现
@end

- (void)viewDidLoad {
 [super viewDidLoad];

 // 添加按钮
 self.logBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 20)];
 [self.logBtn setTitle:@"测 试" forState:UIControlStateNormal];
 [self.logBtn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
 // 添加没有实现的点击事件
 [self.logBtn addTarget:self action:@selector(notHas) forControlEvents:UIControlEventTouchUpInside];
 [self.view addSubview:self.logBtn];

}

// 拦截对不存在的方法的调用
+ (BOOL)resolveInstanceMethod:(SEL)sel {
 NSLog(@"notFind!");
 // 给本类动态添加一个方法
 if ([NSStringFromSelector(sel) isEqualToString:@"notHas"]) {
 class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
 }
 // 注意要返回YES
 return YES;
}

// 要动态添加的方法,这是一个C方法
 void runAddMethod(id self, SEL _cmd, NSString *string) {
 NSLog(@"动态添加一个方法来提示");
}

按照上面的处理,点击按钮后就不会崩溃,而是转到了对 runAddMethod 方法的调用。其实更明确地说,应该是重现了对要调用的方法的实现,将原本要调用的方法的实现,改为了一个新的实现。class_addMethod 方法的第二个参数是要重写的方法,这里用的就是传进来的参数sel,第三个参数就是重写后的实现。第四个参数是方法的签名。

关联对象

什么叫关联对象?说通俗一点,我们都知道用Category类别可以给一些已经存在的,比如系统的类添加方法,但是不能添加新属性,那怎么添加属性呢?一种直接的方法是继承,但如果只是为了添加一个属性就去做一次继承,还是有点重,这时候,就可以用关联对象的方法。

有两个相关的方法:

objc_setAssociatedObject 方法用来给类关联一个属性;
objc_getAssociatedObject 方法用来获取之前关联的属性。

比如说给自己这个类关联一个字符串:


 // 关联对象
 static char associatedObjectKey;
 objc_setAssociatedObject(self, &associatedObjectKey, @"我就是要关联的字符串对象内容", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 NSString *theString = objc_getAssociatedObject(self, &associatedObjectKey);
 NSLog(@"关联对象:%@", theString);