Objective-C 黑魔法 -- 單例新思路

開發(fā)中, 單例模式經(jīng)常會用到, 代碼幾乎都是一致的.

+(instancetype)shared {
    static SomeClass* sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [SomeClass new];
    });
    return sharedInstance;
}

聰明的同學可能會加入到snippet 中, 快速的構建一個單例.
不過有沒有什么更好的方式呢?
當然有. 用黑魔法.
先看看最終效果

singleton(SomeClass, shared)
@interface SomeClass: NSObject
+(instancetype)shared;
@end
@implementation SomeClass
-(NSString *)name {
    return @"some class -- name";
}
@end;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog([[SomeClass shared] name]);
    }
    return 0;
}

不需要寫單列的實現(xiàn), 只需要寫一個單例的函數(shù)的聲明, 甚至這個函數(shù)聲明也不需要, 不過沒有的話, 調用起來不方便, 例如

singleton(SomeAnotherClass, shared)
@interface SomeAnotherClass: NSObject
@end
@implementation SomeAnotherClass
-(NSString *)name {
    return @"some other class -- name";
}
@end;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog([[SomeAnotherClass performSelector:NSSelectorFromString(@"shared")] name]);
    }
    return 0;
}

你可能已經(jīng)注意到了, 有一個奇怪的東西singleton(SomeClass, shared).
對的, 和你猜想的一樣, 這個宏, 對的, 是一個宏, 第一個參數(shù)是類名, 第二個參數(shù)是單例的方法.
具體實現(xiàn)是這樣的

#define __toString(x) #x
#define singleton(class, method) __attribute__((objc_runtime_name(__toString(SGSINGLETON_##class##_##method))))

看不懂沒關系, 這相當于在類上面寫了這樣一個東西

__attribute__((objc_runtime_name("SGSINGLETON_SomeClass_shared")))
@interface SomeClass: NSObject
...

作用很簡單, 運行的時候, 會將類名改為 SGSINGLETON_SomeClass_shared
, 你可以參考這篇文章看看具體細節(jié)
http://blog.sunnyxx.com/2016/05/14/clang-attributes/.
在這里的作用是給類加上一個標記, 然后我們在運行時掃描所有加載的類, 找到這個標記的類后, 加上一個方法, 這個方法就是我們單例的實現(xiàn). 是不是很簡單.
接下來看看具體的步驟

__attribute__((constructor)) void addSingleton(){
    // 獲取所有的類
    int classCount = objc_getClassList(NULL, 0);
    Class *classList = new Class[classCount];
    objc_getClassList(classList, classCount);
    Class singtonClassList[256];
    int singtonClassListIndex = 0;
    for(int i =0; i< classCount; i++) {
        NSString *className = NSStringFromClass(classList[i]);
        // 找到標記過的類
        if([className hasPrefix:@"SGSINGLETON_"]) {
            singtonClassList[singtonClassListIndex++] = classList[i];
        }
    }
    for(int i = 0; i< singtonClassListIndex; i++) {
        Class cls = singtonClassList[i];
        NSString *clsName = NSStringFromClass(cls);
        // 獲取元類
        Class metaClass = objc_getMetaClass(clsName.UTF8String);
        // 獲取方法名
        NSString *sharedMethodName = [clsName componentsSeparatedByString:@"_"].lastObject;
        SEL sel = NSSelectorFromString(sharedMethodName);
        // 添加方法
        BOOL result = class_addMethod(metaClass, sel, IMP(makeSharedInstance), "@@:");
        if(!result) {
            NSLog(@"f**k");
        }
    }
    delete [] classList;
}

接下來是單例的實現(xiàn)方法, 這個就更加簡單了.

id makeSharedInstance(id obj, SEL sel) {
    Class cls = obj;
    // 保存所有單例的字典
    static NSMutableDictionary *instanceMap;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instanceMap = [NSMutableDictionary new];
    });
    NSString *className = NSStringFromClass(obj);
    // 多線程鎖
    static NSLock *lock = [NSLock new];
    [lock lock];
    // 獲取單例
    id instance = [instanceMap objectForKey:className];
    if(! instance) {
        // 沒有初始化則進行初始化
        instance = [[cls alloc] init];
        instanceMap[className] = instance;
        NSLog(@"make instance for %@", className);
    }
    [lock unlock];
    // 返回對應單例
    return instance;
}

創(chuàng)意來自 Sunny 的博客文章--Clang Attributes 黑魔法小記, 覺得很有意思, 勉強實現(xiàn)了一下, 代碼寫的很垃圾, 歡迎各位大俠留言改進.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诚纸,隨后出現(xiàn)的幾起案子工禾,更是在濱河造成了極大的恐慌步悠,老刑警劉巖膀藐,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讯沈,死亡現(xiàn)場離奇詭異铸题,居然都是意外死亡抢呆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門才写,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葡兑,“玉大人奖蔓,你說我怎么就攤上這事赞草。” “怎么了吆鹤?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵厨疙,是天一觀的道長。 經(jīng)常有香客問我疑务,道長沾凄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任知允,我火速辦了婚禮撒蟀,結果婚禮上,老公的妹妹穿的比我還像新娘温鸽。我一直安慰自己保屯,他們只是感情好手负,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著姑尺,像睡著了一般竟终。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上切蟋,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天统捶,我揣著相機與錄音,去河邊找鬼柄粹。 笑死喘鸟,一個胖子當著我的面吹牛,可吹牛的內容都是我干的驻右。 我是一名探鬼主播迷守,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼旺入!你這毒婦竟也來了兑凿?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤茵瘾,失蹤者是張志新(化名)和其女友劉穎礼华,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拗秘,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡圣絮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了雕旨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扮匠。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凡涩,靈堂內的尸體忽然破棺而出棒搜,到底是詐尸還是另有隱情,我是刑警寧澤活箕,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布力麸,位于F島的核電站,受9級特大地震影響育韩,放射性物質發(fā)生泄漏克蚂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一筋讨、第九天 我趴在偏房一處隱蔽的房頂上張望埃叭。 院中可真熱鬧,春花似錦悉罕、人聲如沸赤屋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽益缎。三九已至谜慌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莺奔,已是汗流浹背欣范。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留令哟,地道東北人恼琼。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像屏富,于是被迫代替她去往敵國和親晴竞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內容