關(guān)于性能優(yōu)化

寫(xiě)在前面

本文來(lái)自iOS Tutorial Team 的 Marcelo Fabri占哟,他是Movile的一名 iOS 程序員滓鸠。這是他的個(gè)人網(wǎng)站:http://www.marcelofabri.com/梁呈,你還可以在Twitter上關(guān)注@marcelofabri_嘁锯。

性能對(duì) iOS 應(yīng)用的開(kāi)發(fā)尤其重要掂林,如果你的應(yīng)用失去反應(yīng)或者很慢,失望的用戶(hù)會(huì)把他們的失望寫(xiě)滿(mǎn)App Store的評(píng)論鼓蜒。然而由于iOS設(shè)備的限制痹换,有時(shí)搞好性能是一件難事。開(kāi)發(fā)過(guò)程中你會(huì)有很多需要注意的事項(xiàng)友酱,你也很容易在做出選擇時(shí)忘記考慮性能影響晴音。

這正是我寫(xiě)下這篇文章的原因。這篇文章以一個(gè)方便查看的核對(duì)表的形式整合了你可以用來(lái)提升你app性能的25條建議和技巧缔杉。

請(qǐng)耐心讀完這篇文章,為你未來(lái)的app提個(gè)速搁料!

注意:每在優(yōu)化代碼之前或详,你都要注意一個(gè)問(wèn)題,不要養(yǎng)成”預(yù)優(yōu)化”代碼的錯(cuò)誤習(xí)慣郭计。時(shí)常使用Instruments去profile你的代碼來(lái)發(fā)現(xiàn)需要提升的方面霸琴。Matt Galloway寫(xiě)過(guò)一篇很棒的如何利用Instruments來(lái)優(yōu)化代碼的文章

還要注意的是昭伸,這里列出的其中一些建議是有代價(jià)的梧乘,所建議的方式會(huì)提升app的速度或者使它更加高效,但也可能需要花很多功夫去應(yīng)用或者使代碼變得更加復(fù)雜庐杨,所以要仔細(xì)選擇选调。

目錄

我要給出的建議將分為三個(gè)不同的等級(jí): 入門(mén)級(jí)、 中級(jí)和進(jìn)階級(jí):

入門(mén)級(jí)(這是些你一定會(huì)經(jīng)常用在你app開(kāi)發(fā)中的建議)

1. 用ARC管理內(nèi)存

2. 在正確的地方使用reuseIdentifier

3. 盡可能使Views不透明

4. 避免龐大的XIB

5. 不要block主線(xiàn)程

6. 在Image Views中調(diào)整圖片大小

7. 選擇正確的Collection

8. 打開(kāi)gzip壓縮

中級(jí)(這些是你可能在一些相對(duì)復(fù)雜情況下可能用到的)

9. 重用和延遲加載Views

10. Cache, Cache, 還是Cache灵份!

11. 權(quán)衡渲染方法

12. 處理內(nèi)存警告

13. 重用大開(kāi)銷(xiāo)的對(duì)象

14. 使用Sprite Sheets

15. 避免反復(fù)處理數(shù)據(jù)

16. 選擇正確的數(shù)據(jù)格式

17. 正確地設(shè)定Background Images

18. 減少使用Web特性

19. 設(shè)定Shadow Path

20. 優(yōu)化你的Table View

21. 選擇正確的數(shù)據(jù)存儲(chǔ)選項(xiàng)

進(jìn)階級(jí)(這些建議只應(yīng)該在你確信他們可以解決問(wèn)題和得心應(yīng)手的情況下采用)

22. 加速啟動(dòng)時(shí)間

23. 使用Autorelease Pool

24. 選擇是否緩存圖片

25. 盡量避免日期格式轉(zhuǎn)換

無(wú)需贅述仁堪,讓我們進(jìn)入正題吧~

初學(xué)者性能提升

這個(gè)部分致力于一些能提高性能的基本改變。但所有層次的開(kāi)發(fā)者都有可能會(huì)從這個(gè)記錄了一些被忽視的項(xiàng)目的小小的性能備忘錄里獲得一些提升填渠。

1. 用ARC管理內(nèi)存

ARC(Automatic Reference Counting, 自動(dòng)引用計(jì)數(shù))和iOS5一起發(fā)布弦聂,它避免了最常見(jiàn)的也就是經(jīng)常是由于我們忘記釋放內(nèi)存所造成的內(nèi)存泄露。它自動(dòng)為你管理retain和release的過(guò)程氛什,所以你就不必去手動(dòng)干預(yù)了莺葫。

下面是你會(huì)經(jīng)常用來(lái)去創(chuàng)建一個(gè)View的代碼段:

C++

1

2

3

4UIView*view=[[UIViewalloc]init];

// ...

[self.viewaddSubview:view];

[viewrelease];

忘掉代碼段結(jié)尾的release簡(jiǎn)直像記得吃飯一樣簡(jiǎn)單。而ARC會(huì)自動(dòng)在底層為你做這些工作枪眉。

除了幫你避免內(nèi)存泄露捺檬,ARC還可以幫你提高性能,它能保證釋放掉不再需要的對(duì)象的內(nèi)存瑰谜。這都啥年代了欺冀,你應(yīng)該在你的所有項(xiàng)目里使用ARC!

這里有一些更多關(guān)于ARC的學(xué)習(xí)資源:

Apple’s official documentation

Matthijs Hollemans’sBeginning ARC in iOS Tutorial

Tony Dahbura’sHow To Enable ARC in a Cocos2D 2.X Project

If you still aren’t convinced of the benefits of ARC, check out this article oneight myths about ARCto really convince you why you should be using it!

ARC當(dāng)然不能為你排除所有內(nèi)存泄露的可能性树绩。由于阻塞, retain 周期, 管理不完善的CoreFoundation object(還有C結(jié)構(gòu))或者就是代碼太爛依然能導(dǎo)致內(nèi)存泄露。

這里有一篇很棒的介紹ARC不能做到以及我們?cè)撛趺醋龅奈恼?http://conradstoll.com/blog/2013/1/19/blocks-operations-and-retain-cycles.html隐轩。

2. 在正確的地方使用 reuseIdentifier

一個(gè)開(kāi)發(fā)中常見(jiàn)的錯(cuò)誤就是沒(méi)有給UITableViewCells饺饭, UICollectionViewCells,甚至是UITableViewHeaderFooterViews設(shè)置正確的reuseIdentifier职车。

為了性能最優(yōu)化瘫俊,table view用tableView:cellForRowAtIndexPath:為rows分配cells的時(shí)候,它的數(shù)據(jù)應(yīng)該重用自UITableViewCell悴灵。 一個(gè)table view維持一個(gè)隊(duì)列的數(shù)據(jù)可重用的UITableViewCell對(duì)象扛芽。

不使用reuseIdentifier的話(huà),每顯示一行table view就不得不設(shè)置全新的cell积瞒。這對(duì)性能的影響可是相當(dāng)大的川尖,尤其會(huì)使app的滾動(dòng)體驗(yàn)大打折扣。

自iOS6起茫孔,除了UICollectionView的cells和補(bǔ)充views叮喳,你也應(yīng)該在header和footer views中使用reuseIdentifiers。

想要使用reuseIdentifiers的話(huà)缰贝,在一個(gè)table view中添加一個(gè)新的cell時(shí)在data source object中添加這個(gè)方法:

C++

1

2staticNSString*CellIdentifier=@"Cell";

UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:CellIdentifierforIndexPath:indexPath];

這個(gè)方法把那些已經(jīng)存在的cell從隊(duì)列中排除馍悟,或者在必要時(shí)使用先前注冊(cè)的nib或者class創(chuàng)造新的cell。如果沒(méi)有可重用的cell剩晴,你也沒(méi)有注冊(cè)一個(gè)class或者nib的話(huà)锣咒,這個(gè)方法返回nil。

3.盡量把views設(shè)置為不透明

如果你有不透明的Views赞弥,你應(yīng)該設(shè)置它們的opaque屬性為YES毅整。

原因是這會(huì)使系統(tǒng)用一個(gè)最優(yōu)的方式渲染這些views。這個(gè)簡(jiǎn)單的屬性在IB或者代碼里都可以設(shè)定嗤攻。

Apple的文檔對(duì)于為圖片設(shè)置不透明屬性的描述是:

(opaque)這個(gè)屬性給渲染系統(tǒng)提供了一個(gè)如何處理這個(gè)view的提示毛嫉。如果設(shè)為YES, 渲染系統(tǒng)就認(rèn)為這個(gè)view是完全不透明的妇菱,這使得渲染系統(tǒng)優(yōu)化一些渲染過(guò)程和提高性能承粤。如果設(shè)置為NO,渲染系統(tǒng)正常地和其它內(nèi)容組成這個(gè)View闯团。默認(rèn)值是YES辛臊。

在相對(duì)比較靜止的畫(huà)面中,設(shè)置這個(gè)屬性不會(huì)有太大影響房交。然而當(dāng)這個(gè)view嵌在scroll view里邊彻舰,或者是一個(gè)復(fù)雜動(dòng)畫(huà)的一部分,不設(shè)置這個(gè)屬性的話(huà)會(huì)在很大程度上影響app的性能。

你可以在模擬器中用Debug\Color Blended Layers選項(xiàng)來(lái)發(fā)現(xiàn)哪些view沒(méi)有被設(shè)置為opaque刃唤。目標(biāo)就是隔心,能設(shè)為opaque的就全設(shè)為opaque!

4. 避免過(guò)于龐大的XIB

iOS5中加入的Storyboards(分鏡)正在快速取代XIB。然而XIB在一些場(chǎng)景中仍然很有用尚胞。比如你的app需要適應(yīng)iOS5之前的設(shè)備硬霍,或者你有一個(gè)自定義的可重用的view,你就不可避免地要用到他們。

如果你不得不XIB的話(huà)笼裳,使他們盡量簡(jiǎn)單唯卖。嘗試為每個(gè)Controller配置一個(gè)單獨(dú)的XIB,盡可能把一個(gè)View Controller的view層次結(jié)構(gòu)分散到單獨(dú)的XIB中去躬柬。

需要注意的是拜轨,當(dāng)你加載一個(gè)XIB的時(shí)候所有內(nèi)容都被放在了內(nèi)存里,包括任何圖片允青。如果有一個(gè)不會(huì)即刻用到的view橄碾,你這就是在浪費(fèi)寶貴的內(nèi)存資源了。Storyboards就是另一碼事兒了颠锉,storyboard僅在需要時(shí)實(shí)例化一個(gè)view controller.

當(dāng)家在XIB是堪嫂,所有圖片都被chache,如果你在做OS X開(kāi)發(fā)的話(huà)木柬,聲音文件也是。Apple在相關(guān)文檔中的記述是:

當(dāng)你加載一個(gè)引用了圖片或者聲音資源的nib時(shí)淹办,nib加載代碼會(huì)把圖片和聲音文件寫(xiě)進(jìn)內(nèi)存眉枕。在OS X中,圖片和聲音資源被緩存在named cache中以便將來(lái)用到時(shí)獲取怜森。在iOS中速挑,僅圖片資源會(huì)被存進(jìn)named caches。取決于你所在的平臺(tái)副硅,使用NSImage 或UIImage 的imageNamed:方法來(lái)獲取圖片資源姥宝。

很明顯,同樣的事情也發(fā)生在storyboards中恐疲,但我并沒(méi)有找到任何支持這個(gè)結(jié)論的文檔腊满。如果你了解這個(gè)操作,寫(xiě)信給我培己!

想要了解更多關(guān)于storyboards的內(nèi)容的話(huà)你可以看看 Matthijs Hollemans的Beginning Storyboards in iOS 5Part 1Part 2

5. 不要阻塞主線(xiàn)程

永遠(yuǎn)不要使主線(xiàn)程承擔(dān)過(guò)多碳蛋。因?yàn)閁IKit在主線(xiàn)程上做所有工作,渲染省咨,管理觸摸反應(yīng)肃弟,回應(yīng)輸入等都需要在它上面完成。

一直使用主線(xiàn)程的風(fēng)險(xiǎn)就是如果你的代碼真的block了主線(xiàn)程,你的app會(huì)失去反應(yīng)笤受。這穷缤。。箩兽。正是在App Store中拿到一顆星的捷徑 :]

大部分阻礙主進(jìn)程的情形是你的app在做一些牽涉到讀寫(xiě)外部資源的I/O操作津肛,比如存儲(chǔ)或者網(wǎng)絡(luò)。

你可以使用NSURLConnection異步地做網(wǎng)絡(luò)操作:

+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

或者使用像AFNetworking這樣的框架來(lái)異步地做這些操作比肄。

如果你需要做其它類(lèi)型的需要耗費(fèi)巨大資源的操作(比如時(shí)間敏感的計(jì)算或者存儲(chǔ)讀寫(xiě))那就用 Grand Central Dispatch快耿,或者 NSOperation 和 NSOperationQueues.

下面代碼是使用GCD的模板

C++

1

2

3

4

5

6

7

8dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

// switch to a background thread and perform your expensive operation

dispatch_async(dispatch_get_main_queue(),^{

// switch back to the main thread to update your UI

});

});

發(fā)現(xiàn)代碼中有一個(gè)嵌套的dispatch_async嗎?這是因?yàn)槿魏蜺IKit相關(guān)的代碼需要在主線(xiàn)程上進(jìn)行芳绩。

如果你對(duì) NSOperation 或者GCD 的細(xì)節(jié)感興趣的話(huà)掀亥,看看Ray Wenderlich的Multithreading and Grand Central Dispatch on iOS for Beginners,?還有 Soheil Azarpour 的How To Use NSOperations and NSOperationQueues教程妥色。

6. 在Image Views中調(diào)整圖片大小

如果要在UIImageView中顯示一個(gè)來(lái)自bundle的圖片搪花,你應(yīng)保證圖片的大小和UIImageView的大小相同。在運(yùn)行中縮放圖片是很耗費(fèi)資源的嘹害,特別是UIImageView嵌套在UIScrollView中的情況下撮竿。

如果圖片是從遠(yuǎn)端服務(wù)加載的你不能控制圖片大小,比如在下載前調(diào)整到合適大小的話(huà)笔呀,你可以在下載完成后幢踏,最好是用background thread,縮放一次许师,然后在UIImageView中使用縮放后的圖片房蝉。

7. 選擇正確的Collection

學(xué)會(huì)選擇對(duì)業(yè)務(wù)場(chǎng)景最合適的類(lèi)或者對(duì)象是寫(xiě)出能效高的代碼的基礎(chǔ)。當(dāng)處理collections時(shí)這句話(huà)尤其正確微渠。

Apple有一個(gè)Collections Programming Topics的文檔詳盡介紹了可用的classes間的差別和你該在哪些場(chǎng)景中使用它們搭幻。這對(duì)于任何使用collections的人來(lái)說(shuō)是一個(gè)必讀的文檔。

呵呵逞盆,我就知道你因?yàn)樘L(zhǎng)沒(méi)看…這是一些常見(jiàn)collection的總結(jié):

Arrays: 有序的一組值檀蹋。使用index來(lái)lookup很快,使用value lookup很慢云芦, 插入/刪除很慢俯逾。

Dictionaries: 存儲(chǔ)鍵值對(duì)。 用鍵來(lái)查找比較快焕数。

Sets: 無(wú)序的一組值纱昧。用值來(lái)查找很快,插入/刪除很快堡赔。

8. 打開(kāi)gzip壓縮

大量app依賴(lài)于遠(yuǎn)端資源和第三方API识脆,你可能會(huì)開(kāi)發(fā)一個(gè)需要從遠(yuǎn)端下載XML, JSON, HTML或者其它格式的app。

問(wèn)題是我們的目標(biāo)是移動(dòng)設(shè)備,因此你就不能指望網(wǎng)絡(luò)狀況有多好灼捂。一個(gè)用戶(hù)現(xiàn)在還在edge網(wǎng)絡(luò)离例,下一分鐘可能就切換到了3G。不論什么場(chǎng)景悉稠,你肯定不想讓你的用戶(hù)等太長(zhǎng)時(shí)間宫蛆。

減小文檔的一個(gè)方式就是在服務(wù)端和你的app中打開(kāi)gzip。這對(duì)于文字這種能有更高壓縮率的數(shù)據(jù)來(lái)說(shuō)會(huì)有更顯著的效用的猛。

好消息是耀盗,iOS已經(jīng)在NSURLConnection中默認(rèn)支持了gzip壓縮,當(dāng)然AFNetworking這些基于它的框架亦然卦尊。像Google App Engine這些云服務(wù)提供者也已經(jīng)支持了壓縮輸出叛拷。

如果你不知道如何利用Apache或者IIS(服務(wù)器)來(lái)打開(kāi)gzip,可以讀下這篇文章岂却。

中級(jí)性能提升

你確信你已經(jīng)掌握了前述那些基礎(chǔ)級(jí)的優(yōu)化方案了嗎忿薇?但實(shí)際情況是,有時(shí)一些解決方案并不像那些一樣明顯躏哩,它們往往嚴(yán)重依賴(lài)于你如何架構(gòu)和書(shū)寫(xiě)你的app署浩。下面的這些建議就是針對(duì)這些場(chǎng)景的。

9. 重用和延遲加載(lazy load) Views

更多的view意味著更多的渲染扫尺,也就是更多的CPU和內(nèi)存消耗筋栋,對(duì)于那種嵌套了很多view在UIScrollView里邊的app更是如此。

這里我們用到的技巧就是模仿UITableView和UICollectionView的操作: 不要一次創(chuàng)建所有的subview正驻,而是當(dāng)需要時(shí)才創(chuàng)建二汛,當(dāng)它們完成了使命,把他們放進(jìn)一個(gè)可重用的隊(duì)列中拨拓。

這樣的話(huà)你就只需要在滾動(dòng)發(fā)生時(shí)創(chuàng)建你的views,避免了不劃算的內(nèi)存分配氓栈。

創(chuàng)建views的能效問(wèn)題也適用于你app的其它方面渣磷。想象一下一個(gè)用戶(hù)點(diǎn)擊一個(gè)按鈕的時(shí)候需要呈現(xiàn)一個(gè)view的場(chǎng)景。有兩種實(shí)現(xiàn)方法:

1. 創(chuàng)建并隱藏這個(gè)view當(dāng)這個(gè)screen加載的時(shí)候授瘦,當(dāng)需要時(shí)顯示它醋界;

2. 當(dāng)需要時(shí)才創(chuàng)建并展示。

每個(gè)方案都有其優(yōu)缺點(diǎn)提完。

用第一種方案的話(huà)因?yàn)槟阈枰婚_(kāi)始就創(chuàng)建一個(gè)view并保持它直到不再使用形纺,這就會(huì)更加消耗內(nèi)存。然而這也會(huì)使你的app操作更敏感因?yàn)楫?dāng)用戶(hù)點(diǎn)擊按鈕的時(shí)候它只需要改變一下這個(gè)view的可見(jiàn)性徒欣。

第二種方案則相反-消耗更少內(nèi)存逐样,但是會(huì)在點(diǎn)擊按鈕的時(shí)候比第一種稍顯卡頓。

10. Cache, Cache, 還是Cache!

一個(gè)極好的原則就是,緩存所需要的脂新,也就是那些不大可能改變但是需要經(jīng)常讀取的東西挪捕。

我們能緩存些什么呢?一些選項(xiàng)是争便,遠(yuǎn)端服務(wù)器的響應(yīng)级零,圖片,甚至計(jì)算結(jié)果滞乙,比如UITableView的行高奏纪。

NSURLConnection默認(rèn)會(huì)緩存資源在內(nèi)存或者存儲(chǔ)中根據(jù)它所加載的HTTP Headers。你甚至可以手動(dòng)創(chuàng)建一個(gè)NSURLRequest然后使它只加載緩存的值斩启。

下面是一個(gè)可用的代碼段序调,你可以可以用它去為一個(gè)基本不會(huì)改變的圖片創(chuàng)建一個(gè)NSURLRequest并緩存它:

C++

1

2

3

4

5

6

7

8

9

10+(NSMutableURLRequest*)imageRequestWithURL:(NSURL*)url{

NSMutableURLRequest*request=[NSMutableURLRequestrequestWithURL:url];

request.cachePolicy=NSURLRequestReturnCacheDataElseLoad;// this will make sure the request always returns the cached image

request.HTTPShouldHandleCookies=NO;

request.HTTPShouldUsePipelining=YES;

[requestaddValue:@"image/*"forHTTPHeaderField:@"Accept"];

returnrequest;

}

注意你可以通過(guò) NSURLConnection 獲取一個(gè)URL request, AFNetworking也一樣的浇垦。這樣你就不必為采用這條tip而改變所有的networking代碼了炕置。

如果想了解更多關(guān)于HTTP caching, NSURLCache, NSURLConnection的相關(guān)知識(shí),可以讀下這篇文章()

如果你需要緩存其它不是HTTP Request的東西男韧,你可以用NSCache朴摊。

NSCache和NSDictionary類(lèi)似,不同的是系統(tǒng)回收內(nèi)存的時(shí)候它會(huì)自動(dòng)刪掉它的內(nèi)容此虑。 Mattt Thompson有一篇很棒的關(guān)于它的文章::http://nshipster.com/nscache/

如果你對(duì)HTTP感興趣可以讀下Google的這篇best-practices document on HTTP caching甚纲。

11. 權(quán)衡渲染方法

在iOS中可以有很多方法做出漂亮的按鈕。你可以用整幅的圖片朦前,可調(diào)大小的圖片介杆,uozhe可以用CALayer, CoreGraphics甚至OpenGL來(lái)畫(huà)它們韭寸。

當(dāng)然每個(gè)不同的解決方法都有不同的復(fù)雜程度和相應(yīng)的性能春哨。有一篇Apple UIKit team中的一員Andy Matuschak推薦過(guò)的很棒的關(guān)于graphic性能的帖子很值得一讀。

簡(jiǎn)單來(lái)說(shuō)恩伺,就是用事先渲染好的圖片更快一些赴背,因?yàn)槿绱艘粊?lái)iOS就免去了創(chuàng)建一個(gè)圖片再畫(huà)東西上去然后顯示在屏幕上的程序。問(wèn)題是你需要把所有你需要用到的圖片放到app的bundle里面晶渠,這樣就增加了體積 – 這就是使用可變大小的圖片更好的地方了: 你可以省去一些不必要的空間凰荚,也不需要再為不同的元素(比如按鈕)來(lái)做不同的圖。

然而褒脯,使用圖片也意味著你失去了使用代碼調(diào)整圖片的機(jī)動(dòng)性便瑟,你需要一遍又一遍不斷地重做他們,這樣就很浪費(fèi)時(shí)間了番川,而且你如果要做一個(gè)動(dòng)畫(huà)效果到涂,雖然每幅圖只是一些細(xì)節(jié)的變化你就需要很多的圖片造成bundle大小的不斷增大脊框。

總得來(lái)說(shuō),你需要權(quán)衡一下利弊养盗,到底是要性能能還是要bundle保持合適的大小缚陷。

12. 處理內(nèi)存警告

一旦系統(tǒng)內(nèi)存過(guò)低,iOS會(huì)通知所有運(yùn)行中app往核。在官方文檔中是這樣記述:

如果你的app收到了內(nèi)存警告箫爷,它就需要盡可能釋放更多的內(nèi)存。最佳方式是移除對(duì)緩存聂儒,圖片object和其他一些可以重創(chuàng)建的objects的strong references.

幸運(yùn)的是虎锚,UIKit提供了幾種收集低內(nèi)存警告的方法:

在app delegate中使用applicationDidReceiveMemoryWarning:的方法

在你的自定義UIViewController的子類(lèi)(subclass)中覆蓋didReceiveMemoryWarning

注冊(cè)并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知

一旦收到這類(lèi)通知,你就需要釋放任何不必要的內(nèi)存使用衩婚。

例如窜护,UIViewController的默認(rèn)行為是移除一些不可見(jiàn)的view, 它的一些子類(lèi)則可以補(bǔ)充這個(gè)方法非春,刪掉一些額外的數(shù)據(jù)結(jié)構(gòu)柱徙。一個(gè)有圖片緩存的app可以移除不在屏幕上顯示的圖片。

這樣對(duì)內(nèi)存警報(bào)的處理是很必要的奇昙,若不重視护侮,你的app就可能被系統(tǒng)殺掉。

然而储耐,當(dāng)你一定要確認(rèn)你所選擇的object是可以被重現(xiàn)創(chuàng)建的來(lái)釋放內(nèi)存锤岸。一定要在開(kāi)發(fā)中用模擬器中的內(nèi)存提醒模擬去測(cè)試一下契耿。

13. 重用大開(kāi)銷(xiāo)對(duì)象

一些objects的初始化很慢,比如NSDateFormatter和NSCalendar剩瓶。然而留特,你又不可避免地需要使用它們狐肢,比如從JSON或者XML中解析數(shù)據(jù)舱呻。

想要避免使用這個(gè)對(duì)象的瓶頸你就需要重用他們梁剔,可以通過(guò)添加屬性到你的class里或者創(chuàng)建靜態(tài)變量來(lái)實(shí)現(xiàn)。

注意如果你要選擇第二種方法哟旗,對(duì)象會(huì)在你的app運(yùn)行時(shí)一直存在于內(nèi)存中柳恐,和單例(singleton)很相似。

下面的代碼說(shuō)明了使用一個(gè)屬性來(lái)延遲加載一個(gè)date formatter. 第一次調(diào)用時(shí)它會(huì)創(chuàng)建一個(gè)新的實(shí)例热幔,以后的調(diào)用則將返回已經(jīng)創(chuàng)建的實(shí)例:

C++

1

2

3

4

5

6

7

8

9

10

11

12// in your .h or inside a class extension

@property(nonatomic,strong)NSDateFormatter*formatter;

// inside the implementation (.m)

// When you need, just use self.formatter

-(NSDateFormatter*)formatter{

if(!_formatter){

_formatter=[[NSDateFormatteralloc]init];

_formatter.dateFormat=@"EEE MMM dd HH:mm:ss Z yyyy";// twitter date format

}

return_formatter;

}

還需要注意的是,其實(shí)設(shè)置一個(gè)NSDateFormatter的速度差不多是和創(chuàng)建新的一樣慢的讼庇!所以如果你的app需要經(jīng)常進(jìn)行日期格式處理的話(huà)绎巨,你會(huì)從這個(gè)方法中得到不小的性能提升。

14. 使用Sprite Sheets

你是一個(gè)游戲開(kāi)發(fā)者嗎蠕啄,那么Sprite sheets一定是一個(gè)你的最好的朋友了场勤。Sprite sheet可以讓渲染速度加快戈锻,甚至比標(biāo)準(zhǔn)的屏幕渲染方法節(jié)省內(nèi)存。

我們有兩個(gè)很好的關(guān)于Sprite的教程:

How To Use Animations and Sprite Sheets in Cocos2D

How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats

第二個(gè)教程涵蓋了可能在很大程度上影響你游戲性能的pixel格式的細(xì)節(jié)和媳。

如果你對(duì)于spirte sheet還不是很熟悉格遭,可以看下這兩個(gè)(youtube)視頻SpriteSheets – The Movie,Part 1Part 2。視頻的作者是創(chuàng)建Sprite sheet很流行的工具之一Texture Packer的作者Andreas L?w留瞳。

除了使用Sprite sheets拒迅,其它寫(xiě)在這里的建議當(dāng)然也可以用于游戲開(kāi)發(fā)中。比如你需要很多的Sprite sheets她倘,像敵人璧微,導(dǎo)彈之類(lèi)的動(dòng)作類(lèi)必備元素,你可以重用這些sprites而不用每次都要重新創(chuàng)建硬梁。

15. 避免反復(fù)處理數(shù)據(jù)

許多應(yīng)用需要從服務(wù)器加載功能所需的常為JSON或者XML格式的數(shù)據(jù)前硫。在服務(wù)器端和客戶(hù)端使用相同的數(shù)據(jù)結(jié)構(gòu)很重要。在內(nèi)存中操作數(shù)據(jù)使它們滿(mǎn)足你的數(shù)據(jù)結(jié)構(gòu)是開(kāi)銷(xiāo)很大的荧止。

比如你需要數(shù)據(jù)來(lái)展示一個(gè)table view,最好直接從服務(wù)器取array結(jié)構(gòu)的數(shù)據(jù)以避免額外的中間數(shù)據(jù)結(jié)構(gòu)改變屹电。

類(lèi)似的,如果需要從特定key中取數(shù)據(jù)跃巡,那么就使用鍵值對(duì)的dictionary危号。

16. 選擇正確的數(shù)據(jù)格式

從app和網(wǎng)絡(luò)服務(wù)間傳輸數(shù)據(jù)有很多方案,最常見(jiàn)的就是JSON和XML瓷炮。你需要選擇對(duì)你的app來(lái)說(shuō)最合適的一個(gè)葱色。

解析JSON會(huì)比XML更快一些,JSON也通常更小更便于傳輸娘香。從iOS5起有了官方內(nèi)建的JSON deserialization就更加方便使用了苍狰。

但是XML也有XML的好處,比如使用SAX來(lái)解析XML就像解析本地文件一樣烘绽,你不需像解析json一樣等到整個(gè)文檔下載完成才開(kāi)始解析淋昭。當(dāng)你處理很大的數(shù)據(jù)的時(shí)候就會(huì)極大地減低內(nèi)存消耗和增加性能。

17. 正確設(shè)定背景圖片

在View里放背景圖片就像很多其它iOS編程一樣有很多方法:

使用UIColor的 colorWithPatternImage來(lái)設(shè)置背景色安接;

在view中添加一個(gè)UIImageView作為一個(gè)子View翔忽。

如果你使用全畫(huà)幅的背景圖,你就必須使用UIImageView因?yàn)閁IColor的colorWithPatternImage是用來(lái)創(chuàng)建小的重復(fù)的圖片作為背景的盏檐。這種情形下使用UIImageView可以節(jié)約不少的內(nèi)存:

C++

1

2

3// You could also achieve the same result in Interface Builder

UIImageView*backgroundView=[[UIImageViewalloc]initWithImage:[UIImageimageNamed:@"background"]];

[self.viewaddSubview:backgroundView];

如果你用小圖平鋪來(lái)創(chuàng)建背景歇式,你就需要用UIColor的colorWithPatternImage來(lái)做了,它會(huì)更快地渲染也不會(huì)花費(fèi)很多內(nèi)存:

C++

1

self.view.backgroundColor=[UIColorcolorWithPatternImage:[UIImageimageNamed:@"background"]];

18. 減少使用Web特性

UIWebView很有用胡野,用它來(lái)展示網(wǎng)頁(yè)內(nèi)容或者創(chuàng)建UIKit很難做到的動(dòng)畫(huà)效果是很簡(jiǎn)單的一件事材失。

但是你可能有注意到UIWebView并不像驅(qū)動(dòng)Safari的那么快。這是由于以JIT compilation為特色的Webkit的Nitro Engine的限制硫豆。

所以想要更高的性能你就要調(diào)整下你的HTML了龙巨。第一件要做的事就是盡可能移除不必要的javascript笼呆,避免使用過(guò)大的框架。能只用原生js就更好了旨别。

另外诗赌,盡可能異步加載例如用戶(hù)行為統(tǒng)計(jì)script這種不影響頁(yè)面表達(dá)的javascript。

最后秸弛,永遠(yuǎn)要注意你使用的圖片铭若,保證圖片的符合你使用的大小。使用Sprite sheet提高加載速度和節(jié)約內(nèi)存胆屿。

更多相關(guān)信息可以看下WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS

19. 設(shè)定Shadow Path

如何在一個(gè)View或者一個(gè)layer上加一個(gè)shadow呢奥喻,QuartzCore框架是很多開(kāi)發(fā)者的選擇:

C++

1

2

3

4

5

6

7

8

9#import

// Somewhere later ...

UIView*view=[[UIViewalloc]init];

// Setup the shadow ...

view.layer.shadowOffset=CGSizeMake(-1.0f,1.0f);

view.layer.shadowRadius=5.0f;

view.layer.shadowOpacity=0.6;

看起來(lái)很簡(jiǎn)單,對(duì)吧非迹。

可是环鲤,壞消息是使用這個(gè)方法也有它的問(wèn)題… Core Animation不得不先在后臺(tái)得出你的圖形并加好陰影然后才渲染,這開(kāi)銷(xiāo)是很大的憎兽。

使用shadowPath的話(huà)就避免了這個(gè)問(wèn)題:

view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

使用shadow path的話(huà)iOS就不必每次都計(jì)算如何渲染冷离,它使用一個(gè)預(yù)先計(jì)算好的路徑。但問(wèn)題是自己計(jì)算path的話(huà)可能在某些View中比較困難纯命,且每當(dāng)view的frame變化的時(shí)候你都需要去update shadow path.

想了解更多可以看看Mark Pospesel的這篇西剥。

20. 優(yōu)化Table View

Table view需要有很好的滾動(dòng)性能,不然用戶(hù)會(huì)在滾動(dòng)過(guò)程中發(fā)現(xiàn)動(dòng)畫(huà)的瑕疵亿汞。

為了保證table view平滑滾動(dòng)瞭空,確保你采取了以下的措施:

正確使用reuseIdentifier來(lái)重用cells

盡量使所有的view opaque,包括cell自身

避免漸變疗我,圖片縮放咆畏,后臺(tái)選人

緩存行高

如果cell內(nèi)現(xiàn)實(shí)的內(nèi)容來(lái)自web,使用異步加載吴裤,緩存請(qǐng)求結(jié)果

使用shadowPath來(lái)畫(huà)陰影

減少subviews的數(shù)量

盡量不適用cellForRowAtIndexPath:旧找,如果你需要用到它,只用一次然后緩存結(jié)果

使用正確的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)

使用rowHeight,sectionFooterHeight和sectionHeaderHeight來(lái)設(shè)定固定的高麦牺,不要請(qǐng)求delegate

21. 選擇正確的數(shù)據(jù)存儲(chǔ)選項(xiàng)

當(dāng)存儲(chǔ)大塊數(shù)據(jù)時(shí)你會(huì)怎么做钮蛛?

你有很多選擇,比如:

使用NSUerDefaults

使用XML, JSON, 或者 plist

使用NSCoding存檔

使用類(lèi)似SQLite的本地SQL數(shù)據(jù)庫(kù)

使用 Core Data

NSUserDefaults的問(wèn)題是什么剖膳?雖然它很nice也很便捷魏颓,但是它只適用于小數(shù)據(jù),比如一些簡(jiǎn)單的布爾型的設(shè)置選項(xiàng)吱晒,再大點(diǎn)你就要考慮其它方式了

XML這種結(jié)構(gòu)化檔案呢甸饱?總體來(lái)說(shuō),你需要讀取整個(gè)文件到內(nèi)存里去解析枕荞,這樣是很不經(jīng)濟(jì)的柜候。使用SAX又是一個(gè)很麻煩的事情。

NSCoding躏精?不幸的是渣刷,它也需要讀寫(xiě)文件,所以也有以上問(wèn)題矗烛。

在這種應(yīng)用場(chǎng)景下辅柴,使用SQLite 或者 Core Data比較好。使用這些技術(shù)你用特定的查詢(xún)語(yǔ)句就能只加載你需要的對(duì)象瞭吃。

在性能層面來(lái)講碌嘀,SQLite和Core Data是很相似的。他們的不同在于具體使用方法歪架。Core Data代表一個(gè)對(duì)象的graph model股冗,但SQLite就是一個(gè)DBMS。Apple在一般情況下建議使用Core Data和蚪,但是如果你有理由不使用它止状,那么就去使用更加底層的SQLite吧。

如果你使用SQLite攒霹,你可以用FMDB(https://github.com/ccgus/fmdb)這個(gè)庫(kù)來(lái)簡(jiǎn)化SQLite的操作怯疤,這樣你就不用花很多經(jīng)歷了解SQLite的C API了。

進(jìn)階性能提示

想要一些是你成為程序猿忍者的精英級(jí)的建議嗎催束?下面這些提示可以幫你把你的app優(yōu)化到極致集峦!

22. 加速啟動(dòng)時(shí)間

快速打開(kāi)app是很重要的,特別是用戶(hù)第一次打開(kāi)它時(shí)抠刺,對(duì)app來(lái)講塔淤,第一印象太太太重要了。

你能做的就是使它盡可能做更多的異步任務(wù)矫付,比如加載遠(yuǎn)端或者數(shù)據(jù)庫(kù)數(shù)據(jù)凯沪,解析數(shù)據(jù)。

還是那句話(huà)买优,避免過(guò)于龐大的XIB妨马,因?yàn)樗麄兪窃谥骶€(xiàn)程上加載的。所以盡量使用沒(méi)有這個(gè)問(wèn)題的Storyboards吧杀赢!

注意烘跺,用Xcode debug時(shí)watchdog并不運(yùn)行,一定要把設(shè)備從Xcode斷開(kāi)來(lái)測(cè)試啟動(dòng)速度

23. 使用Autorelease Pool

NSAutoreleasePool負(fù)責(zé)釋放block中的autoreleased objects脂崔。一般情況下它會(huì)自動(dòng)被UIKit調(diào)用滤淳。但是有些狀況下你也需要手動(dòng)去創(chuàng)建它。

假如你創(chuàng)建很多臨時(shí)對(duì)象砌左,你會(huì)發(fā)現(xiàn)內(nèi)存一直在減少直到這些對(duì)象被release的時(shí)候脖咐。這是因?yàn)橹挥挟?dāng)UIKit用光了autorelease pool的時(shí)候memory才會(huì)被釋放铺敌。

好消息是你可以在你自己的@autoreleasepool里創(chuàng)建臨時(shí)的對(duì)象來(lái)避免這個(gè)行為:

C++

1

2

3

4

5

6

7

8

9NSArray*urls=<# An array of file URLs #>;

for(NSURL*urlinurls){

@autoreleasepool{

NSError*error;

NSString*fileContents=[NSStringstringWithContentsOfURL:url

encoding:NSUTF8StringEncodingerror:&error];

/* Process the string, creating and autoreleasing more objects. */

}

}

這段代碼在每次遍歷后釋放所有autorelease對(duì)象

更多關(guān)于NSAutoreleasePool請(qǐng)參考官方文檔

24. 選擇是否緩存圖片

常見(jiàn)的從bundle中加載圖片的方式有兩種屁擅,一個(gè)是用imageNamed偿凭,二是用imageWithContentsOfFile,第一種比較常見(jiàn)一點(diǎn)派歌。

既然有兩種類(lèi)似的方法來(lái)實(shí)現(xiàn)相同的目的弯囊,那么他們之間的差別是什么呢?

imageNamed的優(yōu)點(diǎn)是當(dāng)加載時(shí)會(huì)緩存圖片胶果。imageNamed的文檔中這么說(shuō):

這個(gè)方法用一個(gè)指定的名字在系統(tǒng)緩存中查找并返回一個(gè)圖片對(duì)象如果它存在的話(huà)匾嘱。如果緩存中沒(méi)有找到相應(yīng)的圖片,這個(gè)方法從指定的文檔中加載然后緩存并返回這個(gè)對(duì)象早抠。

相反的霎烙,imageWithContentsOfFile僅加載圖片。

下面的代碼說(shuō)明了這兩種方法的用法:

C++

1

2

3UIImage*img=[UIImageimageNamed:@"myImage"];// caching

// or

UIImage*img=[UIImageimageWithContentsOfFile:@"myImage"];// no caching

那么我們應(yīng)該如何選擇呢贝或?

如果你要加載一個(gè)大圖片而且是一次性使用吼过,那么就沒(méi)必要緩存這個(gè)圖片,用imageWithContentsOfFile足矣咪奖,這樣不會(huì)浪費(fèi)內(nèi)存來(lái)緩存它盗忱。

然而,在圖片反復(fù)重用的情況下imageNamed是一個(gè)好得多的選擇羊赵。

25. 避免日期格式轉(zhuǎn)換

如果你要用NSDateFormatter來(lái)處理很多日期格式趟佃,應(yīng)該小心以待。就像先前提到的昧捷,任何時(shí)候重用NSDateFormatters都是一個(gè)好的實(shí)踐闲昭。

然而,如果你需要更多速度靡挥,那么直接用C是一個(gè)好的方案序矩。Sam Soffes有一個(gè)不錯(cuò)的帖子(http://soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments)里面有一些可以用來(lái)解析ISO-8601日期字符串的代碼,簡(jiǎn)單重寫(xiě)一下就可以拿來(lái)用了跋破。

嗯簸淀,直接用C來(lái)搞,看起來(lái)不錯(cuò)了毒返,但是你相信嗎租幕,我們還有更好的方案!

如果你可以控制你所處理的日期格式拧簸,盡量選擇Unix時(shí)間戳劲绪。你可以方便地從時(shí)間戳轉(zhuǎn)換到NSDate:

C++

1

2

3-(NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp{

return[NSDatedateWithTimeIntervalSince1970:timestamp];

}

這樣會(huì)比用C來(lái)解析日期字符串還快!

需要注意的是,許多web API會(huì)以微秒的形式返回時(shí)間戳贾富,因?yàn)檫@種格式在javascript中更方便使用歉眷。記住用dateFromUnixTimestamp之前除以1000就好了。

更多閱讀

下列這些WWDC視頻強(qiáng)烈推薦給想要提高app性能的開(kāi)發(fā)者颤枪。你首先需要保證你有使你的Apple ID注冊(cè)為一個(gè)開(kāi)發(fā)者身份才能看在這里看WWDC 2012的視頻姥芥。

#406: Adopting Automatic Reference Counting

#238: iOS App Performance: Graphics and Animations

#242: iOS App Performance: Memory

#235: iOS App Performance: Responsiveness

#409: Learning Instruments

#706: Networking Best Practices

#514: OpenGL ES Tools and Techniques

#506: Optimizing 2D Graphics and Animation Performance

#601: Optimizing Web Content in UIWebViews and Websites on iOS

#225: Up and Running: Making a Great Impression with Every Launch

一些01年的WWDC視頻也很有價(jià)值:

#308: Blocks and Grand Central Dispatch in Practice

#323: Introducing Automatic Reference Counting

#312: iOS Performance and Power Optimization with Instruments

#105: Polishing Your App: Tips and tricks to improve the responsiveness and performance

#121: Understanding UIKit Rendering

其它一些值得看的視頻,大部分來(lái)自iOS 5 Tech Talks

Your iOS App Performance Hitlist

Optimizing App Performance with Instruments

Understanding iOS View Compositing

基于《Your iOS App Performance Hitlist》這個(gè)Michael Jurewitz的視頻汇鞭,Ole Begemann寫(xiě)了一篇文字總結(jié)的文章

Apple提供了一個(gè)非常有用的叫做“Performance Tuning| 性能調(diào)優(yōu)”的資源庸追。

–EOF–

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末霍骄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子淡溯,更是在濱河造成了極大的恐慌读整,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咱娶,死亡現(xiàn)場(chǎng)離奇詭異米间,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)膘侮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)屈糊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人琼了,你說(shuō)我怎么就攤上這事逻锐。” “怎么了雕薪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵昧诱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我所袁,道長(zhǎng)盏档,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任燥爷,我火速辦了婚禮蜈亩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘局劲。我一直安慰自己勺拣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布鱼填。 她就那樣靜靜地躺著药有,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上愤惰,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天苇经,我揣著相機(jī)與錄音,去河邊找鬼宦言。 笑死扇单,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奠旺。 我是一名探鬼主播蜘澜,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼响疚!你這毒婦竟也來(lái)了鄙信?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤忿晕,失蹤者是張志新(化名)和其女友劉穎装诡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體践盼,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸦采,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咕幻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渔伯。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肄程,靈堂內(nèi)的尸體忽然破棺而出咱旱,到底是詐尸還是另有隱情,我是刑警寧澤绷耍,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布吐限,位于F島的核電站,受9級(jí)特大地震影響褂始,放射性物質(zhì)發(fā)生泄漏诸典。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一崎苗、第九天 我趴在偏房一處隱蔽的房頂上張望狐粱。 院中可真熱鬧,春花似錦胆数、人聲如沸肌蜻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒋搜。三九已至篡撵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間豆挽,已是汗流浹背育谬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帮哈,地道東北人膛檀。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像娘侍,于是被迫代替她去往敵國(guó)和親咖刃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,312評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)憾筏、插件僵缺、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,123評(píng)論 4 61
  • 1 一位發(fā)小從英國(guó)歸來(lái),她十幾歲的時(shí)候就隨父母移民海外翠胰,現(xiàn)在三十好幾了至今未婚容贝,一個(gè)人過(guò)著悠閑自在的單身生活,...
    韓小霞閱讀 1,097評(píng)論 0 1
  • 在WEB開(kāi)發(fā)世界里之景,PHP是最流行的語(yǔ)言之一斤富,從PHP里,你能夠很容易的找到你所需的腳本锻狗,遺憾的是满力,很少人會(huì)去用“...
    摩洛哥的日光閱讀 527評(píng)論 1 1
  • 我把鬧鈴設(shè)成你的耳畔細(xì)語(yǔ) 從那以后的每個(gè)清晨里 我就沒(méi)了起床氣 你抄下我寫(xiě)的每一行詩(shī)句 畫(huà)上五根線(xiàn),譜寫(xiě)旋律 哼唱...
    小小七閱讀 354評(píng)論 9 9