Objective-C运行时的一些技巧

Apple的iOS8发布以后,大家都开始了适配的工作了。但是这个过程总会遇到一些拦路虎,例如推送的API改动。可是商业项目上嵌入了各种各样的第三方静态库,这些静态库质量参差不齐,其中一个静态库甚至在Xcode6上编译后出现问题。于是只能使用Xcode5来编译,但这样就有一个很纠结的问题就是,UIMutableUserNotificationAction等一些类在旧版的Xcode要么就是无法编译,要么只能用宏来跳过。

这时候,我还是想起了Objective-C的运行时方法,使用NSClassFromString(@"UIMutableUserNotificationAction")来获取到系统的类。光这样子还是有很多不足,因为这个类中有很多方法、属性。虽然保证了运行的正常,但是编写这些方法还是有各种不便。例如各种performSelector:、objc_msgSend、setValue: forKey:,实在写得很痛苦。我这里用了一个比较取巧的方法,新建一个伪造的类如“XXXMutableUserNotificationAction”,继承NSObject即可。然后将UIMutableUserNotificationAction所有的属性和方法的声明添加到XXXMutableUserNotificationAction的头文件。以后,使用UIMutableUserNotificationAction时,就如下方:

1 Class XXXMutableUserNotificationActionClass = NSClassFromString(@"UIMutableUserNotificationAction");
2 XXXUIMutableUserNotificationAction *action = [[XXXMutableUserNotificationActionClass alloc] init];

既可以使用Xcode的补全提示,又可以通过编译。(如果大家有更好的方法,欢迎探讨探讨)

Object-C运行时的方法固然强大,但是使用这些方法还是有一定的风险。例如静态分析对于一些运行时的问题是检查不出来的,这里我举一个内存泄漏的例子。我的项目中使用了一些运行时添加属性的方法,同时为一些View添加了block类型的属性。在使用的时候,不小心在block中直接使用了self,就会出现编译器无法检查的内存泄露。泄漏路径:VC->View->block->VC,形成了循环引用。这种泄漏相对隐蔽一些,但对于经常RAC的童鞋来说,可能已经练就到百毒不侵了(^_^)。因为@weakify和@strongify的频繁使用,我对这类型泄漏已经比较敏感。__weak typeof(self) weakSelf = self,算是一种虽然难看,但是行之有效的方法,如果有兴趣也可以参考RAC的解决方案,本质上也是一样的。
题外话,BlockKit包含了一个很好用的分类“NSObject+BKAssociatedObjects”,可以用更友好的方法实现运行时添加属性,顺带一提bk_weaklyAssociateValue的实现思路相当巧妙,值得借鉴。

最后一个技巧是关于RAC和系统API的一些关系,先来看看一下两段代码:

1 UIGestureRecognizer *dismissKeyboardGR = [[UIGestureRecognizer alloc] init];
2 [self.view addGestureRecognizer:dismissKeyboardGR];
3 [[[self rac_signalForSelector:@selector(gestureRecognizer:shouldReceiveTouch:)
4                  fromProtocol:@protocol(UIGestureRecognizerDelegate)]
5   takeUntil:dismissKeyboardGR.rac_willDeallocSignal]
6   subscribeNext:^(id x) {
7       [Utils hideKeyboardInAllView];
8   }];
9 dismissKeyboardGR.delegate = self;
1 UIGestureRecognizer *dismissKeyboardGR = [[UIGestureRecognizer alloc] init];
2 [self.view addGestureRecognizer:dismissKeyboardGR];
3 dismissKeyboardGR.delegate = self;
4 [[[self rac_signalForSelector:@selector(gestureRecognizer:shouldReceiveTouch:)
5                  fromProtocol:@protocol(UIGestureRecognizerDelegate)]
6   takeUntil:dismissKeyboardGR.rac_willDeallocSignal]
7   subscribeNext:^(id x) {
8       [Utils hideKeyboardInAllView];
9   }];

先来看看这两段代码的区别只在于delegate的设置先后不一样,但这就造成了后一段代码在iOS6上就无法触发RAC里方法,iOS7上正常。为什么呢?

这涉及到一个类似缓存的机制。平时,我们会习惯使用respondsToSelector:来检查一个对象或者类是否实现了对应的方法,但频繁调用respondsToSelector:会对性能有一定的影响。特别是UITableView的dataSource一些方法,调用频率很高的。因此,在设置delegate后,UIGestureRecognizer使用了respondsToSelector:检查了一次self是否gestureRecognizer:shouldReceiveTouch:的方法,然后把这个结果缓存起来。由于RAC也使用了类似Method Swizzling方法,因此在设置delegate以后再使用RAC的方法,UIGestureRecognizer也只读取了缓存,并不会再次检查,所以认为self并未实现gestureRecognizer:shouldReceiveTouch:的方法,于是不作调用。(具体缓存的实现方法,可以参照http://www.cnblogs.com/ipinka/p/3862786.html)

原文地址:https://www.cnblogs.com/ipinka/p/4039299.html