前言
- 在开发过程中,避免不了会使用公共变量,记录共享对象状态、数据最简单的方式就是创建创建公共变量;
- 当业务逻辑变多,还采用这种思想就会变得危险,代码逻辑变得不清晰,慢慢就有一种代码坏味道。
- 具体总结如下:1231、过多逻辑分支,不够清晰,公共变量不利于系统维护和项目拓展;2、安全性收到威胁,过多地方共享变量,变量的写入和读取在多线程下是危险的;3、业务逻辑交叉过多时,很难保证数据-逻辑的一致性;
如何解决呢?
- 出现问题,解决问题,Objective-C针对上述问题,提供了一个解决方案:即使用关联对象(Associated Object);
- 我们可以把关联对象想象成一个Objective-C对象(如字典),这个对象通过给定的key连接到类的一个实例上;
- 不过由于使用的是C接口,所以key是一个
void指针(const void *)
。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。
这个内存管理的策略可以由以下值指定:
|
|
- 当宿主对象被释放时,会根据指定的内存管理策略来处理关联对象;
- 如果指定的策略是OBJC_ASSOCIATION_ASSIGN,则宿主释放时,关联对象不会被释放;
- 而如果指定的是Retain或者是Copy,则宿主释放时,关联对象会被释放。
- 我们甚至可以选择是否是自动Retain/Copy。当我们需要在多个线程中处理访问关联对象的多线程代码时,这就非常有用了,实现线程和逻辑绑定。
具体解决:
- 1、我们将一个对象连接到其它对象所需要做的就是下面两行代码:
|
|
2、使用下面一行代码获取绑定的对象:
1id anObject = objc_getAssociatedObject(self, &anObjectKey);在这种情况下,Self对象将获取一个新的关联的对象anObject,且内存管理策略是自动Retain关联对象,当Self对象释放时,会自动Release关联对象;
另外,如果我们使用同一个key来关联另外一个对象时,也会自动释放之前关联的对象。这种情况下,先前的关联对象会被妥善地处理掉,并且新的对象会使用它的内存;
3、移除关联对象:
1objc_removeAssociatedObjects(anObject);
或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil;
举个栗子
- 在开发工程中,给UIView添加单击手势是非常常见的需求。假定,现在我们就要动态地将一个Tap手势操作连接到任何UIView中,并且根据需要指定点击后的实际操作;
- 这时候我们就可以将一个手势对象及操作的Block对象关联到我们的UIView对象中。这项任务分为一下两部分。
- 首先,如果需要,我们要创建一个手势识别对象并将它及Block作为关联对象。具体实现如下:
|
|
- 这段代码检测了手势识别的关联对象。如果没有,则创建并建立关联关系。同时,将传入的块对象连接到指定的key上。注意Block对象的关联内存管理策略-Copy;
- 然后,处理单击事件,具体实现如下:
123456789101112 - (void) JL_handleActionForTapGesture:(UITapGestureRecognizer *)sender{if (sender.state == UIGestureRecognizerStateRecognized){void(^action)(void) = objc_getAssociatedObject(self, kJLActionHandlerTapBlockKey);if (action){action();}}}
- 我们需要检测手势识别对象的状态,因为我们只需要在点击手势被识别出来时才执行操作。
- 通过上面可以看到,关联对象实现起来也不是很复杂,而且还可以动态的增强类现有的功能。
优化完善
- 但是,还是有一点不太完美,代码过于松散,按照上述的方式去应用到项目中,会写不少重复代码,我们需要封装一下,并不暴露#import
引用,具体实现如下: 重新定义一套表征内存策略的枚举:
12345678910111213141516171819202122232425262728293031typedef NS_ENUM(NSInteger, JLAssociationPolicy) {/**OBJC_ASSOCIATION_ASSIGN < Specifies a weak reference to the associated object>*/JLAssociationPolicyAssign = 1,/**OBJC_ASSOCIATION_RETAIN_NONATOMIC <Specifies a strong reference to the associated object.* The association is not made atomically>*/JLAssociationPolicyRetainNonatomic = 2,/**OBJC_ASSOCIATION_COPY_NONATOMIC < Specifies that the associated object is copied.* The association is not made atomically.>*/JLAssociationPolicyCopyNonatomic = 3,/**OBJC_ASSOCIATION_RETAIN < Specifies a strong reference to the associated object.* The association is made atomically.>*/JLAssociationPolicyRetain = 4,/**OBJC_ASSOCIATION_COPY < Specifies that the associated object is copied.* The association is made atomically.>*/JLAssociationPolicyCopy = 5};声明方法:
123456789101112131415161718/**Set AssociatedObject@param object Be Associated Object@param key associted Key@param value associated value or object@param policy policy*/+ (void)JL_setAssociatedObject:(id _Nonnull)object key:(NSString *_Nullable)key value:(id _Nullable)value policy:(JLAssociationPolicy)policy;/**Get AssociatedObject@param object Be Associated Object@param key associted Key@return associated value or object*/+ (id _Nullable)JL_getAssociatedObject:(id _Nonnull)object key:(NSString *_Nullable)key;/**Remove AssociatedObject@param object associated value or object*/+ (void)JL_removeAsociatedObject:(id _Nonnull)object;
Key,在使用的时候只需要传入NSString类的参数就可以了,不需要const void * _Nonnull key
,接口方法变得更优雅简洁一些。
- 用封装的方法重写上述方法:12345678910111213141516171819202122232425262728//定义绑定对象的Keystatic NSString *const kJLActionHandlerTapGestureKey = @"JLActionHandlerTapGestureKey";static NSString *const kJLActionHandlerTapBlockKey = @"JLActionHandlerTapBlocKey";- (void)setTapActionWithBlock:(void (^)(void))block{UITapGestureRecognizer *tapGR = [JLAssociatedObjectUtils JL_getAssociatedObject:self key:kJLActionHandlerTapGestureKey];if (!tapGR){tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(JL_handleActionForTapGesture:)];[self addGestureRecognizer: tapGR];[JLAssociatedObjectUtils JL_setAssociatedObject:self key:kJLActionHandlerTapGestureKey value:tapGR policy:JLAssociationPolicyRetain];}[JLAssociatedObjectUtils JL_setAssociatedObject:self key:kJLActionHandlerTapBlockKey value:tapGR policy:JLAssociationPolicyCopy];}- (void) JL_handleActionForTapGesture:(UITapGestureRecognizer *)sender{if (sender.state == UIGestureRecognizerStateRecognized){void(^action)(void) = [JLAssociatedObjectUtils JL_getAssociatedObject:self key:kJLActionHandlerTapBlockKey];if (action){action();}}}
小结
今天,讲到这,要分享的内容就结束了。我将上述封装方法整理成了工具类,已经上传至Github.大家有需要的可以自行下载,感觉好的话,欢迎赏赐一个Star~ 谢谢
扫一扫下面的二维码,欢迎关注我的个人微信公众号攻城狮的动态(ID:iOSDevSkills),可在微信公众号进行留言,更多精彩技术文章,期待您的加入!一起讨论,一起成长!一起攻城狮!