iOS開發(fā)中, 之前一直使用swift, 因此對于Objective-C的內(nèi)存管理機制長期處于混亂的一知半解狀態(tài). 今天終于看到一篇講得透徹的博客[Objective-C內(nèi)存管理教程和原理剖析](http://blog.jobbole.com/66197/), 感謝作者.
本文只是個人的一個學(xué)習(xí)摘要, 更詳細內(nèi)容請參考原文.
ARC黃金法則:
>The basic rule to apply is:
***Everything that increases the reference counter with alloc, [mutable]copy[WithZone:] or retain is in charge of the corresponding [auto]release***.
## 基本的內(nèi)存分配
OC的對象是在堆里邊生成, 需要一個指針指向它.
```
ClassA *obj1 = [[ClassA alloc] init];
```
使用完之后不會自動銷毀, 需執(zhí)行dealloc來銷毀, 否則會出現(xiàn)內(nèi)存泄露.
```
[obj1 dealloc];
```
那么看如下代碼:
```
ClassA *obj1 = [[ClassA alloc] init];
ClassA *obj2 = obj1;
[obj1 hello];
[obj1 dealloc];
// obj1, obj2都是指針, 指向同一對象. 而[obj1 dealloc]已經(jīng)銷毀了該對象. 因此下邊的代碼無法執(zhí)行, obj2是無效指針.
[obj2 hello];
[obj2 dealloc];
```
## 引用計數(shù)
為了避免出現(xiàn)上邊的無效指針,? OC采用引用計數(shù)的方式. 引用計數(shù)加1的有:alloc, retain, strong, 減1的有release. 當(dāng)對象的引用計數(shù)為0的時候, 會自動調(diào)用dealloc.
```
ClassA *obj1 = [[ClassA alloc] init]; // 計數(shù)為1
[obj1 release]; // 計數(shù)為0, 自動dealloc
ClassA *obj1 = [[ClassA alloc] init]; // 計數(shù)為1
ClassA *obj2 = obj1; // 計數(shù)為1, 指針賦值不會增加引用計數(shù)
[obj1 hello];
[obj1 release]; // 計數(shù)為0, 自動dealloc
// 因?qū)ο笠驯籨ealloc, 故以下代碼不會執(zhí)行, obj2仍然是無效指針.
[obj2 hello];
[obj2 release];
```
那么如何解決obj2的無效指針問題呢? 通過retain使得引用計數(shù)加1.
```
ClassA *obj1 = [[ClassA alloc] init]; // 計數(shù)為1
ClassA *obj2 = obj1; // 計數(shù)為1
[obj2 retain]; // 計數(shù)為2
[obj1 hello];
[obj1 release]; // 計數(shù)為1
[obj2 hello];
[obj2 release]; // 計數(shù)為0, 自動dealloc
```
所以, 引用計數(shù)的關(guān)鍵在于當(dāng)對象的引用計數(shù)為0時, 會自動調(diào)用dealloc銷毀該對象. 指針賦值時, retain count不會自動增加, 需要手動retain.
另外, release一個對象之后, 應(yīng)立即將指針清空(release一個空指針是合法的). 如:
```
ClassA *obj1 = [[ClassA alloc] init];
[obj1 release];
obj1 = nil;
```
## Autorelease
自動釋放池其實是NSAutoreleasePool, 可以自動釋放對象.
```
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
```
NSAutoreleasePool內(nèi)部包含一個NSMutableArray, 用于保存聲明為autorelease的所有對象.
下邊看一下Apple的例子:
首先不用autorelease,
```
- (NSString *)fullName {
NSString *string = [[NSString alloc] initWithFormat:@"%@ %@", self.firstName, self.lastName];
return string;
}
```
則, 該string不能找到合適的時機釋放, 因此會出現(xiàn)內(nèi)存泄露.
那么, 采用autorelease之后,
```
- (NSString *)fullName {
NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@", self.firstName, self.lastName] autorelease];
return string;
}
```
雖然, string不會立即釋放, 但autorelease會在其release的時機將其釋放.
或者采用更直接簡便的方式, 這種方式?jīng)]有alloc, 所以也不要求release.
```
- (NSString *)fullName {
NSString *string = [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
return string;
}
```
其實, 此方式也是通過alloc+autorelease的方式來實現(xiàn)的, 只是對使用者而言, 省掉了alloc+autorelease的步驟.
NSAutoreleasePool自身在銷毀的時候, 會遍歷并試圖release其中的所有autorelease對象. 如果該對象的引用計數(shù)為1, 則將其引用計數(shù)減1, 銷毀該對象; 如果該對象的引用計數(shù)大于1, 則autorelease之后其引用計數(shù)仍大于0, 對象未銷毀, 出現(xiàn)內(nèi)存泄露.
如在iOS工程的main.m文件中
```
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
```
其實可以自行使用NSAutoreleasePool,
```
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (int i=0;i<100;i++) {
for (int j=0;j<10000;j++) {
// 產(chǎn)生autorelease對象
[NSString stringWithFromat:@"1234567890"];
}
}
[pool release];
return 0;
```
可以看出, 所有autorelease對象都只能在NSAutoreleasePool執(zhí)行release的時候銷毀, 顯然不能很好地利用內(nèi)存. 那么可以使用內(nèi)嵌的NSAutoreleasePool, 代碼優(yōu)化如下:
```
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (int i=0;i<100;i++) {
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
for (int j=0;j<10000;j++) {
// 產(chǎn)生autorelease對象
[NSString stringWithFromat:@"1234567890"];
}
[loopPool release];
}
[pool release];
return 0;
```
這樣, 就可以做到及時地釋放內(nèi)存.
所以, 實例化一個對象可以有兩種方法:
1. 采用alloc + release的方法
```
Dog *dog = [[Dog alloc] init];
// 代碼
[dog release]; // alloc 和 release 必須一一對應(yīng).
```
2. 采用autorelease的方法
```
// 這種方式把dog對象加到autorelease pool中, 就不需要手動release了.
+ (id)dog {
return [[[Dog alloc] init] autorelease];
}
```
所以, NSAutoreleasePool的關(guān)鍵在于, 放置到其中的autorelease對象, 會在該pool release的時候自動銷毀(絕大多數(shù)情況下, 都是正常的引用計數(shù)為1的autorelease對象). 因此對象本身就沒必要調(diào)用release方法. 同時NSAutoreleasePool的release時機很重要. 因為autorelease pool不會立即釋放, 當(dāng)它到了最近的pool release的時候才會自動檢測retainCount是否為0, 一旦為0則釋放pool. 當(dāng)pool本身銷毀的時候, 其中的對象才會執(zhí)行release操作, 否則將一直占用內(nèi)存空間.
所以, 不要濫用autorelease, 如對象的生命周期很清晰, 則結(jié)束使用后立即release. 過多等待autorelease對象浪費內(nèi)存.
## property中的關(guān)鍵字
***@property中的關(guān)鍵字表示屬性如何存儲, 其實全都是用于設(shè)置getter/setter的操作特性***, 如果我們將getter/setter展開來看的話就會很明白.
### assign
assign相當(dāng)于指針賦值, 不對引用計數(shù)進行操作, 如原對象不用了, 一定要將其設(shè)置為nil. 一般基本變量用該屬性聲明, 如int, BOOL.
即: 調(diào)用setter方法時直接賦值, 不進行retain操作. 不改變引用計數(shù).
***assign與weak的區(qū)別在于:***
>當(dāng)指針?biāo)赶驅(qū)ο髍elease之后, weak會賦nil給該指針, 而assign不會賦nil給指針. 所以使用assign的時候, 一旦指針?biāo)赶驅(qū)ο髍elease之后, 再給該對象發(fā)送消息(調(diào)用方法), 程序即會crash.
weak用于OC對象的弱引用, assign用于C原始類型.
### retain
retain其實是指針拷貝(地址相同), 會增加對象的引用計數(shù)五芝,而OC中的基本數(shù)據(jù)類型沒有引用計數(shù)粱快。
如: 把對象添加到數(shù)組中[array addObject:obj];,該對象obj的引用計數(shù)retainCount將加1. 如下圖
![內(nèi)存管理retain](http://img.blog.csdn.net/20150628091306890)
使用retain, 調(diào)用setter方法時, 先release舊值, 對賦予的新值進行引用計數(shù)+1. 二者的內(nèi)存地址一樣.
看下邊的例子:
```
// Person.h
@interface Person: NSObject {
Dog *_dog;
}
@property (retain) Dog *dog;
@end
```
```
// Person.m
@implementation Person
@synthesize dog = _dog;
- (void) dealloc {
self.dog = nil; // 其實調(diào)用[self setDog:nil];方法
[super dealloc];
}
@end
```
參考之前的一篇文章[Objective-C中類的成員變量與屬性](http://blog.csdn.net/icetime17/article/details/45537975), 可知@property與@synthesize的關(guān)系, 總之, 這里編譯器會識別@synthesize, 并自動為dog屬性添加getter/setter方法.
編譯器對 @property (retain) Dog *dog; 展開為:
```
- (Dog *)dog;
- (void)setDog:(Dog *)aDog;
```
編譯器對 @synthesize dog = _dog; 展開為:
```
- (Dog *)dog {
return _dog;
}
- (void)setDog:(Dog *)aDog {
if (_dog != aDog) {
[_dog release]; // 即使_dog的retainCount已為0, 對其做release也不會出錯.
_dog = [aDog retain];
}
}
```
getter/setter方法是被編譯器隱藏的, 如果不使用@synthesize dog = _dog;的話, 必須自己編寫這兩個方法.
### copy
不同于retain的指針拷貝, copy都是內(nèi)容拷貝. 即復(fù)制一個對象變成新的對象(新的內(nèi)存地址), 新對象的引用計數(shù)為1, 原對象的引用計數(shù)不變. 所以, copy得到的均為一個獨立的對象, 跟原對象沒有關(guān)系.
一般來說, ***block都是使用copy關(guān)鍵字***. 如下邊的SelectedCity的使用, 在這里只是簡單地截取部分代碼, 不做太具體的解釋. 需要詳細了解block的同學(xué), 可以參考[轉(zhuǎn): 深入理解Objective-C的Block](http://blog.csdn.net/icetime17/article/details/46649423).
在CityListViewController中
```
// CityListViewController.h
typedef void(^SelectedCity)(NSString *);
@interface CityListViewController: UIViewController
@property (nonatomic, copy) SelectedCity selectedCity;
@end
// CityListViewController.m
if (_selectedCity) {
_selectedCity(cell.textLabel.text);
}
```
在MainViewController中:
```
// MainViewController.h
typedef void(^SelectedCity)(NSString *);
@interface MainViewController: UIVieController
@property (nonatomic, copy) SelectedCity selectedCity;
@end
// MainViewController.m
- (void)viewDidLoad {
__weak MainViewController *weakVC = self;
_selectedCity = ^(NSString *city) {
weakVC.cityLabel.text = city;
weakVC.vTracks.selectedCity = city; // vTracks中有selectedCity屬性
[weakVC.vTracks invokeRefresh];
};
}
- (IBAction)cityListClicked:(UIButton *sender) {
CityListViewController *cityListVC = [CityListViewController alloc] init];
cityListVC.selectedCity = _selectedCity;
[self.navigationController pushViewController:cityListVC animated:YES];
}
```
編譯器對 @property (copy) NSString *str; 展開為
```
- (NSString *)str {
return _str;
}
- (void)setStr:(NSString *)newStr {
if (_str != newStr) {
[_str release];
_str = [newStr copy];
}
}
```
copy其實有兩種:
1. copy得到的是不可變類型, 如NSString, NSArray, NSDictionary, NSData, NSSet.
2. mutableCopy得到的是mutable類型. 如NSMutableString, NSMutableArray, NSMutableDictionary, NSMutableData, NSMutableSet.
***copy操作必須遵循NSCopying和NSMutableCopying協(xié)議, 實現(xiàn)其中的copyWithZone:和mutableCopyWithZone:方法, 即告訴編譯器如何做到copy操作***.
```
- (id)copyWithZone:(NSZone *)zone {
Dog *dog = [[[[self class] allocWithZone:zone] init] autorelease];
dog.name = self.name;
dog.year = self.year;
return dog;
}
```
### deep copy
上邊的copy默認都是淺拷貝, 如對于NSArray而言, 淺拷貝只是copy一份NSArray, 該NSArray中的每一個元素仍然指向原NSArray中指向的對象.? 如下圖:
![內(nèi)存管理copy](http://img.blog.csdn.net/20150628091451099)
而deep copy則將NSArray中元素指向的對象也做一份拷貝, 一般用于NSArray, NSDictionary. 如下圖:
![內(nèi)存管理deepCopy](http://img.blog.csdn.net/20150628091542228)
deep copy有兩種實現(xiàn)方式:
***1. 使用copyItems***
copyItems
```
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag;
- (id)initWithDictionary:(NSDictionary *)otherDictionary copyItems:(BOOL)flag;
```
使用如下:
```
NSMutableArray *_carList = [[NSMutableArray alloc] init];
// 向_carList中填充內(nèi)容
NSMutableArray *carList1 = [[NSMutableArray alloc] initWithArray:_carList];
// 淺拷貝, 同樣會改變原_carList中的內(nèi)容
[[carList1 objectAtIndex:0] setName:@"淺拷貝內(nèi)容"];
NSMutableArray *carList2 = [[NSMutableArray alloc] initWithArray:_carList copyItems:YES];
// deep copy, 則不會改變原_carList中的內(nèi)容
[[carList2 objectAtIndex:0] setName:@"deep copy內(nèi)容"];
```
***2. 使用歸檔NSKeyedArchiever***
歸檔是將整個數(shù)組以二進制形式存到NSData中, 需要的時候就將其讀取出來再轉(zhuǎn)換成原來數(shù)組的形式.
```
// 將NSArray的內(nèi)容保存到NSData中
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:_carList];
// 將二進制對象data還原成NSArray
NSMutableArray *carList3 = NSKeyedArchiver unarchiveObjectWithData:data];
```
歸檔需要繼承NSCoding協(xié)議, 實現(xiàn)encodeWithCoder:和initWithCoder:兩個方法.
```
// 存檔方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.year forKey:@"year"];
}
// 解檔方法
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.year = [aDecoder decodeIntForKey:@"year"];
}
return self;
}
```
### strong
strong是強引用, 持有對象, 對象的引用計數(shù)+1, 如string1和string2都指向一個字符串, 則string1=nil, 而string2不變.
所有strong修飾符的變量在超出其變量作用域時, 釋放其被賦予的對象. 即持有強引用的變量在超出其作用域時被放棄, 隨著強引用的失效, 引用的對象會隨之釋放.
strong變量執(zhí)行ARC計數(shù), 不會自動釋放. 其死亡直接決定了所指向?qū)ο蟮乃劳?
為了確保使用中的實例不會被銷毀,ARC 會跟蹤和計算每一個實例正在被多少屬性吹害,常量和變量所引用。哪怕實例的引用數(shù)為1,ARC都不會銷毀這個實例。
為了使之成為可能采幌,無論你將實例賦值給屬性,常量或者是變量震桶,屬性休傍,常量或者變量,都會對此實例創(chuàng)建強引用蹲姐。之所以稱之為強引用磨取,是因為它會將實例牢牢的保持住人柿,只要強引用還在,實例是不允許被銷毀的忙厌。
實際上, strong和retain的意思相同, 推薦使用strong代替retain.
***id類型和對象類型的所有權(quán)修飾符默認為strong, 會影響對象回收***
### weak
使用strong容易引起循環(huán)引用的問題. 為了防止循環(huán)強引用凫岖,可采用弱引用和無主引用。這兩種允許循環(huán)引用中的一個實例引用另外一個實例而不保持強引用逢净,這樣實例能夠相互引用而不產(chǎn)生循環(huán)強引用哥放。
弱引用? :對于生命周期中會變?yōu)閚il的實例采用。
無主引用:對于初始化賦值后再也不會變?yōu)閚il的實例采用汹胃。
weak是弱引用, 不持有對象, 在超出其變量作用域時, 對象即被釋放. 在持有某對象的弱引用時, 若該對象被釋放, 則此弱引用將自動失效且等于nil.
對象的計數(shù)不變. 如string1和string2都指向一個字符串, 則string1=nil, 那么string2也會變?yōu)閚il.
因其沒有retain內(nèi)存地址, 若其指向的地址對象一旦釋放, 則該指針會指向nil.
ARC空閑時釋放, 對象釋放時自動將指針置NULL. 不決定對象的存亡, 即使一個對象被持有無數(shù)個弱引用, 只要沒有強引用指向它, 則最后還是會清除
如兩個對象互相為對方的成員變量, 則一定不能同時retain, 否則dealloc函數(shù)形成死鎖, 兩個對象都無法釋放. weak可以用于防止野指針和死鎖, 避免循環(huán)引用.
ARC機制中婶芭,為什么UI對象用weak东臀,代理用weak着饥?比如我們往view里加一個button,相當(dāng)于在subviews這個數(shù)組里加一個button惰赋,這個數(shù)組空間里會有一塊內(nèi)存指向button宰掉,是強指針,只要控制器還在赁濒,button就在轨奄,所以當(dāng)創(chuàng)建button時用? weak,防止內(nèi)存泄漏拒炎。代理如果是強指針的話挪拟,它指向控制器,那么所有東西都不能relese了击你,前面道理同UI控件玉组。
***weak不會影響對象回收, 一般UI對象和delegate用weak***
### __weak
__weak 聲明了可以自動nil化的弱引用.
注意: block被copy時, 會對block中用到的對象產(chǎn)生強引用(引用計數(shù)+1). 如果block中又引用了對象的其他成員變量, 就會對該變量本身產(chǎn)生強引用, 則變量本身和它自己的block屬性就形成了循環(huán)引用. 即使用self之類的有可能導(dǎo)致retain cycle, 所以一般用__weak的方式.
請看下邊的城市列表例子.
在CityListVC中, 通過block將city的值傳遞給MainVC.
在CityListVC.h中: 定義block塊對象
```
typedef void(^SelectedCity)(NSString *);
@property(copy,nonatomic) SelectedCity selectCity;
CityListVC.m中:
if (_selectCity) {
_selectCity(cell.textLabel.text); // 這里要通過block進行View之間的數(shù)據(jù)傳遞.
}
```
MainVC.h中:
```
typedef void(^SelectedCity)(NSString *);
@property(copy,nonatomic) SelectedCity selectCity;
```
MainVC.m中:
```
self.vTracks.selectedCity = @"城市";
__weak MainViewController *weakVC = self;
_selectCity = ^(NSString *city){
weakVC.cityLabel.text = ([city isEqualToString:@"全國"]) ? @"城市" : city;
weakVC.vTracks.selectedCity = ([city isEqualToString:@"城市"] | [city isEqualToString:@"全國"]) ? nil : city;
[weakVC.vTracks invokeRefresh];
};
```
在block中, 使用__weak的方式聲明了一個weakVC, 即對自身對象的弱引用. 即block對象不對self對象進行retain, 弱引用的方式可以避免出現(xiàn)循環(huán)引用. 如果是non-ARC, 則使用__block替換__weak, 即在block中引入一個新的結(jié)構(gòu)體成員變量指向這個__block變量, __block typeof(self) weakSelf = self.
### static
static, 全局變量.
1. 函數(shù)體內(nèi)static變量的作用范圍為該函數(shù)體, 該變量只分配一次, 其值在下次調(diào)用的時候仍維持上次的值.
2. 模塊內(nèi)的static可被模塊內(nèi)所有函數(shù)訪問, 但不能被模塊外其他函數(shù)訪問.
3. 類中的static成員變量可以視作類變量, 跟對象實例無關(guān), 屬于整個類所有, 對類的所有對象只有一份拷貝. 已經(jīng)實例化的各個對象之間的該static變量不會相互影響, 但對其的改變會影響到class本身. 若再次實例化新的對象, 則該新對象就擁有最新的static類變量.
4. 類中的static成員函數(shù)屬于整個類所有, 該函數(shù)不接收this指針, 只能訪問類的static成員變量.
### const
const, 常量不能修改. 超出作用域后會釋放. 即const對于對象生存期內(nèi)是常量, 對于整個類而言可變.
1. 可以指定指針, 指針?biāo)赶驍?shù)據(jù), 或二者都為const.
2. 函數(shù)中, const修飾形參, 則該輸入?yún)?shù)在函數(shù)內(nèi)部不能修改.
3. 類的成員函數(shù)若指定const, 則為常函數(shù), 不能修改類的成員變量.? (常見于返回一個const值的函數(shù))
### extern
extern: 該模塊中的變量和函數(shù)可在其他模塊中使用.
### 讀寫權(quán)限
readwrite, readonly: 控制成員變量的訪問權(quán)限, 對setter/getter的作用.
### 原子操作
nonatomic: 非原子性訪問, 不加同步, 多線程并發(fā)訪問.
atomic 線程保護. 互斥鎖.
## 總結(jié)
assign: 一般用于int, BOOL等基本變量類型, 防止循環(huán)引用.
weak: 一般用于storyboard或xib中的UI對象和delegate對象.
delegate對象使用weak是為了防止循環(huán)引用。
assign與weak區(qū)別:當(dāng)對象被釋放后丁侄,weak會自動將指針指向nil惯雳,而assign不會。
所以向nil發(fā)送消息導(dǎo)致野指針錯誤unrecognized selector sent to instance鸿摇。防止野指針
strong: 一般用于id類型(非delegate)和對象類型(NSString, UITableView等). 默認, 對應(yīng)于retain.
如果代碼寫的UI對象(使用alloc+init)石景,要使用strong,否則可能在release的時候出如下警告:
Assigning retained object to weak variable; object will be released after assignment.
而如果是IB拉過來的UI對象拙吉,會自動設(shè)置為weak潮孽。
_unsafe_unretained與weak功能一致, 區(qū)別在于當(dāng)指向的對象銷毀之后, weak將變量置為nil, 防止調(diào)用野指針.
block一般用copy。block一般使用copy關(guān)鍵之進行修飾筷黔,block使用copy是從MRC遺留下來的“傳統(tǒng)”恩商,在MRC中,方法內(nèi)容的block是在棧區(qū)的必逆,使用copy可以把它放到堆區(qū)怠堪。但在ARC中寫不寫都行:編譯器自動對block進行了copy操作揽乱。
copy:用@property聲明 NSString、NSArray粟矿、NSDictionary 經(jīng)常使用copy關(guān)鍵字凰棉,是因為他們有對應(yīng)的可變類型:NSMutableString、NSMutableArray陌粹、NSMutableDictionary撒犀,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動掏秩,應(yīng)該在設(shè)置新屬性值時拷貝一份或舞。
如果我們使用是strong,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。
copy此特質(zhì)所表達的所屬關(guān)系與strong類似蒙幻。然而設(shè)置方法并不保留新值映凳,而是將其“拷貝” (copy)。 當(dāng)屬性類型為NSString時邮破,經(jīng)常用此特質(zhì)來保護其封裝性诈豌,因為傳遞給設(shè)置方法的新值有可能指向一個NSMutableString類的實例。這個類是NSString的子類抒和,表示一種可修改其值的字符串矫渔,此時若是不拷貝字符串,那么設(shè)置完屬性之后摧莽,字符串的值就可能會在對象不知情的情況下遭人更改庙洼。所以,這時就要拷貝一份“不可變” (immutable)的字符串镊辕,確保對象中的字符串值不會無意間變動油够。只要實現(xiàn)屬性所用的對象是“可變的” (mutable),就應(yīng)該在設(shè)置新屬性值時拷貝一份丑蛤。
循環(huán)引用:
typeof (&*self) __weak weakSelf = self;
autorelease:在retainCount為1時叠聋,不會繼續(xù)減一。而是標(biāo)記為需釋放受裹,準(zhǔn)確的釋放時機暫不明確碌补,可能與runloop有關(guān)。