OC-05

1.請盡可能多地列舉程序運(yùn)行時crash的種類(原因)证鸥,并寫出sample code(對僚楞,就是寫出會導(dǎo)致crash的代碼)。

數(shù)組越界枉层,未知消息發(fā)送
第三方的調(diào)用不當(dāng)

這個帖子整理我遇到過的iPhone App Crash類型以及解決辦法泉褐。Crash原因有很多,不同技術(shù)所導(dǎo)致的Crash會不同鸟蜡。整理出來的經(jīng)驗(yàn)應(yīng)該會相對片面兴枯,有錯誤的地方和任何問題,請毫不猶豫的指出矩欠。

保證App持續(xù)穩(wěn)定運(yùn)行是非常必要的财剖,開發(fā)人員應(yīng)該把維護(hù)產(chǎn)品穩(wěn)定性、提高產(chǎn)品性能意識融入到每次編寫代碼過程當(dāng)中癌淮,這也是很多公司考察優(yōu)秀開發(fā)人員的一個重要環(huán)節(jié)躺坟。

Crash原因
Crash原因有共性,歸納起來有:
? 內(nèi)存管理錯誤
? 程序邏輯錯誤
? SDK錯誤 (部署版本< 編譯版本)
? 主線程阻塞

內(nèi)存管理錯誤
內(nèi)存管理是iPhone開發(fā)所要掌握的最基本問題乳蓄,特別是使用引用計(jì)數(shù)手動管理內(nèi)存的情況咪橙。內(nèi)存管理錯誤包括:
? 內(nèi)存泄漏:未釋放不會再使用對象。比如alloc忘記release,malloc忘記free∶勒欤可用XcodeProduct菜單下的Analyze功能來解決該問題产舞;
? 引用出錯:引用已經(jīng)被釋放的對象指針。很多“莫名其妙”的Crash都是由于窗體經(jīng)歷的生命周期所導(dǎo)致的(viewDidUnload菠剩、viewDidLoad)易猫,在iOSSimulator里模擬內(nèi)存警告就可以解決該問題;

? 內(nèi)存警告:App使用的內(nèi)存超出設(shè)備的限制具壮,iOS將強(qiáng)制掛起App准颓,強(qiáng)制掛起iOS是不會記錄Crashlog,F(xiàn)lurry也無法記錄棺妓。內(nèi)存泄漏攘已、快速/大量的分配內(nèi)存都可能導(dǎo)致內(nèi)存警告,這時候應(yīng)該盡可能的釋放不需要的資源怜跑。通過Instruments->Allocations里的Heapshot功能能夠找出哪些資源未被釋放样勃。

WWDC 2012的Session242 - iOS App Performance_ Memory是專門討論內(nèi)存管理這個話題。

程序邏輯錯誤
數(shù)組越界性芬、堆棧溢出峡眶、并發(fā)操作、邏輯錯誤批旺。扎實(shí)的編碼基礎(chǔ)幌陕、嚴(yán)謹(jǐn)細(xì)致的工作習(xí)慣、清晰的思路可以避免這類錯誤汽煮;

SDK錯誤
這個錯誤出現(xiàn)的現(xiàn)象是有的設(shè)備運(yùn)行正常搏熄,有的會Crash。原因是未找到框架暇赤、類心例、方法、屬性鞋囊。比如:用iOS5.0 SDK編譯并運(yùn)行在iOS4.0的設(shè)備上止后,5.0的Twitter框架在4.0的設(shè)備上找不到。這種問題常出現(xiàn)在用蘋果新發(fā)布的Xcode編譯原有的工程溜腐。
未找到框架的解決辦法是:部署版本>= 編譯版本译株。iOS框架向后兼容做的很棒,部署版本> 編譯版本一般不會出現(xiàn)問題挺益。
未找到類歉糜、方法、屬性的解決辦法是:先判斷是否存在再使用
if(NSClassFromString(@"MFMailComposeViewController"))
respondsToSelector:

主線程阻塞
主線程阻塞超過10s望众,iOS將強(qiáng)制掛起App匪补。把長時間的任務(wù)放到后臺線程去執(zhí)行伞辛,可使用NSThread,NSOperation, dispatch。WWDC2012的Session235 - iOS App Performance_ Responsiveness有詳細(xì)的介紹夯缺。

解決Crash
思路是:定位Crash的程序代碼蚤氏,預(yù)測Crash原因,尋找解決方案踊兜,測試竿滨。
有多種方式可以定位Crash的程序代碼:
? Debug模式時,iOSSimulator斷點(diǎn)測試定位Crash的堆棧润文;
? 真機(jī)連接iTunes查看Crashlog (Debug模式下)姐呐;
? 通過Flurry的錯誤記錄查看殿怜;
定位之后典蝌,就是重新思考程序上下文邏輯,并有理由的預(yù)測Crash出現(xiàn)的原因头谜。預(yù)測的越多骏掀,理解的越深。
尋找解決方案的方法有:
? 瀏覽蘋果官方SDK文檔柱告,找出錯誤原因截驮;
? Google搜索Crash輸出的信息,重點(diǎn)查找行業(yè)內(nèi)技術(shù)論壇:cocoachina际度、stackoverflow葵袭、iphonedevsdk等;
? 查看歷屆WWDC的視頻乖菱、示例代碼坡锡;
? 在工程里添加環(huán)境變量: NSZombieEnabled、NSDebugEnabled窒所,輸出有價值的信息鹉勒;
? 如果未找到任何信息,可以尋求蘋果官方論壇吵取、業(yè)內(nèi)技術(shù)論壇的幫助禽额;

測試
找到解決方案后就需要測試,測試功能輸入輸出的準(zhǔn)確性皮官、程序性能脯倒、是否引入新的bug。測試有專業(yè)的測試工程師來負(fù)責(zé)捺氢,但開發(fā)工程師不能依賴測試工程師來發(fā)現(xiàn)問題藻丢,盡量獨(dú)立解決已知存在的問題。
由于Xcode部署工程到真機(jī)上比較耗時間讯沈,如果可以的話盡可能用iOSSimulator來測試郁岩,以減少測試的時間婿奔。
建議開發(fā)工程師有一個checklist,在產(chǎn)品測試時自己逐一過一下上面常見的問題问慎,這個能夠避免大部分Crash萍摊。下圖是我們一個產(chǎn)品的FlurryError記錄,那120個錯誤Session是測試Crash時留下的如叼。當(dāng)然這個記錄是沒有包括iOS將強(qiáng)制掛起App的情況冰木。
==================================================================
1.NSInvalidArgumentException
字典初始化的時候值為nil,使用空對象
解決:在使用時做為空判斷 做類型判斷

2.SIGSEGV
當(dāng)去訪問沒有被開辟的內(nèi)存或者已經(jīng)被釋放的內(nèi)存時笼恰,就會發(fā)生這樣的異常踊沸。另外,在低內(nèi)存的時候社证,也可能會產(chǎn)生這樣的異常逼龟。
解決:這個扯的比較多,釋放內(nèi)存等追葡,還是看自己寫代碼時的邏輯等

3.NSRangeException
數(shù)組越界
解決:從數(shù)組取值時腺律,判斷長度等

4.SIGPIPE
對一個端已經(jīng)關(guān)閉的socket調(diào)用兩次write,第二次write將會產(chǎn)生SIGPIPE信號宜肉,該信號默認(rèn)結(jié)束進(jìn)程匀钧。

5.SIGABRT
這是一個讓程序終止的標(biāo)識,會在斷言谬返、app內(nèi)部之斯、操作系統(tǒng)用終止方法拋出。通常發(fā)生在異步執(zhí)行系統(tǒng)方法的時候 有可能是斷點(diǎn)造成
不是bug
==================================================================
iOS 常見 Crash 及解決方案

一遣铝、訪問了一個已經(jīng)被釋放的對象
在不使用 ARC 的時候佑刷,內(nèi)存要自己管理,這時重復(fù)或過早釋放都有可能導(dǎo)致 Crash翰蠢。
例子
NSObject * aObj = [[NSObject alloc] init];
[aObj release];

NSLog(@"%@", aObj);

原因
aObj 這個對象已經(jīng)被釋放项乒,但是指針沒有置空,這時訪問這個指針指向的內(nèi)存就會 Crash梁沧。

解決辦法

使用前要判斷非空檀何,釋放后要置空。正確的釋放應(yīng)該是:
[aObj release];
aObj = nil;
由于ObjC的特性廷支,調(diào)用 nil 指針的任何方法相當(dāng)于無作用频鉴,所以即使有人在使用這個指針時沒有判斷至少還不會掛掉。

在ObjC里面恋拍,一切基于 NSObject 的對象都使用指針來進(jìn)行調(diào)用垛孔,所以在無法保證該指針一定有值的情況下,要先判斷指針非空再進(jìn)行調(diào)用施敢。

if (aObj) {
//...
}
常見的如判斷一個字符串是否為空:

if (aString && aString.length > 0) {//...}
適當(dāng)使用 autorelease周荐。
有些時候不能知道自己創(chuàng)建的對象什么時候要進(jìn)行釋放狭莱,可以使用 autoRelease,但是不鼓勵使用概作。因?yàn)?autoRelease 的對象要等到最近的一個 autoReleasePool 銷毀的時候才會銷毀腋妙,如果自己知道什么時候會用完這個對象,當(dāng)然立即釋放效率要更高讯榕。如果一定要用 autoRelease 來創(chuàng)建大量對象或者大數(shù)據(jù)對象骤素,最好自己顯式地創(chuàng)建一個 autoReleasePool,在使用后手動銷毀愚屁。以前要自己手動初始化 autoReleasePool济竹,現(xiàn)在可以用以下寫法:
@autoreleasepool{
for (int i = 0; i < 100; ++i) {
NSObject * aObj = [[[NSObject alloc] init] autorelease];
//....
}
}

二、訪問數(shù)組類對象越界或插入了空對象

NSMutableArray/NSMutableDictionary/NSMutableSet 等類下標(biāo)越界霎槐,或者 insert 了一個 nil 對象送浊。

原因
一個固定數(shù)組有一塊連續(xù)內(nèi)存,數(shù)組指針指向內(nèi)存首地址栽燕,靠下標(biāo)來計(jì)算元素地址罕袋,如果下標(biāo)越界則指針偏移出這塊內(nèi)存改淑,會訪問到野數(shù)據(jù)碍岔,ObjC 為了安全就直接讓程序 Crash 了。
而 nil 對象在數(shù)組類的 init 方法里面是表示數(shù)組的結(jié)束朵夏,所以使用 addObject 方法來插入對象就會使程序掛掉蔼啦。如果實(shí)在要在數(shù)組里面加入一個空對象,那就使用 NSNull仰猖。
[array addObject:[NSNull null]];
解決辦法
使用數(shù)組時注意判斷下標(biāo)是否越界捏肢,插入對象前先判斷該對象是否為空。
if (aObj) {
[array addObject:aObj];
}
可以使用 Cocoa 的 Category 特性直接擴(kuò)展 NSMutable 類的 Add/Insert 方法饥侵。比如:

@interface NSMutableArray (SafeInsert)
-(void) safeAddObject:(id)anObject;
@end

@implementation NSMutableArray (SafeInsert)
-(void) safeAddObject:(id)anObject {
if (anObject) {
[self addObject:anObject];
}
}
@end

這樣鸵赫,以后在工程里面使用 NSMutableArray 就可以直接使用 safeAddObject 方法來規(guī)避 Crash。

三躏升、訪問了不存在的方法

ObjC 的方法調(diào)用跟 C++ 很不一樣辩棒。 C++ 在編譯的時候就已經(jīng)綁定了類和方法,一個類不可能調(diào)用一個不存在的方法膨疏,否則就報(bào)編譯錯誤一睁。而 ObjC 則是在 runtime 的時候才去查找應(yīng)該調(diào)用哪一個方法。

這兩種實(shí)現(xiàn)各有優(yōu)劣佃却,C++ 的綁定使得調(diào)用方法的時候速度很快者吁,但是只能通過 virtual 關(guān)鍵字來實(shí)現(xiàn)有限的動態(tài)綁定。而對 ObjC 來說饲帅,事實(shí)上他的實(shí)現(xiàn)是一種消息傳遞而不是方法調(diào)用复凳。

[aObj aMethod];
這樣的語句應(yīng)該理解為瘤泪,像 aObj 對象發(fā)送一個叫做 aMethod 的消息,aObj 對象接收到這個消息之后育八,自己去查找是否能調(diào)用對應(yīng)的方法均芽,找不到則上父類找,再找不到就 Crash单鹿。由于 ObjC 的這種特性掀宋,使得其消息不單可以實(shí)現(xiàn)方法調(diào)用,還能緊系轉(zhuǎn)發(fā)仲锄,對一個 obj 傳遞一個 selector 要求調(diào)用某方法劲妙,他可以直接不理會,轉(zhuǎn)發(fā)給別的 obj 讓別的 obj 來響應(yīng)儒喊,非常靈活镣奋。

例子

[self methodNotExists];
調(diào)用一個不存在的方法,可以編譯通過怀愧,運(yùn)行時直接掛掉侨颈,報(bào) NSInvalidArgumentException 異常:

-[WSMainViewController methodNotExist]: unrecognized selector sent to instance 0x1dd96160
2013-10-23 15:49:52.167 WSCrashSample[5578:907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[WSMainViewController methodNotExist]: unrecognized selector sent to instance 0x1dd96160'

解決方案
像這種類型的錯誤通常出現(xiàn)在使用 delegate 的時候,因?yàn)?delegate 通常是一個 id 泛型芯义,所以 IDE 也不會報(bào)警告哈垢,所以這種時候要用 respondsToSelector 方法先判斷一下,然后再進(jìn)行調(diào)用扛拨。
if ([self respondsToSelector:@selector(methodNotExist)]) {
[self methodNotExist];
}

四耘分、字節(jié)對齊

可能由于強(qiáng)制類型轉(zhuǎn)換或者強(qiáng)制寫內(nèi)存等操作,CPU 執(zhí)行 STMIA 指令時發(fā)現(xiàn)寫入的內(nèi)存地址不是自然邊界绑警,就會硬件報(bào)錯掛掉求泰。iPhone 5s 的 CPU 從32位變成64位,有可能會出現(xiàn)一些字節(jié)對齊的問題導(dǎo)致 Crash 率升高的计盒。

例子

char *mem = malloc(16); // alloc 16 bytes of data
double *dbl = mem + 2;
double set = 10.0;
*dbl = set;
像上面這段代碼渴频,執(zhí)行到

*dbl = set;
這句的時候,報(bào)了 EXC_BAD_ACCESS(code=EXC_ARM_DA_ALIGN) 錯誤北启。

原因

要了解字節(jié)對齊錯誤還需要一點(diǎn)點(diǎn)背景知識卜朗,知道的童鞋可以略過直接看后面了。

背景知識

計(jì)算機(jī)最小數(shù)據(jù)單位是bit(位)暖庄,也就是0或1聊替。

而內(nèi)存空間最小單元是byte(字節(jié)),一個byte為8個bit培廓。

內(nèi)存地址空間以byte劃分惹悄,所以理論上訪問內(nèi)存地址可以從任意byte開始,但是事實(shí)上我們不是直接訪問硬件地址肩钠,而是通過操作系統(tǒng)的虛擬內(nèi)存地址來訪問泣港,虛擬內(nèi)存地址是以字為單位的暂殖。一個32位機(jī)器的字長就是32位,所以32位機(jī)器一次訪問內(nèi)存大小就是4個byte当纱。再者為了性能考慮呛每,數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。原因在于坡氯,為了訪問未對齊的內(nèi)存晨横,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問箫柳。

舉一個栗子:

struct foo {
    char aChar1;
    short aShort;
    char aChar2;
    int i;
};
上面這個結(jié)構(gòu)體手形,在32位機(jī)器上,char 長度為8位悯恍,占一個byte库糠,short 占2個byte, int 4個byte涮毫。
如果內(nèi)存地址從 0 開始瞬欧,那么理論上順序分配的地址應(yīng)該是:

aChar1 0x00000000
aShort 0x00000001
aChar2 0x00000003
i      0x00000004
但是事實(shí)上編譯后,這些變量的地址是這樣的:

aChar1 0x00000000
aShort 0x00000002
aChar2 0x00000004
i      0x00000008
這就是 aChar1 和 aChar2 都被做了內(nèi)存對齊優(yōu)化罢防,都變成 2 byte 了艘虎。

解決辦法

使用 memcpy 來作內(nèi)存拷貝,而不是直接對指針賦值篙梢。對上面的例子作修改就是:
char *mem = malloc(16); // alloc 16 bytes of data
double *dbl = mem + 2;
double set = 10.0;
memcpy(dbl, &set, sizeof(set));
改用 memcpy 之后運(yùn)行就不會有問題了顷帖,這是因?yàn)?memcpy 自己的實(shí)現(xiàn)就已經(jīng)做了字節(jié)對齊的優(yōu)化了。我們來看glibc2.5中的memcpy的源碼:

void *memcpy (void *dstpp, const void *srcpp, size_t len) {
    
    unsigned long int dstp = (long int) dstpp;
    unsigned long int srcp = (long int) srcpp;
    
    if (len >= OP_T_THRES) {
        len -= (-dstp) % OPSIZ;
        BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
        PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
        WORD_COPY_FWD (dstp, srcp, len, len);
    }
    BYTE_COPY_FWD (dstp, srcp, len);
    return dstpp;
}
分析這個函數(shù)渤滞,首先比較一下需要拷貝的內(nèi)存塊大小,如果小于 OP_T_THRES (這里定義為 16)榴嗅,則直接字節(jié)拷貝就完了妄呕,如果大于這個值,視為大內(nèi)存塊拷貝嗽测,采用優(yōu)化算法绪励。

len -= (-dstp) % OPSIZ;
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);

// #define OPSIZ   (sizeof(op_t))
// enum op_t
OPSIZE 是 op_t 的長度,op_t 是字的類型唠粥,所以這里 OPSIZE 是獲取當(dāng)前平臺的字長疏魏。
dstp 是內(nèi)存地址,內(nèi)存地址是按byte來算的晤愧,對內(nèi)存地址 unsigned long 取負(fù)數(shù)再模 OPSIZE 得到需要對齊的那部分?jǐn)?shù)據(jù)的長度大莫,然后用字節(jié)拷貝做內(nèi)存對齊。取負(fù)數(shù)是因?yàn)橐詃stp的地址作為起點(diǎn)來進(jìn)行復(fù)制官份,如果直接取模那就變成0作為起點(diǎn)去做運(yùn)算了只厘。
對 BYTE_COPY_FWD 這個宏的源碼有興趣的同學(xué)可以看看這篇:BYTE_COPY_FWD 源碼解析(感謝 @raincai 同學(xué)提醒)

這樣對齊了之后烙丛,再做大數(shù)據(jù)量部分的拷貝:

PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
看這個宏的源碼,盡可能多地作頁拷貝羔味,剩下的大小會寫入len變量河咽。

/////////////////////////////////////////////////

if PAGE_COPY_THRESHOLD

include <assert.h>

define PAGE_COPY_FWD_MAYBE(dstp, srcp, nbytes_left, nbytes) \

do
{
if ((nbytes) >= PAGE_COPY_THRESHOLD &&
PAGE_OFFSET ((dstp) - (srcp)) == 0)
{
/* The amount to copy is past the threshold for copying
pages virtually with kernel VM operations, and the
source and destination addresses have the same alignment. /
size_t nbytes_before = PAGE_OFFSET (-(dstp));
if (nbytes_before != 0)
{
/
First copy the words before the first page boundary. */
WORD_COPY_FWD (dstp, srcp, nbytes_left, nbytes_before);
assert (nbytes_left == 0);
nbytes -= nbytes_before;
}
PAGE_COPY_FWD (dstp, srcp, nbytes_left, nbytes);
}
} while (0)

/* The page size is always a power of two, so we can avoid modulo division.  */

define PAGE_OFFSET(n) ((n) & (PAGE_SIZE - 1))

else

define PAGE_COPY_FWD_MAYBE(dstp, srcp, nbytes_left, nbytes) /* nada */

endif

PAGE_COPY_FWD 的宏定義:

define PAGE_COPY_FWD ( dstp,

srcp,
nbytes_left,
nbytes
)

Value:
((nbytes_left) = ((nbytes) -
(__vm_copy (__mach_task_self (),
(vm_address_t) srcp, trunc_page (nbytes),
(vm_address_t) dstp) == KERN_SUCCESS
? trunc_page (nbytes)
: 0)))
頁拷貝剩余部分,再做一下字拷貝:

define WORD_COPY_FWD ( dst_bp,

src_bp,
nbytes_left,
nbytes
)

Value:
do
{
if (src_bp % OPSIZ == 0)
_wordcopy_fwd_aligned (dst_bp, src_bp, (nbytes) / OPSIZ);
else
_wordcopy_fwd_dest_aligned (dst_bp, src_bp, (nbytes) / OPSIZ);
src_bp += (nbytes) & -OPSIZ;
dst_bp += (nbytes) & -OPSIZ;
(nbytes_left) = (nbytes) % OPSIZ;
} while (0)
再再最后就是剩下的一點(diǎn)數(shù)據(jù)量了赋元,直接字節(jié)拷貝結(jié)束忘蟹。memcpy 可以用來解決內(nèi)存對齊問題,同時對于大數(shù)據(jù)量的內(nèi)存拷貝搁凸,使用 memcpy 效率要高很多寒瓦,就因?yàn)樽隽隧摽截惡妥挚截惖膬?yōu)化。
或者盡量避免這種內(nèi)存不對齊的情況坪仇,像這個例子杂腰,只要把 +2 改成 +4,內(nèi)存就對齊了椅文。當(dāng)然具體還得看邏輯實(shí)現(xiàn)的需要喂很。
char *mem = malloc(16); // alloc 16 bytes of data
double *dbl = mem + 4;
double set = 10.0;
*dbl = set;
References

    ARM Hacking: EXC_ARM_DA_ALIGN exception
    
    GlibC 2.18 memcpy source code
    
    五、堆棧溢出
    
    一般情況下應(yīng)用程序是不需要考慮堆和棧的大小的皆刺,總是當(dāng)作足夠大來使用就能滿足一般業(yè)務(wù)開發(fā)少辣。但是事實(shí)上堆和棧都不是無上限的,過多的遞歸會導(dǎo)致棧溢出羡蛾,過多的 alloc 變量會導(dǎo)致堆溢出漓帅。
    
    例子
    
    不得不說 Cocoa 的內(nèi)存管理優(yōu)化做得挺好的,單純用 C++ 在 Mac 下編譯后執(zhí)行以下代碼痴怨,遞歸 174671 次后掛掉:

include <iostream>

include <stdlib.h>

    void test(int i) {
        void* ap = malloc(1024);
        std::cout << ++i << "\n";
        test(i);
    }
    
    int main() {
        std::cout << "start!" << "\n";
        test(0);
        return 0;
    }
    而在 iOS 上執(zhí)行以下代碼則怎么也不會掛忙干,連 memory warning 都沒有:
    
    - (void)stackOverFlow:(int)i {
        
        char * aLeak = malloc(1024);
        
        NSLog(@"try %d", ++i);
        [self stackOverFlow:i];
    }
而且如果 malloc 的大小改成比 1024 大的如 10240,其內(nèi)存占用的增長要遠(yuǎn)慢于 1024浪藻。這大概要?dú)w功于 Cocoa 的 Flyweight 設(shè)計(jì)模式捐迫,不過暫時還沒能真的理解到其優(yōu)化原理,猜測可能是雖然內(nèi)存空間申請了但是一直沒用到爱葵,針對這種循環(huán) alloc 的場景施戴,做了記錄,等到用到內(nèi)存空間了才真正給出空間萌丈。

原理

iOS 內(nèi)存布局如下圖所示:

image

在應(yīng)用程序分配的內(nèi)存空間里面赞哗,最低地址位是固定的代碼段和數(shù)據(jù)段,往上是堆辆雾,用來存放全局變量肪笋,對于 ObjC 來說,就是 alloc 出來的變量,都會放進(jìn)這里涂乌,堆不夠用的時候就會往上申請空間艺栈。最頂部高地址位是棧,局部的基本類型變量都會放進(jìn)棧里湾盒。 ObjC 的對象都是以指針進(jìn)行操控的湿右,局部變量的指針都在棧里,全局的變量在堆里罚勾,而無論是什么指針毅人,alloc 出來的都在堆里,所以 alloc 出來的變量一定要記得 release尖殃。

對于 autorelease 變量來說丈莺,每個函數(shù)有一個對應(yīng)的 autorelease pool,函數(shù)出棧的時候 pool 被銷毀送丰,同時調(diào)用這個 pool 里面變量的 dealloc 函數(shù)來實(shí)現(xiàn)其內(nèi)部 alloc 出來的變量的釋放缔俄。

六、多線程并發(fā)操作

這個應(yīng)該是全平臺都會遇到的問題了器躏。當(dāng)某個對象會被多個線程修改的時候俐载,有可能一個線程訪問這個對象的時候另一個線程已經(jīng)把它刪掉了,導(dǎo)致 Crash登失。比較常見的是在網(wǎng)絡(luò)任務(wù)隊(duì)列里面遏佣,主線程往隊(duì)列里面加入任務(wù),網(wǎng)絡(luò)線程同時進(jìn)行刪除操作導(dǎo)致掛掉揽浙。

例子

這個真要寫比較完整的并發(fā)操作的例子就有點(diǎn)復(fù)雜了状婶。

解決方法

加鎖
NSLock
普通的鎖,加鎖的時候 lock馅巷,解鎖調(diào)用 unlock膛虫。

- (void)addPlayer:(Player *)player {
    if (player == nil) return;
    NSLock* aLock = [[NSLock alloc] init];
    [aLock lock];
    
    [players addObject:player];
    
    [aLock unlock];
}

}
可以使用標(biāo)記符 @synchronized 簡化代碼:

  • (void)addPlayer:(Player *)player {
    if (player == nil) return;
    @synchronized(players) {
    [players addObject:player];
    }
    }
    NSRecursiveLock 遞歸鎖
    使用普通的 NSLock 如果在遞歸的情況下或者重復(fù)加鎖的情況下,自己跟自己搶資源導(dǎo)致死鎖令杈。Cocoa 提供了 NSRecursiveLock 鎖可以多次加鎖而不會死鎖走敌,只要 unlock 次數(shù)跟 lock 次數(shù)一樣就行了。
    NSConditionLock 條件鎖
    多數(shù)情況下鎖是不需要關(guān)心什么條件下 unlock 的逗噩,要用的時候鎖上,用完了就 unlock 就完了异雁。Cocoa 提供這種條件鎖纲刀,可以在滿足某種條件下才解鎖示绊。這個鎖的 lock 和 unlock, lockWhenCondition 是隨意組合的锭部,可以不用對應(yīng)起來面褐。
    NSDistributedLock 分布式鎖
    這是用在多進(jìn)程之間共享資源的鎖拌禾,對 iOS 來說暫時沒用處。
    無鎖
    放棄加鎖展哭,采用原子操作,編寫無鎖隊(duì)列解決多線程同步的問題茵休¢泡海酷殼有篇介紹無鎖隊(duì)列的文章可以參考一下:無鎖隊(duì)列的實(shí)現(xiàn)
    使用其他備選方案代替多線程:Operation Objects, GCD, Idle-time notifications, Asynchronous functions, Timers, Separate processes鞍时。
    References

Threading Programming Guide

七、Repeating NSTimer

如果一個 Timer 是不停 repeat锐极,那么釋放之前就應(yīng)該先 invalidate。非repeat的timer在fired的時候會自動調(diào)用invalidate栋猖,但是repeat的不會蒲拉。這時如果釋放了timer,而timer其實(shí)還會回調(diào),回調(diào)的時候找不到對象就會掛掉。

原因

NSTimer 是通過 RunLoop 來實(shí)現(xiàn)定時調(diào)用的凉泄,當(dāng)你創(chuàng)建一個 Timer 的時候,RunLoop 會持有這個 Timer 的強(qiáng)引用,如果你創(chuàng)建了一個 repeating timer,在下一次回調(diào)前就把這個 timer release了碌秸,那么 runloop 回調(diào)的時候就會找不到對象而 Crash。

解決方案

我寫了個宏用來釋放Timer

/*

  • 判斷這個Timer不為nil則停止并釋放
  • 如果不先停止可能會導(dǎo)致crash
    */

define WVSAFA_DELETE_TIMER(timer) { \

if (timer != nil) {
[timer invalidate];
[timer release];
timer = nil;
}
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市狮斗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件是复,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門酬核,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嘶居,你說我怎么就攤上這事佑吝。” “怎么了痹仙?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵谓娃,是天一觀的道長弦悉。 經(jīng)常有香客問我昧甘,道長庸推,這世上最難降的妖魔是什么脖含? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任咳榜,我火速辦了婚禮涌韩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘飘蚯。我一直安慰自己,他們只是感情好泣洞,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布忧风。 她就那樣靜靜地躺著,像睡著了一般球凰。 火紅的嫁衣襯著肌膚如雪狮腿。 梳的紋絲不亂的頭發(fā)上腿宰,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機(jī)與錄音缘厢,去河邊找鬼吃度。 笑死,一個胖子當(dāng)著我的面吹牛贴硫,可吹牛的內(nèi)容都是我干的椿每。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼英遭,長吁一口氣:“原來是場噩夢啊……” “哼间护!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挖诸,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤汁尺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后多律,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痴突,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年狼荞,在試婚紗的時候發(fā)現(xiàn)自己被綠了辽装。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡粘秆,死狀恐怖如迟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情攻走,我是刑警寧澤殷勘,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站昔搂,受9級特大地震影響玲销,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜摘符,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一贤斜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逛裤,春花似錦瘩绒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蝙砌,卻和暖如春阳堕,著一層夾襖步出監(jiān)牢的瞬間跋理,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工恬总, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留前普,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓壹堰,卻偏偏與公主長得像拭卿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缀旁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,302評論 25 707
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法记劈,類相關(guān)的語法,內(nèi)部類的語法并巍,繼承相關(guān)的語法,異常的語法换途,線程的語...
    子非魚_t_閱讀 31,664評論 18 399
  • 37.cocoa內(nèi)存管理規(guī)則 1)當(dāng)你使用new懊渡,alloc或copy方法創(chuàng)建一個對象時,該對象的保留計(jì)數(shù)器值為1...
    如風(fēng)家的秘密閱讀 856評論 0 4
  • 1.Difference between shallow copy and deep copy? 淺復(fù)制和深復(fù)制的...
    用心在飛閱讀 992評論 0 9
  • 回憶军拟,是抹不掉的痕跡剃执;回憶,是痛苦的折磨懈息;回憶肾档,是對以往的懷念! 那年夏天辫继,第一次見面怒见,男未婚,女未嫁姑宽。 幾年后遣耍,...
    楊先生i閱讀 529評論 1 5