先來看一個問題:performSelector:withObject:afterDelay:
在子線程(沒有主動開啟runloop)執(zhí)行呼渣,其中的selector方法是否會被執(zhí)行水评?該方法和performSelector:withObject:
作對比圆凰,那么performSelector:withObject:
在不添加到子線程的Runloop中時是否能執(zhí)行负间?
大多數(shù)人可能會有這樣的考慮:performSelector:withObject:方法和延遲方法類似,只不過是馬上執(zhí)行而已誊册,所以也需要添加到子線程的RunLoop中芭梯。
其實不需要,原因如下:perfromSelector:withObject:只是普通的消息發(fā)送
1)關(guān)于NSObject下的解釋:
The
performSelector:
method is equivalent to sending anaSelector
message directly to the receiver. For example, the following messages all do the same thing:id aClone = [anObject copy]; id aClone = [anObject performSelector:@selector(copy)]; id aClone = [anObject performSelector:sel\_getUid("copy")];
The
performSelector:
method allows you to send messages that aren’t determined until run-time. This means that you can pass a variable selector as the argument:SEL aSelector = findTheAppropriateSelectorForTheCurrentSituation(); id returnedObject = [anObject performSelector:aSelector];
首先這個方法是在運行時直接調(diào)用的音半,在編譯階段不會進行語法檢查则拷。其次:這個方法實質(zhì)就是直接發(fā)送了一個消息。跟runloop下的performSelector 是不同的曹鸠,runloop分類中定義的方法煌茬,是需要添加到runloop中才會執(zhí)行。(前提是runloop存在且包含任意time or model or source 彻桃。這也是為什么:perfromSelector:withObject:afterDely:
在子線程中不一定執(zhí)行的原因L成啤)而performSelector:withObject:
在子線程中不受限制。
2)總結(jié):
大家基本都使用過performSelector
:系列的API,但是在使用時能否分清它們的異同卻未可知:
我們來看下perfromSelector
:這個系列的API浑吟,總共有下面三類:
performSelector
系列API 分別位于不同的分類中:
performSelector:withObject:
(帶有返回值的)是位于NSObject.h 下
performSelector:withObject:afterDelay:
(無返回值,可用來delay執(zhí)行的)是位于NSRunloop.h文件下笙纤。
performSelector: onThread: withObject:
(無返回值,但可在不同線程中執(zhí)行)位于NSThread.h文件下耗溜。
performSelectorOnMainThread: withObject: waitUntilDone:(無返回值)
跟Runloop
有關(guān)系的只有第二個分類中添加的方法组力,而NSObject分類下的performSelector下的方法跟Runloop沒有必然關(guān)系,調(diào)用后就是一次普通的消息發(fā)送抖拴。
3)警告
NSObject分類下perfromSelector API的作用通常是為了跳過編譯器的校驗燎字,在運行期直接調(diào)用selector方法。但是同樣的也會產(chǎn)生警告阿宅!
通常有以下幾種方式來消掉警告:
1)使用宏
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//code
#pragma clang diagnostic pop
缺點:這種方式是最暴力簡單的方式候衍,任何警告都不應(yīng)該直接忽略,應(yīng)該分析下警告出現(xiàn)的原因洒放,消除掉警告蛉鹿!
2) 使用runloop 分類下的perfromSelector:替換:
[self performSelector:@selector(xxxx) withObject:nil afterDelay:0]; // 1
[ss performSelectorOnMainThread:NSSelectorFromString(@"copy") withObject:nil waitUntilDone:NO]; //2
其中2是Apple文檔中給出的建議:
To avoid the warning, if you know that
a<wbr>Selector
has no return value, you might be able to useperformSelector:OnMainThread:withObject:waitUntilDone:
or one of the related methods available inNSObject
缺點:這種方式僅局限于不需要使用方法返回值的情況。
3)使用runtime方式去除警告:
//runtime 去除警告
SEL selector = NSSelectorFromString(@"testSelector:");
IMP imp = [self methodForSelector:selector];
void(*customFunc)(id,SEL,NSString *) = (void *)imp;
customFunc(self,selector,@"參數(shù)1");
4)Apple還推薦使用NSInvocation方式(這種方式既可以傳遞多個參數(shù)往湿,也可以使用返回值)妖异,這里不再贅述。
提示內(nèi)存泄漏的原因:
關(guān)于內(nèi)存泄漏問題Apple官方文檔也有簡單描述:
內(nèi)存管理策略中有一條是:誰創(chuàng)建誰管理领追。假入我們調(diào)用的selector為copy等會創(chuàng)建實例的方法他膳。而且這個操作在編譯期間是無法通過ARC來實現(xiàn)內(nèi)存管理的(也就是說創(chuàng)建的對象沒有被進行自動內(nèi)存管理),這時是存在內(nèi)存泄漏的可能的绒窑。所以會給出內(nèi)存泄漏的提示棕孙!
但這里推薦stackoverflow上高贊的一個解答:performSelector may cause a leak because its selector is unknown.很透徹的講解了產(chǎn)生警告的原因以及如何避免該類問題。
(注:關(guān)于ARC下對于返回值內(nèi)存管理和編譯器添加的優(yōu)化些膨,會在另一篇博客中講)
內(nèi)存泄漏情形總結(jié)
結(jié)論:當selector
是alloc\new\copymutableCopy
等創(chuàng)建對象的方法時蟀俊,會出現(xiàn)內(nèi)存泄漏。
1)例如下面調(diào)用new
代碼會導(dǎo)致出現(xiàn)“內(nèi)存泄漏”:
[NSObject performSelector:@selector(new) withObject:nil];
我們可以看下最終的匯編結(jié)果:
上圖中我們可以看到并沒有objc_release()
的調(diào)用订雾。創(chuàng)建的對象沒有被釋放掉肢预,我們看下正常情況下匯編后的結(jié)果(可以看到最后是有callq objc_release 操作):
[NSObject new];
我們再看下使用performSelector調(diào)用new
方法創(chuàng)建對象時的內(nèi)存圖:(可以看到方法執(zhí)行完后,內(nèi)存中仍然存在一個對象未被釋放)
2)
alloc
方法同上,這里不再贅述葬燎!
3)調(diào)用copy\mutableCopy
方法
先來看下下面的代碼:
Person *pp = [Person new];
[pp performSelector:NSSelectorFromString(@"copy")];//直接使用@selector(copy)會報編譯錯誤
最終匯編結(jié)果如下:
我們在匯編完成后的結(jié)果中沒有找到objc_release()
函數(shù)误甚,這時對象在創(chuàng)建后將會無法釋放。
這時查看內(nèi)存圖谱净,如下(可以看到person
對象仍然停在內(nèi)存中):
綜上可以看出調(diào)用:使用performSelector
系列API調(diào)用alloc\new\copy\mutableCopy
等創(chuàng)建對象的方法時會導(dǎo)致存在內(nèi)存泄漏窑邦,我們應(yīng)該禁止使用performSelector
系列API時調(diào)用此類方法!
補充說明:這里有一個特例對于字符串類型壕探,執(zhí)行
[ss performSelector:NSSelectorFromString(@"copy")];
不會產(chǎn)生內(nèi)存泄漏冈钦,分析原因可能跟系統(tǒng)對字符串做特殊的優(yōu)化處理有關(guān)!(有更好的解答李请,歡迎告知分享G粕浮)
perfromSelector:afterDelay:使用的注意事項
1厉熟、在子線程執(zhí)行perfromSelector: afterDelay:
,selector
會在對應(yīng)的子線程被調(diào)用.
2较幌、子線程執(zhí)行perfromSelector: afterDelay
:存在selector
不被執(zhí)行的問題揍瑟。
原因:該API的實質(zhì)是要執(zhí)行的selector以message方式交給當前線程的Runloop。由Runloop來執(zhí)行該selector乍炉,如果此時執(zhí)行函數(shù)的子線程沒有主動開啟Runloop绢片,selector就不會誒執(zhí)行。
3岛琼、使用了NSRunloop.h下的performSelector相關(guān)API后底循,需要在dealloc
中執(zhí)行cancle
移除掉未執(zhí)行的selector
,(也是performSelector
系列API中唯一有cancle
功能的API.) 防止出現(xiàn)內(nèi)存泄漏或者crash槐瑞。
NSObject下的帶有返回值的performSelector:
1熙涤、函數(shù)的返回值為結(jié)構(gòu)體時
OC的函數(shù)調(diào)用,當方法的返回值是結(jié)構(gòu)體時Runtime調(diào)用的不是objc_msgSend
而是使用objc_msgSend_stret
困檩。
官方文檔中對此的描述為:
Methods that have data structures as return values
are sent using objc_msgSendSuper_stret and objc_msgSend_stret.
而performSelector:在運行時會轉(zhuǎn)換成objc_msgSend()調(diào)用祠挫,如果此時方法的返回值為結(jié)構(gòu)體,而這時通過objc_msgSend()來調(diào)用窗看,編譯器會認為返回值為為一個對象類型茸歧,因此會為返回值添加優(yōu)化: objc_retainAutoreleasedReturnValue。
我們用demo來看下優(yōu)化處理得到的最終匯編結(jié)果:
id rect = [self performSelector:@selector(testPerformSelector) withObject:nil];
- (CGRect)testPerformSelector {
return CGRectMake(0, 0, 0, 0);
}
上述代碼經(jīng)過編譯優(yōu)化后的結(jié)果如下:
可以看到編譯器添加objc_retainAutoreleaseReturnValue
函數(shù)
下面來看下該函數(shù)的實現(xiàn):
// Accept a value returned through a +0 autoreleasing convention for use at +1.
id objc_retainAutoreleasedReturnValue(id obj)
{
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj; //1
return objc_retain(obj);
}
我們看到有一個判斷acceptOptimizedReturn()
:檢查該返回值是否已經(jīng)優(yōu)化過显沈,若果是直接返回對象软瞎,否則將返回值做retain操作!
通過編譯運行源碼進行斷點調(diào)試拉讯,以及查看上面的匯編結(jié)果可以確定:在此之前返回值并沒有進過優(yōu)化涤浇,所以會執(zhí)行objc_retain()
操作。
這時因為返回值是一個結(jié)構(gòu)體(非對象類型)類型魔慷,因此會crashV欢А!
(注:有關(guān) acceptOptimizedReturn() 以及 objc_autoreleaseReturnValue() 實現(xiàn)會在其他博客中詳細講解T憾)
2蜻展、執(zhí)行返回值為結(jié)構(gòu)的函數(shù)崩潰的不同現(xiàn)象分析
函數(shù)返回值為結(jié)構(gòu)體類型時,接受返回值
和不接收返回值
崩潰的原因是不同的:
1)接受返回值情形:(id xx = [self performSelector:@selector(testStruct) withObject:nil];)
crash原因是:編譯器為xx
添加了objc_retainAutoreleased
優(yōu)化操作,在其內(nèi)部對xx做了retain
導(dǎo)致crash邀摆。
2)不接收返回值:[self performSelector:@selector(testStruct) withObject:nil];
crash原因是:函數(shù)返回值被認為是一個對象纵顾,所以會在其不被使用的時候會調(diào)用objc_release()
對其釋放,而事實上返回值是一個結(jié)構(gòu)體栋盹,因此crash施逾。(暫且這么解釋!)
這種情況下的崩潰位置并不在函數(shù)調(diào)用處,很不利于排查問題汉额!
我們看圖來佐證一下:
在普通VC中調(diào)用某個類中的方法:
通過以上截圖可以看到可以看到崩潰的位置是在外面的函數(shù)中曹仗,而不是執(zhí)行performSelector處。
再來看下調(diào)用的堆棧:
可以看到堆棧也只是定位到最外層蠕搜。如果我們線上項目有類似代碼被執(zhí)行并且產(chǎn)生了崩潰怎茫,依靠上報的崩潰日志想要精確定位到出問題得地方是很困難的。
再來看下運行源碼得到的崩潰堆棧(分析下原因):
可以看到是調(diào)用performSelector的對象釋放時出現(xiàn)crash讥脐。這里我們看一下是哪個對象的釋放引起crash遭居。
而這個 0x102423f70 是什么呢?打印一下看:
竟然是調(diào)用“返回結(jié)構(gòu)體函數(shù)”的對象旬渠,這里本人也有點奇怪,為什么會在釋放這個對象時產(chǎn)生crash端壳?告丢?? 實在沒有想通损谦,如有了解的同學(xué)請一定要告知下岖免。感謝
結(jié)論:
1、不要使用performSelector 調(diào)用返回值為結(jié)構(gòu)體的函數(shù)照捡。(出現(xiàn)問題不好定位)
2颅湘、函數(shù)返回值為非對象類型的,也不要使用使用perfromSelector調(diào)用栗精。
3闯参、拓展:
既然函數(shù)返回值為結(jié)構(gòu)體時會產(chǎn)生crash,那如果返回值是“其他非對象類型”時是否會產(chǎn)生crash呢悲立?(這里直接說結(jié)論
)
1)不接收函數(shù)返回值時:(ex:[self performSelector:@selector(testPerformselector) withObject:nil])
通過performSelector
調(diào)用返回值為int
鹿寨、float
、bool
類型的函數(shù)時薪夕,不會發(fā)生crash脚草。
2)接收返回值時:(ex: id xx = [self performSelector:@selector(testPerformselector): withObject:nil])
通過performSelector
調(diào)用返回值為int、bool
函數(shù)會crash原献,調(diào)用 返回值為float類型的函數(shù)
不會產(chǎn)生crash馏慨!
調(diào)用返回值為float的函數(shù)會有兩種情況:
在源碼編譯運行,會發(fā)現(xiàn)得到的返回值xx是函數(shù)的調(diào)用對象姑隅。但是普通demo上測試 xx是null写隶。這里為什么是這樣,作者也是沒想通(可能還是欠缺某些關(guān)聯(lián)知識點粤策,有了解的同學(xué)希望留言告知)
4樟澜、關(guān)于OC消息發(fā)送的不同情況:
oc的消息發(fā)送并不都是轉(zhuǎn)化為objc_msgSend
,編譯器會根據(jù)不同情況選擇不同方法:
1)當返回值為結(jié)構(gòu)體時,會使用objc_msgSend_stret
:(當返回值為較大的結(jié)構(gòu)體時秩贰,無法使用寄存器來傳遞返回值霹俺,而是通過棧來傳遞。)
2 )返回浮點數(shù)時毒费,還會調(diào)用objc_msgSend_fpret(objc_msgSend_fp2ret)
3 )給父類發(fā)送消息丙唧,會調(diào)用objc_msgSendSuper
參考:
perfromSelector一點注意
stackoverflow-perfromSelector-warning
perfromSelector內(nèi)存泄漏問題