最近在恶补 Objective-C 的一些知识。这篇文章记录一下 Runtime 的两个常见的使用场合。
用 Associated Objects 为类动态添加属性
其实给类添加属性的说法是错误的,因为不能给类添加实例变量,确切的说法应该是添加键值数据。实现的思路是:
- 为你想动态添加属性的类创建一个
Category
,在该 Category 中声明想添加的 property。
- 在该 Category 的实现中声明 property 为
@dynamic
,即声明该 property 为运行时动态存取的。
- 实现 property 的
getter
和 setter
。
Runtime 中涉及的方法有:
1 2 3
| void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) id objc_getAssociatedObject(id object, const void *key) void objc_removeAssociatedObjects(id object)
|
其中的 policy 参数决定了对应的内存管理语义,大家可以看到熟悉的 RETAIN
、COPY
等:
1 2 3 4 5 6 7
| enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 };
|
eg1:给 UIViewController 类添加一个 dynamicProperty
属性
1 2 3 4
| @interface UIViewController (DynamicProperty) @property (nonatomic, strong) NSObject *dynamicProperty; @end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #import <objc/runtime.h> @implementation UIViewController(DynamicProperty) @dynamic dynamicProperty; static void *StaticDynamicPropertyKey = "StaticDynamicPropertyKey"; - (void)setDynamicProperty:(NSObject *)dynamicProperty { [self willChangeValueForKey:@"StaticDynamicPropertyKey"]; objc_setAssociatedObject(self, &StaticDynamicPropertyKey, dynamicProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self didChangeValueForKey:@"StaticDynamicPropertyKey"]; } - (NSObject *)dynamicProperty { return objc_getAssociatedObject(self, &StaticDynamicPropertyKey); } @end
|
eg2:给 UIAlertView 类添加一个处理操作结果的 Block
我们把自定义的键值数据声明为属性,可以方便使用。不过实际应用时,我们可以在适当的地方直接调用 runtime 的这些方法来满足我们的需求。此处引用《Effective Objective-C 2.0》书中第 10 条的一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| static void *EOCMyAlertViewKey = "EOCMyAlertViewKey"; - (void)askUserAQuestion { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What do you want to do?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil]; void (^block)(NSInteger) = ^(NSInteger buttonIndex) { if (buttonIndex == 0) { [self doCancel]; } else { [self doContinue]; } }; objc_setAssociatedObject(alert, EOCMyAlertViewKey, block, OBJC_ASSOCIATION_COPY); } // UIAlertViewDelegate protocol method - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { void (^block)(NSInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey); block(buttonIndex); }
|
如果一个类里有多个 UIAlertView,一般的做法可能是加 tag
然后在 delegate 方法中用 tag
判断。这样改写后,delegate 方法中不用判断做特殊处理,而且处理操作结果的代码和 UIAlertView 的代码在同一个地方更明了和方便阅读了。
不过正如原书作者的提醒:
因为产生 retain cycle
的原因很难查明,只有在其他做法行不通的时候,才考虑选用 Associated Objects
技术。
Method Swizzling
有时候我们 class-dump
一些私有库,会找到一些我们感兴趣的私有方法,甚至会想更改或者替换这些方法的实现,这时候用 Method Swizzling
就很合适了。一般会涉及到的方法有:
1 2 3 4
| Method class_getInstanceMethod(Class cls, SEL name) BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) void method_exchangeImplementations(Method m1, Method m2)
|
eg:看多了 iOS 的例子,我们来看个 OS X 的例子
之前做 Mac 项目的时候,有自定义窗口背景的需求,就用到了下面的代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| id class = [[[window contentView] superview] class]; Method m0 = class_getInstanceMethod([self class], @selector(drawRect:)); class_addMethod(class, @selector(drawRectOriginal:), method_getImplementation(m0), method_getTypeEncoding(m0)); Method m1 = class_getInstanceMethod(class, @selector(drawRect:)); Method m2 = class_getInstanceMethod(class, @selector(drawRectOriginal:)); method_exchangeImplementations(m1, m2);
|
1 2 3 4 5 6 7
| - (void)drawRect:(NSRect)rect { // 这里的 drawRectOriginal: 就是系统原来的 drawRect: [self drawRectOriginal:rect]; ...自定义的绘制 }
|
例子很简单,先用 class_addMethod
方法为 class 添加一个 drawRectOriginal:
方法,然后用 method_exchangeImplementations
方法交换 drawRectOriginal:
和 class 原生的 drawRect:
的实现。
第一次看 Method Swizzling 的例子可能会感觉有点绕,因为一般情况我们自定义的方法都会调用一下原生的方法来 “继承” 原生的实现。
The end:
这里粗浅地介绍了两个常见的 runtime 的使用场合,有时间还要好好深入学习一下 runtime 啊!!