寫在前面
這篇文章是閱讀 Transitioning to ARC Release Notes 的筆記。
主要內容是關於 ARC 的規則。
簡介
Automatic Reference Counting(ARC) 作為一個編譯工具,自動管理 Objective-C 物件。
簡單地說,就是不再需要開發者使用 retain, release, autorelease 這些方法。
ARC 會在編譯時期,新增記憶體管理程式碼,確保物件儘可能地存活。
事實上,它使用的記憶體管理方式與 MRC 一致。
規則
-
不能呼叫 dealloc,不能呼叫或者覆寫 retain, release, retainCount, autorelease 也不能使用 @selector(retain) 這樣的形式去呼叫。 可以覆寫 dealloc,去處理 ARC 未能進行管理的物件,但不需要呼叫
[super dealloc]
。 Core Foundation 物件不受 ARC 管理,可繼續使用 CFRetain, CFRelease 進行管理。 -
不能使用 NSAllocateObject 或 NSDeallocateObject 可以使用 alloc 建立物件,執行期系統管理需要銷燬的物件。
-
不能在 C 結構體中,使用物件指標 建立 Objective-C 類去管理資料,而不是使用 struct。
-
轉換 id 和
void *
需要特定規則 -
不能使用 NSAutoreleasePool 物件 使用 @autoreleasepool{}
-
不需要使用 NSZone
-
存取方法名稱不能以
new
開頭// Won't work: @property NSString *newTitle; // Works: @property (getter=theNewTitle) NSString *newTitle; 複製程式碼
生命週期修飾詞
變數修飾詞
__strong
預設值,被修飾物件會一直存活到:沒有強引用指向它__weak
不會影響引用計數,當沒有指向的物件被銷燬時,指標會被設定成 nil__unsafe_unretained
不保證修飾物件存活,當沒有強引用時,也不會設定為 nil,即使物件被銷燬,指標還是指向它__autoreleasing
主要用來修飾傳遞引用的引數 常見的是傳遞 NSError 物件,返回後,NSError 物件會自動釋放。
正確使用形式
ClassName * qualifier variableName;
複製程式碼
其他形式在技術上來講是錯誤的,但編譯器“原諒”了它們。
當方法引數是個引用時,尤其需要注意,以下程式碼可以正常執行:
NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// Report the error.
// ...
}
複製程式碼
然而,實際 NSError
物件是這樣宣告的
NSError * __strong e;
複製程式碼
而其中的方法宣告是
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
複製程式碼
所以,編譯器會重寫程式碼:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
// Report the error.
// ...
}
複製程式碼
如果不希望編譯器這樣重寫程式碼的話,可以將 NSError 物件宣告成 __autoreleasing
。
使用修飾詞避免迴圈引用
使用 __weak
MyViewController *myController = [[MyViewController alloc] init];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler = ^(NSInteger result) {
// 新增 __strong,避免使用 weakMyViewController 時,它已經被釋放
MyViewController * __strong strongMyViewController = weakMyViewController;
[strongMyViewController dismissViewControllerAnimated:YES completion:nil];
};
複製程式碼
使用 __block,然後在 block 結束時,將引用的物件設為 nil。
MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
myController = nil;
};
複製程式碼
使用 @autoreleasepool{} 管理自動釋放池,比使用 NSAutoreleasePool 高效。
開發介面時,outlets 應該使用 weak 修飾。
棧上的變數,無論是 strong, weak 還是 autorelease,都預設初始化成 nil。
使用 -fobjc-arc
編譯器標誌來設定某個檔案,使用 ARC 環境。
使用 -fno-objc-arc
編譯器標誌來禁止某個檔案使用 ARC。
無縫橋接
-
__bridge 在 Objective-C 和 Core Foundation 物件間不轉換持有關係
-
__bridge_retained 或 CFBridgingRetain 可讓一個 Objective-C 指標轉換成 Core Foundation 指標,並持有它。 所以呼叫 CFRelease 或相關方法去釋放它
-
__bridge_transfer 或 CFBridgingRelease 將一個非 Objective-C 指標轉換成 Objective-C 指標,並持有它。 ARC 負責釋放它
以下程式碼,很好地展示了無縫橋接的使用:
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGFloat locations[2] = {0.0, 1.0};
NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
[colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
CGColorSpaceRelease(colorSpace); // Release owned Core Foundation object.
CGPoint startPoint = CGPointMake(0.0, 0.0);
CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient); // Release owned Core Foundation object.
}
複製程式碼
要點
-
不能呼叫 retain, release, autorelease
-
不能呼叫 dealloc
-
不能使用 NSAutoreleasePool 物件 取而代之的是 @autoreleasepool{},它的執行效率是 NSAutoreleasePool 的6倍。
-
ARC 需要將
[super init]
的結果賦值給 self。 所以一般是這樣寫self = [super init]; if (self) { ... } 複製程式碼
-
不需要實現 retain 或 release 方法 自定義 retain 或 release 會破壞弱指標。
目前 ARC 的效率已經足夠高了,若發現問題,可以提交 bug。
- ARC 環境下,預設使用 strong 修飾符
- 不能在 C 語言結構體裡使用 strong ids 替代方式:使用 Objective-C 物件替代。
如果不行,就將 Objective-C 物件轉換成 void*
(使用 __unsafe_unretained
修飾)。
- 不能直接轉換 id 和 void* (包括 Core Foundation),需要使用無縫橋接
struct x { NSString * __unsafe_unretained S; int X; }
複製程式碼
FAQ
blocks 在 ARC 下是如何工作的?
blocks 在它處於棧頂的時候工作,不需要呼叫 Block copy。
需要注意的是 NSString * __block myString
在 ARC 模式下,是會被持有的,而不是一個危險指標。
在 ARC 環境下,如何建立一個 C 語言陣列?
示例程式碼:
// Note calloc() to get zero-filled memory.
__strong SomeClass **dynamicArray = (__strong SomeClass **)calloc(entries, sizeof(SomeClass *));
for (int i = 0; i < entries; i++) {
dynamicArray[i] = [[SomeClass alloc] init];
}
// When you're done, set each entry to nil to tell ARC to release the object.
for (int i = 0; i < entries; i++) {
dynamicArray[i] = nil;
}
free(dynamicArray);
複製程式碼
需要注意的是:
- 有時需要新增
__strong SomeClass **
,因為預設是__autoreleasing SomeClass **
- 被分配的記憶體必須是
zero-filled
- 在釋放陣列時,必須將每個物件設定成 nil(無法使用 memset 或 bzero)
- 你應該要避免使用
memcpy
或realloc
ARC 是否會比較慢
這跟如何使用有關係,但一般來說,不會慢。
編譯器高效地減少無關的 ratain 或 release 呼叫,而且做了很多加速 Objective-C 執行的工作。
特別地,在 ARC 下,呼叫 “return a retain/autoreleased objec” 的物件,也不會每次都被放到自動釋放池中。
可以在 debug 模式下,建立大量物件,讓 reatain 和 release 不斷被呼叫,可以觀察到,使用的時間接近0。
ARC 是否支援 ObjC++ 模式?
支援。 可以將 strong / weak 物件放到類或容器裡,ARC 編譯器會把 retain / release 邏輯複製到“copy constructors and destructors” 中。
哪些類不支援弱引用?
NSATSTypesetter, NSColorSpace, NSFont, NSMenuView, NSParagraphStyle, NSSimpleHorizontalTypesetter, and NSTextView.
在這種情況下,宣告屬性,需要使用 assign 代替 weak;宣告變數,需要使用 __unsafe_unretained。
當繼承 NSCell 或其他使用 NSCopyObject 的類,需要額外做些什麼?
不需要。
在 ARC 下,所有的複製方法僅僅複製例項變數。