慎用公共变量

谨~~慎

前言

  • 在开发过程中,避免不了会使用公共变量,记录共享对象状态、数据最简单的方式就是创建创建公共变量;
  • 当业务逻辑变多,还采用这种思想就会变得危险,代码逻辑变得不清晰,慢慢就有一种代码坏味道。
  • 具体总结如下:
    1
    2
    3
    1、过多逻辑分支,不够清晰,公共变量不利于系统维护和项目拓展;
    2、安全性收到威胁,过多地方共享变量,变量的写入和读取在多线程下是危险的;
    3、业务逻辑交叉过多时,很难保证数据-逻辑的一致性;

如何解决呢?

  • 出现问题,解决问题,Objective-C针对上述问题,提供了一个解决方案:即使用关联对象(Associated Object)
  • 我们可以把关联对象想象成一个Objective-C对象(如字典),这个对象通过给定的key连接到类的一个实例上;
  • 不过由于使用的是C接口,所以key是一个void指针(const void *)。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。
这个内存管理的策略可以由以下值指定:
1
2
3
4
5
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、我们将一个对象连接到其它对象所需要做的就是下面两行代码:
1
2
3
static char anObjectKey;
objc_setAssociatedObject(self, &anObjectKey, anObject, OBJC_ASSOCIATION_RETAIN)
  • 2、使用下面一行代码获取绑定的对象:

    1
    id anObject = objc_getAssociatedObject(self, &anObjectKey);
  • 在这种情况下,Self对象将获取一个新的关联的对象anObject,且内存管理策略是自动Retain关联对象,当Self对象释放时,会自动Release关联对象;

  • 另外,如果我们使用同一个key来关联另外一个对象时,也会自动释放之前关联的对象。这种情况下,先前的关联对象会被妥善地处理掉,并且新的对象会使用它的内存;

  • 3、移除关联对象:

    1
    objc_removeAssociatedObjects(anObject);

或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil;

举个栗子
  • 在开发工程中,给UIView添加单击手势是非常常见的需求。假定,现在我们就要动态地将一个Tap手势操作连接到任何UIView中,并且根据需要指定点击后的实际操作;
  • 这时候我们就可以将一个手势对象及操作的Block对象关联到我们的UIView对象中。这项任务分为一下两部分。
    • 首先,如果需要,我们要创建一个手势识别对象并将它及Block作为关联对象。具体实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
- (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;
  • 然后,处理单击事件,具体实现如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    - (void) JL_handleActionForTapGesture:(UITapGestureRecognizer *)sender
    {
    if (sender.state == UIGestureRecognizerStateRecognized)
    {
    void(^action)(void) = objc_getAssociatedObject(self, kJLActionHandlerTapBlockKey);
    if (action)
    {
    action();
    }
    }
    }
  • 我们需要检测手势识别对象的状态,因为我们只需要在点击手势被识别出来时才执行操作。
  • 通过上面可以看到,关联对象实现起来也不是很复杂,而且还可以动态的增强类现有的功能。
优化完善
  • 但是,还是有一点不太完美,代码过于松散,按照上述的方式去应用到项目中,会写不少重复代码,我们需要封装一下,并不暴露#import 引用,具体实现如下:
  • 重新定义一套表征内存策略的枚举:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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
    };
  • 声明方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    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,接口方法变得更优雅简洁一些。

  • 用封装的方法重写上述方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //定义绑定对象的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~ 谢谢

传送门

扫一扫下面的二维码,欢迎关注我的个人微信公众号攻城狮的动态(ID:iOSDevSkills),可在微信公众号进行留言,更多精彩技术文章,期待您的加入!一起讨论,一起成长!一起攻城狮!

公众号

Jack_lin wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!