為什么進行內(nèi)存管理?
由于移動設(shè)備的內(nèi)存極其有限,所以每個APP所占的內(nèi)存也是有限制的,當(dāng)app所占用的內(nèi)存較多時,系統(tǒng)就會發(fā)出內(nèi)存警告,這時需要回收一些不需要再繼續(xù)使用的內(nèi)存空間,比如回收一些不再使用的對象和變量等。如果內(nèi)存占用過大,用戶會感覺界面卡頓,app運行速度慢等,甚至系統(tǒng)可能會強制退出app已釋放資源。這時候app就會發(fā)生閃退現(xiàn)象,給用戶造成影響。
那么問題來了,內(nèi)存管理范圍有那些呢?
- 任何繼承NSObject的對象,
- 對其他的基本數(shù)據(jù)類型無效(int、char、float、double、struct、enum等 )。
- 本質(zhì)原因是因為對象和其他數(shù)據(jù)類型在系統(tǒng)中的存儲空間不一樣,其它局部變量主要存放于棧中,而對象存儲于堆中,當(dāng)代碼塊結(jié)束時這個代碼塊中涉及的所有局部變量會被回收,指向?qū)ο蟮闹羔樢脖换厥眨藭r對象已經(jīng)沒有指針指向,但依然存在于內(nèi)存中,造成內(nèi)存泄露。
知道了需要管理內(nèi)存的范圍,我們就需要知道如何管理那些不需要使用的對象。這時候我們需要引入一個概念。
引用計數(shù)器
- 每個OC對象都有自己的引用計數(shù)器
- 它是一個整數(shù)
- 從字面上, 可以理解為”對象被引用的次數(shù)”
- 也可以理解為: 它表示有多少人正在用這個對象
- 簡單來說, 可以理解為:引用計數(shù)器表示有多少人正在使用這個對象 當(dāng)沒有任何人使用這個對象時, 系統(tǒng)才會回收這個對象。 也就是說,當(dāng)對象的引用計數(shù)器為0時,對象占用的內(nèi)存就會被系統(tǒng)回收;如果對象的計數(shù)器不為0,那么在整個程序運行過程,它占用的內(nèi)存就不可能被回收(除非整個程序已經(jīng)退出 )。
- 任何一個對象, 剛生下來的時候, 引用計數(shù)器都為1
- 當(dāng)使用alloc、new或者copy創(chuàng)建一個對象時,對象的引用計數(shù)器默認就是1
引用計數(shù)器的操作
-
引用計數(shù)器的常見操作
- 給對象發(fā)送一條retain消息,可以使引用計數(shù)器值+1(retain方法返回對象本身)
- 給對象發(fā)送一條release消息, 可以使引用計數(shù)器值-1
- 給對象發(fā)送retainCount消息, 可以獲得當(dāng)前的引用計數(shù)器值
需要注意的是: release并不代表銷毀\回收對象, 僅僅是計數(shù)器-1
dealloc方法基本概念
當(dāng)對象銷毀的時候,有一個常用的方法(dealloc)也需要我們了解。
當(dāng)一個對象的引用計數(shù)器值為0時,這個對象即將被銷毀,其占用的內(nèi)存被系統(tǒng)回收對象即將被銷毀時系統(tǒng)會自動給對象發(fā)送一條dealloc消息
(因此, 從dealloc方法有沒有被調(diào)用,就可以判斷出對象是否被銷毀)
一般會重寫dealloc方法,在這里釋放相關(guān)資源。一旦重寫了dealloc方法, 就必須調(diào)用[super dealloc],并且放在最后面調(diào)用。-
使用注意
- 不能直接調(diào)用dealloc方法(該方法是系統(tǒng)自動調(diào)用)
- 一旦對象被回收了, 它占用的內(nèi)存就不再可用,堅持使用會導(dǎo)致程序崩潰(野指針錯誤)
例子:
// 類聲明文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
// 類實現(xiàn)文件
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@"%s", __func__);
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
// 默認情況下所有的指針都是強指針
Person *p = [[Person alloc] init];// 引用計數(shù)為1
// 調(diào)用release方法,引用計數(shù)會減一,
[p release]; // 引用計數(shù)為0,自動調(diào)用delloc方法
return 0;
}
輸出結(jié)果:
[849:245988] -[Person dealloc]
MRC和ARC
- MRC:手動引用計數(shù)(Manual Referecen Counting)所有對象的內(nèi)容都需要我們手動管理, 需要程序員自己編寫release/retain等代碼
內(nèi)存管理的原則就是有加就有減
也就是說, 一次alloc對應(yīng)一次release, 一次retain對應(yīng)一次relese
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@"Person dealloc");
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 只要創(chuàng)建一個對象默認引用計數(shù)器的值就是1
Person *p = [[Person alloc] init];
NSLog(@"retainCount = %lu", [p retainCount]); // 1
// 只要給對象發(fā)送一個retain消息, 對象的引用計數(shù)器就會+1
[p retain];
NSLog(@"retainCount = %lu", [p retainCount]); // 2
// 通過指針變量p,給p指向的對象發(fā)送一條release消息
// 只要對象接收到release消息, 引用計數(shù)器就會-1
// 只要一個對象的引用計數(shù)器為0, 系統(tǒng)就會釋放對象
[p release];
// 需要注意的是: release并不代表銷毀\回收對象, 僅僅是計數(shù)器-1
NSLog(@"retainCount = %lu", [p retainCount]); // 1
[p release]; // 0
NSLog(@"--------");
}
return 0;
}
輸出結(jié)果:
[928:316970] retainCount = 1
[928:316970] retainCount = 2
[928:316970] retainCount = 1
[928:316970] Person dealloc
[928:316970] --------
- MRC注意點:
1.如果一個對象的 retainCount = 0 了之后繼續(xù)調(diào)用release方法會報錯。也就是說不能過度release。
MRC手動引用計數(shù)管理在2011蘋果推出ARC之后開發(fā)的過程中基本不再使用了,但是我們還是要了解其中的原理,方便我們對ARC的理解。
- ARC: Automatic Reference Counting(自動引用計數(shù))
ARC機制可以說是WWDC2011和iOS5所引入的最大的變革和最激動人心的變化。它是LLVM編譯器的特性。可以說ARC解決了讓程序員一直以來感覺淡淡的憂傷的手動內(nèi)存管理的麻煩。- ARC的判斷原則
- 只要還有一個強指針變量指向?qū)ο螅瑢ο缶蜁3衷趦?nèi)存中
- 強指針
- 默認所有指針變量都是強指針
- 被__strong修飾的指針
- 弱指針
- 被__weak修飾的指針
- ARC的判斷原則
Person *p1 = [[Person alloc] init];
__strong Person *p2 = [[Person alloc] init];
__weak Person *p = [[Person alloc] init];
在工程中使用ARC非常簡單:只需要像往常那樣編寫代碼,只不過永遠不寫retain,release和autorelease三個關(guān)鍵字就好~這是ARC的基本原則。當(dāng)ARC開啟時,編譯器在編輯階段將自動在代碼合適的地方插入retain, release和autorelease,而作為開發(fā)者,完全不需要擔(dān)心編譯器會做錯(除非開發(fā)者自己錯用ARC了),在默認情況下ARC機制是自動開啟的。上邊的MRC的代碼用ARC寫就簡單多了。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
#import "Person.h"
@implementation Person
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
Person *p = [[Person alloc] init];
NSLog(@"--------");
return 0;
}
在main方法中,使用代碼Person *p = [[Person alloc] init];創(chuàng)建了一個Person對象,拿一個指針p來指向這個對象的內(nèi)存地址,此時對象的引用計數(shù) = 1。開啟了ARC手動內(nèi)存管理機制后,在程序編譯的時候編譯器發(fā)現(xiàn) Person這個對在初始化了之后沒有其他的對象對它進行引用,那么編譯器會自動在return之前添加一句[p release];的代碼,引用計數(shù) - 1 = 0,當(dāng)main的大括號代碼執(zhí)行完的時候Person這個對象的內(nèi)存會被釋放。這一點和java的垃圾回收機制不太一樣,在java中的垃圾回收是jvm中的gc進行處理的(不知道是否準(zhǔn)確),而OC是編譯器處理的。
- ARC下的內(nèi)存管理
- 局部變量釋放對象隨之被釋放
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
} // 執(zhí)行到這一行局部變量p釋放
// 由于沒有強指針指向?qū)ο? 所以對象也釋放
return 0;
}
- 清空指針對象隨之被釋放
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
p = nil; // 執(zhí)行到這一行, 由于沒有強指針指向?qū)ο? 所以對象被釋放
}
return 0;
}
- 默認清空所有指針都是強指針
int main(int argc, const char * argv[]) {
@autoreleasepool {
// p1和p2都是強指針
Person *p1 = [[Person alloc] init];
__strong Person *p2 = [[Person alloc] init];
}
return 0;
}
- 弱指針需要明確說明
- 注意: 千萬不要使用弱指針保存新創(chuàng)建的對象
int main(int argc, const char * argv[]) {
@autoreleasepool {
// p是弱指針, 對象會被立即釋放
__weak Person *p1 = [[Person alloc] init];
}
return 0;
}
了解更多請點擊:蘋果官網(wǎng)文檔-關(guān)于內(nèi)存管理
- Xcode中ARC和MRC的配置
-
要想手動調(diào)用retain、release等方法 , 就必須關(guān)閉ARC功能
-
ARC和MRC的混編
轉(zhuǎn)變?yōu)榉茿RC:-fno-objc-arc
轉(zhuǎn)變?yōu)锳RC的:-f-objc-arc在Xcode設(shè)置方法
-
MRC轉(zhuǎn)換ARC
如果一個工程時間比較老,那時候應(yīng)該是使用的MRC,在Xcode提供了功能將MRC的工程轉(zhuǎn)換為ARC。如果工程比較簡單還是可以使用的。如果比較復(fù)雜一般都會轉(zhuǎn)換失敗(原因有可能c和oc語言的橋接,dealloc方法,autorelease等等),這個有點坑。。。不多說了。
-
@property修飾符內(nèi)存管理
- 1.控制set方法的內(nèi)存管理
- retain : release舊值,retain新值(用于OC對象)
- assign : 直接賦值,不做任何內(nèi)存管理(默認,用于非OC對象類型)
- copy : release舊值,copy新值(一般用于NSString *)
- 2.控制需不需生成set方法
- readwrite :同時生成set方法和get方法(默認)
- readonly :只會生成get方法
- 3.多線程管理
- atomic :性能低(默認)
- nonatomic :性能高
-
非ARC
- copy : 只用于NSString\block
- retain : 除NSString\block以外的OC對象
- assign :基本數(shù)據(jù)類型、枚舉、結(jié)構(gòu)體(非OC對象),當(dāng)2個對象相互引用,一端用retain,一端用assign
-
ARC
- copy : 只用于NSString\block
- strong : 除NSString\block以外的OC對象
- weak : 當(dāng)2個對象相互引用,一端用strong,一端用weak
- assgin : 基本數(shù)據(jù)類型、枚舉、結(jié)構(gòu)體(非OC對象)
autorelease
-
autorelease是一種支持引用計數(shù)的內(nèi)存管理方式,只要給對象發(fā)送一條autorelease消息,會將對象放到一個自動釋放池中,當(dāng)自動釋放池被銷毀時,會對池子里面的
所有對象做一次release操作注意,這里只是發(fā)送release消息,如果當(dāng)時的引用計數(shù)(reference-counted)依然不為0,則該對象依然不會被釋放。
autorelease方法會返回對象本身
Person *p = [Person new];
p = [p autorelease];
- 調(diào)用完autorelease方法后,對象的計數(shù)器不變
Person *p = [Person new];
p = [p autorelease];
NSLog(@"count = %lu", [p retainCount]); // 1
- autorelease的好處
- 不用再關(guān)心對象釋放的時間
- 不用再關(guān)心什么時候調(diào)用release
- autorelease的原理
- autorelease實際上只是把對release的調(diào)用延遲了,對于每一個autorelease,系統(tǒng)只是把該 Object放入了當(dāng)前的autorelease pool中,當(dāng)該pool被釋放時,該pool中的所有Object會被調(diào)用Release。
自動釋放池
- 創(chuàng)建自動釋放池格式:
- iOS 5.0前
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 創(chuàng)建自動釋放池
[pool release]; // [pool drain]; 銷毀自動釋放池
- iOS 5.0 開始
@autoreleasepool
{ //開始代表創(chuàng)建自動釋放池
} //結(jié)束代表銷毀自動釋放池
- 在iOS程序運行過程中,會創(chuàng)建無數(shù)個池子。這些池子都是以棧結(jié)構(gòu)存在(先進后出)
- 當(dāng)一個對象調(diào)用autorelease方法時,會將這個對象放到棧頂?shù)尼尫懦?/li>
autorelease基本使用
NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init];
Person *p = [[[Person alloc] init] autorelease];
[autoreleasePool drain];
@autoreleasepool
{ // 創(chuàng)建一個自動釋放池
Person *p = [[Person new] autorelease];
} // 銷毀自動釋放池(會給池子中所有對象發(fā)送一條release消息)
autorelease使用注意
- 并不是放到自動釋放池代碼中,都會自動加入到自動釋放池
@autoreleasepool {
// 因為沒有調(diào)用 autorelease 方法,所以對象沒有加入到自動釋放池
Person *p = [[Person alloc] init];
[p run];
}
- 在自動釋放池的外部發(fā)送autorelease 不會被加入到自動釋放池中
- autorelease是一個方法,只有在自動釋放池中調(diào)用才有效。
@autoreleasepool {
}
// 沒有與之對應(yīng)的自動釋放池, 只有在自動釋放池中調(diào)用autorelease才會放到釋放池
Person *p = [[[Person alloc] init] autorelease];
[p run];
// 正確寫法
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
}
// 正確寫法
Person *p = [[Person alloc] init];
@autoreleasepool {
[p autorelease];
}
- 自動釋放池的嵌套使用
- 自動釋放池是以棧的形式存在
- 由于棧只有一個入口, 所以調(diào)用autorelease會將對象放到棧頂?shù)淖詣俞尫懦?/li>
- 棧頂就是離調(diào)用autorelease方法最近的自動釋放池
@autoreleasepool { // 棧底自動釋放池
@autoreleasepool {
@autoreleasepool { // 棧頂自動釋放池
Person *p = [[[Person alloc] init] autorelease];
}
Person *p = [[[Person alloc] init] autorelease];
}
}
- 自動釋放池中不適宜放占用內(nèi)存比較大的對象
- 盡量避免對大內(nèi)存使用該方法,對于這種延遲釋放機制,還是盡量少用
- 不要把大量循環(huán)操作放到同一個 @autoreleasepool 之間,這樣會造成內(nèi)存峰值的上升
// 內(nèi)存暴漲
@autoreleasepool {
for (int i = 0; i < 99999; ++i) {
Person *p = [[[Person alloc] init] autorelease];
}
}
// 內(nèi)存不會暴漲
for (int i = 0; i < 99999; ++i) {
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
}
}
autorelease錯誤用法
- 不要連續(xù)調(diào)用autorelease
@autoreleasepool {
// 錯誤寫法, 過度釋放
Person *p = [[[[Person alloc] init] autorelease] autorelease];
}
- 調(diào)用autorelease后又調(diào)用release
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
[p release]; // 錯誤寫法, 過度釋放
}
集合對象內(nèi)存管理
-
1.官方內(nèi)存管理原則
- 當(dāng)調(diào)用alloc、new、copy(mutableCopy)方法產(chǎn)生一個新對象的時候,就必須在最后調(diào)用一次release或者autorelease
- 當(dāng)調(diào)用retain方法讓對象的計數(shù)器+1,就必須在最后調(diào)用一次release或者autorelease
-
2.集合的內(nèi)存管理細節(jié)
- 當(dāng)把一個對象添加到集合中時,這個對象會做了一次retain操作,計數(shù)器會+1
- 當(dāng)一個集合被銷毀時,會對集合里面的所有對象做一次release操作,計數(shù)器會-1
- 當(dāng)一個對象從集合中移除時,這個對象會一次release操作,計數(shù)器會-1
-
3.普遍規(guī)律
- 如果方法名是add\insert開頭,那么被添加的對象,計數(shù)器會+1
- 如果方法名是remove\delete開頭,那么被移除的對象,計數(shù)器-1
例子
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person new]; // 1
NSLog(@"reatinCount = %lu", [p retainCount]);
NSMutableArray *arrM = [[NSMutableArray alloc] init];
[arrM addObject:p]; // 2
NSLog(@"reatinCount = %lu", [p retainCount]);
[p release]; // 1
// 當(dāng)數(shù)組移除一個對象之后, 會給這個對象發(fā)送一條release消息
[arrM removeObject:p];
}
return 0;
}
copy內(nèi)存管理
- 淺拷貝
- 原對象引用計數(shù)器+1
- 必須對原對象進行釋放
char *cstr = "this is a c string";
NSString *str1 = [[NSString alloc] initWithUTF8String:cstr];
NSLog(@"str = %lu", [str1 retainCount]);
NSString *str2 = [str1 copy];
NSLog(@"str = %lu", [str1 retainCount]);
[str2 release];// 必須做一次release
- 深拷貝
- 必須釋放新對象
char *cstr = "this is a c string";
NSString *str1 = [[NSString alloc] initWithUTF8String:cstr];
NSLog(@"str = %lu", [str1 retainCount]);
NSMutableString *str2 = [str1 mutableCopy];
NSLog(@"str = %lu", [str1 retainCount]);
[str2 release]; // 必須做一次release
block內(nèi)存管理
block就是一段可以靈活使用的代碼,你可以把它當(dāng)變量傳遞,賦值,甚至可以把它聲明到函數(shù)體里,更靈活的是你可以在里面引用外部的環(huán)境。 最后一條使得block要有更多的考慮,既然block可以引用外部環(huán)境,那如何保證block被調(diào)用的時候當(dāng)時的環(huán)境變量不被釋放呢?(block調(diào)用的時機可能是隨意的)在說明block的內(nèi)存管理之前需要先理解幾個概念。
- 函數(shù)指針
int sum(int value1, int value2)
{
return value1 + value2;
}
int minus(int value1, int value2)
{
return value1 - value2;
}
int main(int argc, const char * argv[]) {
int (*sumP) (int, int) = sum;
int res = sumP(10, 20);
NSLog(@"res = %i", res);
int (*minusP) (int , int) = minus;
res = minusP(10, 20);
NSLog(@"res = %i", res);
return 0;
}
該代碼中定義了指向函數(shù)的指針sumP和minusP,然后利用這兩個指針調(diào)用對應(yīng)的函數(shù),返回函數(shù)計算后的結(jié)果。
- 函數(shù)指針別名
typedef int (*calculate) (int, int);
int main(int argc, const char * argv[]) {
calculate sumP = sum;
int res = sumP(10, 20);
NSLog(@"res = %i", res);
calculate minusP = minus;
res = minusP(10, 20);
NSLog(@"res = %i", res);
return 0;
}
typedef關(guān)鍵字:給函數(shù)或者變量起別名,就像給我們某個人起個外號一樣,叫這個外號就是叫那個人。typedef的作用個人理解主要是為了簡化代碼,方法代碼編寫。
typedef int (*calculate) (int, int);給函數(shù)起別名,定義calculate是一個函數(shù)指針,有兩個int類型的參數(shù),并且有一個int類型的返回值。
calculate sumP = sum; 定義calculate類型的函數(shù)指針,該指針指向sum函數(shù)。
int res = sumP(10, 20);利用函數(shù)指針調(diào)用該函數(shù),根據(jù)傳遞的參數(shù)值函數(shù)返回對應(yīng)計算結(jié)果。
- block和typedef
知道了函數(shù)別名的概念后同樣的代碼使用block代碼如下。
int main(int argc, const char * argv[]) {
int (^sumBlock) (int, int) = ^(int value1, int value2){
return value1 + value2;
};
int res = sumBlock(10 , 20);
NSLog(@"res = %i", res);
int (^minusBlock) (int, int) = ^(int value1, int value2){
return value1 - value2;
};
res = minusBlock(10 , 20);
NSLog(@"res = %i", res);
return 0;
}
- block別名
int main(int argc, const char * argv[]) {
typedef int (^calculteBlock)(int , int);
calculateBlock sumBlock = ^(int value1, int value2){
return value1 + value2;
};
int res = sumBlock(10, 20);
NSLog(@"res = %i", res);
calculateBlock minusBlock = ^(int value1, int value2){
return value1 - value2;
};
res = minusBlock(10, 20);
NSLog(@"res = %i", res);
return 0;
}
利用typedef給block起別名, 和指向函數(shù)的指針一樣, block變量的名稱就是別名。
- Block注意事項
- 在block內(nèi)部可以訪問block外部的變量
{
int a = 10;
void (^myBlock)() = ^{
NSLog(@"a = %i", a);
}
myBlock();
}
輸出結(jié)果: 10
- block內(nèi)部使用static修飾符的全局變量
{
static int base = 100;
void (^sum)(int, int) = ^ void (int a, int b) {
base++;
NSLog(@"block內(nèi)部base = %d",base + a + b);
};
base = 0;
sum(1,2);
NSLog(@"base = %d",base);
輸出結(jié)果:
[1410:348974] block內(nèi)部base = 4
[1410:348974] base = 1
}
static修飾的全局變量在內(nèi)存中只有一份,在Block中使用全局靜態(tài)變量的時候會去當(dāng)前該變量的最新的值。
- block內(nèi)部也可以定義和block外部的同名的變量(局部變量),此時局部變量會暫時屏蔽外部(就近原則)
int a = 10;
void (^myBlock)() = ^{
int a = 50;
NSLog(@"a = %i", a);
}
myBlock();
輸出結(jié)果: 50
- 默認情況下, Block內(nèi)部不能修改外面的局部變量
int b = 5;
void (^myBlock)() = ^{
b = 20; // 報錯
NSLog(@"b = %i", b);
};
myBlock();
- Block內(nèi)部可以修改使用__block修飾的局部變量
__block int b = 5;
void (^myBlock)() = ^{
b = 20;
NSLog(@"b = %i", b);
};
myBlock();
輸出結(jié)果: 20
局部變量加上__block修飾符之后Block內(nèi)部為什么可以改變局部變量的值呢?
默認情況下, 不可以在block中修改外界變量的值,因為block中的變量和外界的變量并不是同一個變量。
如果block中訪問到了外界的變量, block會將外界的變量拷貝一份到堆內(nèi)存中。因為block中使用的外界變量是copy的, 所以在調(diào)用之前修改外界變量的值, 不會影響到block中copy的值。
代碼如下
int a = 10;
NSLog(@"外部a的地址:&a = %p", &a);
void (^myBlock)() = ^{
// a = 50;
NSLog(@"Block內(nèi)部a的地址:&a = %p", &a);
NSLog(@"Block內(nèi)部a的值:a = %i", a);
};
a = 20;
NSLog(@"Block內(nèi)部a的值:a = %i", a);
myBlock();
運行結(jié)果:
[854:137804] 外部a的地址:&a = 0x7fff5fbff7fc
[854:137804] Block內(nèi)部a的地址:&a = 0x7fff5fbff7e8
[854:137804] Block內(nèi)部a的值:a = 10
[854:137804] 外部a的值:a = 20
如果將上邊代碼做如下修改
int a = 10; => __block int a = 10;
運行結(jié)果如下:
[1029:184764] 外部a的地址:&a = 0x7fff5fbff7f8
[1029:184764] Block內(nèi)部a的地址:&a = 0x7fff5fbff7f8
[1029:184764] Block內(nèi)部a的值:a = 10
[1029:184764] 外部a的值:a = 20
運行結(jié)果我們可以發(fā)現(xiàn),Block內(nèi)部a的地址和外部a的地址是一樣的,
因此我們可以說加上__block之后是地址傳遞,而不加__block使用外部的變量是做了某些操作分配了新的內(nèi)存地址。而這個操作就是copy。
- Block位于堆還是棧(ARC)
根據(jù)Block在內(nèi)存中的位置分為三種類型NSGlobalBlock,NSStackBlock, NSMallocBlock。
NSGlobalBlock:類似函數(shù),位于text段;
NSStackBlock:位于棧內(nèi)存,函數(shù)返回后Block將無效;
NSMallocBlock:位于堆內(nèi)存。
直接看代碼
typedef void (^blk) (void);
{
__block int val = 10;
__strong blk strongPointerBlock = ^{NSLog(@"val = %d", ++val);};
NSLog(@"strongPointerBlock: %@", strongPointerBlock); //1
__weak blk weakPointerBlock = ^{NSLog(@"val = %d", ++val);};
NSLog(@"weakPointerBlock: %@", weakPointerBlock); //2
NSLog(@"copyBlock: %@", [weakPointerBlock copy]); //3
NSLog(@"默認test %@", ^{NSLog(@"val = %d", ++val);}); //4
}
運行結(jié)果:
[1437:366203] strongPointerBlock: <__NSMallocBlock__: 0x7fbe7a6762e0>
[1437:366203] weakPointerBlock: <__NSStackBlock__: 0x7fff5746d9e0>
[1437:366203] copyBlock: <__NSMallocBlock__: 0x7fbe7a743240>
[1437:366203] 默認test <__NSStackBlock__: 0x7fff5746d9b8>
從運行結(jié)果可以看出
1>默認情況下,Block是在棧內(nèi)存中。
2>strong指針指向的block已經(jīng)放到堆上了。
3>weak指針指向的block還在棧上(這種聲明方法只在block上有效,正常的weak指針指向堆上對象,直接就會變nil,需要用strong指針過一道。)
4>copy了Block之后就會從棧轉(zhuǎn)移到堆上
在ARC下的另外一種情況,將block作為參數(shù)返回
- (__unsafe_unretained blk) blockTest {
int val = 11;
return ^{NSLog(@"val = %d", val);};
}
調(diào)用方
NSLog(@"block return from function: %@", [self blockTest]);
輸出結(jié)果:
block return from function: <__NSMallocBlock__: 0x7685640>
5>在ARC環(huán)境下,當(dāng)block作為參數(shù)返回的時候,block也會自動被移到堆上。
所有我們可以做出如下總結(jié):
如果block在棧中, block中訪問了外界的對象, 那么不會對對象進行retain操作。
如果block在堆中, block中訪問了外界的對象, 那么會對外界的對象進行一次retain。(在ARC下沒有retain的概念,這里只是為了后面循環(huán)引用的理解)。
了解更多:Objective-C Block的實現(xiàn)
[Block內(nèi)存管理](http://www.tanhao.me/pieces/310.html/)
- block的循環(huán)引用防止
@property(nonatomic, readwrite, copy) completionBlock completionBlock;
self.completionBlock = ^ {
if (self.success) {
self.success(self.responseData);
}
}
};
//對象有一個Block屬性,然而這個Block屬性中又引用了對象的其他
//成員變量,那么就會對這個變量本身產(chǎn)生強應(yīng)用,那么變量本身和他
//自己的Block屬性就形成了循環(huán)引用。在ARC下需要修改成這樣:
@property(nonatomic, readwrite, copy) completionBlock completionBlock;
__weak typeof(self) weakSelf = self;
self.completionBlock = ^ {
if (weakSelf.success) {
weakSelf.success(weakSelf.responseData);
}
};
也就是生成一個對自身對象的弱引用,如果是倒霉催的項目還需要支持iOS4.3(好low啊),就用__unsafe_unretained替代__weak。如果是non-ARC環(huán)境下就將__weak替換為__block即可。non-ARC情況下,__block變量的含義是在Block中引入一個新的結(jié)構(gòu)體成員變量指向這個__block變量,那么__block typeof(self) weakSelf = self;就表示Block別再對self對象retain啦,這就打破了循環(huán)引用。
循環(huán)引用問題
- MRC下對象的循環(huán)引用
如下代碼
Dog類
#import <Foundation/Foundation.h>
@class Person;
@interface Dog : NSObject
//@property(nonatomic, retain)Person *owner;
@property(nonatomic, assign)Person *owner;
@end
#import "Dog.h"
#import "Person.h"
@implementation Dog
-(void)dealloc
{
NSLog(@"%s", __func__);
[super dealloc];
}
@end
Person類
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property(nonatomic, retain)Dog *dog;
@end
#import "Person.h"
#import "Dog.h"
@implementation Person
- (void)dealloc
{
NSLog(@"%s", __func__);
self.dog = nil;
[super dealloc];
}
@end
main函數(shù)
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h"
int main(int argc, const char * argv[]) {
Person *p = [Person new];
Dog *d = [Dog new];
p.dog = d; // retain
d.owner = p; // retain assign
[p release];
[d release];
return 0;
}
Person要擁有dog對象, 而dog對應(yīng)又要擁有Person對象, 此時會形成循環(huán)retain,在main執(zhí)行完的時候,dealloc方法不會自動被調(diào)用,造成內(nèi)存泄露。
引用關(guān)系如圖

解決辦法: 不要讓A retain B, B retain A,讓其中一方不要做retain操作即可。
替換前:@property(nonatomic, retain)Person *owner;
替換后:@property(nonatomic, assign)Person *owner;
- 此處替換不用weak的原因,weak是ARC提供的防止循環(huán)引用的關(guān)鍵字。此處代碼是MRC的,所有使用assign關(guān)鍵字。
修正后引用關(guān)系

- ARC下對象的循環(huán)引用
- ARC和MRC一樣, 如果A擁有B, B也擁有A, 那么必須一方使用弱指針
@interface Person : NSObject
//@property (nonatomic, retain) Dog *dog;
@property (nonatomic, strong) Dog *dog;
@end
@interface Dog : NSObject
// 錯誤寫法, 循環(huán)引用會導(dǎo)致內(nèi)存泄露
//@property (nonatomic, strong) Person *owner;
// 正確寫法, 當(dāng)如果保存對象建議使用weak
//@property (nonatomic, assign) Person *owner;
@property (nonatomic, weak) Person *owner;
@end
其他
- Objective-C指針與CoreFoundation指針之間的轉(zhuǎn)換(Toll-Free Bridging)
1.MRC下的內(nèi)存管理
倘若不使用ARC,手動管理內(nèi)存,思路比較清晰,使用完,release轉(zhuǎn)換后的對象即可。
//NSString 轉(zhuǎn) CFStringRef
CFStringRef aCFString = (CFStringRef) [[NSString alloc] initWithFormat:@"%@", string];
//...
CFRelease(aCFString);
//CFStringRef 轉(zhuǎn) NSString
CFStringRef aCFString = CFStringCreateWithCString(kCFAllocatorDefault,
bytes,
NSUTF8StringEncoding);
NSString *aNSString = (NSString *)aCFString;
//...
[aNSString release];
2.ARC下的內(nèi)存管理
ARC :只會對OC對象進行內(nèi)存管理,蘋果有句名言:ARC is only for NSObject。但是對c對象或是CF開頭的對象,即存在于 Core Foundation框架 (CoreFoundation.framework 是一組C語言接口)中的對象無效,需要手動的retain 和release(CF中沒有autorelease)。
CocoaFoundation指針與CoreFoundation指針轉(zhuǎn)換,需要考慮的是所指向?qū)ο笏袡?quán)的歸屬。ARC提供了3個修飾符來管理。
- __bridge,__bridge_retained和__bridge_transfer三個轉(zhuǎn)換關(guān)鍵字。
- __bridge只做類型轉(zhuǎn)換,但是不修改對象(內(nèi)存)管理權(quán);
- __bridge_retained(也可以使用CFBridgingRetain)將Objective-C的對象轉(zhuǎn)換為Core Foundation的對象,同時將對象(內(nèi)存)的管理權(quán)交給我們,后續(xù)需要使用CFRelease或者相關(guān)方法來釋放對象;
- __bridge_transfer(也可以使用CFBridgingRelease)將Core Foundation的對象轉(zhuǎn)換為Objective-C的對象,同時將對象(內(nèi)存)的管理權(quán)交給ARC。
使用__bridge_retained 或者 CFBridgingRetain()
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;
(void)aCFString;
//正確的做法應(yīng)該執(zhí)行CFRelease
//CFRelease(aCFString);
}
使用__bridge_transfer 或者 CFBridgingRelease()
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;
aNSString = (__bridge_transfer NSString *)aCFString;
}
使用__bridge
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
CFStringRef aCFString = (__bridge CFStringRef)aNSString;
(void)aCFString;
}
- 3中轉(zhuǎn)換方式總結(jié)
| 轉(zhuǎn)換方式 | 轉(zhuǎn)換結(jié)果 |
|---|---|
| __bridge | 不改變對象所有權(quán) |
| __bridge_retained 或者 CFBridgingRetain() | 解除 ARC 所有權(quán) |
| __bridge_transfer 或者 CFBridgingRelease() | 給予 ARC 所有權(quán) |
了解更多:Toll-Free Bridging
iOS中內(nèi)存分析
-
靜態(tài)內(nèi)存分析
靜態(tài)內(nèi)存分析是不運行程序,直接對代碼進行內(nèi)存分析。查看代碼是否偶內(nèi)存泄露。Analyze主要分析以下四種問題
- 邏輯錯誤:訪問空指針或未初始化的變量等;
- 內(nèi)存管理錯誤:如內(nèi)存泄漏等;
- 聲明錯誤:從未使用過的變量;
- Api調(diào)用錯誤:未包含使用的庫和框架。
優(yōu)點:分析速度快,并且可以對所有的代碼進行內(nèi)存分析。
缺點:分析不一定準(zhǔn)確(沒有真正的運行程序,根據(jù)代碼的上下文語法結(jié)構(gòu))
注意:如果有提示內(nèi)存泄露,一定要檢查代碼,90%以上的都是準(zhǔn)確的。
1>在XCode Product菜單下,點擊Analyze對工程進行靜態(tài)分析(shift+command+b)。

2>在開啟arc的環(huán)境下,輸入以下一段代碼:
+(UIImage*)getSubImage:(unsigned long)ulUserHeader
{
UIImage * sourceImage = [UIImage imageNamed:@"header.png"];
CGFloat height = sourceImage.size.height;
CGRect rect = CGRectMake(0 + ulUserHeader*height, 0, height, height);
CGImageRef imageRef = CGImageCreateWithImageInRect([sourceImage CGImage], rect);
UIImage* smallImage = [UIImage imageWithCGImage:imageRef];
//CGImageRelease(imageRef);
return smallImage;
}
3>使用Analyze進行分析,在導(dǎo)航欄Analyze選擇Analyzer查看分析結(jié)果:

- 動態(tài)內(nèi)存分析
動態(tài)內(nèi)存分析:程序真正運行起來的時候?qū)Τ绦蜻M行內(nèi)存分析、
(查看內(nèi)存的分配/內(nèi)存泄露)。
優(yōu)點:分析非常準(zhǔn)確,如果發(fā)現(xiàn)內(nèi)存泄露,基本可以判斷是代碼問題。
缺點:分析效率低(只有真正運行了一段代碼,才能對該代碼進行內(nèi)存分析,如果程序中的某一段有內(nèi)存泄露的沒有執(zhí)行到就不會被檢測出來。)
注意:如果發(fā)現(xiàn)內(nèi)存泄露,基本都需要修改代碼。





