Objective-C运行时的一些技巧

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

这时候,我仍是想起了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的补全提示,又能够经过编译。(若是你们有更好的方法,欢迎探讨探讨)ide

 

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的一些关系,先来看看一下两段代码:ui

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上正常。为何呢?spa

这涉及到一个相似缓存的机制。平时,咱们会习惯使用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)code

相关文章
相关标签/搜索