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

输出 “数组”。


本文到此结束。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值