前言
基礎篇介紹了一些關于Objective-C內(nèi)存管理的常見概念。本文將在前文的基礎上擴展以下知識:成員變量set方法內(nèi)存分析、屬性拾徙、屬性特質(zhì)內(nèi)存分析等內(nèi)容,內(nèi)容淺顯易懂感局、屬于學習總結尼啡。如果有需要瀏覽上一篇文章的同學請點擊Objective-C 內(nèi)存管理基礎。希望本文能給正在學習Objective-C的小伙伴們更多啟發(fā)询微。
成員變量的set方法
在屬性(property) 這個語法提出之前崖瞭,iOS開發(fā)者常常這樣構建一個類:
@interface Person : NSObject
{
NSUInteger _number;
}
- (void)setNumber:(NSUInteger)number;
- (NSUInteger)number;
如上的代碼為Person
類添加了一個成員變量_number
(添加下劃線是為了命名規(guī)范)。同時手動申明和實現(xiàn)該成員變量的存取方法:setter
方法用于寫入值撑毛,getter
方法用于讀取值书聚。 這樣的寫法看起來像是一個世紀之前的事情了,但我們能夠從中探究到很多關于內(nèi)存管理方面的問題藻雌。例如:
-
基本數(shù)據(jù)類型直接賦值
由于_number
是NSUInteger
類型雌续,所以其不再內(nèi)存管理的范圍,對于基本類型的setter
方法只需要直接賦值就好了胯杭。
- (void)setNumber:(NSUInteger)number
{
_number = number;
}
- ObjC 對象類型需要進行內(nèi)存管理
@class Book;
@interface Person : NSObject
{
Book *_book;
}
- (void)setBook:(Book *)book;
- (Book *)book;
上面的Person
類存在一個繼承自NSObject
類型的成員變量驯杜,當實現(xiàn)其setter
方法時就需要做到以下幾點了。
-
retain 傳入的變量
根據(jù)內(nèi)存管理原則:“需要持有對象時就對其做一次retain
操作”做个。所以應該對傳入的book
做一次retain
操作鸽心。初步代碼如下:
@implementation Person
- (void)setBook:(Book *)book
{
_book = [book retain]; //持有該對象對book做一次retain操作
}
@end
-
對象銷毀時 release 其成員變量
Person
對象銷毀時代表_book
沒有人使用了滚局,應該在Person
的delloc
方法里做一次release
操作。
@implementation Person
- (void)setBook:(Book *)book
{
_book = [book retain]; //持有該對象對book做一次retain操作
}
- (void)dealloc
{
[_book release]; //Person對象銷毀時不再擁有_book顽频,需要對其做一次release操作
[super dealloc];
}
@end
- ** release 舊的成員變量**
上面的代碼非常不嚴謹藤肢,因為當出現(xiàn)下面的情況時,舊的成員變量無法被釋放糯景。
Book *b1 = [Book new];
[p setBook:b1];
Book *b2 = [Book new];
[p setBook:b2];; //重新為_book賦不同的值
[b2 release];
[b1 release];
由于setter
方法對傳入的b1
嘁圈、b2
都做了一次retain
,在Person
對象的delloc
方法里卻只對當前的_book
(也就是b2
) release
蟀淮,導致舊的成員變量b1
無法被釋放丑孩,由此產(chǎn)生了內(nèi)存泄露問題。對setter
方法優(yōu)化后的代碼應該如下:
- (void)setBook:(Book *)book
{
[_book release]; //對原來使用的成員變量做一次release操作
_book = [book retain]; //再持有新傳入的變量
}
-
判斷傳入的變量
即便是上面的代碼仍然還有問題灭贷,比如:
Person *p = [Person new];
Book *b1 = [Book new];
[p setBook:b1];
[b1 release];
[p setBook:b1];; //對_book 重新賦相同的值
[p release];
[b1 release]
代碼執(zhí)行完畢,b1
的引用計數(shù)器值為 1 略贮,接著執(zhí)行[p setBook:b1]
為p.book
重新賦相同的值時甚疟,會進行下面的操作:
[_book release];
_book = [book retain];
【注意】:此時的_book
是第一次設置的變量b1
,其引用計數(shù)器值為1逃延;再進行release
览妖,變量b1
的引用計數(shù)變?yōu)?,系統(tǒng)回收該對象內(nèi)存揽祥;接著執(zhí)行
_book = [book retain]
將發(fā)生錯誤讽膏,因為此時的b1
已經(jīng)是僵尸對象。 retain
無法將一個僵尸對象起死回生(需要開啟僵尸對象檢測功能)拄丰。
【解決方案】:應該對傳入的變量(book
)進行判斷府树,如果傳入的變量(book
)和當前成員變量(_book
)是同一個變量,那么setter
方法不需要做任何操作料按。
最后的一個完整的setter
方法應該如下:
- (void)setBook:(Book *)book
{
if (book != _book) { //兩者進行判斷
[_book release]; //對原來的成員變量進行release
_book = [book retain]; //對傳入的變量進行retain
}
}
屬性
屬性(property)是Objective-C 2.0引入的新特性奄侠,其通過將成員變量包裝達到封裝對象中數(shù)據(jù)的作用,并且提供了“點語法”使開發(fā)者更簡單的依照類對象訪問其中的數(shù)據(jù)载矿。具體來說使用屬性構建類主要有以下好處:
-
自動合成存取方法
使用屬性封裝數(shù)據(jù)可以讓編譯器自動申明和實現(xiàn)與屬性相關的存取方法垄潮,此過程有一個專業(yè)名稱--“自動合成”(synthesize)。而且該過程是在編譯執(zhí)行的期間自動完成的闷盔,因此無法看到 “合成方法(synthesized method)”的源碼弯洗。
@class Book;
@interface Person : NSObject
@property Book *book;
@end
@class Book;
@interface Person : NSObject
{
Book *_book;
}
- (void)setBook:(Book *)book;
- (Book *)book;
上面兩段代碼實際上是等效的。
-
自動生成對應的成員變量
除了自動生成方法代碼外逢勾,編譯器還會自動向類中添加屬性對應的成員變量牡整,并且在屬性名前面加上下劃線,以此作為成員變量的名稱并和屬性名區(qū)分開來溺拱。
- (void)dealloc
{
[_book release];
NSLog(@"Person - dealloc");
[super dealloc];
}
在上面使用屬性語法的Person
類中果正,重寫dealloc
能調(diào)用[_book release]
說明的確是生成了屬性對應的成員變量_book
炎码。
-
支持自定義成員變量名
如果開發(fā)中對編譯器自動生成的成員變量名不滿意贡定,使用@synthesize
語法在類的實現(xiàn)文件里可以自定義其名稱棠枉。
注意:該語法是將已經(jīng)存在的屬性名替換成左邊自定義的名稱,以后屬性對應的系統(tǒng)生成的帶有下劃線的成員變量名就被替換成了自定義的名字哨免。同時其存取方法中使用到的成員變量名也將被替換迫皱。但是通過點語法調(diào)用self.property
是不受影響的歉闰。
@implementation Person
@synthesize book = _myBook;
//自定義的_myBook名稱用來替換之前的屬性名稱,
//但是通過點語法調(diào)用self.book是不受影響的卓起。
- (void)dealloc
{
[_myBook release];
// [self.book release]; 這行代碼和上面那行代碼是一樣的和敬。
NSLog(@"Person - dealloc");
[super dealloc];
}
@end
-
支持自定義成員變量存取方法
1,如果通過屬性語法自動生成的getter
戏阅、setter
方法不能滿足開發(fā)要求昼弟,我們可以重寫屬性對應的getter
、setter
方法奕筐,達到自定義存取方法的目的舱痘。
2,使用dynamic關鍵字离赫,該語法告知編譯器不要為某些屬性創(chuàng)建對應的成員變量和默認實現(xiàn)的存取方法(這樣做的后果是屬性對應的成員變量和其存取方法都需要自己定義和實現(xiàn))芭逝。
@implementation Person
@dynamic book;
- (void)dealloc
{
[_book release]; //此行代碼報錯,具體原因為_book成員變量沒有申明
NSLog(@"Person - dealloc");
[super dealloc];
}
@end
通過上面的解釋我們得出這樣的結論:
屬性 = 成員變量 + 存取方法 (@property = ivar + getter + setter)
屬性語法極大的簡化了開發(fā)人員在構建類時封裝數(shù)據(jù)的工作量(編譯器默認實現(xiàn))渊胸。另外需要注意的是 synthesize旬盯、**dynamic **關鍵字用的較少,因為大部分時候編譯器默認實現(xiàn)的代碼還是比較符合開發(fā)需求的翎猛。
屬性特質(zhì)內(nèi)存分析
在上面講解屬性特性時申明的屬性胖翰,仍然是不符合要求的。
@class Book;
@interface Person : NSObject
@property Book *book;
@end
這樣的定義的屬性切厘,其setter
方法僅僅類似于普通的基本數(shù)據(jù)類型直接賦值泡态,驗證代碼如下:
Person *p = [Person new];
Book *b = [Book new];
p.book = b;
NSLog(@"%lu",b.retainCount); //打印b的引用計數(shù)器值為 1
[b release];
[p release];
通過打印b
的引用計數(shù)器值(b.retainCount
= 1),我們發(fā)現(xiàn)代碼p.book = b
執(zhí)行后迂卢,b
的引用計數(shù)并沒有增加某弦;如果此時進行的是“處理過的setter
方法”,b
必定會被retain
一次而克,其引用計數(shù)值應該為2靶壮,所以得出結論:單純的@property Book *book
定義屬性,其setter
方法只是進行了簡單的賦值運算员萍,并不符合Objc對象需要進行內(nèi)存管理的原則腾降。因此在MRC中我們常常這樣改進:
@class Book;
@interface Person : NSObject
@property (retain) Book *book;
@end
通過使用retain
編譯器會將該屬性對應的setter
方法替換成上面提到的 “完整的setter
方法”,進而解決內(nèi)存管理問題碎绎。 在ARC中對于屬性后面的修飾詞處理的更加嚴謹和豐富螃壤,具體來說引入了 屬性特質(zhì)(attributes)概念抗果。
-
屬性特質(zhì)(attributes)
屬性特質(zhì)(attributes)也可被稱為屬性修飾詞、屬性特性奸晴。通過分析源碼可以一探究竟冤馏。
property
在runtime
中是objc_property_t
,其結構如下:
typedef struct objc_property *objc_property_t;
而objc_property
是一個結構體寄啼,包括name
和attributes
逮光,其結構如下:
struct property_t {
const char *name;
const char *attributes;
};
而attributes
本質(zhì)是objc_property_attribute_t
,定義了property
的一些特性墩划,其結構如下如下:
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
attributes
主要描述特性有:原子性和非原子性涕刚、讀寫權限、存取方法名乙帮、內(nèi)存管理語義等杜漠。
修飾詞對內(nèi)存的影響(ARC)
-
原子性
atomic:原子性的(編譯器默認該特性)。該特性會為屬性的setter
方法加鎖察净,以保證其操作是線程安全的驾茴,同時會影響性能。nonatomic:非原子性的塞绿。該特性不會為屬性的
setter
方法加鎖,非線程安全的恤批,但是能極大的提升性能异吻。由于在iOS開發(fā)中移動設備性能有限,所以絕大多數(shù)情況下使用的該特性喜庞。iOS中使用同步鎖的開銷加大诀浪,這會帶來性能問題。一般情況下并不要求屬性必須是“原子性”的延都,因為這并不能保證線程安全雷猪。若要實現(xiàn)“線程安全”的操作,還需采用更深層的鎖定機制才行晰房。開發(fā)iOS程序是一般都會使用
nonatomic
特性求摇,因為atomic
會嚴重影響性能,但是在開發(fā)Mac OS X 程序時殊者,使用atomic
不會有性能瓶頸与境。 讀寫權限
readwrite:可讀可寫的(編譯器默認該特性)。該特性表明編譯器會為其生成對應的getter
和setter
方法猖吴。同時你可以設置和讀取該值摔刁。
readonly:只讀的。該特性修飾的屬性你將不能直接修改其值海蔽,例如使用點語法賦值時報錯共屈,提示不能為readonly
的屬性賦值(但是可以通過KVC機制為該屬性賦值)绑谣。
-
內(nèi)存管理語義
assign:直接賦值。用于Objective-C中基本數(shù)據(jù)類型的變量作為屬性拗引,該特性修飾的屬性其
setter
方法只會針對“純量類型”進行簡單的賦值操作借宵。例如枚舉類型、布爾類型寺擂、整型暇务、浮點型等基本數(shù)據(jù)類型的變量(基本數(shù)據(jù)類型變量無需內(nèi)存管理)。weak:非持有關系 怔软。用于Objective-C中對象作為屬性垦细。同
assign
類似該特性修飾的屬性其setter
方法既不保留新值,也不釋放舊值挡逼;然而在屬性所指的對象銷毀時括改,屬性值會被清空。ARC下家坎,在有可能出現(xiàn)循環(huán)引用情況中往往要通過讓其中一端使用
weak
來解決嘱能,比如:兩個類相互為對方的屬性、申明delegate
代理屬性等虱疏。另外自身已經(jīng)對它進行一次強引用惹骂,沒有必要再強引用一次,此時也會使用weak
做瞪,比如:添加子控件是常常用到[self.view addSubView: self.imageView]
对粪;self.imageView
這個屬性就可以使用weak
定義。xib
中拖線時IBOutlet
控件屬性一般也使用weak
(當然装蓬,也可以使用strong
)著拭。如果你對 “屬性所指的對象銷毀時,屬性值會被清空”不是很理解牍帚,可以參考下面的例子:
@interface ViewController ()
@property (nonatomic, weak) UIView *redView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIView *redView = [[UIView alloc] init]; //創(chuàng)建子視圖
redView.backgroundColor = [UIColor redColor]; //設置子視圖背景色
[self.view addSubview:redView]; //添加到視圖上
self.redView = redView; //屬性賦值
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%@",self.redView);
self.redView.frame = CGRectMake(50, 50, 100, 100); //點擊屏幕設置尺寸
}
@end
【分析】示例代碼將`redView`添加的控制器的`View`上儡遮,當用戶點擊屏幕的時候設置其尺寸顯示出來。對于一般繼承自`NSObject`類型的變量作為屬性時通常使用`strong`修飾暗赶。這里的`redView`明顯是對象類型鄙币,其使用`weak`修飾是因為`[self.view addSubview:redView]`該行代碼將`redView `作為一個元素加入到`self.view.subviews`數(shù)組中,`self.view`本身隱式的對`redView `做了一次持有操作蹂随。
(1)當將`[self.view addSubview:redView]`代碼注釋爱榔。
控制臺輸出:`self.redView = (null)`
【分析】`self.view`未對`self.redView`進行持有操作(`weak`修飾只是簡單的賦值,未對傳入的變量進行`retain`)糙及,當`viewDidLoad `方法調(diào)完详幽,系統(tǒng)為`UIView`對象在堆區(qū)分配出來的空間(`[[UIView alloc] init]`)就被回收了,所以此時`self.redView`指向的對象已經(jīng)被銷毀,同時由于其是`weak`修飾唇聘,編譯器將其指向`nil`版姑,所以打印`self.redView`時其值為`null`。
(2)如果此時將`redView`聲明為`assign`修飾迟郎,即:
@property (nonatomic, assign) UIView *redView;
點擊屏幕時就會崩潰剥险。
![壞內(nèi)存訪問錯誤](http://upload-images.jianshu.io/upload_images/2474121-15ca43eb54eabcc3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
【分析】在屬性所指的對象銷毀時,`assign`不會將屬性指向`nil`宪肖,所以此時`self.redView`已經(jīng)是一個野指針其指向了一個僵尸對象表制,打印`self.redView`時訪問了一塊已經(jīng)被回收的內(nèi)存。所以崩潰控乾。
希望能通過這個簡單的例子解釋一下`weak`和`assign`的區(qū)別么介。
**unsafe_unretained:**非持有關系(同`assign`)。用于Objective-C中對象或者是基本數(shù)據(jù)類型的變量作為屬性蜕衡。同`weak`一樣該特性修飾的屬性其`setter`方法既不保留新值(`unretained`)壤短,也不釋放舊值。不同的是如果屬性所指的對象被銷毀慨仿,指向該對象的指針依然指向原來的內(nèi)存地址久脯,如果此時繼續(xù)訪問該對象很容易產(chǎn)生壞內(nèi)存訪問/野指針/僵尸對象訪問(`unsafe`)。
**strong:**持有關系镰吆。用于Objective-C中對象作為屬性帘撰。該特性修飾的屬性的`setter`方法會對傳入變量做一次`retain`并對之前的成員變量做一次`release`。其作用和MRC下的`retain`操作是一樣的万皿。
**copy:**拷貝關系摧找。`copy` 的作用與`strong`類似,然而其修飾的屬性的`setter`方法并非`retain`新值相寇,而是將其“拷貝” (`copy`)一份慰于。因為父類指針可以指向子類對象(多態(tài)性)钮科,使用`copy`的目的是為了讓本對象的屬性不受外界影響唤衫,無論給對象傳入是一個可變對象還是不可對象,對象本身持有的就是一個不可變的副本绵脯。
>定義`NSString`佳励、`NSArray`、`NSDictionary`等類型的變量作為屬性時常使用`copy`關鍵字蛆挫, 因為其有對應的可變類型:
`NSMutableString`赃承、`NSMutableArray`、`NSMutableDictionary`(可變類型用`strong`)悴侵。
開發(fā)中常用的`block`代碼塊習慣使用`copy`關鍵字修飾(`strong`也是可以的)瞧剖。
* **存取方法名**
**getter = <name>:**通常在為類定義布爾類型的屬性時用于自定義其`getter`方法名。例如:
/// 是否正在工作
@property (nonatomic, assign, getter=isWorking) BOOL working;
/// 是否有訂單有顯示中
@property (nonatomic, assign, getter=isShowing) BOOL showing;
**setter = <name>:**該修飾詞極少使用,除非特殊情況下抓于,例如:
>在數(shù)據(jù)反序列化做粤、轉(zhuǎn)模型的過程中,服務器返回的字段如果以`init`開頭捉撮,所以你需要定義一個`init`開頭的屬性怕品,但默認生成的`setter`與`getter`方法也會以`init`開頭,而編譯器會把所有以`init`開頭的方法當成初始化方法巾遭,而初始化方法只能返回`self`類型肉康,因此編譯器會報錯。這時你就可以使用下面的方式來避免編譯器報錯:
@property(nonatomic, copy, getter=p_initBy, setter=setP_initBy:) NSString *initBy;
* **空值約束(iOS9推出的新特性)**
**nullable:**該屬性值可以為空灼舍。用于Objective-C中對象作為屬性吼和,表示該屬性的`getter`和`setter`方法中賦值和取值都是可以為空。
盡管在定義屬性時寫或不寫`nullable `對該屬性的賦值和操作沒有任何影響片仿,但寫上`nullable `更多的作用在于程序員之間的溝通交流纹安,時刻提醒開發(fā)人員該屬性不一定是有值的,可能需要空值判斷砂豌,要注意使用厢岂。
**nonnull:**該屬性值不能為空。和`nullable `對立阳距,該特質(zhì)修飾的屬性其`getter`和`setter`均不能為空值塔粒,否則會有警告。
另外下面的寫法是等價的筐摘。
@property (nonnull卒茬,nonatomic, copy) NSArray *array;
@property (nonatomic, copy) NSArray * __nonnull array1;
`nullable`、`nonnull`除了在定義屬性時使用咖熟,還可以用在函數(shù)和方法中對參數(shù)和返回值的空值進行約束圃酵。例如:
//函數(shù)
void text(NSArray * __nonnull array) {
}
//方法
- (NSString *__nonnull)creat:(NSArray * __nonnull)array {
return @"coderYQ";
}
![nullable和nonnull的使用](http://upload-images.jianshu.io/upload_images/2474121-8cb863891576d6cc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
當然如果你嫌麻煩,還可以使用宏一次性聲明:
NS_ASSUME_NONNULL_BEGIN
@interface ViewController ()
@property (nonatomic, copy) NSArray *array;
@property (nonatomic, copy) NSArray *array1;
@property (nonatomic, copy) NSArray *array2;
@end
NS_ASSUME_NONNULL_END
如此兩個宏`NS_ASSUME_NONNULL_BEGIN `馍管、`NS_ASSUME_NONNULL_END `之間定義的屬性均有`nonnull `提示郭赐,當然你也可以修改其中的約束,例如:
NS_ASSUME_NONNULL_BEGIN
@interface ViewController ()
@property (nullable, nonatomic, copy) NSArray *array;
@property (null_resettable, nonatomic, copy) NSArray *array1;
@property (nonatomic, copy) NSArray *array2;
@end
NS_ASSUME_NONNULL_END
**null_resettable:**該特性修飾的屬性表示其`setter`中可以賦值為空确沸,但是`getter`方法中返回的值不會為空捌锭。使用該特性定義屬性時編譯器會提出警告:
應該重寫`setter`方法處理賦值為空的情況。
![null_resettable的使用](http://upload-images.jianshu.io/upload_images/2474121-72ca8eb876123438.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
一個`null_resettable `經(jīng)典的使用示例就是蘋果定義`UIViewController`的`view`屬性:
@property(null_resettable, nonatomic,strong) UIView *view;
![null_resettable的使用](http://upload-images.jianshu.io/upload_images/2474121-c61f1fc4a4261aab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
大家都知道控制器的`view`是懶加載的罗捎,每次調(diào)用`getter`方法一旦發(fā)現(xiàn)`view`屬性為空观谦,系統(tǒng)會調(diào)用`loadView`方法創(chuàng)建并返回`view`。該關鍵字告訴開發(fā)人員`self.view`是永遠不會為空的 桨菜,你可以放心的使用豁状。
由于在構建類時為其定義的各種屬性有不同的類型捉偏,所以可以通過屬性修飾詞對存取方法進行微調(diào)進而滿足內(nèi)存管理規(guī)范。
>但是開發(fā)中有時候會重寫`getter`或`setter`方法泻红。這時我們應該保證實現(xiàn)的方法是具備相關屬性的特質(zhì)告私。例如將某個屬性申明為`copy`,那么重寫`setter`方法時應該拷貝傳入的對象承桥,否則誤導該屬性的使用者驻粟,嚴重時會產(chǎn)生bug。同理在其他方法中設置屬性值時凶异,也需要遵循屬性修飾詞的規(guī)定蜀撑。
例如:在下面的自定義初始化方法中`name`應該使用`copy`,而`age`則直接賦值剩彬。
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (instancetype)initWithName:(NSString *)name
age:(int)age;
@end
@implementation Person
- (instancetype)initWithName:(NSString *)name age:(int)age
{
if (self = [super init]) {
_name = [name copy];
_age = age;
}
return self;
}
@end
---
##屬性和成員變量的選擇使用
在上面的`- (instancetype)initWithName: age:`初始化方法中酷麦,初學者可能會有疑問為什么使用的`_name`成員變量而不是`self.name`屬性呢?這里本人通過查閱《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法》對屬性和成員變量的使用場景做了一些總結:
* 初始化方法中設置屬性值時使用成員變量喉恋,因為子類可能會覆蓋該屬性的`setter`方法沃饶。例如:
- (instancetype)initWithName:(NSString *)name age:(int)age
{
if (self = [super init]) {
_name = [name copy];
_age = age;
}
return self;
}
* 懶加載方法中使用成員變量初始化,并通過屬性訪問轻黑。例如:
- (Book *)book
{
if (!_book) {
_book = [[Book alloc] init];
}
return _book;
}
self.book //懶加載的屬性必須通過“getter”方法訪問糊肤,否則成員變量永遠不會被初始化
因為若沒有使用getter
方法直接訪問成員變量(_book),該成員變量(_book)此時尚未設置完成氓鄙。
* 在`delloc`方法中使用成員變量
- (void)dealloc
{
[_book release];
[super dealloc];
}
* 在對象內(nèi)部盡量直接訪問成員變量
關于屬性和成員變量使用的更多細節(jié)請閱讀《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法》第6馆揉、7條。
##文章最后
以上就是筆者對于Objective-C內(nèi)存管理深入知識的學習總結抖拦,部分描述引自《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法》這里做出說明升酣。
如果文中有任何紕漏或錯誤歡迎在評論區(qū)留言指出,本人將在第一時間修改過來态罪;喜歡我的文章噩茄,可以關注我以此促進交流學習; 如果覺得此文戳中了你的G點請隨手點贊复颈;轉(zhuǎn)載請注明出處绩聘,謝謝支持。