所有的 Objective-C 物件都必須要由指標來建立。一般我們很習慣在 C 語言裡面,使用 malloc 產生一塊可以讓指標所指向的「值」。在物件導向設計裡面,我們簡單的稱這個「值」叫做「實體」(instance)。蘋果在設計 Cocoa 的基礎物件 NSObject 裡面,設計了一個簡單的物件方法 (Object Method): +(id)alloc,可以讓產生一個鄉對應物件所需要的一塊實體的記憶體空間,並且回傳實體的指標。當實體被成功建立之後,我們必須要使用 NSObject 實體方法 (Instance Method) 的 -(id)init 來初始化它。這一條基本規則建立起 Retain Counting 所需具備的一切條件。所有繼承 NSObject 的子孫物件都必須要遵守這一條規則。
OWNObject *val;
// call object method [object_name method_name]
val = [OWNObject alloc];
// call instance method [var_name method_name]
val = [val init];
Retain Counting 的機制很簡單,物件建立之後,所有繼承 NSObject 的物件都會有一個 retainCount 的 變數 (ivar) 這個變數會在方法 -(id)init 的時候變成 1,-(void)retain 的時候增加, -(void) release 的時候減少。當這個數值變成 0 的時候,物件會自動呼叫 -(void)dealloc,並且將記憶體釋放出來。
// retain count = 1
val = [val init];
// retain count = 2
[val retain]
// retain count = 1
[val release]
// retain count = 0
[val release] -> call [self dealloc] automatically
!記住,千萬不可以自己去呼叫 -(void)dealloc,程式會崩潰!
講到這裡,會有很多人覺得這個跟 Java 的 Reference Counting 的機制不是一樣嗎?其實是不同的。在這裡,記憶體的分配、釋放都是由物件:也就是應用程式本身來執行,而 Java 的 Reference Counting 則是由 Garbage Collocation Daemon 一段時間去掃描所有在記憶體中的物件,找出 Reference Count = 0 的實體並且釋放。這裡雖然遺留了 C 語言在動態記憶體分配之後,程式設計師必須要自己管控記憶體的工作,也有些現代程式語言的精巧。
雖然說蘋果在最新的 LLVM Compiler 3.0 裡面,新增了 Auto Retain Count 的機制,可以讓編譯器幫助你決定在程式碼的哪一個部位 retain、release。但是自動化機制總是會犧牲掉一般程式設計師可以掌握的彈性。我認為理解清楚這個機制,可以有助益於任何記憶體管理方面的進步!
9 則留言:
最近新版的xcode / ios 已經變得好奇怪,
我print出剛init完的retainCount竟然是-1
如果你是用 lldb 當 Debugger 的話,試試看改回 GDB 或許會正常一點。
是用simulator, GDB 6.3.50-20050815.
NSString *str = [[NSString alloc] initWithString:@"test~~"];
NSLog(@"count=%d!", [str retainCount]);
===> count=-1!
喔,因為你用的是 NSString 來看 retain count。OBJC 的編譯器很聰明,他發現你是用一個 Constant String 去 Initial 他,所以他並不會真的去產生一個新的 NSString 的 Instance,而是系統所產生的 NSConstantString,並且把指標 assign 給你,所以你得到的 retain count 會是 UINT_MAX,也就是 signed int 的 -1。
你可以試試看:
char *s = "test";
NSString *str = [[NSString alloc] initWithCString:s encoding:NSUTF8StringEncoding];
NSLog(@"%d", [str retainCount])
[str release];
原來是這樣,我以為是@autorelease的關係...
剛剛有發現一個怪問題,release完之後,
str的retainCount還可以印,但值還是1 ???
不是應該要歸0? 又或者是應該印了會crash... 因為memory已經free了?
char *s = "test";
NSString *str = [[NSString alloc] initWithCString:s encoding:NSUTF8StringEncoding];
NSLog(@"%d", [str retainCount]);
[str release];
//======
NSLog(@"after release: %d", [str retainCount]);
在 debug 階段的時候的 memory management 的行為會跟 release 不一樣。為了讓 programmer 在特殊時候得到某些 object 的信息,autorelease pool 或是 framework 會維持住 object 的 instance。所以 retainCount 這個 method 不太適用在 debug 的設定下。
你可以看到 Xcode 的 Profile build 預設是用 release build,就是這個原因。
謝啦~ 了解~ 獲益良多~
但是初期開發大部分是用simulator...
如果有需要用retainCount查看資源是否正常釋放,不就... 要改用其他方法?
我只能說:靠經驗
如果覺得經驗不夠,容易犯錯的話,可以用
product > analyze
不過,程式化的東西都不是百分之百正確的,得要自己判斷。
不好意思,想再請教...
大部分看到範例:
@interface MainViewController : UITableViewController
{
NSMutableArray *menuList;
}
@property (nonatomic, retain) NSMutableArray *menuList;
@end
最近有看到一些範例:
@interface TransitionsViewController : UIViewController
@property (nonatomic, retain) UIView *containerView;
@property (nonatomic, retain) UIImageView *mainView;
@property (nonatomic, retain) UIImageView *flipToView;
- (IBAction)curlAction:(id)sender;
- (IBAction)flipAction:(id)sender;
@end
//======
想請教,這兩種寫法有甚麼差別嗎?
張貼留言