前言
- 在开发过程中,避免不了会使用公共变量,记录共享对象状态、数据最简单的方式就是创建创建公共变量;
- 当业务逻辑变多,还采用这种思想就会变得危险,代码逻辑变得不清晰,慢慢就有一种代码坏味道。
- 具体总结如下:
1、过多逻辑分支,不够清晰,公共变量不利于系统维护和项目拓展;
2、安全性收到威胁,过多地方共享变量,变量的写入和读取在多线程下是危险的;
3、业务逻辑交叉过多时,很难保证数据-逻辑的一致性;
如何解决呢?
- 出现问题,解决问题,Objective-C针对上述问题,提供了一个解决方案:即使用关联对象(Associated Object);
- 我们可以把关联对象想象成一个Objective-C对象(如字典),这个对象通过给定的key连接到类的一个实例上;
- 不过由于使用的是C接口,所以key是一个
void指针(const void *)
。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。
这个内存管理的策略可以由以下值指定:
OBJC_ASSOCIATION_ASSIGN /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC/**< Specifies a strong reference to the associated object. * The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC /**< Specifies that the associated object is copied.* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN /**< Specifies a strong reference to the associated object. * The association is made atomically. */
OBJC_ASSOCIATION_COPY /**< Specifies that the associated object is copied.* The association is made atomically. */
- 当宿主对象被释放时,会根据指定的内存管理策略来处理关联对象;
- 如果指定的策略是OBJC_ASSOCIATION_ASSIGN,则宿主释放时,关联对象不会被释放;
- 而如果指定的是Retain或者是Copy,则宿主释放时,关联对象会被释放。
- 我们甚至可以选择是否是自动Retain/Copy。当我们需要在多个线程中处理访问关联对象的多线程代码时,这就非常有用了,实现线程和逻辑绑定。
具体解决:
- 1、我们将一个对象连接到其它对象所需要做的就是下面两行代码:
static char anObjectKey;
objc_setAssociatedObject(self, &anObjectKey, anObject, OBJC_ASSOCIATION_RETAIN)
- 2、使用下面一行代码获取绑定的对象:
id anObject = objc_getAssociatedObject(self, &anObjectKey);
在这种情况下,Self对象将获取一个新的关联的对象anObject,且内存管理策略是自动Retain关联对象,当Self对象释放时,会自动Release关联对象;
另外,如果我们使用同一个key来关联另外一个对象时,也会自动释放之前关联的对象。这种情况下,先前的关联对象会被妥善地处理掉,并且新的对象会使用它的内存;
3、移除关联对象:
objc_removeAssociatedObjects(anObject);
或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil;
举个栗子
- 在开发工程中,给UIView添加单击手势是非常常见的需求。假定,现在我们就要动态地将一个Tap手势操作连接到任何UIView中,并且根据需要指定点击后的实际操作;
- 这时候我们就可以将一个手势对象及操作的Block对象关联到我们的UIView对象中。这项任务分为一下两部分。
- 首先,如果需要,我们要创建一个手势识别对象并将它及Block作为关联对象。具体实现如下:
- (void)setTapActionWithBlock:(void (^)(void))block
{
UITapGestureRecognizer *tapGR = objc_getAssociatedObject(self, &kJLActionHandlerTapGestureKey);
if (!tapGR)
{
tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(JL_handleActionForTapGesture:)];
[self addGestureRecognizer: tapGR];
objc_setAssociatedObject(self, & kJLActionHandlerTapGestureKey, tapGR, OBJC_ASSOCIATION_RETAIN);
}
objc_setAssociatedObject(self, & kJLActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);
}
- 这段代码检测了手势识别的关联对象。如果没有,则创建并建立关联关系。同时,将传入的块对象连接到指定的key上。注意Block对象的关联内存管理策略-Copy;
- 然后,处理单击事件,具体实现如下:
- (void) JL_handleActionForTapGesture:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized)
{
void(^action)(void) = objc_getAssociatedObject(self, kJLActionHandlerTapBlockKey);
if (action)
{
action();
}
}
}
- 我们需要检测手势识别对象的状态,因为我们只需要在点击手势被识别出来时才执行操作。
- 通过上面可以看到,关联对象实现起来也不是很复杂,而且还可以动态的增强类现有的功能。
优化完善
- 但是,还是有一点不太完美,代码过于松散,按照上述的方式去应用到项目中,会写不少重复代码,我们需要封装一下,并不暴露#import <objc/runtime.h>引用,具体实现如下:
- 重新定义一套表征内存策略的枚举:
typedef 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
};
- 声明方法:
/**
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
,接口方法变得更优雅简洁一些。
- 用封装的方法重写上述方法:
//定义绑定对象的Key
static 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();
}
}
}