EOC中介绍与案例
有时候需要在对象中存放相关的信息 这时候我们通常会从对象所属的类中继承一个子类,然后改用这个子类对象。然而并非所有的情况都能这么做。有的时候 类的实例可能是由某种机制创建的,而开发者无法令这种机制创建出自己写的子类的实例,OC中有一强大的特性可以解决这个问题 就是“关联对象”
可以给某对象关联许多其他的对象 这些对象通过“键”来区分。存储对象值得实惠 可以指明“存储策略”,用以维护相对应的“内存管理语义”。存储策略由名为objc_AssociationPolicy的枚举所定义。以下是该枚举的取值 同时列出了与之等效的@property属性:假如关联对象成为了属性 那么它就会具备对应语义。
关联类型 等效的@property属性
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic,retain
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic,copy
OBJC_ASSOCIATION_RETAIN retain
OBJC_ASSOCIATION_COPY copy
下列方法可以管理关联对象。
void objc_setAssociatedObject(id object, void* key, id value, objc_AssociationPolicy policy)
此方法以给定的键和策略为某对象设置关联对象值。
id objc_getAssociatedObject(id object, void* key)
此方法根据给定的键从某对象中获取相应的关联对象值。
void objc_removeAssociatedObjects(id object)
此方法移除指定对象的全部关联对象。
我们可以把某对象想想成NSDictionary 把关联到该对象的值 理解为字典中的条目,于是存取关联对象的值就相当于在NSDictionary对象上调用[object setObject:object valueforkey:key]和[object objectForKey:key]方法。然而两者之间有个重要的区别:设置关联对象的时候使用的键(key)是不透明的指针,如果在两个键上调用isEqual:方法的返回值YES 那么NSDictonary认为二者相等
然而对于关联对象 想让两个键匹配到同一个值 则二者必须是完全相同的指针才可以。因此 在设置关联对象值时,通常使用静态全局变量做键。
关联对象的用法举例:
开发时用到UIAlertView类,该类提供了一种标准视图。可以向用户展示警告信息。当用户按下按钮关闭这个视图时,需用代理方法来处理这个动作 但是要想设置好这个代理机制 需要把创建警告视图和处理按钮动作的代码分开。
如:
-(void)showAlertView { UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"问题" message:@"你要做什么" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"继续", nil]; [alert show]; } -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { NSLog(@"%@",@"取消"); } else { NSLog(@"%@",@"继续"); } }
如果想在同一个类中处理多个警告视图 那么代码就会变得更加复杂,我们必须在delegate方法中检查传入的alertview的参数,并且据此选用相应的逻辑,要是能在创建警告视图的时候直接把处理每个按钮的逻辑都写好 那就简单多了。这可以通过关联对象来做。创建完警告视图之后 设定一个与之关联的“块”,等到执行delegate方法时再将其读出来。此方案的实现代码如下。
#import <objc/runtime.h> @interface HomeViewController ()<UIAlertViewDelegate> @end @implementation HomeViewController static void * MyAlertViewKey = "MyAlertViewKey"; - (void)viewDidLoad { [super viewDidLoad]; } -(void)createAlertView { UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"你要作甚" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil]; void (^block)(NSInteger) = ^ (NSInteger buttonIndex) { if(buttonIndex == 0) { [self doCancel]; } else if(buttonIndex == 1) { [self doContinue]; } }; objc_setAssociatedObject(alert, MyAlertViewKey, block, OBJC_ASSOCIATION_COPY); [alert show]; } -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { void (^block)(NSInteger) = objc_getAssociatedObject(alertView, MyAlertViewKey); block(buttonIndex); } -(void)doCancel { NSLog(@"%@",@"cancel"); } -(void)doContinue { NSLog(@"%@",@"继续"); }
amazing!!!太棒了!
以这种方式改写之后 创建警告视图与处理操作结果的代码都放在一起了 这样比原来更容易读懂,因为我们无需在两部分代码之间来回游走,就可以明白警告视图的用处。但是 采用这个方案的时候需要注意 块可能需要捕获某些变量 这也许会造成 “循环引用”。(解除循环引用的方法:弱引用)。第40条详细描述了该问题。
这种做法很有用 但是只应该在其他办法行不通的时候才去考虑用,如果滥用 则代码很容易失控,使其难以调试。“循环引用”的原因很难查明,因为关联对象之间的关系并没有正式定义 其内存管理语义是在关联的时候才定义的 而不是在接口中预先定好的。使用这种写法要很小心 不能仅仅因为某处可以用该写法就一定要用它想创建这种UIAlertView还有一个方法,那就是从中继承子类,把块保存为子类中的属性。如果需要多次用到alertview 那么这种做法比使用关联对象要好。
要点:
(1)可以通过“关联对象”机制把两个对象连起来
(2)定义关联对象时可指定内存管理的语义 用以模仿定义属性时所采用的“拥有关系” 与“非拥有关系”。
(3)只有在其他做法不可行时才应该选用关联对象 因为这种做法通常会引入难以查找的bug。
查阅资料
在创建应用后,你可能创建类别来扩展内核类 如NSString、NSMutableString等。但是类别无法添加属性和私有变量。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
/**
* @description 用给定key值和策略 为给定对象设置一个关联对象。
* @param object 为谁设置关联对象
* @param key 关联对象的key
* @param value key关联的值 传递nil后清除存在的关联
* @param policy 关联策略 可能的值见上述枚举。
*/
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
/**
* 根据key返回关联对象
*
* @param object 设置关联对象的对象。
* @param key 关联对象的key
*/
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
/**
* 移除该对象所有的关联对象。
*
* @param object 管理关联对象的对象
*
* @note 主要目的是容易获取一个对象的原始状态,在实际情况不应该用这个方法,因为会移除所有的关联,而关联可能包括其他的地方设置的关联。
应该使用objc_setAssociatedObject 传递nil来清除关联。
*/
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
通过运行时来给类别建立关联引用。接下来以添加一个这样的属性为例子
@property (nonatomic, copy) NSString * str;
1.引入头文件
2.在匿名分类或者头文件中添加属性,区别是,匿名分类中添加的是私有属性 只在本类中可以使用,类的实例中不可以使用。头文件中添加的在类的实例中也可以使用。
3.在实现中添加属性的getter setter方法。
.h文件
@interface NSArray (extension)
@property (nonatomic, copy) NSString * name;
-(void)print;
@end
.m文件
#import "NSArray+extension.h"
#import <objc/runtime.h>
static void * NameKey = & NameKey;
@implementation NSArray (extension)
- (void)print {
NSLog(@"%@",self.name);
}
//getter
- (NSString *)name {
return objc_getAssociatedObject(self, NameKey);
}
//setter
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY);
}
@end
main文件
NSArray * arr = @[@"1", @"2", @"3", @"4"];
arr.name = @"数组";
[arr print];
输出 “数组”。
本文到此结束。