概述
有时候我们需要关联一些数据给已有的对象,通常你可能会使用子类化的方式.然而,有可能不会一直这么做,因为有些实例可能是你因为某些意义而创建,而你又不想把这个暴露给创建的子类,或者说你只想加到已有的类中去,那么这个时候你该尝试一下Objective - C的对象关联(Associated Objects
).
当对象被关联的时候,通常会使用一个key
来标识.并且对象会指定一种存储策略来管理被存储值.管理策略如下:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定关联对象为弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定关联对象为强引用,并且为非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定关联对象为copy,并且为非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定关联对象为强引用
OBJC_ASSOCIATION_COPY = 01403 // 指定关联对象为copy
};
关联管理使用以下方法来执行:
// 设置关联对象的key和策略
void objc_setAssociatedObject(id object, void *key,id value, objc_AssociationPolicy policy);
// 通过给定的key取出关联对象
id objc_getAssociatedObject(id object, void *key);
// 移除关联的对象
void objc_removeAssociatedObjects(id object);
访问关联对象看起来很像NSDictionary
的存取值方法
// 设置
[object setObject:value forKey:key];
// 取出
[object objectForKey:key];
但是他们有一个关键的不同点,就是如果是NSDictionary
在设置key
的时候可以随意使用一个key
,不用在意其作用域(scope
),而对于Associated Objects
来说这个key
必须是全局的.
示例
在iOS开发中,我们经常会使用到一个控件UIAlertView
,通常我们会先使用系统提供的初始化方法设置其title
,message
,delegate
和相应的button
.这样很是不方便,有时候我们会遇到这样的问题,我们要根据用户的点击来做一些处理,比如改变一个控件的颜色,那么这时候我们就要把该控变成全局的,然后在UIAlertView
的delegate
回调方法中去改变颜色.代码如下:
- (void)changeViewBackGroundColor
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"友情提示"
message:@"你确定要改变view的颜色?"
delegate:self
cancelButtonTitle:@"取消"
otherButtonTitle:@"确定",nil];
[alert show];
}
// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
// 改变颜色
} else {
// 不改变颜色
}
}
这个方法看起来很方便,但是如果你需要在同一个试图控制器里面弹出多个不同的alert
,这绝对会让你蛋疼不已,你需要在delegate
方法中判断是哪一个alerView
,然后根据不同的alerView
做不同的处理,哦对了,最蛋疼的是你需要把这些alerView
写成全局的... …
当然啦,如果你能灵活的使用Associated Objects
给系统的UIAlertView
关联一个block
回调,那么事情就简单多了
呃... …
是不是感觉要掉渣天了?要起飞了?Let’s Go!!!
#import <UIKit/UIKit.h>
// 声明block类型
typedef void(^AlertViewCallBack)(NSInteger buttonIndex);
@interface UIAlertView (Block)<UIAlertViewDelegate>
/** 关联值block,在Category里面写属性自带@dynamic,所以在.m里面已定要对应的实现其setter和getter */
@property (copy, nonatomic) AlertViewCallBack callBackBlock;
/** 去掉了delegate,加入了block*/
+ (void)alertWithCallBackBlock:(AlertViewCallBack)callBackBlock
title:(NSString *)title
message:(NSString *)message
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION;
@end
#import "UIAlertView+Block.h"
#import <objc/runtime.h>
// 声明全局的key
static NSString *UIAlertViewKey = @"AlertViewKey";
@implementation UIAlertView (Block)
+ (void)alertWithCallBackBlock:(AlertViewCallBack)callBackBlock title:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ...
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:nil
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles, nil];
// 使用系统的宏来实现多参数
NSString *other = nil;
va_list args;
if (otherButtonTitles) {
va_start(args, otherButtonTitles);
while ((other = va_arg(args, NSString*))) {
[alert addButtonWithTitle:other];
}
va_end(args);
}
alert.delegate = alert;
[alert show];
// 形参block赋值给属性block
alert.callBackBlock = callBackBlock;
}
// setter
- (void)setCallBackBlock:(AlertViewCallBack)callBackBlock
{
// 设置关联值
[self willChangeValueForKey:@"callBackBlock"];
objc_setAssociatedObject(self, &UIAlertViewKey, callBackBlock, OBJC_ASSOCIATION_COPY);
[self didChangeValueForKey:@"callBackBlock"];
}
// getter
- (AlertViewCallBack)callBackBlock
{
// 取出关联值
return objc_getAssociatedObject(self, &UIAlertViewKey);
}
// delegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (self.callBackBlock) {
self.callBackBlock(buttonIndex);
}
}
@end
使用方法如下:
// 使用方法,注意这里会造成循环引用,所以记住加__weak,其实更为优雅的方式是使用影子变量
// __weak typeof(self) weakSelf = self;
// @weakify(self);
[UIAlertView alertWithCallBackBlock:^(NSInteger buttonIndex) {
// @strongify(self);
// 这里就可以直接拿到点击button的index,爽歪歪有木有?
// 再也不用写又臭又长的delegate方法了,在不怕需要弹出多个alertView了
// 想怎么弹就怎么弹,想再哪里弹就在哪里弹
if (buttonIndex == 0) { // 确定
NSLog(@"我弹弹弹,弹走鱼尾纹...😂");
}
// 如果用到了本类的属性或者调用方法,一定要注意循环引用问题💣💣💣
// 非影子变量
// [weakSelf.view setBackgroundColor:[UIColor redColor]];
// 影子变量
// [self.view setBackgroundColor:[UIColor redColor]];
} title:@"友情提示" message:@"你确定要改变view的颜色?" cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
Conclusion(总结)
感受到了么?这是个多么强大的东西!
- Associated objects提供了一种关联两个对象的方法
- 注意使用内存管理的策略
- 不要滥用Associated objects,因为这玩意一旦出bug很难查找