歡迎加入QQ討論群258996829
來自星星的你 頭像
蘋果3袋
3
來自星星的你

Swift如何優(yōu)化性能?

發(fā)布時間:2015-03-09 20:39  回復:0  查看:2784   最后回復:2015-03-09 20:39  

Swift在內(nèi)存管理上使用的是自動引用計數(shù)(ARC)的一套方法,在ARC中雖然不需要手動地調(diào)用像是retain,release或者是autorelease這樣的方法來管理引用計數(shù),但是這些方法還是都會被調(diào)用的——只不過是編譯器在編譯時在合適的地方幫我們加入了而已。其中retain和release都很直接,就是將對象的引用計數(shù)加一或者減一。但是autorelease就比較特殊一些,它會將接受該消息的對象放到一個預先建立的自動釋放池 (auto release pool)中,并在自動釋放池收到drain消息時將這些對象的引用計數(shù)減一,然后將它們從池子中移除(這一過程形象地稱為“抽干池子”)。


在App中,整個主線程其實是跑在一個自動釋放池里的,并且在每個主Runloop結束時進行drain操作。這是一種必要的延遲釋放的方式,因為我們有時候需要確保在方法內(nèi)部初始化的生成的對象在被返回后別人還能使用,而不是立即被釋放掉。

在Objective-C中,建立一個自動釋放池的語法很簡單,使用@autoreleasepool就行了。如果你新建一個Objective-C項目,可以看到main.m中就有我們剛才說到的整個項目的autoreleasepool:


int main(int argc, char *argv[]) {  
    @autoreleasepool {  
        int retVal = UIApplicationMain(  
            argc,   
            argv,   
            nil,   
            NSStringFromClass([AppDelegate class]));  
        return retVal;  
    }  
}  

更進一步,其實@autoreleasepool在編譯時會被展開為NSAutoreleasePool,并附帶drain方法的調(diào)用。

而在Swift項目中,因為有了@UIApplicationMain,我們不再需要main文件和main函數(shù),所以原來的整個程序的自動釋放池就不存在了。即使我們使用main.swift來作為程序的入口時,也是不需要自己再添加自動釋放池的。

但是在一種情況下我們還是希望自動釋放,那就是在面對在一個方法作用域中要生成大量的autorelease對象的時候。在Swift 1.0時,我們可以寫這樣的代碼:

func loadBigData() {  
    if let path = NSBundle.mainBundle()  
        .pathForResource("big", ofType: "jpg") {  
        for i in 1...10000 {  
            let data = NSData.dataWithContentsOfFile(  
                path, options: nil, error: nil)  
            NSThread.sleepForTimeInterval(0.5)  
        }          
    }  
}  

dataWithContentsOfFile返回的是autorelease的對象,因為我們一直處在循環(huán)中,因此它們將一直沒有機會被釋放。如果數(shù)量太多而且數(shù)據(jù)太大的時候,很容易因為內(nèi)存不足而崩潰。在Instruments下可以看到內(nèi)存alloc的情況:

這顯然是一幅很不妙的情景。在面對這種情況的時候,正確的處理方法是在其中加入一個自動釋放池,這樣我們就可以在循環(huán)進行到某個特定的時候施放內(nèi)存,保證不會因為內(nèi)存不足而導致應用崩潰。在Swift中我們也是能使用autoreleasepool的——雖然語法上略有不同。相比于原來在Objective-C中的關鍵字,現(xiàn)在它變成了一個接受閉包的方法:

func autoreleasepool(code: () -> ())  
利用尾隨閉包的寫法,很容易就能在Swift中加入一個類似的自動釋放池了:

func loadBigData() {  
    if let path = NSBundle.mainBundle()  
        .pathForResource("big", ofType: "jpg") {  
        for i in 1...10000 {  
            autoreleasepool {  
                let data = NSData.dataWithContentsOfFile(  
                    path, options: nil, error: nil)  
                NSThread.sleepForTimeInterval(0.5)  
            }  
        }          
    }  
}   

這樣改動以后,內(nèi)存分配就沒有什么憂慮了:

這里我們每一次循環(huán)都生成了一個自動釋放池,雖然可以保證內(nèi)存使用達到最小,但是釋放過于頻繁也會帶來潛在的性能憂慮。一個折中的方法是將循環(huán)分隔開加入自動釋放池,比如每10次循環(huán)對應一次自動釋放,這樣能減少帶來的性能損失。

其實對于這個特定的例子,我們并不一定需要加入自動釋放。在Swift中更提倡的是用初始化方法而不是用像上面那樣的類方法來生成對象,而且在Swift 1.1中,因為加入了可以返回nil的初始化方法,像上面例子中那樣的工廠方法都已經(jīng)從API中刪除了。今后我們都應該這樣寫:

let data = NSData(contentsOfFile: path)  

使用初始化方法的話,我們就不需要面臨自動釋放的問題了,每次在超過作用域后,自動內(nèi)存管理都將為我們處理好內(nèi)存相關的事情。

作者:王?。?a target="_blank">@onevcat),iOS和Unity3D開發(fā)者。

本文轉(zhuǎn)載自:Swifter

您還未登錄,請先登錄

熱門帖子

最新帖子

?