概述 移動設備的記憶體極其有限,每個app所有占用的記憶體是有限的。當app所占用的記憶體比較多時,系統會發出記憶體警告,這時得回收一些不需要再使用的記憶體空間。 "任何集成了NSObject的對象都需要手動進行記憶體管理。因為OC對象存放於堆裡面。" 引用計數器 每一個OC對象都有內部有自己的引用計數器。該計 ...
概述
移動設備的記憶體極其有限,每個app所有占用的記憶體是有限的。當app所占用的記憶體比較多時,系統會發出記憶體警告,這時得回收一些不需要再使用的記憶體空間。
任何集成了NSObject的對象都需要手動進行記憶體管理。因為OC對象存放於堆裡面。
引用計數器
- 每一個OC對象都有內部有自己的引用計數器。該計數器占用4個位元組。從字面可以理解為"多少人在使用這個對象"。
- 當對象的引用計數器為0時。該對象會才會被釋放。
- 一個對象通過alloc、 copy 、new創建一個對象,該對象預設的引用計數器為1。
引用計數器的操作
* 給對象發送一條retain消息,對象的引用計數器+1
* 給對象發送一條release消息,對象的引用計數器-1
* 給對象發送一條retainCount消息,對獲得當前對象的引用計數器值
dealloc方法
當一個對象的引用計數器值為0時,這個對象即將被銷毀,器占用的記憶體被系統回收。對象即將被銷毀前系統會自動給改對象發送一條dealloc
消息。
通常重寫對象的dealloc
方法釋放相關的資源。
* 一旦重寫了dealloc方法,就必須調用[super dealloc],並且放在最後面
* dealloc不能直接調用
* 一旦對象被回收,它占用的記憶體就不再可用被稱之為僵屍對象。指向僵屍對象的指針被稱為野指針。給僵屍對象發消息會造壞訪問報錯。
多對象的記憶體管理
多個對象進行記憶體管理,並且對象之間是有聯繫的,那麼管理就會變得比較複雜。
比如在記憶體中有一個聊天室對象,多個Person對象共同使用者聊天室對象。
對上面的實例我們如果想做好聊天室對象跟Person對象的記憶體管理。需要做到下麵兩點
* 只要有人在使用聊天室對象聊天室對象就不能銷毀
* 沒有人使用聊天室對象聊天室對象需要銷毀
當有Person對象使用聊天室就需要對聊天室對象的引用計數器+1。且當Person對象銷毀之前需要對使用的房間對象引用計數器-1,這樣就可以保證以上兩點。
因此可以這樣定義一個Person類
#import <Foundation/Foundation.h>
#import "ChatRoom.h"
@interface Person : NSObject
{
ChatRoom *_cRoom;
}
- (void)setCRoom:(ChatRoom *)cRoom;
- (ChatRoom *)cRoom;
@end
#import "Person.h"
@implementation Person
- (void)setCRoom:(ChatRoom *)cRoom
{
if (_cRoom != cRoom) {
// 1.先對以前的對象計數器-1
[_cRoom release];
// 2.調用對象的retain返回對象本身
_cRoom = [_cRoom retain];
}
}
- (ChatRoom *)cRoom
{
return _cRoom;
}
- (void)dealloc
{
// 在Person對象即將銷毀先對內部的聊天室對象計數器-1
[_cRoom release];
[super dealloc];
}
@end
Property修飾符
assign
預設Proerty修飾符為assig。比如當為Person聲明一個成員屬性@property int age;
編譯器自動轉換成@property(assign) int age;
。
retain
Proerty修飾符為retain在生成的set方法會自動生成set方法的記憶體管理代碼
@property (retain) ChatRoom *cRoom;
retain修飾生成的set方法:
- (void)setCRoom:(ChatRoom *)cRoom
{
if (_cRoom != cRoom) {
// 1.先對以前的對象計數器-1
[_cRoom release];
// 2. 調用對象的retain返回對象本身
_cRoom = [_cRoom retain];
}
}
nonatomic與nonnull
nonatomic是保證set方法是線程安全的,nonnull則相反。iOS開發一般使用nonatomic性能高。
@class
@class主要用於簡單的引用一個類。@class與#import有著本質的區別,#import是預處理指令在程式預處理時期對#import的文件進行拷貝,而@class只是告訴編譯器@class後面引用的是一個類。
import有一個特點,只要引用的文件發生的變化,那麼import就會重新拷貝一次。比如現在有三個類Person、 Car、 Wheel。Person.h文件import Car.h,Car.h文件import Wheel.h。一旦wheels Wheel.h發生變化,Car.h需要重新拷貝Wheel.h並且Person.h也重新拷貝Car.h。
@class具體使用
* 在.h文件中使用@class引用一個類
* 在.m文件中使用#import包含這個類的.h文件
@class其他使用場景
- 對於迴圈依賴關係來說,比方A類引用B類,同時B類也引用A類 這種嵌套包含的代碼編譯報錯
- 當使用@class在兩個類相互聲明,就不會出現編譯報錯。
retain的迴圈引用
現在有兩個類Person與Car。Person中有個成員屬性car,Car中有個成員屬性person。
Person類
#import <Foundation/Foundation.h>
@class Car;
@interface Person : NSObject
@property (retain) Car *car;
@end
#import "Person.h"
@implementation Person
- (void)dealloc
{
[_car release];
[super dealloc];
NSLog(@"%s", __func__);
}
@end
Car類
#import <Foundation/Foundation.h>
@class Person;
@interface Car : NSObject
@property (retain) Person *per;
@end
#import "Car.h"
@implementation Car
- (void)dealloc
{
[_per release];
[super dealloc];
NSLog(@"%s", __func__);
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
Car *c = [[Car alloc] init];
// 人賦值一輛車對象
p.car = c;
// 車賦值人對象
c.per = p;
[p release];
[c release];
}
return 0;
}
main.m代碼執行完控制台並沒有Person對象與Car對象dealloc方法的列印。因此記憶體兩個對象並未銷毀存在記憶體泄漏。下麵畫圖展現main.m執行完畢記憶體的情況。
為什麼執行p跟c的release方法,對象沒有銷毀。因為在給人對象賦值car對象的時候通過set方法會對car對象引用計數器+1,同理對car對象賦值人對象,人對象引用計數器也會+1。
分別執行兩個對象release只是讓對象應用計數器-1,因此兩個對象都沒有銷毀。
那麼我們可以嘗試讓其中一個類Property修飾符修改為assign。註意:修改為assign修飾一方dealloc千萬不要在dealloc對對象屬性進行release。否則會造成記憶體壞訪問
此時的Person類
#import <Foundation/Foundation.h>
@class Car;
@interface Person : NSObject
// 改為assign修飾
@property (assign) Car *car;
@end
#import "Person.h"
@implementation Person
- (void)dealloc
{
// [_car release]; 此時Propery修飾符為assign不需要對car進行release
[super dealloc];
NSLog(@"%s", __func__);
}
@end
autorelease
通過上面講解,OC多對象的記憶體管理主要有以下幾點:
* 1.在外界通過alloc、copy、new關鍵字創建的對象需要釋放的時候發送release消息
* 2.在set方法中對賦值的對象引用計數器+1,可以使用retain關鍵字自動生成set方法記憶體管理代碼
* 3.在一個對象即將要銷毀前在dealloc方法中對內部引用的對象進行release。
這裡就會發現,我們需要時時刻刻關註對象什麼時候需要銷毀時發送release消息。接下來就可以通過autorelease的機制來解決手動編寫大量的對象的release代碼。不需要一直再關註對象什麼時候需要release。只要可以訪問到對象的作用域都可以使用對象。
autorelease是一種支持引用計數器的記憶體管理方式,只要給對象發送一條autorelease消息會將對象放到自動釋放池中。當自動釋放池銷毀,會對池子中的所有對象發送一條release消息。autorelease操作對象的引用計數器。當一個對象發送release消息返回時對象的本身。
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
// 將對象p添加到autorelease釋放池
p = [p autorelease];
// [p release]; 這裡不需要在寫release 否則壞訪問
}// 自動釋放池銷毀會給對象中所有的對象發送一條release消息
return 0;
}
在iOS5.0之前autorelease的寫法
int main(int argc, const char * argv[]) {
// 創建一個自動釋放池
NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init];
Person *p = [[Person alloc] init];
// 將對象p添加到autorelease釋放池
p = [p autorelease];
[pool drain]; // 自動釋放池銷毀會給對象中所有的對象發送一條release消息
return 0;
}
MRC類工廠方法的編寫
我們在初始化一個對象將它添加進自動釋放池中
@autoreleasepool{
// 創建一個對象
Person *p = [[[Person alloc] init] autorelease];
}
可見每次創建一個Person對象都需要寫很長的代碼,於是我們可以封裝內部細節提供一個類工廠方法
/**快速創建一個Person實例*/
+ (instancetype)person;
+ (instancetype)person
{
return [[[self alloc] init] autorelease];
}
外界調用類工廠對象初始化對象
int main(int argc, const char * argv[]) {
@autoreleasepool{
// 創建一個對象
Person *p = [[[Person alloc] init] autorelease];
// 通過類工廠方法 創建的對象預設添加進自動釋放池當中
Person *p1 = [Person person];
}
return 0;
}
我們想讓Person在初始化後就有一個預設的名字。我們可以提供自定義構造方法。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
/**快速創建一個Person實例*/
+ (instancetype)person;
/**自定義構造方法*/
-(instancetype)initWithName:(NSString *)name;
+(instancetype)personWithName:(NSString *)name;
@end
#import "Person.h"
@implementation Person
/**自定義構造方法*/
-(instancetype)initWithName:(NSString *)name
{
if(self = [super init]){
self.name = name;
}
return self;
}
+ (instancetype)person
{
return [[[self alloc] init] autorelease];
}
+(instancetype)personWithName:(NSString *)name
{
// 類方法中將對象添加進自動釋放池中
return [[self initWithName:name] autorelease];
}
@end
上面的代碼會發現在MRC類方法中生成的對象預設內部將其添加自動釋放池當中,其實這也是蘋果的規範。蘋果Foundation框架中通過類方法創建的對象都是autorelease。
ARC的記憶體管理
ARC是編譯器的特性,而不是運行時特性,跟其他語言的垃圾回收有著本質的區別。ARC原理就是在編譯階段為我們在必要的時候添加release這些代碼。
ARC的判斷原則
* 只要還有一個強指針指向對象,對象就會保持在記憶體中
ARC與MRC的混編
* 在ARC項目中執行某個文件使用MRC -fno-objc-arc
* 在MRC項目中執行某個文件使用ARC -fobjc-arc
使用Xcode將一個MRC工程轉化成ARC項目