iOS-舊項目中手動內存管理(MRC)轉ARC

在ARC以前,iOS內存管理不管對資深級仍是菜鳥級開發者來講都是一件很頭疼的事。我參 加過幾個使用手動內存管理的項目,印象最深入的是一個地圖類應用,因爲應用自己就很是耗內存,當時爲了解決內存泄露問題,每週都安排有人值班用 Instruments挨個跑功能,關鍵是每次都總能檢查出來很多。其實不論是菜鳥級仍是資深級開發者都避免不了寫出內存泄露的代碼,規則你們都懂,但是 天知道何時手一抖就少寫了個release?html

好在項目決定轉成ARC了,下面將本身轉換的過程和中間遇到的問題寫出來和你們共享,但願能減小你們解決同類問題的時間。ios

1、前言objective-c

項目簡介app

須要轉換的Objective-C文件數量:1000個左右。工具

開發工具:Xcode 6.0.1開發工具

轉換方式網站

我使用的是Xcode自己提供的ARC轉換功能。固然你也能夠手動手動轉換,那不屬於本文範疇,並且其工做量絕對能讓你崩潰。ui

2、轉換過程this

代碼備份spa

在進行如此大規模的更改以前,必定要先進行代碼備份:直接在本地將代碼複製一份,或者記住更改前代碼在VCS上的版本號。

過濾無需轉換的文件

找出項目中引用的仍使用手動內存管理的第三方庫,或者某些你不但願轉換的文件,對其添加-fno-objc-arc標記。

Xcode自動轉換工具只針對Objective-C對象,只會處理Objective-C/Objective-C++即後綴名爲.m/.mm的兩種文件,所以其餘的C/C++對應的.c/.cpp都無需理會。

執行檢查操做

使用Xcode轉換工具入口如圖所示:

refactor.png

點擊Convert to Objective-C ARC後會進入檢查操做入口,如圖:

066.png

該步驟要選擇哪些文件須要轉換,若是前面將無需轉換的文件都添加了-fno-objc-arc標記後,這裏能夠全選。

點擊check按鈕後Xcode會幫助咱們檢查代碼中存在的不符合ARC使用規則的錯誤或警告,只有全部的錯誤都解決之後才能執行真正的轉換操做。

解決錯誤/告警

執行完check操做後,會給出提示:

error.png

三百多個錯誤,同時還有一千兩百多個警告信息,都要哭了。。。

錯誤和警告的解決內容較多,後面會單獨介紹。

執行轉換操做

解決完全部的error後,會彈出下述提示界面:

t20_18_link_binary1.png

大意是Xcode將要將你的工程轉換成使用ARC管理內存,全部更改的代碼在真正更改以前 會在一個review界面展現。同時全部的更改完成之後,Xcode會講項目Target對應的工程設置的使用ARC設置(Objective-C Automatic Reference Counting)會被置成YES(上圖右上角的警告標識就是在告訴咱們項目已經支持ARC了,但工程中有文件還不支持):

069.png

這時離成功就不遠了,勝利在望!

點擊next按鈕後跳轉到review界面,樣式相似於使用Xcode提交SVN的確認提交界面,以下圖所示:

070.png

該界面列出了全部須要有代碼更改的文件,同時可以直接對比轉換前和轉換後的代碼變化。爲了穩妥起見,我選擇了每一個文件都點進去掃了一眼,這也給咱們一次機會檢查是否漏掉了不能轉換的文件。肯定一切無誤之後,點擊右下角的save按鈕,一切就大功告成了!

錯誤/警告解決

錯誤

ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute

071.png

property屬性必須指定一個內存管理關鍵字,在屬性定義處增長strong關鍵字便可。

ARC forbids explicit message send of ‘release’

072.png


這種狀況一般是使用包含release的宏定義,將該宏和使用該宏的地方刪除便可。

Init methods must return a type related to the receiver type

073.png

錯誤緣由是A類裏的一個方法以init開頭,並且返回的是B類型,好吧,乖乖改方法名。

Cast of C pointer type ‘ivPointer’ (aka ‘void ’) to Objective-C pointer type ‘iFlyTTSManager_old ’ requires a bridged cast

cast_pointer_objective-c

074.png

這是Toll-Free Bridging轉換問題,在ARC下根據狀況使用對應的轉換關鍵字就好了,後文會專門介紹。

警告

解決警告的目的是消除警告處代碼存在的隱患,既然Xcode給了提示,那麼每個警告信息都值得咱們認真對待。

Capturing self in this block is likely to lead to a retain cycle

075.png

這是典型的block循環引用問題,將block中的self改爲使用指向self的weak指針便可。

Using ‘initWithArray:’ with a literal is redundant

076.png

好吧,原來是不必的alloc操做,直接按Xcode提示將alloc刪除便可:

077.png

Init methods must return a type related to the receiver type

078.png

原來是A類裏的一個方法以init開頭,並且返回的是B類型,好吧,乖乖改方法名。

Property follows Cocoa naming convention for returning ‘owned’ objects

079.png

這是由於@property屬性的命名以new開頭了,可惡。。。修改方法是將對應的getter方法改爲非new開頭命名的:

080.png

ARC下方法名若是是以new/alloc/init等開頭的,並且還不是類的初始化方法,就該當心了,要麼報錯,要麼警告,緣由你懂的。

Block implicitly retains ‘self’; explicitly mention ‘self’ to indicate this is intended behavior

081.png

意思是block中使用了self的實例變量 _selectedModeMarkerView,所以block會隱式的retain住self。Xcode認爲這可能會給開發者形成困惑,或者所以而 因襲循環引用,因此警告咱們要顯示的在block中使用self,以達到block顯示retain住self的目的。

該警告有兩種改法: ①按照Xcode提示,改爲self->_selectedModeMarkerView:

082.png

②直接將該警告關閉 警告名稱爲:Implicit retain of ‘self’ within blocks 對應的Clang關鍵字是:-Wimplicit-retain-self

083.png

Weak property may be unpredictably set to nil 和 Weak property ‘delegate’ is accessed multiple times in this method but may be unpredictably set to nil; assign to a strong variable to keep the object alive

084.png

這是工程中數目最多的警告,這是由於全部的delegate屬性都是weak的,Xcode默認開啓了下圖中的兩個警告設置,將其關閉便可:

085.png

Capturing ‘self’ strongly in this block is likely to lead to a retain cycle

086.png

這是明顯的block致使循環引用內存泄露的狀況,以前代碼中坑啊!修改方案:

087.png

Method parameter of type ‘NSError __autoreleasing ’ with no explicit ownership

088.png

這種就不用說了,按警告中的提示添加__autoreleasing關鍵字便可。

以上列出的錯誤和警告只是數量較多的,還有不少其餘就不在這裏一一列舉了。

另外,推薦  Mattt Thompson 大神關於Clang中幾乎全部warning的名稱和對應報錯提示語的網站:http://fuckingclangwarnings.com/,之後解決warning類問題就簡單多了!

Xcode自動轉換

關鍵字轉換

Xcode會自動將某些關鍵字自動轉換成ARC的對應版本。

retain自動轉成strong,如圖:

089.png

assign關鍵字轉成weak

修飾Objective-C對象或者id類型對象的assign關鍵字會被轉成weak,如圖:

090.png

可是修飾Int/bool等數值型變量的assign不會自動轉換成weak,如圖:

091.png

關鍵字刪除

和手動內存管理相關的幾個關鍵字,好比:release/retain/autorelease/super dealloc等會被刪除;

dealloc方法中若是除了release/super dealloc語句外,若是別的代碼,dealloc方法會保留,如圖:

092.png

若是沒有整個方法都會被刪除:

093.png

關鍵字替換

在轉換時block關鍵字會被自動替換成weak:

094.png


@autoreleasepool

NSAutoreleasePool不支持ARC,會被替換成@autoreleasepool:

095.png

關於被宏註釋代碼

使用宏定義的對象釋放代碼

宏定義以下所示:

1
2
#define RELEASE_SAFELY(__POINTER) { \
[(__POINTER) release]; (__POINTER) = nil; }

在執行ARC轉換檢查操做時,Xcode會在使用該宏的地方報錯:

096.png

將該宏和使用該宏的地方刪除便可。

被宏註釋掉的代碼,Xcode在轉換時是不會處理的,如圖:

097.png

PS:這是至關坑的一點,由於你根本預料不到工程中使用了多少宏,註釋掉了多少代碼。當你執行完轉換操做,覺得就大功告成的時候,卻在某天由於一個宏的開啓遇到了一堆新的轉ARC不完全的問題。這種問題也沒招,只能遇到一個改一個了。

ARC和block

不論是手動內存管理仍是ARC,block循環引用致使的內存泄露都是一個使人頭疼的問題。在MRC中,解決block循環引用只須要使用__block關鍵字,在ARC下解決與block的使用就略顯複雜了:

__block關鍵字

block內修改外部定義變量

和手動內存管理同樣,ARC若是在block中須要修改block以外定義的變量須要使用__block關鍵字修飾,好比:

1
2
3
4
__block NSString *name = @ "foggry" ;
self.expireCostLabel.completionBlock = ^(){
     name = @ "wangzz" ;
};

上例中name變量須要在block中修改,所以必須使用__block關鍵字。

__block在MRC和ARC中的區別

在ARC下的block中使用__block關鍵字修飾的對象時,block會retain該對象;而在MRC下卻不會retain。關於這點在官方文檔Transitioning to ARC Release Notes中有詳細的描述:

In manual reference counting mode, block id x; has the effect of not retaining x. In ARC mode, block id x; defaults to retaining x (just like all other values).

下面的代碼無論在MRC仍是ARC中myController對象都是有內存泄露的:

1
2
3
4
5
MyViewController *myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
};

內存泄露問題在MRC中能夠按以下方式更改:


1
2
3
4
5
MyViewController * __block myController = [[MyViewController alloc] init…];
// ...  
myController.completionHandler =  ^(NSInteger result) {
     [myController dismissViewControllerAnimated:YES completion:nil];
};

然而在ARC中這麼改就不行了。正如開始所說的那樣,在ARC中myController.completionHandler的block會retainmyController對象,使得內存泄露問題仍然存在!!

在ARC中該問題有兩種解決方案,第一種:

1
2
3
4
5
6
MyViewController * __block myController = [[MyViewController alloc] init…];
// ...  
myController.completionHandler =  ^(NSInteger result) {
     [myController dismissViewControllerAnimated:YES completion:nil];
     myController = nil;
};

該方法在block中使用完myController時,是它指向nil。沒有strong類型的指針指向myController指向的對象時,對象會被釋放掉。

第二種種解決方案,直接使用weak代替block關鍵字:

1
2
3
4
5
6
MyViewController *myController = [[MyViewController alloc] init…];
// ...  
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler =  ^(NSInteger result) {
     [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};

該方法直接避免了對block對myController對象的retain。

存在循環引用關係

若是self直接或者間接的對block存在強引用,在block中又須要使用self關鍵字,此時self和block就存在循環引用的關係。此時必須使用__weak關鍵字定義一個指針指向self,在block中使用該指針來引用self:

1
2
3
4
MessageListController * __weak weakSelf = self;
self.messageLogic.loadMoreBlock = ^(IcarMessage * theMessage) {
     [weakSelf.tableView setPullTableIsLoadingMore:YES];
};

須要說明的是,儘管上例中weakSelf指針對self只是弱引用,可是self對block倒是強引用,self的生命週期必定是長於block的,所以不用擔憂在block中使用weakSelf指針時,其指向的self會被釋放掉。

不存在循環引用關係

下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
     MyViewController *strongMyController = weakMyController;
     if  (strongMyController) {
         // ...
         [strongMyController dismissViewControllerAnimated:YES completion:nil];
         // ...
     }
     else  {
         // Probably nothing...
     }
};

如前面所說,myController.completionHandler的block中不能直接使用myController對象,會形成內存泄露, 所以須要先用一個weak的指針指向myController對象,而後在block中使用該weak指針。可是爲了確保在block執行的時候 myController對象沒有被釋放掉,就在block一開始的地方定義了一個臨時的strong類型的指針strongMyController指 向weak指針weakMyController,其實最終的結果就是block中對myController對象強引用了。在block執行完被銷燬的 時候,strongMyController指針變量會被銷燬,其最終指向的myController對象所以也會被銷燬。這樣在使用一個對象的時候作就 保證了該對象是存在的,使用完了再放棄該對象的全部權。

ARC和Toll-Free Bridging

MRC下的Toll-FreeBridging不涉及內存管理的轉移,Objective-C(後文簡稱OC)和Core Foundation(後文簡稱CF)各自管理各自的內存,相互之間能夠直接交換使用,好比:

1
2
NSLocale *gbNSLocale = [[NSLocale alloc] initWithLocaleIdentifier:@ "en_GB" ];
CFLocaleRef gbCFLocale = (CFLocaleRef)gbNSLocale;

而在ARC下,事情就會變得複雜一些。由於ARC可以管理OC對象的內存,卻不能管理CF對象,CF對象依然須要咱們手動管理內存。在CF和OC之間 bridge對象的時候,問題就出現了,編譯器不知道該如何處理這個同時有OC指針和CF指針指向的對象。這時候,須要使用__bridge, __bridge_retained, __bridge_transfer等修飾符來告訴編譯器該如何去作。

__bridge

它告訴編譯器仍然負責管理好在OC一端的引用計數的事情,開發者也繼續負責管理好在CF一端的事情,好比:

1
2
3
4
CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault,  "CFString" , kCFStringEncodingUTF8);
NSString *ocString = (__bridge NSString *)cfString;
CFRelease(cfString);
NSLog(@ "%@" ,ocString);

__bridge_retained 或 CFBridgingRetain

兩者做用是同樣的,只是用法不一樣。

告訴編譯器須要retain對象,而開發者在CF一端負責釋放。這樣就算對象在OC一端被釋放,只要開發者不釋放CF一端的對象, 對象就不會被真的銷燬。

1
2
3
4
5
6
NSArray *ocArray = [[NSArray alloc] initWithObjects:@ "foggry" , nil];
CFArrayRef cfArray = (__bridge_retained CFArrayRef)ocArray;
/**
  使用cfArray
  **/
CFRelease(cfArray);

__bridge_transfer 或 CFBridgingRelease

兩者做用也是同樣的,只是用法不一樣。

該關鍵字告訴編譯器bridge的同時,也轉移了對象的全部權,好比:

1
2
3
4
CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault,  "CFString" , kCFStringEncodingUTF8);
NSString *ocString = (__bridge_transfer NSString *)cfString;
//CFRelease(cfString); //再也不須要釋放操做
NSLog(@ "%@" ,ocString);

轉換過程當中你們只須要根據具體需求選用適當的關鍵字便可。

另外,在ARC中id和void *也不能直接相互轉換了,必須經過Toll-FreeBridging使用適當的關鍵字修飾。

ARC和IBOutLet

對於IBOutLet屬性應該用strong仍是weak一直都有疑惑。關於這一點官方文檔是這麼介紹的:

From a practical perspective, in iOS and OS X outlets should be defined as declared properties. Outlets should generally be weak, except for those from File’s Owner to top-level objects in a nib >>>file (or, in iOS, a storyboard scene) which should be strong. Outlets that you create should therefore typically be weak.

那麼長的一段英文想說的是:若是nib文件構建的view是直接被Controller引用的頂層view,對應的IBOutLet屬性應該是strong;

若是view是頂層view上的一個子view,那麼該view的屬性應該是weak,由於頂層view被Controller使用strong屬性引用了,而頂層view自己又持有該view;

若是Controller對某個view須要單獨引用,或者Controller沒有引用某個view的父view,那麼其屬性也應該是strong。

好吧,其實我能說若是你實在懶得區分何時用strong,何時用weak,那就將因此後的IBOutLet屬性都設成strong吧!在 Controller銷燬的時候,對應的IBOutLet實例變量也會被銷燬,strong指針會被置成nil,所以也不會有內存問題。

參考文檔

Transitioning to ARC Release Notes

Managing the Lifetimes of Objects from Nib Files

Nib Memory Management

相關文章
相關標籤/搜索