@sychronized和dispatch_once,以及对单例的思考

在iOS开发中,经常使用到单例。单例是Cocoa中被广泛使用的设计模式之一。单例使得某个类在整个application的生命周期中只有一个实例,减少内存开销,统一了某些具体操作的逻辑,方便管理。开发中常用构造单例的方法有两种@sychronizeddispatch_once

@sychronized

使用@sychronized构造单例通常如下:

static SomeClass * instance = nil;
+ (instancetype)shareInstance {
    @synchronized(self) {
        if (instance == nil) {
            instance = [[SomeClass alloc] init];
        }
    }
    return instance;
}

@sychronized是一个编译器指令,方便我们对临界区提供互斥锁(mutex locks )。也就是说在多线程并发访问临界区(@synchronized(self) {//临界区})时,它保证同一时刻只有一个线程处于临界区中,其他线程阻塞等待。那么@sychronized如何标识一个互斥锁?苹果文档中说了,@sychronized可以使用任意的Objective-C对象作为互斥锁的标识符(lock token)。那么问题来了,如果互斥锁的标识符不一样呢(动态的)?比如下面这样:

- (void)instantMethod:(id)lockTocken
{
    @synchronized(lockTocken)
    {
        //临界区
    }
}

假设现在有多个线程并发调用上面的实例方法- (void)instantMethod:(id)lockTocken;,它们分别为A、B、C和D线程,其中A、B调用该方法时候传入的lockTocken一样,C、D传入的lockTocken一样。那么答案是只有标识变量(lockTocken)一样的线程才会互斥,标识变量(lockTocken)不一样的线程相互之间没有影响。回到最早的例子,其中使用了self(类对象)作为互斥锁的标识符,由此可见,多进程并发访问,使用的互斥锁是一样的,并且在第一个进入临界区的线程初始化instance后,其后进入的线程就不会再次初始化(instance不再是nil),保证了SomeClass类只有一个实例。

好奇的你一定会想,既然@synchronized是编译器指令,那么编译器对这段代码做了什么?

有如下代码:

id obj = ...
@synchronized(obj) {
    //临界区
}

clang -rewrite-objc之后:

id obj = ...
{ id _rethrow = 0; id _sync_obj = (id)obj; objc_sync_enter(_sync_obj);
    try {
        struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
            ~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
            id sync_exit;
        } _sync_exit(_sync_obj);
        
        
    } catch (id e) {_rethrow = e;}
    { struct _FIN { _FIN(id reth) : rethrow(reth) {}
        ~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
        id rethrow;
    } _fin_force_rethow(_rethrow);}
}

即编译器把@synchronized相关代码转成下面这一大坨东西。代码的关键在两个函数的调用:objc_sync_exitobjc_sync_enter。代码的逻辑还是比较好理解,想深入了解的可以看下这篇博文


dispatch_once

使用dispatch_once构造单例通常如下:

static SomeClass * instance = nil;
+ (instancetype)shareInstance {
    static dispatch_once_t onceTocken;
    dispatch_once(&onceTocken, ^{
        instance = [[SomeClass alloc] init];
    });
    return instance;
}

dispatch_once是GCD中提供的函数,通常使用它来初始化全局数据(单例),它接受两个参数,dispatch_once_t *类型的谓语和dispatch_block_t类型的block(block中的代码就是临界区)。文档中说到,dispatch_once_t *类型的谓语用于测试block是否已经执行结束或者还没与执行,它配合上dispatch_once函数保证了在applecation的生命周期中block只会运行一次,并且是线程安全的。可以看到dispatch_once@synchronized一样,是线程安全,不同指出在于@synchronized的临界区代码可能在application生命周期中多次调用,而dispatch_once只会调用一次(使用dispatch_once_t *类型的谓语做判断)。因此@synchronized的临界区代码要判断instance是否是nil,来判断是实例是否已经构造了。

注意dispatch_once_t *类型的谓语必须是全局变量或者静态变量,如果使用自动或者动态变量(包括Objective-C实例变量),dispatch_once的结果是无法预知的。


单例思考

单例用着用着就被滥用了。最近正在思考如何对公司APP的分享模块重构。其中有个类叫SSShareManager,作为的单例,它管理着所有模块的分享逻辑。滥用点就在既然是单例,却使用它来保存来自不同模块的数据以及状态(比如:分享到微信还是QQ、友盟统计的数据等),无形中增加了不同模块之间的耦合。因为在某个模块调用SSShareManager单例分享之前,还要记得清理其他模块可能留下的数据=,=。这篇博文给我很大启发,其中印象深刻的一段话:

The lesson here is that singletons should be preserved only for state that is global, and not tied to any scope. If state is scoped to any session shorter than “a complete lifecycle of my app,” that state should not be managed by a singleton.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容