每個OC對象都有自己的引用計數器,是一個四字節的整數,表示“對象被引用的次數”,即有多少人正在使用這個OC對象
每個OC對象內部專門有4個字節的存儲空間來存儲引用計數器(面試題)
引用計數器的作用
當使用alloc、new或者copy創建一個新對象時,新對象的引用計數器默認就是1,當一個對象的引用計數器值為0時,對象占用的內存就會被系統回收。換句話說,如果對象的計數器不為0,那么在整個程序運行過程,它占用的內存就不可能被回收,除非整個程序已經退出。
引用計數器的操作
給對象發送一條retain消息(代表調用這個對象方法),可以使引用計數器值+1(retain方法返回對象本身)
給對象發送一條release消息,可以使引用計數器值-1,沒有返回值
可以給對象發送retainCount消息獲得當前的引用計數器值
對象的銷毀(IOS6之前)
當一個對象的引用計數器值為0時,那么它將被銷毀,其占用的內存被系統回收,
一旦重寫了dealloc方法,就必須調用[super dealloc],并且放在最后面調用(類似 c++的析構函數,析構函數的調用順序和構造函數的調用順序完全相反),
Xcode的設置,取消 ARC
要想手動調用retain、release等方法,在創建項目的時候不要勾選ARC(自動引用計數)
/* 文件名:Person.h */#import <Foundation/Foundation.h>@interface Person : NSObject@PRoperty int age;@end/* 文件名:Person.m */#import "Person.h"@implementation Person// 當一個Person對象被回收的時候,就會自動調用這個方法- (void)dealloc{ NSLog(@"Person對象被回收"); // super的dealloc一定要調用,而且放在最后面 [super dealloc];}@end// main.m 1> retain :計數器+1,會返回對象本身 2> release :計數器-1,沒有返回值 3> retainCount :獲取當前的計數器 4> dealloc,當一個對象要被回收的時候,就會調用這個函數,一定要調用[super dealloc],且這句調用要放在最后面。 概念 1> 僵尸對象 :所占用內存已經被回收的對象,僵尸對象不能再使用 2> 野指針 :指向僵尸對象(不可用內存)的指針,給野指針發送消息會報錯(EXC_BAD_access) 3> 空指針 :沒有指向任何東西的指針(存儲的東西是nil、NULL、0),給空指針發送消息不會報錯 ,這是 oc 的語法,但是其他語言不一定。比如 java 就不行。
#import <Foundation/Foundation.h>#import "Person.h"int main(){ // 計數器=1 Person *p = [[Person alloc] init]; //NSUInteger c = [p retainCount]; //NSLog(@"計數器:%ld", c); //這樣寫不太好,注意是長整型 // 計數器=2 retain方法返回的是對象本身 [p retain]; // 計數器=1,沒有返回值 [p release]; // 計數器=0,對象的內存被釋放,p 成了野指針:指向僵尸對象(不可用內存)的指針 [p release];
//給野指針發送消息,錯誤!也就是不能使用僵尸對象,人死不能復生! // [p retain]; // 給已經釋放的對象發送了一條-setAge:消息: // p.age = 10;使用野指針,報錯
//[p setAge:10]; // -[Person setAge:]: message sent to deallocated instance 0x100109a10 //如果繼續 release,也會報錯。重復析構 // EXC_BAD_ACCESS : 訪問了一塊壞的內存(已經被回收、已經不可用的內存
// 野指針錯誤 // 但是記住:OC不存在空指針錯誤,給空指針發送消息,oc里不報錯,故解決辦法: // 把指針p變成空指針 //p = nil;這樣下面的語就對了,否則就是野指針錯誤 [p release]; [p release]; [p release]; [p release]; [nil release];//ok return 0;}
1.當需要使用int類型的變量的時候,可以像寫C的程序一樣,用int,也可以用NSInteger,但更推薦使用NSInteger,因為這樣就不用考慮設備是32位的還是64位的。
2.NSUInteger是無符號的,即沒有負數,NSInteger是有符號的。
開啟僵尸對象監控
默認情況下,Xcode是不會管僵尸對象的,使用一塊被釋放的內存也不會報錯。為了方便調試,應該開啟僵尸對象監控
內存管理原則
QQ堂開房間原理:只要房間還有人在用,就不會解散
誰創建,誰release
如果你通過alloc、new或[mutable]copy來創建一個對象,那么你必須調用release或autorelease,換句話說,不是你創建的,就不用你去[auto]release
誰retain,誰release
只要你調用了retain,無論這個對象是如何生成的,你都要調用release
總結
有始有終,有加就有減
曾經讓對象的計數器+1,就必須在最后讓對象計數器-1
你想使用(占用)某個對象,就應該讓對象的計數器+1(讓對象做一次retain操作),不想再使用(占用)某個對象,就應該讓對象的計數器-1(讓對象做一次release)
set方法的內存管理
如果有個OC對象類型的成員變量,就必須管理這個成員變量的內存。比如有個Book *_book,而我們之前的做法并不嚴謹。
/*文件名:Car.h */#import <Foundation/Foundation.h>@interface Car : NSObject{ int _speed;}- (void)setSpeed:(int)speed;- (int)speed;@end/*文件名:Car.m */#import "Car.h"@implementation Car- (void)setSpeed:(int)speed{ _speed = speed;}- (int)speed{ return _speed;}- (void)dealloc{ /*只有對象計數器=0,這個方法是自動調用的! _speed :最直接的訪問成員變量 self->_speed :直接訪問成員變量 self.speed : 兼容的get方法 [self speed] : 原裝的get方法 */ NSLog(@"速度為%d的Car對象被回收了", _speed); [super dealloc];}@end/*文件名:Person.h */#import <Foundation/Foundation.h>#import "Car.h"@interface Person : NSObject{ Car *_car; int _age;}- (void)setAge:(int)age;- (int)age;- (void)setCar:(Car *)car;- (Car *)car;@end/*文件名:Person.m */#import "Person.h"// _car -> c1 0@implementation Person- (void)setCar:(Car *)car{ if (car != _car) { // 對當前正在使用的車(舊車)做一次release,因為假如換車了,說明舊車不用了!那么沒有這個操作,就會出問題 [_car release]; // 對新車做一次retain操作 _car = [car retain]; }}- (Car *)car{ return _car;}- (void)setAge:(int)age{ // 基本數據類型不需要管理內存 _age = age;}- (int)age{ return _age;}- (void)dealloc{ // 當人不在了,代表不用車了 // 對車做一次release操作 [_car release]; NSLog(@"%d歲的Person對象被回收了", _age); [super dealloc];}@end/*文件名:Student.h */#import <Foundation/Foundation.h>#import "Car.h"#import "Dog.h"@interface Student : NSObject{ int _no; NSString *_name; Car *_car; Dog *_dog;}- (void)setNo:(int)no;- (int)no;- (void)setName:(NSString *)name;- (NSString *)name;- (void)setCar:(Car *)car;- (Car *)car;- (void)setDog:(Dog *)dog;- (Dog *)dog;@end/*文件名:Student.m */#import "Student.h"@implementation Student- (void)setNo:(int)no{ _no = no;}- (int)no{ return _no;}- (void)setName:(NSString *)name{ //如果換名字了,說明舊名字不用了,那么必須 release,否則出錯!同時別 忘記給新名字 retain(記住原則,我想用或者占用了,就必須 retain 一次,不想要或者丟掉就必須 release 一次) if ( name != _name ) { [_name release]; _name = [name retain]; }} //get 方法不用內存管理,基本數據類型的成員也不要內存管理- (NSString *)name{ return _name;}- (void)setCar:(Car *)car{ if ( car != _car ) { [_car release]; _car = [car retain]; }}- (Car *)car{ return _car;}- (void)setDog:(Dog *)dog{ if ( dog != _dog ) { [_dog release]; _dog = [dog retain]; }}- (Dog *)dog{ return _dog;}- (void)dealloc{ [_name release]; [_car release]; [_dog release]; [super dealloc];}@end/*文件名:Dog.h */#import <Foundation/Foundation.h>@interface Dog : NSObject@end/*文件名:Dog.m */#import "Dog.h"@implementation Dog@end// main.m#import <Foundation/Foundation.h>#import "Car.h"#import "Person.h"#import "Student.h"#import "Dog.h"int main(){ // stu = 1 Student *stu = [[Student alloc] init]; // Car = 2 // 這行內存有內存泄露 //stu.car = [[Car alloc] init]; // stu = 0 // Car = 1 [stu release]; // 這行內存有內存泄露 // [[Car alloc] init].speed = 100; return 0;}void test3(){ Student *stu = [[Student alloc] init]; Car *c = [[Car alloc] init]; stu.car = c; Dog *d = [[Dog alloc] init]; stu.dog = d; NSString *s = @"Jack"; stu.name = s; [d release]; [c release]; [stu release];}void test2(){ Person *p1 = [[Person alloc] init]; p1.age = 20; // c1 - 1 Car *c1 = [[Car alloc] init]; c1.speed = 100; // c1 - 2 p1.car = c1; // c1 - 1 [c1 release]; Car *c2 = [[Car alloc] init]; c2.speed = 200; // c1 - 0 p1.car = c2; [c2 release]; [p1 release];}void test1(){ // p-1 Person *p = [[Person alloc] init]; p.age = 20; // c1-1 Car *c1 = [[Car alloc] init]; c1.speed = 250; // c1-2 p.car = c1; // c1-1 [c1 release]; p.car = c1; p.car = c1; p.car = c1; p.car = c1; p.car = c1; p.car = c1; p.car = c1; [p release];}void test(){ // p-1 Person *p = [[Person alloc] init]; p.age = 20; // c1-1 Car *c1 = [[Car alloc] init]; c1.speed = 250; // p想擁有c1 // c1-2 p.car = c1; // [p setCar:c1]; // c2-1 Car *c2 = [[Car alloc] init]; c2.speed = 300; // p將車換成了c2 // c1-1 // c2-2 p.car = c2; // c2-1 [c2 release]; // c1-0 [c1 release]; // p-0 c2-0 [p release];}
內存管理代碼規范:
1.只要調用了alloc,必須有release(autorelease), 對象不是通過alloc產生的,就不需要release
2.set方法的代碼規范
1> 基本數據類型:直接復制
- (void)setAge:(int)age { _age = age; }
2> OC對象類型
- (void)setCar:(Car *)car { // 1.先判斷是不是新傳進來對象 if ( car != _car ) { // 2.對舊對象做一次release [_car release]; // 3.對新對象做一次retain _car = [car retain]; } }
3.dealloc方法的代碼規范
1> 一定要[super dealloc],而且放到最后面
2> 對self(當前)所擁有的其他對象做一次release
- (void)dealloc { [_car release]; [super dealloc]; }
什么是碼農,這樣的重復代碼的編寫就是碼農的工作,so蘋果出了對策,IDE 自動完成一些重復性代碼的編寫工作。
XCODE 可以幫我們省掉編寫那些惡心代碼的工作。使用@property自動生成內存管理的代碼,但是 dealloc 方法還是要自己寫,這時候就有了 ARC,IOS5之后出的新特性。
@property參數
控制set方法的內存管理
/*文件名:Book.h */#import <Foundation/Foundation.h>@interface Book : NSObject@end/*文件名:Book.m */#import "Book.h"@implementation Book@end/*文件名:Person.h */#import <Foundation/Foundation.h>#import "Book.h"@interface Person : NSObject@property int age;// retain : 生成的set方法里面,release舊值,retain新值@property (retain) Book *book;@property (retain) NSString *name;@end/*文件名:Person.m */#import "Person.h"@implementation Person- (void)dealloc{ [_book release]; [_name release]; [super dealloc];}@end/*文件名:Student.h */#import <Foundation/Foundation.h>#import "Book.h"@interface Student : NSObject@property (retain) Book *book;@property (retain) NSString *name;@end/*文件名:Student.m */#import "Student.h"@implementation Student- (void)dealloc{ [_book release]; [_name release]; [super dealloc];}@end// main.m#import <Foundation/Foundation.h>#import "Person.h"#import "Book.h"int main(){ Book *b = [[Book alloc] init]; Person *p = [[Person alloc] init]; p.book = b; NSLog(@"%ld", [b retainCount]); [p release]; [b release]; return 0;}
/*文件名:Person.h */#import <Foundation/Foundation.h>/* 1.set方法內存管理相關的參數 * retain : release舊值,retain新值(適用于OC對象類型) * assign : 直接賦值(默認,適用于非OC對象類型,寫不寫都行) * copy : release舊值,copy新值 2.是否要生成set方法 * readwrite : 同時生成setter和getter的聲明、實現(默認) * readonly : 只會生成getter的聲明、實現 3.多線程管理 * nonatomic : 性能高 (一般就用這個) * atomic : 性能低(默認) 4.setter和getter方法的名稱 * setter : 決定了set方法的名稱,一定要有個冒號 : * getter : 決定了get方法的名稱(一般用在BOOL類型) */@interface Person : NSObject// 返回BOOL類型的方法名一般以is開頭@property (getter = isRich) BOOL rich;// @property (nonatomic, assign, readwrite) int weight;// setWeight:// weight// @property (readwrite, assign) int height;@property (nonatomic, assign) int age;@property (retain) NSString *name;@end/*文件名:Person.m */#import "Person.h"@implementation Person@end// main.m#import <Foundation/Foundation.h>#import "Person.h"int main(){ Person *p = [[Person alloc] init]; p.rich = YES; BOOL b = p.isRich; return 0;}
使用場景;對于循環依賴關系來說,比方A類引用B類,同時B類也引用A類
這種代碼直接編譯會報錯。當使用@class在兩個類相互聲明,就不會出現編譯報錯
用法概括
使用 @class 類名; 就可以引用一個類,說明一下它是一個類
和#import的區別
#import方式會包含被引用類的所有信息,包括被引用類的變量和方法;
@class方式只是告訴編譯器在A.h文件中 B *b 只是類的聲明,具體這個類里有什么信息,這里不需要知道,等實現文件中真正要用到時,才會真正去查看B類中信息,如果有上百個頭文件都#import了同一個文件,或者這些文件依次被#improt,那么一旦最開始的頭文件稍有改動,后面引用到這個文件的所有類都需要重新編譯一遍,這樣的效率也是可想而知的,而相對來 講,使用@class方式就不會出現這種問題了。
在.m實現文件中,如果需要引用到被引用類的實體變量或者方法時,還需要使用#import方式引入被引用類
循環retain
比如A對象retain了B對象,B對象retain了A對象,這樣會導致A對象和B對象永遠無法釋放
解決方案
當兩端互相引用時,應該一端用retain、一端用assign
/*描述:每個身份證都對應一個人,同時,每個人都對于一個身份證,這就是相互依賴的關系文件名:Card.h */#import <Foundation/Foundation.h>@class Person;@interface Card : NSObject//@property (nonatomic, retain) Person *person;@property (nonatomic, assign) Person *person;@end/*文件名:Card.m */#import "Card.h"#import "Person.h"@implementation Card- (void)dealloc{ NSLog(@"Car被銷毀了"); // [_person release];有 retain 才有 release,否則不能寫 [super dealloc];}@end/* 文件名:Person.h */#import <Foundation/Foundation.h>#import "Card.h"// @class僅僅是告訴編譯器,Card是一個類//@class Card;@interface Person : NSObject@property (nonatomic, retain) Card *card;@end/* 文件名:Person.m */#import "Person.h"#import "Card.h"@implementation Person- (void)dealloc{ NSLog(@"Person被銷毀了"); [_card release]; [super dealloc];}@end// main.m#import <Foundation/Foundation.h>#import "Card.h"#import "Person.h"int main(){ // p - 1 Person *p = [[Person alloc] init]; // c - 1 Card *c = [[Card alloc] init]; // c - 2 p.card = c; // p - 1 c.person = p; // c - 1 [c release]; // p - 0 c - 0,如果不這樣使用( 1> 一端用retain 2> 一端用assign) [p release]; return 0;}////如果不這樣使用( 1> 一端用retain 2> 一端用assign),會出錯//// p - 1//Person *p = [[Person alloc] init];//// c - 1//Card *c = [[Card alloc] init];////// c - 2//p.card = c;////// p - 2,因為兩個類的 set 方法生成 都是 retain 屬性,那么就是這樣的代碼//c.person = p;//// if (_card != card)//// {//這個步驟要有,舊的(當前的)對象不再被使用,就把它的計數器-1操作,之后再對新的對象使用+1,防止出錯。//// [_card release];//// _card = [book retain];//// }////// c - 1//[c release];////// p - 1 此時不會調用 dealloc 方法,會出現內存泄露問題,而使用了雙端循環引用解決方案,那么p=0,則自動調用 person 類的 dealloc方法,c=0,完美結束。////- (void)dealloc////{//// NSLog(@"Person被銷毀了");//// [_card release];//// //// [super dealloc];////}//[p release];
@class的作用:僅僅告訴編譯器,某個名稱是一個類
@class Person; 僅僅告訴編譯器,Person是一個類,不會把類的方法等引入
實際開發中引用一個類的規范是:
1> 在.h文件中用@class來聲明類,比如,有100個類同時引用了 Card 類,如果 card 修改了,那么剩下的100個類也要重新引入編譯,效率不高,且還能解決雙端引用(循環引用)的出現錯誤問題。
2> 在.m文件中用#import來包含類的所有東西
為了提高編譯的效率!頭文件不使用#import,只有一個特例,那就是在繼承里,父類需要使用#import 引入到子類
兩端循環引用解決方案,這是特例,區別對待,以前說了,對象用 retain,非對象不需要內存管理,使用 assign 就行了,但是這里特殊
1> 一端用retain
2> 一端用assign
autorelease方法(半自動釋放)
autorelease
使用 autorelease 方法防止了每次使用對象,都要在對象 release 之前使用的弊端,因為那樣總是小心翼翼的,怕出現野指針。但是出現了 ARC 之后這些都不需要了。
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); } return 0;}
ios 5.0后,以后一直使用這個了.
ios 5.0前
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];// .....[pool release]; // 或[pool drain];
在程序運行過程中,可以創建多個自動釋放池,它們是以棧的形式存在內存中,OC對象只需要發送一條autorelease消息,就會把這個對象添加到最近的自動釋放池中(棧頂的釋放池)
/*文件名:Person.h */#import <Foundation/Foundation.h>@interface Person : NSObject@property (nonatomic, assign) int age;@end/*文件名:Person.m */#import "Person.h"@implementation Person- (void)dealloc{ NSLog(@"Person---dealloc"); [super dealloc];}@end// main.m/* 1.autorelease的基本用法 1> 會將對象放到一個自動釋放池中 2> 當自動釋放池被銷毀時,會對池子里面的所有對象做一次release操作 3> 會返回對象本身 4> 調用完autorelease方法后,對象的計數器不變 2.autorelease的好處 1> 不用再關心對象釋放的時間 2> 不用再關心什么時候調用release 3.autorelease的使用注意 1> 占用內存較大的對象不要隨便使用autorelease 2> 占用內存較小的對象使用autorelease,沒有太大影響 4.錯誤寫法 1> alloc之后調用了autorelease,又調用release @autoreleasepool { // 1 Person *p = [[[Person alloc] init] autorelease]; // 0 [p release]; } 2> 連續調用多次autorelease @autoreleasepool { Person *p = [[[[Person alloc] init] autorelease] autorelease]; } 5.自動釋放池 1> 在iOS程序運行過程中,會創建無數個池子。這些池子都是以棧結構存在(先進后出) 2> 當一個對象調用autorelease方法時,會將這個對象放到棧頂的釋放池 6.自動釋放池的創建方式 1> iOS 5.0前 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [pool release]; // MAC 上是 [pool drain]; 2> iOS 5.0 開始 @autoreleasepool { } */#import <Foundation/Foundation.h>#import "Person.h"int main(){ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Person *pp = [[[Person alloc] init] autorelease]; [pool release]; // [pool drain]; @autoreleasepool { // 1 Person *p = [[[Person alloc] init] autorelease]; // 0 // [p release]; } return 0;}void test(){ @autoreleasepool {// { 開始代表創建了釋放池 // autorelease方法會返回對象本身 // 調用完autorelease方法后,對象的計數器不變 // autorelease會將對象放到一個自動釋放池中 // 當自動釋放池被銷毀時,會對池子里面的所有對象做一次release操作 Person *p = [[[Person alloc] init] autorelease]; p.age = 10; @autoreleasepool { // 1 Person *p2 = [[[Person alloc] init] autorelease]; p2.age = 10; } Person *p3 = [[[Person alloc] init] autorelease]; } // } 結束代表銷毀釋放池}
跟release的對比
以前:
Book *book = [[Book alloc] init];
[book release];
現在:
Book *book = [[[Book alloc] init] autorelease];
// 不要再調用[book release];但是這樣顯得代碼很長,臃腫,能不能創建對象的時候,直接就是放入自動釋放池里呢?可以的:
一般可以為類添加一個快速創建對象的類方法
+ (id)book { return [[[self alloc] init] autorelease];}
外界調用[Book book]時,根本不用考慮在什么時候釋放返回的Book對象,實際開發中常用。
開發中經常會提供一些類方法,快速創建一個已經autorelease過的對象,創建對象時不要直接用類名,用self,因為這樣寫,此類的子類都能調用這個方法,自動識別,self 指向調用這個方法的類,不會出錯。
一般來說,除了alloc、new或copy之外,其他的方法創建的對象都被聲明了autorelease
比如下面的對象都已經是autorelease的,不需要再release
NSNumber *n = [NSNumber numberWithInt:100];NSString *s = [NSString stringWithFormat:@"jack"];NSString *s2 = @"rose";
一、計數器的基本操作
1> retain : +1
2> release :-1
3> retainCount : 獲得計數器
二、set方法的內存管理
1> set方法的實現
- (void)setCar:(Car *)car{ if ( _car != car ) { [_car release]; _car = [car retain]; }}
2> dealloc方法的實現(不要直接調用dealloc)
- (void)dealloc{ [_car release]; [super dealloc];}
三、@property參數
1> OC對象類型
@property (nonatomic, retain) 類名 *屬性名;
@property (nonatomic, retain) Car *car;
@property (nonatomic, retain) id car;
// 被retain過的屬性,必須在dealloc方法中release屬性
- (void)dealloc
{
[_car release];
[super dealloc];
}
2> 非OC對象類型(int/float/enum/struct)
@property (nonatomic, assign) 類型名稱 屬性名;
@property (nonatomic, assign) int age;
四、autorelease
1.系統自帶的方法中,如果不包含alloc、new、copy,那么這些方法返回的對象都是已經autorelease過的
[NSString stringWithFormat:....];
[NSDate date];
2.開發中經常寫一些類方法快速創建一個autorelease的對象
* 創建對象的時候不要直接使用類名,用self
新聞熱點
疑難解答