慎用公共变量

前言

  • 在开发过程中,避免不了会使用公共变量,记录共享对象状态、数据最简单的方式就是创建创建公共变量;
  • 当业务逻辑变多,还采用这种思想就会变得危险,代码逻辑变得不清晰,慢慢就有一种代码坏味道。
  • 具体总结如下:
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();
        }
    }
}

小结

  • 今天,讲到这,要分享的内容就结束了。我将上述封装方法整理成了工具类,已经上传至Github.大家有需要的可以自行下载,感觉好的话,欢迎赏赐一个Star~~~ 谢谢~~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • iOS开发中的这些权限,你搞懂了吗?

    ![Uploading 144446-b8aca7ba38c5f8c0_695906.png . . .]获取相册权限

    Jacklin999
  • 简谈常用算法

    Jacklin999
  • 为Next主题添加多说评论系统

    几个月前,在好奇心的鼓动下,利用Github Pages和Hexo以及Next主题搭建一个属于自己的个人主站,由于时间伧俗,搭建成功后就没有好好完善一下,可以参...

    Jacklin999
  • 一组匹配中国大陆手机号码的正则表达式

    [^(?:\+?86)?1(?:3\d{3}|5[^4\D]\d{2}|8\d{3}|7(?:[01356789]\d{2}|4(?:0\d|1[0-2]|9\...

    芋道源码
  • vue-cli2.0 与 3 引入方式

    vue-cli 现在出到3 了 不同的版本使用方式还是稍有不同的,根据引入方式可以选择不同版本 2的引入方式 vue install -g vue-cli

    py3study
  • 有趣的rownum测试(r10笔记第49天)

    rownum在平时的使用中总是一个很自然的语法。如果说这个rownum是否有规律,可能很多人都会模棱两可。到底是还是不是呢,我们来做几个测试来说明。 这个结果也...

    jeanron100
  • 用ABAP代码调用Netweaver里的where used list功能

    Jerry Wang
  • 百度入局, 一文读懂年交易过4亿「超级链」究竟是什么?

    从去年至今,百度在区块链上的动作不断,先是推出了区块链开放平台「BaaS」,又推出了区块链养成游戏莱茨狗,接着又用区块链的信息展现百度百科,将词条的版本信息签名...

    区块链大本营
  • 探索SQL Server元数据(三):索引元数据

    在第一篇中我介绍了如何访问元数据,元数据为什么在数据库里面,以及如何使用元数据。介绍了如何查出各种数据库对象的在数据库里面的名字。第二篇,我选择了触发器的主题,...

    用户1217611
  • AI预测系统:可预知0.5秒后的动作

    近期,东京工业大学的研究团队发布了一套格斗训练系统「FuturePose」,该AI系统可以实时预判侦测对象0.5秒后的动作,颇有武侠小说中“看穿敌人动作”的风范...

    用户7699929

扫码关注云+社区

领取腾讯云代金券