您好,登錄后才能下訂單哦!
我建了一個iOS開發QQ交流群:188647173,大家可以一起來相互學習。
還有一個群里面大神的個人站點www.mylonly.com,大家有不會的可以向他請教。
iOS的對象都繼承自NSObject,NSOjbect對象有一個方法retainCount,用以獲取對象的內存引用計數。一般情況下,一下幾種情況會使對象的引用計數發生改變,
(1)alloc(或new) 對象分配后,引用計數為1
(2)retain 調用retain方法(或者說向對象發送retain消息),對象的引用計數增加1
(3)copy 調用copy方法(或者說向對象發送copy消息),創建一個新的對象,其引用計數為1;原來對象的引用計數不變。
(4)release 向對象發送release消息,對象內存引用計數較少1,如果引用計數為0,那么就釋放對象所占有的內存
(5)autorelease 想對象發送autorelease消息,對象引用計數較少1,如果引用計數為0,不會馬上釋放對象內存,等到最近一個自動釋放池autoreleasepool時候釋放。
那么我們以代碼添加一個UIImageView為例,看看內存管理的代碼應該怎么寫,
第一種情況,局部變量的內存管理
- (void)viewDidLoad
{
[super viewDidload];
UIImageView *p_w_picpathView = [[UIImageView alloc] initWithImage:...];//通過alloc/init創建對象,retainCount=1
[self.view addSubview:p_w_picpathView];//將其添加到superView,引用計數加1,retainCount=2
[p_w_picpathView release];//向對象發送release消息,引用計數減1,retainCount=1
}
上面的代碼[self.view addSubview:p_w_picpathView];造成p_w_picpathView引用計數增加1,可能讓人不明白,所以這里需要說明一下,在子視圖添加到父視圖的時候,子視圖的引用計數會自動的增加1,當父視圖被release的時候,該父視圖上面的所有子視圖的引用計數都會被release一次,使子視圖的引用計數減1,這樣一增一減保持了retain/release的平衡。
這段話很重要,請仔細理解:關于上面的UIImageView對象p_w_picpathView的引用計數變化和生命周期是這樣的,當使用alloc/init創建p_w_picpathView對象的時候,它的引用計數retainCount=1;將它將入到父視圖的時候,引用計數增加1,retainCount=2;向其發送release消息時候,引用計數減少1,retainCount=1;最后,在ViewController的dealloc方法中,self.view被釋放,self.view就是p_w_picpathView的父視圖,這時候self.view上面的所有子視圖都會收到release消息,當p_w_picpathView收到release消息的時候,引用計數減少1, retainCount=0。這時候p_w_picpathView對象內存被安全釋放,該對象的生命周期到此結束。
好了,上面的情況講述的就是在一對括號內{}創建局部變量時候遵循的手動管理內存的規則,那就是你通過alloc/init創建了對象,那你就需要在使用結束以后向其發送release消息。
第二種情況,成員變量的內存管理
什么是成員變量,以一個ViewController1舉例說明,
@interface ViewController1:NSObject
{
//括號內的變量就是成員變量
Person *_person;
NSString *_name;
NSString *_schoolName;
}
@end
上面括號內就定義了三個成員變量,分別是_person、_name、_schoolName,成員變量最好以'_'下劃線開頭,接著我們在viewDidLoad中對三個對象分別初始化,成員如下所示,
- (void)viewDidLoad
{
[super viewDidLoad];
//alloc/init創建對象
_person = [[Person alloc] init];
_schoolName = [[NSString alloc] initWithFormat:@"yyy"];
//類方法創建對象
_name = [NSString stringWithFormat:@"xxx"];
}
上面用兩種不同的方法創建了對象,一是使用alloc/init創建,二是使用類方法創建,因為成員變量我們需要在整個ViewController作用域都需要使用到,所以我們不能立即向其發送release消息,我們應該在dealloc時候向其發送release消息,如下代碼,
- (void)dealloc
{
[_person release];
[_schoolName release];
[super dealloc];
}
我們在dealloc中向兩個通過alloc/init方式創建的對象_person和_schoolName發送release消息,為什么不向_name發送release消息呢,這是因為_name是通過NSString的類方法stringWithFormat:創建的,而類方法返回的對象已經在方法內部加入了自動釋放池,它的引用計數和生命周期已經交給了自動釋放池來管理,不需要向其發送release消息了。
上面描述了“成員變量”內存管理的規則,如果是通過alloc/init創建的對象,那么需要在dealloc時候向其發送release消息;如果不是通過alloc/init方法創建的對象,例如通過類方法創建的,那么不需要在dealloc時候向其發送release消息,比如_name = [NSString stringWithFormat:@"xxx"];或者數組_petArray = [NSArray arrayWithObject:@"dog",@"cat",@"duck",nil];。
第三種,屬性@property對象的內存管理
上面說的兩種對象都是ViewController類內部管理的對象,這些對象不會與其他類有交互,所以說內存管理的規則相對簡單。如果類中的變量需要與其他類進行交互,這時候就需要用到@property屬性來定義變量。我們一般這樣編寫@property的代碼,
@property (nonatomic, assign) TestObject *testObject;//默認情況是assign
//或者
@property (nonatomic, retain) TestObject *testObject;
//又或者
@property (nonatomic, copy) TestObject *testObject;
我們知道,當我們定義屬性的時候,Xcode會根據修飾關鍵字(例如retain、assign、copy)自動生成getter/setter,不同修飾符的getter方法沒什么差別,主要就是setter有很大的不同,那么我們來不同修飾符時候setter方法不同的地方,
//assign修飾符
- (void)setTestObject:(id)newValue
{
testObject = newValue;
}
assign修飾符,相當于指針賦值。對象的引用計數不發生改變。注意源對象不用了,一定要把這個對象設置為nil
//retain修飾符
- (void)setTestObject:(id)newValue
{
if(testObject != newValue){
[testObject release];
[newValue retain];
testObject = newValue;
}
}
retain修飾符的setter方法表示,先向原來的值發送release,然后向新的值newValue發送retain消息,使其引用計數增加1,然后新的值newValue賦值給原來的值。
//copy修飾屬性
- (void)setTestObject:(id)newValue
{
if(testObject != newValue){
[testObject release];
testObject = [newValue copy];
}
}
copy修飾符的setter方法就是,先向原來的值發送release,然后復制一份新的值賦值給testObject,它的引用計數為1。
下面主要說一說retain修飾符修飾的屬性,在使用的時候需要注意的地方,我們知道當我們使用self.testObject的時候有兩種情況,一種是左值例如self.testObject = [[TestObject alloc] init];,另一種是又值例如TestObject *tempTestObject = self.testObject。
當我們使用self.testObject作為右值的時候,相當于調用了getter方法,這沒有什么需要注意的地方;當使用self.testObject作為左值的時候,就有一些注意的地方了,例如下面的語句,
self.testObject = [[TestObject alloc] init];//這是錯誤的,因為alloc/init創建的對象,在setter內部又被retain了一次,導致引用計數retainCount=2,造成內存泄露
//修改代碼如下
TestObject *tempTestObject = [[TestObject alloc] init];
self.testObject = tempTestObject;
[tempTestObject release];
這樣就保證了self.testObject引用計數為1,在整個類的作用域都可以放心安全使用,然后在dealloc的時候向該對象再發送release對象,釋放其內存空間,
- (void)dealloc
{
[self.testObject release];
[super dealloc];
}
還有一種情況,self.testObject是通過TestObject的類方法初始化的,這時候就可以直接初始化,如下代碼,
self.testObject = [TestObject testObjectWithClassMehtod:xxx];//這樣寫沒有問題
這樣創建的對象是被類方法放入自動釋放池來管理其引用計數和生命周期,而不用擔心內存泄露的問題。雖然這時候內存不需要我們來手動釋放了,但是為了編碼規范,還是在dealloc時候向其發送release消息,
- (void)dealloc
{
[self.testObject release];
[super dealloc];
}
下面的內容可能比較多,但是我是詳細的列出了每一個細節和不同,讀者也可以按照這種方式去總結,應該會有幫助,我自己就是這樣總結,感覺對于內存管理的了解越來越明確了。
通過實際的例子來說明問題,下面我來描述一個場景:點擊ViewController1導航欄上面的rightBarButtonItem,push到ViewController2頁面,ViewContorller2需要用到Student類,那么我們在ViewController2內部定義Student時候就有很多細節需要注意了,我會分情況討論,
(1)Student作為ViewController2的成員變量
前面我說過,為了讓自己和別人一眼就看出這是一個成員變量,最好在成員變量的前面加上'_'下劃線,所以這時候我們這樣在ViewController2中使用Student作為成員變量,
ViewController2.h file
@interface ViewController2:UIViewController
{
Student *_student;
}
@end
ViewController2.m file
@implementation ViewController2
- (void)dealloc
{
[_student release];
[super dealloc];
}
- (void)viewDidLoad
{
[super viewDidLoad];
_student = [[Student alloc] init];//引用計數為1,即retainCount=1
}
@end
(2)不定義Student成員變量_student,定義Student屬性,不寫@synthesie代碼
ViewController2.h file
@interface ViewController:UIViewController
{
//這里不定義成員變量_student
}
@property (nonatomic, retain) Student *student;
@end
ViewController2.m file
@implementation ViewController2
//沒有寫@synthesize
- (void)viewDidLoad
{
[super viewDidLoad];
//第一種實例化方法
Student *tempStudent = [[Student alloc] init];//tempStudent引用計數為1
self.student = tempStudent;//tempStudent引用計數為2,self.student引用計數為2
[tempStudent release];//tempStudent引用計數為1,self.student引用計數為1
//這時候定義的self.student引用計數為1,可以在ViewController2整個作用域范圍內使用,要在dealloc中向其發送release消息
//第二種實例化方法
_student = [[Student alloc] init];
//需要解釋一下這個代碼,我們這時候沒有定義成員變量,為什么還可以使用_student呢,我也不明白為什么,應該是屬性property自動幫助我們生成了對應的成員變量,我之前還有所疑惑,后來查看了(注釋1)_student和self.student的內存地址,發現兩者的內存地址相同。所以,我們定義了屬性@property (nonatomic, retain) Student *student;,就可以直接使用_student成員變量。
//那么接著解釋一下此時的內存吧,這時候_student的引用計數為1,self.student引用計數為1
}
- (void)dealloc
{
//dealloc方法怎么寫?
//如果你是使用第一種方式實例化self.student,為了對應,那么就用這種方式釋放對象
[self.student release];
//如果你使用第二種方式實例化,那么就用下面的方式發送release消息
[_student release];
//需要說明的是,其實上面兩種方式,釋放對象都是可以的,這里只是為了與實例化方法對應。
[super dealloc];
}
@end
(3)不定義Student成員變量_student,定義Student屬性,寫@synthesize sutdent;,而不寫@synthesize sutdent = _student;
ViewController2.h file
@interface ViewController:UIViewController
{
//不定義成員變量_student
}
@property (nonatomic, retain) Student *student;
@end
ViewController2.m file
@implementation ViewController2
//注意這里不是@synthesize student = _student;這是下面會說到的
@synthesize student;
- (void)viewDidLoad
{
[super viewDidLoad];
//這時候有兩種實例化方法
//第一種實例化方法
Student *tempStudent = [[Student allloc] init];//引用計數retainCount=1
self.student = tempStudent;//引用計數self.student's retainCount=2,tempStudent's retainCount=2
[tempStudent release];//引用計數tempStudent's retainCount=1,self.student's retainCount=1
//第二種實例化方法
student = [[Student alloc] init];
}
@end
- (void)dealloc
{
//釋放對象,對應第一種實例化方法
[self.student release];
//釋放對象,對應第二種實例化方法
[super dealloc];
//當然,這兩種釋放對象的方式其實都可以,只是為了對應實例化方法
}
需要特別注意的是,我們寫了@synthesize student;而不是@synthesize student = _student;這時候,就不可以在ViewController2作用域范圍內使用_student這個成員變量了。
(4)不定義Student成員變量,定義Student屬性,寫@synthesize student = _student,而不是@synthesize sutdent;
ViewController2.h file
@interface ViewController2:UIViewControler
{
//不定義_student成員變量
}
@property (nonatomic, retain) Student *student;
@end
ViewController2.m file
@implementation ViewContorller2
//這里不是@synthesize sutdent;
@synthesze student = _student;
- (void)viewDidLoad
{
[super viewDidLoad];
//第一種實例化方法,不在解釋引用計數
Student *tempStudent = [[Student alloc] alloc];
self.student = tempStudent;
[tempStudent release];
//第二種實例化方法
_student = [[Student alloc] release];
}
- (void)dealloc
{
//釋放對象,對應第一種實例化方法
[self.student release];
//釋放對象,對應第二種實例化方法
[_student release];
[super dealloc];
}
@end
(5)定義成員變量_student,定義student屬性,不寫@synthesize
ViewController2.h
@interface ViewController2:UIViewContorller
{
Student *_student;
}
@property (nonatomic, retain) Student *student
@end
ViewContorller2.m
@implementation ViewContorller2
//不寫@synthesize
- (void)viewDidLoad
{
[super viewDidLoad];
//第一種實例化方法
Student *tempStudent = [[Student alloc] init];
self.student = tempStudent;
[tempStudent release];
//第二種實例化方法
_student = [[Student alloc] init];
}
- (void)deallc
{
//釋放對象,對應第一種實例化方法
[self.student release];
//釋放對象,對應第二種實例化方法
[_student release];
[super dealloc];
}
@end
(6)定義Student成員屬性_student,定義Student屬性,寫@synthesize student;,而不寫@syntheise student = _student;
ViewController.h
@interface ViewContorller:UIViewContorller
{
Student *_sutdent;
}
@property (nonatomic, retain) Student *student
@end
ViewController2.m file
@implementation ViewController2
//這里不是@synthesize student = _student;
@synthesieze student;
- (void)viewDidLoad
{
[super viewDidLoad];
//第一種實例化方法
Student *tempStudent = [[Student alloc] init];
self.student = tempStudent;
[tempStudent release];
//第二種實例化方法
student = [[Student alloc] init];
}
- (void)dealloc
{
//釋放對象,對應第一種實例化方法
[self.student release];
//釋放對象,對應第二種實例化方法
[student release];
[super dealloc];
}
@end
(7)定義Student成員屬性_student,定義Student屬性,寫@synthesize student = _student;,而不寫@syntheise student;
ViewController.h
@interface ViewController2:UIViewContorller
{
Student *_student;
}
@property (nonatomic, retain) Student *student;
@end
ViewController2.m file
@implementation ViewController2
@synthesize student = _student;
- (void)viewDidLoad
{
[super viewDidLoad];
//第一種實例化方法
Student *tempStudent = [[Student alloc] init];
self.student = tempStudent;
[tempStudent release];
//第二種實例化方法
_student = [[Student alloc] init];
}
- (void)dealloc
{
//釋放對象,對應第一種實例化方法
[self.student release];
//釋放對象,對應第二種實例化方法
[_student release];
[super dealloc];
}
@end
上面我自己羅列了我所知道的定義成員變量和屬性時候用到的所有方式,如果有遺漏還請讀者指出,這里面的代碼我都驗證了有效性,可能在寫博客的時候會有一些書寫方面的疏漏,有錯誤也請讀者指正。
總結:
如果定義了屬性student,則自動生成_student成員變量;如果寫了@synthesize student,此時只能在ViewController2作用域范圍內使用student和self.student,而不能使用_student;如果寫了@synthesize student = _student,則在ViewController2作用域范圍內只能使用_student和self.studetn,而不能使用student,也就是說_student和student不能共存,只能使用其一。
注釋1:怎樣查看一個對象的內存呢?在使用該對象的地方打一個斷點,當運行到斷點時候,在控制臺使用po命令工具,例如我想知道self.student內存地址,那么我可以在斷點時候在控制臺中這樣做,po self.student,如下圖,
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。