Effective-OC 10.在既有类中使用关联对象存储自定义数据

来源:互联网 发布:隐语义模型推荐算法 编辑:程序博客网 时间:2024/06/09 23:48

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 HomeViewControllerstatic 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];

输出 “数组”。


本文到此结束。

0 0
原创粉丝点击