关联变量相关总结。
一 :关联变量操作方法
(1)添加关联对象
void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy);
id object:被关联的对象
const void *key:关联的key,要求唯一
id value:关联的对象
objc_AssociationPolicy policy:内存管理的策略
(2)获得关联对象
id objc_getAssociatedObject(id object, const void * key)
(3)移除所有的关联对象
void objc_removeAssociatedObjects(id object)
(4)移除单个关联变量
移除一个关联变量时,可以直接将对应的成员变量赋值为nil
二:如何使用关联变量
设置和取值时的关联变量用法,分别在category的setter和getter 方法中实现。
使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))、
三:关联变量的 修饰符
OBJC_ASSOCIATION_ASSIGN(用于修饰非对象变量);
OBJC_ASSOCIATION_RETAIN_NONATOMIC(相当于,(strong,nonatiomic))用于修饰强引用对象;
OBJC_ASSOCIATION_COPY_NONATOMIC((copy,nonatomic))修饰copy属性的对象;
OBJC_ASSOCIATION_RETAIN((strong,atomic)),修饰原子性的强引用对象;
OBJC_ASSOCIATION_COPY((copy,atomic))修饰原子性的copy对象。
四:关联对象的原理
本质是通过类似字典的形式,保存对象与要关联对象的对照关系。
四种核心对象:
(1)AssociationsManager
是全局对象,含有一个AssociationsHashMap对象。
(2)AssociationsHashMap
保存着内存程序中所有的关联对象信息。 是一个字典数组,其中的每个元素的key为有关联对象的一个对象,value值为ObjectAssociationMap对象。
(3)ObjectAssociationMap
这个对象保存了一个拥有关联变得的对象的所有的关联变量的 key 与关联变量的关系的一个字典。
(4)ObjcAssociation
保存了该关联的关联策略,以及关联变量实际存储的值。
五:关联变量的使用
(1)需求:比如你为UIView添加事件,可以在上面添加一个UITapGestureRecognizer,但是这个点击事件无法携带NSString信息(虽然可以携带int类型的tag),这就无法让后续响应该事件的方法区分到底是哪里激活的事件。那么,你是否能为这种添加事件的方式携带另外的信息呢?
方案就是为UITapGestureRecognizer追加一个“属性”,利用runtime新建一个UITapGestureRecognizer的分类即可。
分类:
UITapGestureRecognizer+NSString.h
@interface UITapGestureRecognizer (NSString)
//类拓展添加属性
@property (nonatomic, strong) NSString *dataStr;
@end
UITapGestureRecognizer+NSString.m
#import "UITapGestureRecognizer+NSString.h"
#import //定义常量 必须是C语言字符串
static char *PersonNameKey = "PersonNameKey";
@implementation UITapGestureRecognizer (NSString)
- (void)setDataStr:(NSString *)dataStr{
objc_setAssociatedObject(self, PersonNameKey, dataStr, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)dataStr{
return objc_getAssociatedObject(self, PersonNameKey);
}
@end
调用处:
VC的tableView:cellForRowAtIndexPath:代理方法中由cell激发事件 UITapGestureRecognizer *signViewSingle0 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
signViewSingle0.dataStr = [cell.cellMdl.partnercode copy];
[cell.contractView addGestureRecognizer:signViewSingle0];复制代码
VC单独写一个响应方法
- (void)tapAction:(UITapGestureRecognizer *)sender
{
UITapGestureRecognizer *tap = (UITapGestureRecognizer *)sender;
//partnercode
[self requestCallConSetWithPartnerCode:tap.dataStr];
}
如此一来,响应事件的方法就可以根据事件激活方携带过来的信息进行下一步操作了,比如根据它携带过来的某个参数进行网络请求等等。
2.5 这样就能生成_变量?
尽管可以模拟地为分类添加“属性”,但毕竟只是模拟。在分类中@property不会生成_变量,也不会实现getter和setter方法。我们实现的只是getter和setter方法,并没有自动生成下划线开头的变量!
3. 关联对象:为UI控件关联事件Block体
3.1 UIAlertView
开发iOS时经常用到UIAlertView类,该类提供了一种标准视图,可向用户展示警告信息。当用户按下按钮关闭该视图时,需要用委托协议(delegate protocol)来处理此动作,但是,要想设置好这个委托机制,就得把创建警告视图和处理按钮动作的代码分开。由于代码分作两块,所以读起来有点乱。
方案1 :传统方案
比方说,我们在使用UIAlertView时,一般都会这么写:
Test2ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor whiteColor]];
self.title = @"Test2ViewController";
[self popAlertViews1];
}
#pragma mark - way1
- (void)popAlertViews1{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What do you want to do?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil];
[alert show];
}
// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
[self doCancel];
} else {
[self doContinue];
}
}
如果想在同一个类里处理多个警告信息视图,那么代码就会变得更为复杂,我们必须在delegate方法中检查传入的alertView参数,并据此选用相应的逻辑。
要是能在创建UIAlertView的时候直接把处理每个按钮的逻辑都写好,那就简单多了。这可以通过关联对象来做。创建完警告视图之后,设定一个与之关联的“块”(block),等到执行delegate方法时再将其读出来。下面对此方案进行改进。
方案2:关联Block体
除了上一个方案中的传统方法,我们可以利用关联对象为UIAlertView关联一个Block:首先在创建UIAlertView的时候设置关联一个回调(objc_setAssociatedObject),然后在UIAlertView的代理方法中取出关联相应回调(objc_getAssociatedObject)。
Test2ViewController.m
#pragma mark - way2
- (void)popAlertViews2 {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What do you want to do?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil];
void (^clickBlock)(NSInteger) = ^(NSInteger buttonIndex){
if (buttonIndex == 0) {
[self doCancel];
} else {
[self doContinue];
}
};
objc_setAssociatedObject(alert,CMAlertViewKey,clickBlock,OBJC_ASSOCIATION_COPY);
[alert show];
}
// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
void (^clickBlock)(NSInteger) = objc_getAssociatedObject(alertView, CMAlertViewKey);
clickBlock(buttonIndex);
}复制代码
方案3:继续改进:封装关联的Block体,作为属性
上面方案,如果需要的位置比较多,相同的代码会比较冗余地出现,所以我们可以将设置Block的代码封装到一个UIAlertView的分类中去。
UIAlertView+Handle.h
#import // 声明一个button点击事件的回调block
typedef void (^ClickBlock)(NSInteger buttonIndex) ;
@interface UIAlertView (Handle)
@property (copy, nonatomic) ClickBlock callBlock;
@end复制代码
UIAlertView+Handle.m
#import "UIAlertView+Handle.h"
#import @implementation UIAlertView (Handle)
- (void)setCallBlock:(ClickBlock)callBlock
{
objc_setAssociatedObject(self, @selector(callBlock), callBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (ClickBlock )callBlock
{
return objc_getAssociatedObject(self, _cmd);
// return objc_getAssociatedObject(self, @selector(callBlock));
}
@end复制代码
Test2ViewController.m
#pragma mark - way3
- (void)popAlertViews3 {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What do you want to do?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil];
[alert setCallBlock:^(NSInteger buttonIndex) {
if (buttonIndex == 0) {
[self doCancel];
} else {
[self doContinue];
}
}];
[alert show];
}
// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
void (^block)(NSInteger) = alertView.callBlock;
block(buttonIndex);
}复制代码
方案4:继续改进:封装关联的Block体,跟初始化方法绑在一起
练习:可以对这个分类进一步改进,将设置Block属性的方法与初始化方法写在一起。
3.2 UIButton
除了上述的UIAlertView,这节以UIButton为例,使用关联对象完成一个功能函数:为UIButton增加一个分类,定义一个方法,使用block去实现button的点击回调。
UIButton+Handle.h
#import #import // 导入头文件
// 声明一个button点击事件的回调block
typedef void(^ButtonClickCallBack)(UIButton *button);
@interface UIButton (Handle)
// 为UIButton增加的回调方法
- (void)handleClickCallBack:(ButtonClickCallBack)callBack;
@end复制代码
UIButton+Handle.m
#import "UIButton+Handle.h"
// 声明一个静态的索引key,用于获取被关联对象的值
static char *buttonClickKey;
@implementation UIButton (Handle)
- (void)handleClickCallBack:(ButtonClickCallBack)callBack {
// 将button的实例与回调的block通过索引key进行关联:
objc_setAssociatedObject(self, &buttonClickKey, callBack, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 设置button执行的方法
[self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonClicked {
// 通过静态的索引key,获取被关联对象(这里就是回调的block)
ButtonClickCallBack callBack = objc_getAssociatedObject(self, &buttonClickKey);
if (callBack) {
callBack(self);
}
}
@end复制代码
在Test3ViewController中,导入我们写好的UIButton分类头文件,定义一个button对象,调用分类中的这个方法:
Test3ViewController.m
[self.testButton handleClickCallBack:^(UIButton *button) {
NSLog(@"block --- click UIButton+Handle");
}];复制代码
4. 关联对象:关联观察者对象
有时候我们在分类中使用NSNotificationCenter或者KVO,推荐使用关联的对象作为观察者,尽量避免对象观察自身。
例如大名鼎鼎的AFNetworking为菊花控件监听NSURLSessionTask以获取网络进度的分类:
UIActivityIndicatorView+AFNetworking.m
@implementation UIActivityIndicatorView (AFNetworking)
- (AFActivityIndicatorViewNotificationObserver *)af_notificationObserver {
AFActivityIndicatorViewNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));
if (notificationObserver == nil) {
notificationObserver = [[AFActivityIndicatorViewNotificationObserver alloc] initWithActivityIndicatorView:self];
objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return notificationObserver;
}
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
[[self af_notificationObserver] setAnimatingWithStateOfTask:task];
}
@end复制代码
@implementation AFActivityIndicatorViewNotificationObserver
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
if (task) {
if (task.state != NSURLSessionTaskStateCompleted) {
UIActivityIndicatorView *activityIndicatorView = self.activityIndicatorView;
if (task.state == NSURLSessionTaskStateRunning) {
[activityIndicatorView startAnimating];
} else {
[activityIndicatorView stopAnimating];
}
[notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingTaskDidResumeNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidCompleteNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidSuspendNotification object:task];
}
}
}复制代码
5. 关联对象:为了不重复执行
有时候OC中会有些方法是为了获取某个数据,但这个获取的过程只需要执行一次即可,这个获取的算法可能有一定的时间复杂度和空间复杂度。那么每次调用的时候就必须得执行一次吗?有没有办法让方法只执行一次,每次调用方法的时候直接获得那一次的执行结果?有的,方案就是让某个对象的方法获得的数据结果作为“属性”与这个对象进行关联。
有这么一个需求:需要将字典转成模型对象
方案:我们先获取到对象所有的属性名(只执行一次),然后加入到一个数组里面,然后再遍历,利用KVC进行键值赋值。在程序运行的时候,抓取对象的属性,这时候,要利用到运行时的关联对象了,详情见下面的代码。
获取对象所有的属性名
+ (NSArray *)propertyList {
// 0. 判断是否存在关联对象,如果存在,直接返回
/**
1> 关联到的对象
2> 关联的属性 key
提示:在 OC 中,类本质上也是一个对象
*/
NSArray *pList = objc_getAssociatedObject(self, propertiesKey);
if (pList != nil) {
return pList;
}
// 1. 获取`类`的属性
/**
参数
1> 类
2> 属性的计数指针
*/
unsigned int count = 0;
// 返回值是所有属性的数组 objc_property_t
objc_property_t *list = class_copyPropertyList([self class], &count);
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
// 遍历数组
for (unsigned int i = 0; i < count; ++i) {
// 获取到属性
objc_property_t pty = list[i];
// 获取属性的名称
const char *cname = property_getName(pty);
[arrayM addObject:[NSString stringWithUTF8String:cname]];
}
NSLog(@"%@", arrayM);
// 释放属性数组
free(list);
// 设置关联对象
/**
1> 关联的对象
2> 关联对象的 key
3> 属性数值
4> 属性的持有方式 reatin, copy, assign
*/
objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
return arrayM.copy;
}复制代码
KVC进行键值赋值
+ (instancetype)objectWithDict:(NSDictionary *)dict {
id obj = [[self alloc] init];
// [obj setValuesForKeysWithDictionary:dict];
NSArray *properties = [self propertyList];
// 遍历属性数组
for (NSString *key in properties) {
// 判断字典中是否包含这个key
if (dict[key] != nil) {
// 使用 KVC 设置数值
[obj setValue:dict[key] forKeyPath:key];
}
}
return obj;
}