當程序執行某個方法(或函數)時,會從內存中一個叫棧的區域分配一塊內存空間,這塊內存空間我們叫幀。幀負責保護程序在方法內聲明的變量的值。在方法內聲明的變量我們稱之為局部變量。
當我們的程序開始啟動,作為程序的入口main函數,他的幀會被保存在棧的地步。當main調用另一個方法時,這個方法會被壓入棧的頂部。被調用的方法還會調用其他的方法,這樣一直調用,就會形成一個幀序列。當調用的方法執行結束的時候,程序會將其幀從棧頂“彈出”並釋放響應的內存。
所以棧的內存形式是先進後出。
堆是值內存中的另一塊區域,是和棧分開的。堆中包含了大量無序的活動對象,需要通過指針來保存這些對象在堆中的地址。當應用向某個類發送alloc消息時,系統會從堆中分配出一塊內存,其大小為對象的全部的實例變量大小。
iOS應用在啟動和運行時會持續創建需要的對象,如果堆的空間是無限的,則可以隨意創建所需的對象。但是可惜,我們可用應用支配的內存空間是很有限的。因此,當應用不再需要某些對象時,就要將其釋放掉。釋放掉的對象可以將其占用的內存歸還給堆,使之能夠重新使用。最終要的是,我們要時刻避免釋放應用正在使用的對象。
指針變量暗含了對其所指向的對象的所有權。
當某個方法(或函數)有一個指向某個對象的局部指針時,可以成該變量擁有該變量所指向的對象。 當某個對象有一個指向其他對象的實驗變量時,可以稱該對象擁有該實例變量所指向的對象。 如果某個對象沒有擁有者,就應該將其釋放掉。沒有擁有者,程序是無法向其發送消息的。保留這樣的對象就會造成內存洩漏 如果某個對象有一個或者多個擁有者,就必須保留下來,不能被釋放。如果釋放了某個對象,但是其他對象或者方法仍然有指向該對象的指針,那麼向該指針指向的對象發送消息就會使應用崩潰。指向不存在的對象的指針稱為空指針
那些情況是使對象失去擁有者
當程序修改某個指向特定對象的變量並將其指向另一個對象的時候就會失去擁有者。 當程序將某個指向特定對象的變量設置為nil的時候。 當程序釋放對象的某個擁有者的時候 當從collection類中刪除對象的時候。
只要指針變量指向某個對象,那麼相應的對象就會多一個擁有者,並且不會被程序釋放。這種指針特性稱為強引用。
程序也可以選擇讓指針變量不影響其指向對象的擁有者個數。這種不會改變對象擁有者個數的指針特性稱之為弱引用。
弱引用非常適合解決一種稱為強引用循環的內存管理問題。當兩個或者以上的對象相互之間有強引用特性的指針關聯的時候,就會產生強引用循環。這種循環會導致內存洩漏。因為這種循環中程序無法通過ARC機制來釋放內存。
在RandomItems添加一處強引用循環,來解釋如何解決此類問題。
#import <Foundation/Foundation.h> @interface JXItem : NSObject { NSString *_itemName; NSString *_serialNumber; int _valueInDollars; NSDate *_dateCreated; // 這裡添加讓JXItem對象能夠保存另一個JXItem對象。 JXItem *_containedItem; JXItem *_container; } // 初始化方法 - (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber; - (instancetype)initWithItemName:(NSString *)name; - (void)setContainedItem:(JXItem *)item; - (JXItem *)containedItem; - (void)setContainer:(JXItem *)item; - (JXItem *)container; // 存方法 - (void)setItemName:(NSString *)str; // 取方法 - (NSString *)itemName; - (void)setSerialNumber:(NSString *)str; - (NSString *)serialNumber; - (void)setValueInDollars:(int)v; - (int)valueInDollars; - (NSDate *)dateCreated; @end
實現方法中:
#import "JXItem.h" @implementation JXItem - (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber { // 調用父類的初始化方法 self = [super init]; // 判斷父類的指定初始化方法是否是成功創建 if (self) { // 為實例變量設置初始值 _itemName = name; _serialNumber = sNumber; _valueInDollars = value; // 設置 _dateCreated 的值為系統當前時間 // 因為我們沒有為該實例變量設置 set 方法來從外部獲取 (只是一個只讀屬性) _dateCreated = [NSDate date]; } // 返回初始化後的對象的新地址 return self; } - (void)setContainedItem:(JXItem *)item { _containedItem = item; // 將item加入容納他的JXItem對象時,會將它的container實例變量指向容納它的對象 item.container = self; } - (JXItem *)containedItem { return _containedItem; } - (void)setContainer:(JXItem *)item { _container = item; } - (JXItem *)container { return _container; } - (instancetype)initWithItemName:(NSString *)name { return [self initWithItemName:name valueInDollars:0 serialNumber:@""]; } // 默認的初始化方法,調用自定義的初始化方法,並將之出入一個默認值 - (instancetype)init { return [self initWithItemName:@"Item"]; } - (void)setItemName:(NSString *)str { _itemName = str; } - (NSString *)itemName { return _itemName; } - (void)setSerialNumber:(NSString *)str { _serialNumber = str; } - (NSString *)serialNumber { return _serialNumber; } - (void)setValueInDollars:(int)v { _valueInDollars = v; } - (int)valueInDollars { return _valueInDollars; } - (NSDate *)dateCreated { return _dateCreated; } - (NSString *)description { NSString * descriptionString = [[NSString alloc] initWithFormat:@"%@ (%@): Worth $%d,recorded on %@",self.itemName,self.serialNumber,self.valueInDollars,self.dateCreated]; return descriptionString; } @end
在main.m實現方法
#import <Foundation/Foundation.h> #import "JXItem.h" int main(int argc, const char * argv[]) { @autoreleasepool { // 創建一個NSMutableArray對象,並用items變量保存該對象的地址 NSMutableArray * items = [[NSMutableArray alloc] init]; JXItem * backpack = [[JXItem alloc] initWithItemName:@"Backpack"]; [items addObject:backpack]; JXItem * calculator = [[JXItem alloc] initWithItemName:@"Calculator"]; [items addObject:calculator]; backpack.containedItem = calculator; backpack = nil; calculator = nil; // 使用快速枚舉法來遍歷 for (NSString * item in items) { NSLog(@"%@",item); } } return 0; }
打印結果:
2016-09-09 00:47:04.537 RandomItems[27531:2815858] Backpack (): Worth $0,recorded on 2016-09-08 16:47:04 +0000 2016-09-09 00:47:04.537 RandomItems[27531:2815858] Calculator (): Worth $0,recorded on 2016-09-08 16:47:04 +0000 Program ended with exit code: 0
可見,並沒有打印出釋放的信息。對此我們可以這麼理解:當執行backpack.containedItem = calculator;其實就是執行了JXItem中的set方法。也就是[backpack setContainer:calculator];我們可以查看在類中我們自定義的方法,可以看到這時候backpack就是self,此時的self擁有指向calculator的指針,也就是calculator的擁有方;同時,我們在實現方法中item.container = self;此時的 item 就是calculator,所有就造成了循環引用。
要解決這種循環引用問題,我們就需要在新創建的兩個對象之間的任意一個指針改為弱引用特性。在決定哪個指針改為弱引用之前,我們可以先為存在強引用循環問題的多個對象決定響應的父-子關系。我們可以讓父對象擁有子對象,並確保子對象不會擁有父對象。定義形式為: __weak JXItem *_container;
屬性是用來簡化聲明變量和存儲方法。申明方式:@property NSString * itemName;
#import <Foundation/Foundation.h> @interface JXItem : NSObject @property NSString * itemName; @property NSString * serialNumber; @property int valueInDollars; @property NSDate * dateCreated; @property JXItem * containedItem; @property JXItem * container; // 初始化方法 - (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber; - (instancetype)initWithItemName:(NSString *)name; - (void)setContainedItem:(JXItem *)item; - (JXItem *)containedItem; - (void)setContainer:(JXItem *)item; - (JXItem *)container; // 存方法 - (void)setItemName:(NSString *)str; // 取方法 - (NSString *)itemName; - (void)setSerialNumber:(NSString *)str; - (NSString *)serialNumber; - (void)setValueInDollars:(int)v; - (int)valueInDollars; - (NSDate *)dateCreated; @end
實現方法:屬性的名字是實例變量的名字去掉下劃線,編譯器會根據屬性生成實例變量時會自動在變量名前加上下劃線,同時還能自動生成相應的存取方法。
#import "JXItem.h" @implementation JXItem - (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber { // 調用父類的初始化方法 self = [super init]; // 判斷父類的指定初始化方法是否是成功創建 if (self) { // 為實例變量設置初始值 _itemName = name; _serialNumber = sNumber; _valueInDollars = value; // 設置 _dateCreated 的值為系統當前時間 // 因為我們沒有為該實例變量設置 set 方法來從外部獲取 (只是一個只讀屬性) _dateCreated = [NSDate date]; } // 返回初始化後的對象的新地址 return self; } - (void)setContainedItem:(JXItem *)item { _containedItem = item; // 將item加入容納他的JXItem對象時,會將它的container實例變量指向容納它的對象 item.container = self; } - (JXItem *)containedItem { return _containedItem; } - (void)setContainer:(JXItem *)item { _container = item; } - (JXItem *)container { return _container; } - (instancetype)initWithItemName:(NSString *)name { return [self initWithItemName:name valueInDollars:0 serialNumber:@""]; } // 默認的初始化方法,調用自定義的初始化方法,並將之出入一個默認值 - (instancetype)init { return [self initWithItemName:@"Item"]; } - (void)setItemName:(NSString *)str { _itemName = str; } - (NSString *)itemName { return _itemName; } - (void)setSerialNumber:(NSString *)str { _serialNumber = str; } - (NSString *)serialNumber { return _serialNumber; } - (void)setValueInDollars:(int)v { _valueInDollars = v; } - (int)valueInDollars { return _valueInDollars; } - (NSDate *)dateCreated { return _dateCreated; } - (NSString *)description { NSString * descriptionString = [[NSString alloc] initWithFormat:@"%@ (%@): Worth $%d,recorded on %@",self.itemName,self.serialNumber,self.valueInDollars,self.dateCreated]; return descriptionString; } @end
屬性的特性
任何屬性都可以有一組特性,用於描述響應存取方法的行為。這些特性需要寫在小括號裡,並跟在@property指令之後。例如:@property (nonatomic,copy) NSString * itemName;任何屬性都有三個特性,每個特性都有多種不同的可選類型。
屬性的特性-多線程特性
此特性有兩種可選類型:nonatomic和atomic。前一種是非原子訪問,不會加線程鎖,後一種相反,但是線程鎖雖然是絕對安全的,但是效率很低,一般不推薦使用。默認是 原子訪問。
#import <Foundation/Foundation.h> @interface JXItem : NSObject @property (nonatomic) NSString * itemName; @property (nonatomic) NSString * serialNumber; @property (nonatomic) int valueInDollars; @property (nonatomic) NSDate * dateCreated; @property (nonatomic) JXItem * containedItem; @property (nonatomic) JXItem * container; // 初始化方法 - (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber; - (instancetype)initWithItemName:(NSString *)name; @end
屬性的特性-讀/寫特性
此特性也有兩種可選類型:readwrite和readonly。編譯器默認會是readwrite特性的屬性生成存取方法。但是如果是readonly屬性,只會生成取方法。
#import <Foundation/Foundation.h> @interface JXItem : NSObject @property (nonatomic) NSString * itemName; @property (nonatomic) NSString * serialNumber; @property (nonatomic) int valueInDollars; @property (nonatomic,readonly) NSDate * dateCreated; @property (nonatomic) JXItem * containedItem; @property (nonatomic) JXItem * container; // 初始化方法 - (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber; - (instancetype)initWithItemName:(NSString *)name; @end
屬性的特性-內存管理特性
此特性有四種可選類型:strong (默認屬性), weak ,copy, unsafe_unretained。對於不指向任何對象的屬性,也就是基本數據類型,我們不需要做內存管理,這時候我們應該選用unsafe_unretained,他表示存取方法會直接為實例變量賦值。在ARC 之前使用的是assign,現在我們仍舊可以使用這個屬性。
通常情況下,但給某個屬性是指向其他對象的指針,而且這個屬性的類有可修改的子類(NSString/NSMutableString,NSArray/NSMutableArray)這時我們應該將其屬性內存管理設置為copy。
#import <Foundation/Foundation.h> @interface JXItem : NSObject @property (nonatomic,copy) NSString * itemName; @property (nonatomic,copy) NSString * serialNumber; @property (nonatomic) int valueInDollars; @property (nonatomic,readonly,strong) NSDate * dateCreated; @property (nonatomic,strong) JXItem * containedItem; @property (nonatomic,weak) JXItem * container; // 初始化方法 - (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber; - (instancetype)initWithItemName:(NSString *)name; @end
更改為copy特性之後,其屬性的存方法可能類似於如下代碼:
- (void)setItemName:(NSString *)itemName { _itemName = [itemName copy]; }
這段代碼沒有將傳入的值itemName直接賦值給實例變量_itemName ,而是先向itemName發送了copy信息。該對象的copy 方法會返回一個新的NSString對象。我們這麼做的原因就是:如果屬性指向的對象的類有可修改的子類,那麼該屬性可能會指向可修改的子類對象,同時,該對象可能會被其他擁有者修改。因此,我們在操作的時候最好先復制該對象,然後再將屬性指向復制後的對象。但是在copy 方法中,NSString 對象是不會發生任何變化的,所以我們一般只有對可變對象設置為copy,復制不可變對象就是浪費空間而已。
自定義屬性的存取方法
默認情況下回自動生成存取方法,而且非常簡單;
- (void)setContainedItem:(JXItem *)item { _containedItem = item; } - (JXItem *)containedItem { return _containedItem; }
屬性自定義添加的存取方法我們可以直接拿來用,同時我們也可以自定義存取方法;當我們自定義的存取方法之後,編譯器就不會為我們創建默認的存取方法了。
- (void)setContainedItem:(JXItem *)containedItem { _containedItem = containedItem; self.containedItem.container = self; }