谨~~ 慎
前言
在开发过程中, 避免不了会使用公共变量, 记录共享对象状态, 数据最简单的方式就是创建创建公共变量;
当业务逻辑变多, 还采用这种思想就会变得危险, 代码逻辑变得不清晰, 慢慢就有一种代码坏味道.
具体总结如下:
, 过多逻辑分支, 不够清晰, 公共变量不利于系统维护和项目拓展;
, 安全性收到威胁, 过多地方共享变量, 变量的写入和读取在多线程下是危险的;
, 业务逻辑交叉过多时, 很难保证数据 - 逻辑的一致性;
如何解决呢?
出现问题, 解决问题, 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();
}
}
}
小结
今天, 讲到这, 要分享的内容就结束了. 我将上述封装方法整理成了工具类, 已经上传至 Github . 大家有需要的可以自行下载, 感觉好的话, 欢迎赏赐一个 Star~~~ 谢谢~~
来源: http://www.jianshu.com/p/1e7d27ccca40