慎用公共变量

谨~~慎

前言

  • 在开发过程中,避免不了会使用公共变量,记录共享对象状态、数据最简单的方式就是创建创建公共变量;
  • 当业务逻辑变多,还采用这种思想就会变得危险,代码逻辑变得不清晰,慢慢就有一种代码坏味道。
  • 具体总结如下:
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();
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容