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

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

输出 “数组”。

本文到此结束。

时间: 2024-08-07 03:13:31

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

第10条:在既有类中使用关联对象存放自定义数据

在对象中存放相关信息的方式: 1.从对象所属的类中继承一个子类,然后改用这个子类对象. 然而有时类的实例可能是由某种机制所创建的,无法使用这种机制创建自己所写的子类实例.所以采用第2种方式-----“关联对象” 2.关联对象 可以给某对象关联多个对象,这个对象用“键”来区分,存储对象值的时,可以指明“存储策略”.存储策略由名为objc_AssociationPolicy的枚举所定义. 对象的关联类型: OBJC_ASSOCIATION_ASSIGN     等价于  assign OBJC_AS

OC学习篇之---Foundation框架中的NSString对象和NSMutableString对象

在之前的一篇文章中我们说到了Foundation框架中的NSObject对象: http://blog.csdn.net/jiangwei0910410003/article/details/41788121 那么今天在在来继续看一下Foundation框架中的常用对象:NSString和NSMutableString 在OC中NSString对象是不可变的,和Java中的String一样的,而NSMutableString是可变的,和Java中的StringBuilder一样. 一.NSStr

(转载)OC学习篇之---Foundation框架中的NSObject对象

前一篇文章讲到了OC中的代理模式,而且前几篇文章就介绍了OC中的类相关知识,从这篇文章开始我们开始介绍Foundation框架. OC中的Foundation框架是系统提供了,他就相当于是系统的一套api,和Java中的一些系统jar很相似,又早起的一批人开发的,内部有很多现有的类和功能提供给我们使用.那么今天首先来介绍一下Foundation框架中的第一个类NSObject. 在之前介绍了类的相关知识,我们看到我们自定义的类都必须实现NSObject类,这个类内部有很多现有的方法可以供我们使用

(转载)OC学习篇之---Foundation框架中的NSString对象和NSMutableString对象

在之前的一篇文章中我们说到了Foundation框架中的NSObject对象,那么今天在在来继续看一下Foundation框架中的常用对象:NSString和NSMutableString. 在OC中NSString对象是不可变的,和Java中的String一样的,而NSMutableString是可变的,和Java中的StringBuilder一样. 一.NSString对象 在OC中,NSString对象是非常重要的一个对象,也是最基础的一个对象,是用来处理字符串的常用类,和Java中的St

OC学习篇之---Foundation框架中的NSObject对象

前一篇文章讲到了OC中的代理模式:http://blog.csdn.net/jiangwei0910410003/article/details/41777627,而且前几篇文章就介绍了OC中的类相关知识,从这篇文章开始我们开始介绍Foundation框架. OC中的Foundation框架是系统提供了,他就相当于是系统的一套api,和Java中的一些系统jar很相似,又早起的一批人开发的,内部有很多现有的类和功能提供给我们使用.那么今天首先来介绍一下Foundation框架中的第一个类NSOb

多文档中建立一个对话框类,通过这个方法来在其他类中得到对话框对象指针,访问对话框成员

{ // 添加内容 m_pDrawTool = new CDrawToolDlg; m_pDrawTool->Create(IDD_DRAWTOOLS, this); m_pDrawTool->ShowWindow(SW_SHOW); // 让窗口出现在屏幕右下方 CRect dlgRect; CRect mainRect; m_pDrawTool->GetClientRect(&dlgRect); GetWindowRect(mainRect); // 计算显示的坐标 int

mybatis中的关联对象查询

方式1(嵌套查询): 在本类的mapper映射配置文件中的ResultMap标签中使用association子标签,对关联对象的属性进行关联 例如:User中关联Department(多对一) ----------User的mapper映射配置文件---------<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Map

Effective C++ -----条款14: 在资源管理类中小心copying行为

复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为. 普遍而常见的RAII class copying行为是:抑制copying(使用私有继承Uncopyable).施行引用计数法(reference counting)(即std::tr1::shared_ptr,可以自己指定删除器).不过其他行为也都可能被实现.

IOS中AppDelegate类中的方法触发时机-----自定义AppDelegate的写法

@implementation AppDelegate //当应用程序加载时触发,创建window窗口对象,让对象的window成为程序的主窗口,并且可视. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen ma