慎用公共变量

前言

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

前言 很多人都说熟悉UIKit,那对于常见的API是否熟悉? 多线程是前端经久不衰的考点。 大家对于Block的weak-strong dance都耳熟能详...

4467
来自专栏影子

SpringBoot中关于Mybatis使用的三个问题

975
来自专栏开源FPGA

基于Verilog HDL的二进制转BCD码实现

       在项目设计中,经常需要显示一些数值,比如温湿度,时间等等。在数字电路中数据都是用二进制的形式存储,要想显示就需要进行转换,对于一个两位的数值,对1...

1261
来自专栏开发技术

flying-saucer + iText + Freemarker实现pdf的导出, 支持中文、css以及图片

      项目中有个需求,需要将合同内容导出成pdf。上网查阅到了 iText , iText 是一个生成PDF文档的开源Java库,能够动态的从XML或者数...

2931
来自专栏清晨我上码

spring websocket推送

3064
来自专栏码农分享

4.1、苏宁百万级商品爬取 代码讲解 索引建立

Lucene是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作。

1393
来自专栏腾讯数据库技术

时间精度引起MySQL主从不一致问题剖析

1. 主从数据不一致          近日接报某实例一个datetime字段主从数据不一致,其它数据暂未发现异常。第一反应可能是人为修改,如果用户有高权限帐...

2452
来自专栏Android 开发学习

JsBridge 源码分析

1633
来自专栏软件开发

MyBatis学习总结(四)——MyBatis缓存与代码生成

缓存可以提高系统性能,可以加快访问速度,减轻服务器压力,带来更好的用户体验。缓存用空间换时间,好的缓存是缓存命中率高的且数据量小的。缓存是一种非常重要的技术。

2113
来自专栏GreenLeaves

oracle 层次化查询(生成菜单树等)

1、简介:Oracle层次化查询是Oracle特有的功能实现,主要用于返回一个数据集,这个数据集存在树的关系(数据集中存在一个Pid记录着当前数据集某一条记录的...

2258

扫码关注云+社区

领取腾讯云代金券