objc:Issue13——Architecture【2】

避免單例濫用——by Stephen Poletto

單例是整個(gè)Cocoa使用的核心設(shè)計(jì)模式之一散劫。事實(shí)上肃续,蘋果的開發(fā)庫(kù)把單例當(dāng)做“Cocoa核心競(jìng)爭(zhēng)力”之一商叹。作為iOS開發(fā)者穆趴,從UIApplicationNSFileManager割去,我們對(duì)與單例的交互已經(jīng)很熟悉了窟却。在開源項(xiàng)目、蘋果代碼示例和StackOverflow中呻逆,我們見到過的單例已多如牛毛夸赫。甚至,Xcode還有默認(rèn)的代碼片段咖城,如:”Dispatch Once“茬腿,這使得你往代碼中添加單例變的非常的簡(jiǎn)單:

+ (instancetype)sharedInstance {
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

因?yàn)檫@些原因呼奢,單例在iOS編程中就很常見。但問題是切平,它很容易被濫用握础。

其他人把單例稱作‘反面模式’,‘邪惡’和‘病態(tài)騙子’悴品,然而我并沒有完全抹去單例的價(jià)值禀综。相反,我想論證單例的幾個(gè)問題苔严,從而定枷,讓你在下次打算自動(dòng)完成dispatch_once代碼片段的時(shí)候再三思考這樣做可能帶來的后果。

全局狀態(tài)

大多數(shù)開發(fā)者都認(rèn)為可變的全局狀態(tài)是不可取的届氢。有狀態(tài)性使程序難以理解和調(diào)試欠窒。在最小化有狀態(tài)代碼方面,面向?qū)ο蟪绦騿T有很多東西需要從函數(shù)編程上面學(xué)習(xí)退子。

@implementation SPMath {
    NSUInteger _a;
    NSUInteger _b;
}
- (NSUInteger)computeSum {
    return _a + _b;
}

在上述簡(jiǎn)單數(shù)學(xué)庫(kù)的實(shí)現(xiàn)中岖妄,在調(diào)用computeSum方法之前程序員希望為實(shí)例變量_a_b設(shè)置合適的值。這存在幾個(gè)問題:

  1. computeSum方法沒有通過把_a_b的值作為參數(shù)而顯式的指出方法依賴于上述的兩個(gè)值絮供。其他閱讀代碼的人必須通過檢查實(shí)現(xiàn)去理解依賴關(guān)系衣吠,而不是通過檢查接口并理解哪些變量控制函數(shù)輸出。隱藏依賴關(guān)系這樣是不好的壤靶。
  2. 當(dāng)為了準(zhǔn)備調(diào)用computeSum而修改_a_b的時(shí)候缚俏,程序員需要確定這些修改不會(huì)影響其它依賴這些變量的代碼的正確性。這在多線程環(huán)境尤為困難贮乳。

把這下面這個(gè)例子與上述的例子比較一下:

+ (NSUInteger)computeSumOf:(NSUInteger)a plus:(NSUInteger)b {
    return a + b;
}

這里方法對(duì)ab的依賴就很明顯忧换。為了調(diào)用這個(gè)方法我們不需要改變實(shí)例的狀態(tài)。我們也不必?fù)?dān)心由于調(diào)用此方法而導(dǎo)致的持久的副作用向拆,我們甚至可以把這個(gè)方法當(dāng)做類方法亚茬,以表明我們調(diào)用此方法不需要修改實(shí)例狀態(tài)。

但是浓恳,這個(gè)例子和單例有什么關(guān)系呢刹缝?用Mi?ko Hevery的話說,“單例是披著羊皮的全局狀態(tài)颈将∩液唬”單例可以使用在任何地方,而不用明確的聲明依賴關(guān)系晴圾。就像computeSum方法中的_a_b沒有明確的依賴關(guān)系一樣颂砸,程序的任何模塊都可以調(diào)用[SPMySingleton sharedInstance]并使用單例。這意味著與單例交互的任何副作用都會(huì)影響到程序的任何地方的任何代碼。

@interface SPSingleton: NSObject

+ (instancetype)sharedInstance;
- (NSUInteger)badMutableState;
- (void)setBadMutableState:(NSUInteger)badMutableState;

@end

@implementation SPConsumerA
- (void)someMethod {
    if([[SPSingleton sharedInstance] badMutableState]) {
        //...
    }
}
@end

@implementation SPConsumerB
- (void)someOtherMethod {
    [[SPSingleton sharedInstance] setBadMutableState:0];
}

@end

在上述的例子中人乓,SPConsumerASPConsumerB是程序中兩個(gè)完全獨(dú)立的模塊勤篮。然而SPConsumerB可以通過單例提過的共享狀態(tài)影響SPConsumerA的行為。在不使用單例的情況下色罚,只有在消費(fèi)者B中引入消費(fèi)者A碰缔,明確兩者之間的關(guān)系才能達(dá)到上述這樣的效果。在單例中保屯,由于它的全局有狀態(tài)的性質(zhì)手负,導(dǎo)致了看似兩個(gè)不相關(guān)的模塊之間的隱藏和隱式的耦合涤垫。

讓我們看一個(gè)更具體的例子姑尺,并提出另外一個(gè)由全局可變狀態(tài)而引起的問題。假設(shè)我們想在我們的應(yīng)用中創(chuàng)建一個(gè)web查看器蝠猬。為了支持這個(gè)web查看器切蟋,我們創(chuàng)建了一個(gè)簡(jiǎn)單地URL緩存:

@interface SPURLCache

+ (SPURLCache *)sharedURLCache;
- (void)storeCacheResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;

@end

編寫web查看器的開發(fā)者開始寫幾個(gè)單元測(cè)試,以保證代碼在期望的幾個(gè)不同的情況下能夠正常工作榆芦。首先柄粹,寫一個(gè)測(cè)試程序保證web查看器在沒有設(shè)備連接的時(shí)候會(huì)顯示一個(gè)錯(cuò)誤。然后匆绣,寫一個(gè)測(cè)試程序保證web查看器可以適當(dāng)?shù)奶幚矸?wù)器錯(cuò)誤驻右。最后,為簡(jiǎn)單地成功情況寫一個(gè)測(cè)試程序崎淳,保證返回的web內(nèi)容能被適當(dāng)?shù)恼故境鰜砜柏病i_發(fā)者運(yùn)行所有的測(cè)試程序,并且它們會(huì)像預(yù)期的那樣工作拣凹。Nice森爽!

幾個(gè)月后,這些測(cè)試程序開始失敗嚣镜,盡管web查看器的代碼自從第一次寫過后在沒有進(jìn)行任何更改爬迟!發(fā)生了什么?

結(jié)果是有人改變了測(cè)試程序的執(zhí)行順序菊匿。成功情況的測(cè)試首先執(zhí)行付呕,其次是另外的兩個(gè)。現(xiàn)在失敗的情況以外的成功了跌捆,因?yàn)檎麄€(gè)測(cè)試是通過單例URL緩存對(duì)結(jié)果進(jìn)行緩存的徽职。

持久狀態(tài)是單元測(cè)試的死敵,因?yàn)閱卧獪y(cè)試是由每個(gè)測(cè)試的相對(duì)立而產(chǎn)生的疹蛉。如果狀態(tài)從一個(gè)測(cè)試保留到下一個(gè)測(cè)試活箕,然后,測(cè)試的執(zhí)行循序突然就變的重要了。Buggy測(cè)試育韩,特別是當(dāng)測(cè)試應(yīng)該失敗的時(shí)候而它反而成功了克蚂,這不是一個(gè)好現(xiàn)象。

對(duì)象生命周期

單例的另外一個(gè)主要的問題是他們的生命周期筋讨。當(dāng)向你的代碼中添加添加單例時(shí)埃叭,很容易想到“只存在這樣的一個(gè)∠ず保”但是赤屋,我在自己項(xiàng)目之外看到的大部分iOS代碼中,這個(gè)假設(shè)都有可能失效壁袄。

例如类早,假設(shè)我們要?jiǎng)?chuàng)建一個(gè)能看見用戶好友列表的應(yīng)用。他們的每一個(gè)好友都有一個(gè)頭像嗜逻,并且我們想讓應(yīng)用把這個(gè)照片下載下來并把它緩存到設(shè)備上涩僻。使用dispatch_once代碼片段很方便,但我們可能會(huì)發(fā)現(xiàn)自己正在編寫一個(gè)SPThumbnailCache單例:

@interface SPThumbnailCache: NSObject
+ (instancetype)sharedThumbnailCache;
- (void)cacheProfileImage:(NSData *)imageData forUserId:(NSString *)userId;
- (NSData *)cachedProfileImageForUserId:(NSString *)userId;
@end

我們繼續(xù)開發(fā)這個(gè)應(yīng)用栈顷,并且看起來一切正常逆日,直到某一天,當(dāng)我們決定是時(shí)候?qū)崿F(xiàn)“l(fā)og out”函數(shù)了萄凤,這樣就可以在應(yīng)用中切換用戶了室抽。突然,我們出現(xiàn)了一個(gè)難以處理的問題:特定用戶的狀態(tài)保存到了全局的單例中了靡努。當(dāng)用戶退出登錄坪圾,我希望能夠把磁盤上的持久狀態(tài)清除掉。否則颤难,我們會(huì)在用戶設(shè)備上遺留下孤立數(shù)據(jù)神年,從而浪費(fèi)寶貴的磁盤空間。萬一行嗤,用戶退出后轉(zhuǎn)用另一個(gè)賬戶登錄已日,我們同樣希望能夠?yàn)樾掠脩魟?chuàng)建一個(gè)新的SPThumbnailCache單例。

這里的問題是栅屏,根據(jù)定義飘千,單例被假定為“創(chuàng)建一次,永遠(yuǎn)存活”的實(shí)例栈雳。對(duì)于上述的問題你可能會(huì)想到好幾個(gè)解決方案护奈。也許當(dāng)用戶退出登陸的時(shí)候我們可以把單例實(shí)例銷毀掉:

static SPThumbnailCache *sharedThumbnailCache;
+ (instancetype)sharedThumbnailCache {
    if(!sharedThumbnailCache) {
        sharedThumbnailCache = [[self alloc] init];
    }
    return sharedThumbnailCache;
}

+ (void)tearDown {
    sharedThumbnailCache = nil;
}

這是明目張膽的對(duì)單例模式的濫用,但是很管用對(duì)不對(duì)哥纫?

我們當(dāng)然可以讓這個(gè)解決方案起作用霉旗,但是代價(jià)太大了。舉例來說,我們已經(jīng)失去了dispatch_once方案的簡(jiǎn)單性厌秒,并且這解決方案可以保證線程安全读拆,所有的代碼都調(diào)用[SPThumbnailCache sharedThumbnailCache]這個(gè)方法只是獲取同一個(gè)實(shí)例。對(duì)于使用縮略圖緩存的代碼的執(zhí)行順序鸵闪,我們需要格外的小心檐晕。假設(shè)在用戶退出登陸的過程中,有一些保存圖片到緩存的后臺(tái)任務(wù)正在執(zhí)行:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [[SPThumbnailCache sharedThumbnailCache] cacheProfileImage:newImage forUserId:userId];
});

我們需要確定在后臺(tái)任務(wù)執(zhí)行完之前不能執(zhí)行tearDown方法蚌讼。這保證newImage數(shù)據(jù)能夠正確的清除掉辟灰。或者篡石,我們需要保證當(dāng)縮略圖緩存被清除的時(shí)候能把后臺(tái)任務(wù)取消芥喇。否者,新的縮略圖緩存將被懶創(chuàng)建并且舊用戶狀態(tài)(也就是newImage)將被存儲(chǔ)到它里面夏志。

因?yàn)槟死ぃ瑔卫龑?shí)例沒有明顯的所有者(例如:?jiǎn)卫约汗芾砺暶髦芷冢┛寥茫怨得铮P(guān)閉’單例就變得非常困難。

就因?yàn)檫@點(diǎn)狱杰,我希望你說瘦材,“縮略圖緩存就不應(yīng)該使用單例的!”問題是在項(xiàng)目剛開始并不能完全理解對(duì)象的生命周期仿畸。對(duì)于一個(gè)具體的例子食棕,DropboxiOS應(yīng)用僅僅支持單用戶的登陸。直到有一天错沽,當(dāng)我們?cè)试S多用戶(個(gè)人用戶和企業(yè)賬戶)同時(shí)登陸時(shí)簿晓,應(yīng)用在單用戶登陸這種情況下已經(jīng)存在好幾年了。突然千埃,假定“同一時(shí)刻只允許一個(gè)用戶登錄”開始閃退了憔儿。通過假設(shè)一個(gè)對(duì)象的生命周期匹配你的應(yīng)用的生命周期,你將會(huì)限制你的代碼的擴(kuò)展性放可,并且當(dāng)產(chǎn)品需要改變的時(shí)候你需要為此付出代價(jià)谒臼。

這里的教訓(xùn)是,單例應(yīng)該保存為全局的狀態(tài)耀里,而不是在某一個(gè)范圍內(nèi)蜈缤。如果把狀態(tài)限制在任何一個(gè)比“應(yīng)用完整生命周期”短的會(huì)話范圍內(nèi),這個(gè)狀態(tài)則不應(yīng)該被單例管理冯挎。管理特定用戶狀態(tài)的單例是“代碼異味”底哥,你應(yīng)該審慎的重新評(píng)估你的對(duì)象圖的設(shè)計(jì)。

避免(使用)單例

所以,如果單例對(duì)于范圍化的狀態(tài)如此的不利趾徽,那如何避免使用它們呢奶陈?

重新看一下上面例子。由于我們有一個(gè)緩存特定個(gè)體用戶狀態(tài)的縮略圖緩存附较,讓我們定義一個(gè)用戶對(duì)象:

@interface SPUser:NSObject
@property (nonatomic, readonly) SPThumbnailCache *thumbnailCache;
@end

@implementation SPUser
- (instancetype)init {
    if((self = [super init])) {
        _thumbnailCache = [[SPThumbnailCache alloc] init];
    }
    return self;
}
@end

現(xiàn)在我們有一個(gè)對(duì)象可以模擬授權(quán)的用戶會(huì)話了吃粒,我們可以把所有的特定用戶狀態(tài)存儲(chǔ)在這個(gè)對(duì)象內(nèi)。現(xiàn)在拒课,假設(shè)我們有一個(gè)渲染了好友列表的視圖控制器徐勃。

@interface SPFriendListViewController: UIViewController
- (instancetype)initWithUser:(SPUser *)user;
@end

我們可以明確地把授權(quán)的用戶對(duì)象傳遞到視圖控制器中。這種傳遞依賴到獨(dú)立的對(duì)象中的技術(shù)的一個(gè)更為正式的名字叫依賴注入(dependency injection)早像,并且他有一大堆的好處:

  1. 它能夠讓閱讀此接口的人清楚的明白:當(dāng)用戶登陸的時(shí)候SPFriendListViewController才會(huì)顯示出來僻肖。
  2. 只要SPFriendListViewController在使用它就可以保持用戶對(duì)象的強(qiáng)引用。例如卢鹦,更新先前的例子臀脏,我們可以使用下面的后臺(tái)任務(wù)把圖片保存到縮略圖緩存。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [_user.thumbnailCache cacheProfileImage:newImage forUserId:userId];
});

即使這個(gè)后臺(tái)任務(wù)仍然沒有完成冀自,應(yīng)用中其他地方的代碼也可以創(chuàng)建并使用全新的SPUser對(duì)象揉稚,而不需要阻塞進(jìn)一步的交互因?yàn)榈谝粋€(gè)實(shí)力已經(jīng)被銷毀了。

為了進(jìn)一步證明第二點(diǎn)熬粗,讓我們想象一下使用依賴注入前后的對(duì)象圖搀玖。

假設(shè),我們的SPFriendListViewController是當(dāng)前窗口的根視圖控制器驻呐。在單例對(duì)象模型中灌诅,我們有如下如這樣的一個(gè)對(duì)象圖:

image

視圖控制器和自定義圖片視圖列表與sharedThumbnailCache交互。當(dāng)用戶退出含末,我們希望清空更試圖控制器并把用戶帶入登錄界面猜拾。
image

問題是,好友列表試圖控制器可能仍然在執(zhí)行代碼(由于后臺(tái)操作)佣盒,因此挎袜,仍會(huì)有未結(jié)束的調(diào)用掛起sharedThumbnailCache方法。

把這解決方案同使用依賴注入的解決方案對(duì)比:

image

假設(shè)沼撕,為簡(jiǎn)單起見宋雏,SPApplicationDelegate管理SPUser實(shí)例(事實(shí)上,你可能想會(huì)想著把用戶狀態(tài)的管理拆分到里一個(gè)對(duì)象里面以保持你的應(yīng)用代理更輕)务豺。當(dāng)列表視圖控制器被安裝到了窗口上后磨总,用戶對(duì)象的引用也被傳了進(jìn)去。這個(gè)應(yīng)用也會(huì)順著對(duì)象圖到個(gè)人圖片視圖×ぃ現(xiàn)在蚪燕,當(dāng)用戶退出時(shí)娶牌,我們的對(duì)象圖想起來是這樣的:
image

這個(gè)對(duì)象圖看起來和我們使用單例的情況沒有什么區(qū)別。所以有什么嚴(yán)重的問題馆纳?

問題是作用域诗良。在單例情況下,sharedThumbnailCache在程序中的任何模塊都是可用的鲁驶。假設(shè)鉴裹,用戶快速的登錄一個(gè)新的賬戶。新用戶想看他的好友钥弯,這意味著又一次和縮略圖緩存交互:

image

當(dāng)用戶使用新賬戶登陸時(shí)径荔,我們應(yīng)該可以重新構(gòu)建并與全新的SPThumbnailCache進(jìn)行交互,而不必關(guān)心舊縮略圖緩存的銷毀脆霎。根據(jù)對(duì)象管理的標(biāo)準(zhǔn)規(guī)則总处,舊的視圖控制器和縮略圖緩存應(yīng)該在后臺(tái)自動(dòng)清理。簡(jiǎn)言之睛蛛,我們應(yīng)該把用戶A的狀態(tài)和用戶B的狀態(tài)隔離開來:
image

結(jié)論

這篇文章沒有什么新穎的東西鹦马。人們對(duì)單例的抱怨已經(jīng)存在多年,而且也知道全局的狀態(tài)非常不好忆肾。但是在iOS開發(fā)的領(lǐng)域荸频,單例已司空見慣,以至于有時(shí)會(huì)忘記多年來從其他地方的面向?qū)ο缶幊塘?xí)得的教訓(xùn)难菌。

所有這一切的關(guān)鍵是试溯,在面向?qū)ο缶幊讨校覀兿M钚』勺儬顟B(tài)的作用域郊酒。單例站在了這種情況的對(duì)立面,因?yàn)樗茏尶勺儬顟B(tài)從程序中的任何地方獲取到键袱。下一次在你想要使用單例的時(shí)候燎窘,我希望你考慮一下依賴注入作為替代。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蹄咖,一起剝皮案震驚了整個(gè)濱河市褐健,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌澜汤,老刑警劉巖蚜迅,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異俊抵,居然都是意外死亡谁不,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門徽诲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刹帕,“玉大人吵血,你說我怎么就攤上這事⊥的纾” “怎么了蹋辅?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)挫掏。 經(jīng)常有香客問我侦另,道長(zhǎng),這世上最難降的妖魔是什么尉共? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任淋肾,我火速辦了婚禮,結(jié)果婚禮上爸邢,老公的妹妹穿的比我還像新娘樊卓。我一直安慰自己,他們只是感情好杠河,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布碌尔。 她就那樣靜靜地躺著,像睡著了一般券敌。 火紅的嫁衣襯著肌膚如雪唾戚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天待诅,我揣著相機(jī)與錄音叹坦,去河邊找鬼。 笑死卑雁,一個(gè)胖子當(dāng)著我的面吹牛募书,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播测蹲,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼莹捡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了扣甲?” 一聲冷哼從身側(cè)響起篮赢,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎琉挖,沒想到半個(gè)月后启泣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡示辈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年寥茫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顽耳。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坠敷,死狀恐怖妙同,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膝迎,我是刑警寧澤粥帚,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站限次,受9級(jí)特大地震影響芒涡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卖漫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一费尽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧羊始,春花似錦旱幼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至匀油,卻和暖如春缘缚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背敌蚜。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工桥滨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人弛车。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓齐媒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親帅韧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子里初,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,744評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)忽舟,斷路器,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,334評(píng)論 8 265
  • 原文地址:淺談程序員的英語學(xué)習(xí) 作為在中國(guó)工作的程序員淮阐,不懂得英語似乎也不妨礙找到好工作叮阅,升職加薪。但程序員這個(gè)工...
    Albert陳凱閱讀 510評(píng)論 0 2
  • 那些不請(qǐng)自到泣特,不告而別的人們浩姥,那些深埋心底的感情,那些無法言語的悲傷状您,那些甚是離奇的邂逅勒叠,宛如夏日里清涼的...
    孤郁閱讀 178評(píng)論 0 0