IOS5 中最具顛覆性的變化當屬自動引用計數(Automatic Reference Counting)的引入,縮寫為 ARC。ARC 是新的 LLVM 3.0 編譯器具備的特性之一,這項技術完全摒棄了讓所有 IOS 開發者由愛生恨的手動內存管理。在你的工程中使用 ARC非常簡單。你還像往常一樣編程,只是不再調用 retain,release 和 autorelease 了。這基本上就是 ARC 的全部。如果開啟了自動引用計數,編譯器就會在你程序中的恰當位置,插入 retain,release 和 autorelease。你不必再為這個操心,因為編譯器會替你搞定。
ARC 基本上是 LLVM 3.0 編譯器而非 iOS5 的新特征,所以你也可以將它用在 iOS4.0 及以上版本上。ARC 唯一需要 iOS5 的部分是弱指針。這意味着如果你想在 iOS4 上 部署 ARC,你就不能使用弱屬性或者__weak 變量。
認識到 ARC 是 objective-c 編譯器的一個特性是很重要的,因此與 ARC 相關的一切都發生在構建你的程序時。ARC 不是一個運行時特性(有一小部分例外,就是弱指針系統), 它也不是你從其他語言了解的垃圾回收。ARC 所作的只是在編譯代碼時向其中插入 retain 和 release,就在你會自己添加他們的地 方 -- 或者至少是你應該添加他們的地方。這就使 ARC 和手動管理代碼一樣快速,有時候 甚至更快一些,因為它私下可以執行某些優化。
諸如firstName 和 textField 這類指針稱為"強"(""strong")指針,因為他們保持了對象的存在。默認情況下,成員變量和局部變量是強指針。 同樣也存在"弱"指針("weak" pointer)。弱指針變量仍然何以指向對象,但是不再成為所有者。
1 __weak NSString *weakName = self.textField.text;
weakName 指針變量指向和 textField.text 屬性相同的 string 對象,但不再是所有者。如果文本框的內容改變,該 string 對象不再有所有者,所以被釋放了。當這發生(對象被釋放)時,weakNae 的值被自動置為 nil。即所謂"歸零"("zeroing")弱指針。
注意這是特別便利的特性,因為它防止了弱指針指向已被釋放的內存。這類事情過去曾導致了大量的 BUG -- 你可能聽說過"野指針" 或者 "僵屍" -- 但是感謝這些歸零弱指針,那些事不再會發生!你大概不會頻繁使用弱指針。他們在兩個對象是父子關系時最有用。父母會對孩子擁有 強指針 --- 因此"擁有"孩子 -- 但是為了防止所有權循環,孩子僅對父母擁有弱指針。
1 __weak NSString *str = [[NSString alloc] initWithFormat:...];
2 NSLog(@"%@", str); // will output "(null)"
string 對象沒有所有者(因為 str 是弱指針),所以對象會在創建后立刻被釋放。Xcode 在你做這件事的時候會給出一個警告因為這可能並非是你所希望發生的事情("Warning: assigning retained object to weak variable; object will be released after assignment")。
自動引用計數也有一些限制。作為起步,ARC 只對 objective-c 有效。如果你的程序使用 core fundation 或者 malloc()和 free(),那么你仍然對其內存管理負有責任。
AFHTTPRequestOperation.h/.m: AFNetworking 庫的一部分,它使對 web service 的請 求更易於執行。 https://github.com/gowalla/AFNetworking
SVProgresHUD.h/.m/.bundle 一個會在搜索時顯示於屏幕上進度指示器。你以前可能 沒見過,bundle 文件。這是一個特殊類型的文件夾,它包含了 SVProgressHUD 要用 到的圖片文件。
要查看這些文件,可以右鍵點擊.bundle 文件,選擇“查看包內容” (Show Package Contents)菜單選項。 https://github.com/samvermette/SVProgressHUD
1.xcode 有一個自動轉化工具,能夠轉換你的代碼。
2.你可以手工轉化這些文件。
3.你可以對你不希望轉化的源文件禁用 ARC。
ARC 是新的 LLVM 3.0 編譯器的一個特性。你的現有工程很可能使用的是早先的 gcc 4.2 或者 LLVM-GCC 編譯器,所以你應該首先將工程切換到新編譯器,看看在非 ARC 模式下是否存在錯誤。
轉到 Project Settings 屏幕,選中 Artists target,在 Build Setting 的搜 索框中輸入"compiler"。修改"Compiler for C/C++/Objective-C"選項,選擇 Apple LLVM compiler 3.0+。
在 warning 抬頭部分,還要將"Other Warning Flags"設置為-wall。編譯器現在會檢查所有可能導致問題的狀況。默認情況下,大多數這類警告都被關掉了,但是我發現總是將他們當 作致命錯誤是很有用的。
在 Build Options 抬頭下的將 Run Static Analyzer 設置為YES.
Build Settings 屏幕中,切換到"All"來查看所有可用的設置項(和 Basic 不一樣, Basic 僅僅顯示常用設置項)。搜索"automatic"並設置"Objective-C Automatic Reference Counting"選項為 YES。
xcode 有一個自動轉化工具,能夠轉換你的代碼,選擇"Edit\Refactor\Convert to Objective-C ARC"。
一般說來,iOS 中基於 C 的 API 會使用 core fundation 對象(CF 就代表它),而基於 Objective-C 的 API 使用由 NSObject 擴展而來的真正的對象。有時候你需要在這二者之間 進行轉換,而這是免費橋接技術所允許的。
Apple 引入了一組關鍵字:__bridge, __bridge_transfer 和 __bridge_retained 。
@property (strong, nonatomic) strong 關鍵字代表你的意圖。它告訴 ARC 屬性背后被同步的 ivar 擁有對對象的強引用。也就是說,window 屬性包含了一個指向 UIWindow 對象的指針,同時它也成為了 UIWindow 對象的所有者。只要 window 屬性還保存着 UIWindow 對象的值,它(UIWindow 對象)就仍然存在於內存中。
在 ARC 以前,如果屬性被聲名為 retain,下面的代碼會導致內存泄漏:
1 self.someProperty = [[SomeClass alloc] init];
init 方法返回一個被保留的對象,而將其賦給這個屬性會再次保留它。這就是你為什么 要用 autoreleae,為了平衡 init 方法中的 retain。但是用 ARC 的話,上面的代碼是沒問題的。
編譯器足夠聰明,它知道這里不應該做兩次 retain。
我喜愛 ARC 的特性之一是,在大多數情況下,你都不必寫 dealloc 方法。 有時dealloc 方法還是必要的。大多數情況下,你可以忘掉 dealloc,編譯器會幫你 搞定。但是有時,你還是需要手動釋放資源。這個類就是這種情況。
當 SoundEffect 對象被 析構時,我們仍然需要調用 AudioServicesDisposeSystemSoundID()來清理聲音對象,dealloc 就是調用它的絕佳位置。
類擴展很酷的地方在於,它允許你為類添加私有屬性和方法名。如果你不希望將某些屬 性或方法暴露在公共的@interface 中的話,你可以用類擴展。
我們將 Build Settings 內的 Objective-C Automatic Reference Counting 設置為 YES 時,已經在程序范圍內啟用了 ARC。但是通過使用-fno-objc-arc 標志,你可以讓編譯器對特定的文件不使用 ARC。
xcode 會以關閉 ARC 的方式對這些文件進行編譯。
"Cast ... requires a bridged cast"
這是我們之前碰到過的。如果編譯器不能自己確定如何轉換,他會期待你插入一個 __bridge 修飾符。另外還有兩種 bridge 類型,__bridge_trasfer 和__bridge_retained,你要使用 哪一個完全取決於你想做什么。
"ARC forbids Objective-C objects in structs or unions"
ARC 的限制之一是,你不再能夠將 Objective-C 對象放到 C 結構中了。
一般情況下,實例變量應該是 類的內部實現的一部分,而不是你想暴露在公共接口中的東西。對類的使用者來說,類的成 員變量是什么無關緊要。
從數據隱藏的角度來看,我們應該將這些實現細節放到類的 @implementation 部分。
1 #import <UIKit/UIKit.h>
2 @interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate>
3 @property (nonatomic, retain) IBOutlet UITableView *tableView; 4 @property (nonatomic, retain) IBOutlet UISearchBar *searchBar; 5 @end
6
7 @implementation MainViewController { 8 NSOperationQueue *queue;
9 NSMutableString *currentStringValue;
10 }
這項技術讓你的.h 文件更加簡潔,而將成員變量放到他們真正屬於的地方。
保留 dealloc 方法的唯一情況是,當你需要釋放某些不在 ARC 保護傘之下的資源時。比如說對 Core Fundation 對象調用 CFRelease(),對使用 malloc()分配的內存調用 free(),反注 冊通知,停止記時器,等等。
作為最佳實踐,如果你定義了一個屬性,你應該總是使用屬性。唯一需要訪問屬性背后的成員變量的地方是在 init 中,或者當你提供自定義的 getter 和 setter 方法時。這就是為什么同步聲明經常修改成員變量的原因:
@synthesize propertyName = _propertyName;
這種結構將防止你意在使用"self.propertyName"的時候,輸入"propertyName"從而誤用了 其背后的成員變量。
@property (nonatomic, weak) IBOutlet UITableView *tableView;
@property (nonatomic, weak) IBOutlet UISearchBar *searchBar;
對於所有的 outlet 屬性推薦使用弱關系。這些視圖對象已經是視圖控制器層次結構的一部分了,不需要在別的地方 retain 它。
將 outlet 聲明為弱引用的最大好處是,你不需要再花時間編寫你的 viewDidUnload 方法了。
因為 tableView 和 searchBar 屬性是弱引用,他們在所指向的對象被銷毀后自動被 設為 nil。這就是我們所謂的“歸零”弱指針。
• strong. 是 retain 的同義詞。一個強屬性會成為所指向對象的所有者。
• weak. 這個屬性代表一個弱指針。當所指向的對象被釋放時,他會自動被設為 nil。記 住,對於 outlet 使用它。
• unsafe_unretained.這是原來的"assign"的同義詞。它只在特殊情況下以及你想將目標設 為 iOS4 時使用。后面會講到它。
• copy. 這還是和以前一樣。這將制作對象的一份拷貝,並創建強關系。
• assign. 你能再為對象使用它了,但你還是可以用於基礎類型如 BOOL, int 和 float。
CFSTR()宏用一個指定的字符串創建一個 CFStringRef 對象。這個字符串常量是一個標 准的 C 字符串,所以不需要以@打頭。不需要將 NSString 對象轉換為 CFStringRef,我們能直接得到一個 CFStringRef 對象。
當你在兩個世界之間移動對象時,橋接轉換(bridged cast)是必要的。一邊是 objective-c 的世界,另一邊是 Core Foundation。
在所有情況下,NSString 和 CFStringRef 可以當作一個東西對待。你可以接受一個 NSString 對象,把它當作一個 CFStringRef 對象,或者將 CFStringRef 對象用作 NSString。這 就是免費橋接背后的思想。之前這只需要做一個簡單的轉換:
1 CFStringRef s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name]; 2
3 // ....
4
5 CFRelease(s1); 6
7
8 CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, bytes, kCFStringEncodingMacRoman);
9 NSString *s3 = (NSString *)s2; 10 // release the object when you're done
11 [s3 release];
現在我們有了 ARC,編譯器需要知道誰負責釋放那些轉換的對象。如果你將 NSObject 作為 Core Fundation 對象,那么 ARC 不會負責釋放它。但你確實需要告訴 ARC 你的意圖, 編譯器不能自己來推斷。同樣的,如果你創建了一個 Core Fundation 對象但將其轉換為了 NSObject 對象,你就需要告訴 ARC 得到它的所有權,並及時釋放它。這就是橋接轉換要做 的。
1 - (NSString *)escape:(NSString *)text 2 { 3 return (NSString *)CFURLCreateStringByAddingPercentEscapes( 4 NULL, 5 (__bridge CFStringRef)text, 6 NULL, 7 CFSTR("!*'();:@&=+$,/?%#[]"), 8 CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
9 }
• __bridge 當你想將一種類型臨時當作另一種類型使用,而不轉移所有權時,需要使用__bridge, ARC 仍舊管理着它。
• __bridge_transfer: 給予 ARC 所有權
1 NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name]; 2 CFStringRef s2 = (__bridge_retained CFStringRef)s1; 3 // do something with s2 // . . .
4 CFRelease(s2);
• __bridge_retained: 解除 ARC 的所有權
不是所有的 Objective-c 和 Core Fundation 對象可以免費橋接的。比如,CGImage 和 UIImage 就不能彼此轉換,CGColor 和 UIColor 也不行。
我們已經添加了一個委托協議,它帶有一個方法,同時添加了一個遵守該協議的屬性。 注意該屬性被聲明為"weak"。保持委托指針為弱指針是必須的,這樣可以防止所有權循環 (ownership cycles)。
你應該熟悉保留循環(retain cycle)的概念,兩個對象互相保留,這會導致他們都得不到 釋放。這是內存泄漏的常見形式。在實現垃圾回收(GC)處理內存管理的系統中,垃圾搜 集器可以識別這種循環,並會釋放他們。但是 ARC 不是垃圾回收,仍然需要你自己處理所 有權循環。弱指針是打斷這類循環的重要工具。
除了 strong 和 weak,還有一個新的修飾符,unsafe_unretained。一般情況下你不會用到 它。編譯器不會為被聲明為 unsafe_unretained 的變量或屬性自動添加 retain 或 release。
這個新修飾符采用"unsafe"來組成它的名字,這是因為它可以指向一個不再存在的對 象。如果你試着使用那種指針,你的程序極有可能崩潰。這是需要用 NSZombieEnabled 調 試工具來找出的問題。技術上講,如果你不使用任何 unsafe_unretained 屬性或變量,你絕不 會再向已經被釋放的對象發送消息。
大多數時間里,你都希望用 strong,有時也用 weak,幾乎不會用 unsafe_unretained。 unsafe_unretained 仍然還存在的原因,是為了兼容 iOS4,在 iOS4 中弱指針系統不可用,另 外的原因是為了一些其他的技巧。