Objective-C开发者应该小心谨慎地遵循这个危险咒语的各种准则。一个很好的原因的就是:混乱的运行时代码会改变运行在其架构之上的所有代码。
当然,<objc/runtime.h>中的函数能实现许多强大的功能,而且又是其他很难做到的功能。以我自己遇到的举例:曾接手一个项目,继承混乱,要加新功能,对每个页面进行统计。能做到对现有代码没有侵入性的方法就是runtime,通过关联系统方法,自定义方法去实现真正的功能,仅仅需要一个UIViewController的category就可以做到。
Associated Objects(关联对象)
主要函数
在系统文件<objc/runtime.h>中很容易就找到主要相关的三个函数:
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
/**
* Removes all associations for a given object.
*
* @param object An object that maintains associated objects.
*
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use \c objc_setAssociatedObject
* with a nil value to clear an association.
*
* @see objc_setAssociatedObject
* @see objc_getAssociatedObject
*/
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
函数的命名意思十分明显
- objc_setAssociatedObject 给对象添加关联对象
- objc_getAssociatedObject 获取关联对象
- objc_removeAssociatedObjects 移除所有关联对象(返回一个对象为"原始状态")
这里第三个函数注意下上面官方的注释
The main purpose of this function is to make it easy to return an object to a "pristine state”. You should not use this function for general removal of associations from objects, since it also removes associations that other clients may have added to the object. Typically you should use \c objc_setAssociatedObject with a nil value to clear an association.
因为这个函数会移除所有的关联对象,很可能把别人添加的关联对象也删除。所以通常使用objc_setAssociatedObject函数传入nil值根据key移除存在的某关联对象。
函数参数
id object | 被关联对象 |
---|---|
const void *key | 唯一的key值 |
id value | 关联对象 |
objc_AssociationPolicy policy | 对关联对象的持有策略 |
其中key的值是void指针,可以用三种方式做唯一值。
- 声明static char SomeAssociatedKey; 使用&SomeAssociatedKey 作为唯一值。
- 声明static void *SomeAssociatedKey; 使用SomeAssociatedKey 作为唯一值。
- 利用selector函数地址作为唯一值。
属性可以根据定义在枚举类型objc_AssociationPolicy policy上的行为被关联在对象上:
OBJC_ASSOCIATION_ASSIGN | @property (assign) 或 @property (unsafe_unretained) | 指定一个关联对象的弱引用。 |
---|---|---|
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | 指定一个关联对象的强引用,不能被原子化使用。 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 指定一个关联对象的copy引用,不能被原子化使用。 |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 指定一个关联对象的强引用,能被原子化使用。 |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 指定一个关联对象的copy引用,能被原子化使用。 |
简单例子
以UIButton为例为UIButton的category添加block属性
#import <UIKit/UIKit.h>
typedef void(^touchBlock)(void);
@interface UIButton (Block)
- (void)handleTouchUpInside:(touchBlock)block;
@end
#import "UIButton+Block.h"
#import <objc/runtime.h>
@implementation UIButton (Block)
- (void)handleTouchUpInside:(touchBlock)block {
objc_setAssociatedObject(self, @selector(handleTouchUpInside:), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(touchUpInsideBlock) forControlEvents:UIControlEventTouchUpInside];
}
- (void)touchUpInsideBlock {
touchBlock block = objc_getAssociatedObject(self, @selector(handleTouchUpInside:));
if (block) {
block();
}
}
@end
这样为UIButton添加block属性,在使用的时候代码会比较集中:
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
button.backgroundColor = [UIColor redColor];
[button handleTouchUpInside:^{
NSLog(@"触发touchUpInside事件");
}];
[self.view addSubview:button];
比起其他解决问题的方法,关联对象应该被视为最后的选择(事实上category也不应该作为首选方法)。
和其他精巧的trick、hack、workaround一样,一般人都会在刚学习完之后乐于寻找场景去使用一下。尽你所能去理解和欣赏它在正确使用时它所发挥的作用,同时当你选择这个解决办法时,也要避免当被轻蔑地问起“这是个什么玩意?”时的尴尬。
相关参考: Associated Objects