动态添加属性-Associated Object
前言
从今天开始,我们将要开始逐步接近OC的动态特性,慢慢揭开OC底层runtime系统的神秘面纱,超强的动态特性,是OC和一般面向对象的语言最明显的区别,也是这门古老的语言最具魅力的地方,如果可以正确的利用动态特性,那么可以帮助我们方便的解决很多棘手的问题.
我们今天来看的呢,是runtime库里非常有用的一组函数,Associated Object动态关联对象.这个功能,能够让我们动态的对一个对象绑定相关联的数据.
有了这个功能,我们不仅能够为自己编写的类添加属性,还能够为系统框架里的类动态添加我们需要的属性,甚至能够在分类里动态添加属性.我们这就来看看这个方便又强大的功能吧.
Associated Object使用场景
我们都有这样的经验,我们希望在既有的类上添加新的属性,通常情况下:
如果这个类是我们自己编写的,那么我们就可以方便的在文件中添加对应的属性就可以
如果这个类是系统框架的,而并非我们自己编写的,那么,我们通常会采取集成该系统类,产生新的类,添加我们需要的方法和属性
但是,很多时候,某个对象并不是我们产生,而是通过其它机制产生的,比如,系统为我们返回了一个系统类的实例,但是,我们却希望在这个实例加上这个类原本并没有的属性,来记录或存储一些数据,在这个时候我们按照常规的方法就不好解决了.
这时,我们的Associated Object就要发挥它的作用了.
相关函数
Associated Object的相关函数在runtime.h 文件中,使用前,我们需要先引入该库.而该库中和Associated Object相关的函数有三个:
- objc_setAssociatedObject:用来给对象动态绑定关联对象(也就是添加相应属性)
- objc_getAssociatedObject:用来读取对某对象动态绑定的关联对象(读取相应属性)
- objc_removeAssociatedObjects:这个函数是用来解除某对象的所有关联对象这项操作会将该对象所有的关联对象都全部删除.使用时,我们要格外注意.
Associated Object存储策略:
在调用objc_setAssociatedObject:函数时,我们需要设置关联对象的存储策略,这类似于property属性的存储策略关键字,这些存储策略名称从字面上就可以轻易的与property属性的存储策略关键字轻松匹配,相信大家一定可以选对的.我就不再一一举例了.
Associated Object存储策略 | property属性存储策略关键字 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, strong |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong |
OBJC_ASSOCIATION_COPY | copy |
示例
说了这么多,我们通过一个小小的例子,来感受下Associated Object的妙用吧
我们都有过这样的经历,我们需要在用户进行过某些操作的时候触发一个提醒框,即AlertView.
通常代码会像这个样子:
-(void)click
{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"alert" message:@"msg" delegate:self cancelButtonTitle:@"cancle" otherButtonTitles:@"confirm", nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}
但是经常我们会在一个控制器中有多个潜在将会触发的提醒框,如果是这样,那么我们通常会为alertView指定tag,在代理方法里区分不同的提醒框,做不同处理,那么代码就会变成这个样子.
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (alertView.tag==0) {
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}else if (alertView.tag==1)
{
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}else if (alertView.tag==2)
{
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}else if (alertView.tag==3)
{
if (buttonIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
}
}
这样的代码,不仅繁琐冗长,并不利于我们的代码可读性和高内聚低耦合的设计原则
我们将要通过Associated Object方式,对该情况进行处理
大概思路为,在编写alertView时为每一个alertView关联一个处理Block,在代理方法中我们只要调用alertView关联的处理block就可以了,这样的代码将会变得异常清晰,而且具有极高的可读性我们一起来看一下吧:
我们需要先导入需要的文件,并声明一个用来充当关联Key的字符常量:
#import <objc/runtime.h>
static char const alertDealBlockKey;
然后在编写提示框的时候,我们需要编写一个处理alertView的block,并和alertView进行关联:
-(void)click
{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"alert" message:@"msg" delegate:self cancelButtonTitle:@"cancle" otherButtonTitles:@"confirm", nil];
void (^alertDealBlock)(NSInteger btnIndex)=^(NSInteger btnIndex){
if (btnIndex==0) {
NSLog(@"do someThing");
}else
{
NSLog(@"do otherThing");
}
};
objc_setAssociatedObject(alert, &alertDealBlockKey, alertDealBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
[alert show];
}
这样处理过得alertView中就会绑定一个处理的block了,接下来我们在alertview的代理方法中,仅仅需要将这个block取出并调用,就可以了:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
void (^alertDealBlock)(NSInteger btnIndex)=objc_getAssociatedObject(alertView, &alertDealBlockKey);
alertDealBlock(buttonIndex);
}
这样无论该文件中将有多少个alertView,我们的代理方法中也只需要这样短短两行的代码就可以处理,是不是很简单呢,而且,在编写alertView的时候我们就可以顺便把处理方式编写好,可读性也大大提高,是不是还是很优雅的呢?
总结
上面的这个例子只是一个Associated Object一个简单使用,还有很多妙用等待大家发现,Associated Object之所以能够动态的为实例添加关联对象,这要依附于我们强大的运行时系统,这点大家要好好理解,最后虽然Associated Object非常好用,但是也不建议大家滥用,只有在别的方式都不可行的情况下才建议大家使用关联对象处理,因为如果你的编码有误,产生问题将非常难于查找,因为编译器并不能够检测出运行时才会关联的对象的相关问题,对于关联对象,编译器是有心无力的,保证它的正确性只能我们人为去检测验证