亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

iOS開發(fā)KVO實(shí)現(xiàn)細(xì)節(jié)解密

 更新時(shí)間:2022年08月08日 14:45:29   作者:高少東  
這篇文章主要為大家介紹了iOS開發(fā)KVO實(shí)現(xiàn)細(xì)節(jié)解密,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

導(dǎo)讀

大多數(shù) iOS 開發(fā)人員對(duì) KVO 的認(rèn)識(shí)只局限于 isa 指針交換這一層,而 KVO 的實(shí)現(xiàn)細(xì)節(jié)卻鮮為人知。

如果自己也仿照 KVO 基礎(chǔ)原理來(lái)實(shí)現(xiàn)一套類 KVO 操作且獨(dú)立運(yùn)行時(shí)會(huì)發(fā)現(xiàn)一切正常,然而一旦你的實(shí)現(xiàn)和系統(tǒng)的 KVO 實(shí)現(xiàn)同時(shí)作用在同一個(gè)實(shí)例上那么各種各樣詭異的 bug 和 crash 就會(huì)層出不窮。

這究竟是為什么呢?此類問題到底該如何解決呢?接下來(lái)我們將嘗試從匯編層面來(lái)入手以層層揭開 KVO 的神秘面紗......

1. 緣起 Aspects

SDMagicHook 開源之后很多小伙伴在問“ SDMagicHook 和 Aspects 的區(qū)別是什么?”,我在 GitHub 上找到 Aspects 了解之后發(fā)現(xiàn) Aspects 也是以 isa 交換為基礎(chǔ)原理進(jìn)行的 hook 操作,但是兩者在具體實(shí)現(xiàn)和 API 設(shè)計(jì)上也有一些區(qū)別,另外 SDMagicHook 還解決了 Aspects 未能解決的 KVO 沖突難題。

1.1 SDMagicHook 的 API 設(shè)計(jì)更加友好靈活

SDMagicHook 和 Aspects 的具體異同分析見:

https://github.com/larksuite/SDMagicHook/issues/3

1.2 SDMagicHook 解決了 Aspects 未能解決的 KVO 沖突難題

在 Aspects 的 readme 中我還注意到了這樣一條關(guān)于 KVO 兼容問題的描述:

SDMagicHook 會(huì)不會(huì)有同樣的問題呢?測(cè)試了一下發(fā)現(xiàn) SDMagicHook 果然也中招了,而且其實(shí)此類問題的實(shí)際情況要比 Aspects 作者描述的更為復(fù)雜和詭異,問題的具體表現(xiàn)會(huì)隨著系統(tǒng) KVO(以下簡(jiǎn)稱 native-KVO)和自己實(shí)現(xiàn)的類 KVO(custom-KVO)的調(diào)用順序和次數(shù)的不同而各異,具體如下:

  • 先調(diào)用 custom-KVO 再調(diào)用 native-KVO,native-KVO 和 custom-KVO 都運(yùn)行正常
  • 先調(diào)用 native-KVO 再調(diào)用 custom-KVO,custom-KVO 運(yùn)行正常,native-KVO 會(huì) crash
  • 先調(diào)用 native-KVO 再調(diào)用 custom-KVO 再調(diào)用 native-KVO,native-KVO 運(yùn)行正常,custom-KVO 失效,無(wú) crash

目前,SDMagicHook 已經(jīng)解決了上面提到的各類問題,具體的實(shí)現(xiàn)方案我將在下文中詳細(xì)介紹。

2. 從匯編層面探索 KVO 本質(zhì)

想要弄明白這個(gè)問題首先需要研究清楚系統(tǒng)的 KVO 到底是如何實(shí)現(xiàn)的,而系統(tǒng)的 KVO 實(shí)現(xiàn)又相當(dāng)復(fù)雜,我們?cè)搹哪睦锶胧帜兀?/p>

想要弄清楚這個(gè)問題,我們首先需要了解下當(dāng)對(duì)被 KVO 觀察的目標(biāo)屬性進(jìn)行賦值操作時(shí)到底發(fā)生了什么。這里我們以自建的 Test 類為例來(lái)說明,我們對(duì) Test 類實(shí)例的 num 屬性進(jìn)行 KVO 操作:

當(dāng)我們給 num 賦值時(shí),可以看到斷點(diǎn)命中了 KVO 類自定義的 setNum:的實(shí)現(xiàn)即_NSSetIntValueAndNotify 函數(shù)

那么_NSSetIntValueAndNotify 的內(nèi)部實(shí)現(xiàn)是怎樣的呢?我們可以從匯編代碼中發(fā)現(xiàn)一些蛛絲馬跡:

Foundation`_NSSetIntValueAndNotify:
????0x10e5b0fc2?<+0>:???pushq??%rbp
->??0x10e5b0fc3?<+1>:???movq???%rsp,?%rbp
????0x10e5b0fc6?<+4>:???pushq??%r15
????0x10e5b0fc8?<+6>:???pushq??%r14
????0x10e5b0fca?<+8>:???pushq??%r13
????0x10e5b0fcc?<+10>:??pushq??%r12
????0x10e5b0fce?<+12>:??pushq??%rbx
????0x10e5b0fcf?<+13>:??subq???$0x48,?%rsp
????0x10e5b0fd3?<+17>:??movl???%edx,?-0x2c(%rbp)
????0x10e5b0fd6?<+20>:??movq???%rsi,?%r15
????0x10e5b0fd9?<+23>:??movq???%rdi,?%r13
????0x10e5b0fdc?<+26>:??callq??0x10e7cc882???????????????;?symbol?stub?for:?object_getClass
????0x10e5b0fe1?<+31>:??movq???%rax,?%rdi
????0x10e5b0fe4?<+34>:??callq??0x10e7cc88e???????????????;?symbol?stub?for:?object_getIndexedIvars
????0x10e5b0fe9?<+39>:??movq???%rax,?%rbx
????0x10e5b0fec?<+42>:??leaq???0x20(%rbx),?%r14
????0x10e5b0ff0?<+46>:??movq???%r14,?%rdi
????0x10e5b0ff3?<+49>:??callq??0x10e7cca26???????????????;?symbol?stub?for:?pthread_mutex_lock
????0x10e5b0ff8?<+54>:??movq???0x18(%rbx),?%rdi
????0x10e5b0ffc?<+58>:??movq???%r15,?%rsi
????0x10e5b0fff?<+61>:??callq??0x10e7cb472???????????????;?symbol?stub?for:?CFDictionaryGetValue
????0x10e5b1004?<+66>:??movq???0x36329d(%rip),?%rsi??????;?"copyWithZone:"
????0x10e5b100b?<+73>:??xorl???%edx,?%edx
????0x10e5b100d?<+75>:??movq???%rax,?%rdi
????0x10e5b1010?<+78>:??callq??*0x2b2862(%rip)???????????;?(void?*)0x000000010eb89d80:?objc_msgSend
????0x10e5b1016?<+84>:??movq???%rax,?%r12
????0x10e5b1019?<+87>:??movq???%r14,?%rdi
????0x10e5b101c?<+90>:??callq??0x10e7cca32???????????????;?symbol?stub?for:?pthread_mutex_unlock
????0x10e5b1021?<+95>:??cmpb???$0x0,?0x60(%rbx)
????0x10e5b1025?<+99>:??je?????0x10e5b1066???????????????;?<+164>
????0x10e5b1027?<+101>:?movq???0x36439a(%rip),?%rsi??????;?"willChangeValueForKey:"
????0x10e5b102e?<+108>:?movq???0x2b2843(%rip),?%r14??????;?(void?*)0x000000010eb89d80:?objc_msgSend
????0x10e5b1035?<+115>:?movq???%r13,?%rdi
????0x10e5b1038?<+118>:?movq???%r12,?%rdx
????0x10e5b103b?<+121>:?callq??*%r14
????0x10e5b103e?<+124>:?movq???(%rbx),?%rdi
????0x10e5b1041?<+127>:?movq???%r15,?%rsi
????0x10e5b1044?<+130>:?callq??0x10e7cc2b2???????????????;?symbol?stub?for:?class_getMethodImplementation
????0x10e5b1049?<+135>:?movq???%r13,?%rdi
????0x10e5b104c?<+138>:?movq???%r15,?%rsi
????0x10e5b104f?<+141>:?movl???-0x2c(%rbp),?%edx
????0x10e5b1052?<+144>:?callq??*%rax
????0x10e5b1054?<+146>:?movq???0x364385(%rip),?%rsi??????;?"didChangeValueForKey:"
????0x10e5b105b?<+153>:?movq???%r13,?%rdi
????0x10e5b105e?<+156>:?movq???%r12,?%rdx
????0x10e5b1061?<+159>:?callq??*%r14
????0x10e5b1064?<+162>:?jmp????0x10e5b10be???????????????;?<+252>
????0x10e5b1066?<+164>:?movq???0x2b22eb(%rip),?%rax??????;?(void?*)0x00000001120b9070:?_NSConcreteStackBlock
????0x10e5b106d?<+171>:?leaq???-0x68(%rbp),?%r9
????0x10e5b1071?<+175>:?movq???%rax,?(%r9)
????0x10e5b1074?<+178>:?movl???$0xc2000000,?%eax?????????;?imm?=?0xC2000000
????0x10e5b1079?<+183>:?movq???%rax,?0x8(%r9)
????0x10e5b107d?<+187>:?leaq???0xf5d(%rip),?%rax?????????;?___NSSetIntValueAndNotify_block_invoke
????0x10e5b1084?<+194>:?movq???%rax,?0x10(%r9)
????0x10e5b1088?<+198>:?leaq???0x2b7929(%rip),?%rax??????;?__block_descriptor_tmp.77
????0x10e5b108f?<+205>:?movq???%rax,?0x18(%r9)
????0x10e5b1093?<+209>:?movq???%rbx,?0x28(%r9)
????0x10e5b1097?<+213>:?movq???%r15,?0x30(%r9)
????0x10e5b109b?<+217>:?movq???%r13,?0x20(%r9)
????0x10e5b109f?<+221>:?movl???-0x2c(%rbp),?%eax
????0x10e5b10a2?<+224>:?movl???%eax,?0x38(%r9)
????0x10e5b10a6?<+228>:?movq???0x364fab(%rip),?%rsi??????;?"_changeValueForKey:key:key:usingBlock:"
????0x10e5b10ad?<+235>:?xorl???%ecx,?%ecx
????0x10e5b10af?<+237>:?xorl???%r8d,?%r8d
????0x10e5b10b2?<+240>:?movq???%r13,?%rdi
????0x10e5b10b5?<+243>:?movq???%r12,?%rdx
????0x10e5b10b8?<+246>:?callq??*0x2b27ba(%rip)???????????;?(void?*)0x000000010eb89d80:?objc_msgSend
????0x10e5b10be?<+252>:?movq???0x362f73(%rip),?%rsi??????;?"release"
????0x10e5b10c5?<+259>:?movq???%r12,?%rdi
????0x10e5b10c8?<+262>:?callq??*0x2b27aa(%rip)???????????;?(void?*)0x000000010eb89d80:?objc_msgSend
????0x10e5b10ce?<+268>:?addq???$0x48,?%rsp
????0x10e5b10d2?<+272>:?popq???%rbx
????0x10e5b10d3?<+273>:?popq???%r12
????0x10e5b10d5?<+275>:?popq???%r13
????0x10e5b10d7?<+277>:?popq???%r14
????0x10e5b10d9?<+279>:?popq???%r15
????0x10e5b10db?<+281>:?popq???%rbp
????0x10e5b10dc?<+282>:?retq

上面這段匯編代碼翻譯為偽代碼大致如下:

typedef?struct?{
????Class?originalClass;????????????????//?offset?0x0
????Class?KVOClass;?????????????????????//?offset?0x8
????CFMutableSetRef?mset;???????????????//?offset?0x10
????CFMutableDictionaryRef?mdict;???????//?offset?0x18
????pthread_mutex_t?*lock;??????????????//?offset?0x20
????void?*sth1;?????????????????????????//?offset?0x28
????void?*sth2;?????????????????????????//?offset?0x30
????void?*sth3;?????????????????????????//?offset?0x38
????void?*sth4;?????????????????????????//?offset?0x40
????void?*sth5;?????????????????????????//?offset?0x48
????void?*sth6;?????????????????????????//?offset?0x50
????void?*sth7;?????????????????????????//?offset?0x58
????bool?flag;??????????????????????????//?offset?0x60
}?SDTestKVOClassIndexedIvars;
typedef?struct?{
????Class?isa;??????????????????????????//?offset?0x0
????int?flags;??????????????????????????//?offset?0x8
????int?reserved;
????IMP?invoke;?????????????????????????//?offset?0x10
????void?*descriptor;???????????????????//?offset?0x18
????void?*captureVar1;??????????????????//?offset?0x20
????void?*captureVar2;??????????????????//?offset?0x28
????void?*captureVar3;??????????????????//?offset?0x30
????int?captureVar4;????????????????????//?offset?0x38
}?SDTestStackBlock;
void?_NSSetIntValueAndNotify(id?obj,?SEL?sel,?int?number)?{
????Class?cls?=?object_getClass(obj);
????//?獲取類實(shí)例關(guān)聯(lián)的信息
????SDTestKVOClassIndexedIvars?*indexedIvars?=?object_getIndexedIvars(cls);
????pthread_mutex_lock(indexedIvars->lock);
????NSString?*str?=?(NSString?*)CFDictionaryGetValue(indexedIvars->mdict,?sel);
????str?=?[str?copyWithZone:nil];
????pthread_mutex_unlock(indexedIvars->lock);
????if?(indexedIvars->flag)?{
????????[obj?willChangeValueForKey:str];
????????((void(*)(id?obj,?SEL?sel,?int?number))class_getMethodImplementation(indexedIvars->originalClass,?sel))(obj,?sel,?number);
????????[obj?didChangeValueForKey:str];
????}?else?{
????????//?生成block
????????SDTestStackBlock?block?=?{};
????????block.isa?=?_NSConcreteStackBlock;
????????block.flags?=?0xC2000000;
????????block.invoke?=?___NSSetIntValueAndNotify_block_invoke;
????????block.descriptor?=?__block_descriptor_tmp;
????????block.captureVar2?=?indexedIvars;
????????block.captureVar3?=?sel;
????????block.captureVar1?=?obj;
????????block.captureVar4?=?number;
????????[obj?_changeValueForKey:str?key:nil?key:nil?usingBlock:&SDTestStackBlock];
????}
}

這段代碼的大致意思是說首先通過 object_getIndexedIvars(cls)獲取到 KVO 類的 indexedIvars,如果 indexedIvars->flag 為 true 即開發(fā)者自己重寫實(shí)現(xiàn)過 willChangeValueForKey:

或者 didChangeValueForKey:方法的話就直接以 class_getMethodImplementation(indexedIvars->originalClass, sel))(obj, sel, number)的方式實(shí)現(xiàn)對(duì)被觀察的原方法的調(diào)用,否則就用默認(rèn)實(shí)現(xiàn)為 NSSetIntValueAndNotify_block_invoke 的棧 block 并捕獲 indexedIvars、被 KVO 觀察的實(shí)例、被觀察屬性對(duì)應(yīng)的 SEL、賦值參數(shù)等所有必要參數(shù)并將這個(gè) block 作為參數(shù)傳遞給 [obj _changeValueForKey:str key:nil key:nil usingBlock:&SDTestStackBlock]調(diào)用。

看到這里你或許會(huì)有個(gè)疑問:

偽代碼中通過 object_getIndexedIvars(cls)獲取到的 indexedIvars 是什么信息呢?

block.invoke = ___ NSSetIntValueAndNotify_block_invoke 又是如何實(shí)現(xiàn)的呢?

首先我們看下 NSSetIntValueAndNotify_block_invoke 的匯編實(shí)現(xiàn):

Foundation`___NSSetIntValueAndNotify_block_invoke:
->??0x10bf27fe1?<+0>:??pushq??%rbp
????0x10bf27fe2?<+1>:??movq???%rsp,?%rbp
????0x10bf27fe5?<+4>:??pushq??%rbx
????0x10bf27fe6?<+5>:??pushq??%rax
????0x10bf27fe7?<+6>:??movq???%rdi,?%rbx
????0x10bf27fea?<+9>:??movq???0x28(%rbx),?%rax
????0x10bf27fee?<+13>:?movq???0x30(%rbx),?%rsi
????0x10bf27ff2?<+17>:?movq???(%rax),?%rdi
????0x10bf27ff5?<+20>:?callq??0x10c1422b2???????????????;?symbol?stub?for:?class_getMethodImplementation
????0x10bf27ffa?<+25>:?movq???0x20(%rbx),?%rdi
????0x10bf27ffe?<+29>:?movq???0x30(%rbx),?%rsi
????0x10bf28002?<+33>:?movl???0x38(%rbx),?%edx
????0x10bf28005?<+36>:?addq???$0x8,?%rsp
????0x10bf28009?<+40>:?popq???%rbx
????0x10bf2800a?<+41>:?popq???%rbp
????0x10bf2800b?<+42>:?jmpq???*%rax

___NSSetIntValueAndNotify_block_invoke 翻譯成偽代碼如下:

void?___NSSetIntValueAndNotify_block_invoke(SDTestStackBlock?*block)?{
????SDTestKVOClassIndexedIvars?*indexedIvars?=?block->captureVar2;
????SEL?methodSel?=??block->captureVar3;
????IMP?imp?=?class_getMethodImplementation(indexedIvars->originalClass);
????id?obj?=?block->captureVar1;
????SEL?sel?=?block->captureVar3;
????int?num?=?block->captureVar4;
????imp(obj,?sel,?num);
}

這個(gè) block 的內(nèi)部實(shí)現(xiàn)其實(shí)就是從 KVO 類的 indexedIvars 里取到原始類,然后根據(jù) sel 從原始類中取出原始的方法實(shí)現(xiàn)來(lái)執(zhí)行并最終完成了一次 KVO 調(diào)用。我們發(fā)現(xiàn)整個(gè) KVO 運(yùn)作過程中 KVO 類的 indexedIvars 是一個(gè)貫穿 KVO 流程始末的關(guān)鍵數(shù)據(jù),那么這個(gè) indexedIvars 是何時(shí)生成的呢?

indexedIvars 里又包含哪些數(shù)據(jù)呢?想要弄清楚這個(gè)問題,我們就必須從 KVO 的源頭看起,我們知道既然 KVO 要用到 isa 交換那么最終肯定要調(diào)用到 object_setClass 方法,這里我們不妨以 object_setClass 函數(shù)為線索,通過設(shè)置條件符號(hào)斷點(diǎn)來(lái)追蹤 object_setClass 的調(diào)用,lldb 調(diào)試截圖如下:

斷點(diǎn)到 object_setClass 之后,我們?cè)衮?yàn)證看下寄存器 rdi、rsi 里面的參數(shù)打印出來(lái)分別是

<Test: 0x600003df01b0>、NSKVONotifying_Test

不錯(cuò),我們現(xiàn)在已經(jīng)成功定位到 KVO 的 isa 交換現(xiàn)場(chǎng)了,然而為了找到 KVO 類的生成的地方我們還需要沿著調(diào)用棧向前回溯,最終我們定位到 KVO 類的生成函數(shù)_NSKVONotifyingCreateInfoWithOriginalClass

其匯編代碼如下:

Foundation`_NSKVONotifyingCreateInfoWithOriginalClass:
->??0x10c557d79?<+0>:???pushq??%rbp
????0x10c557d7a?<+1>:???movq???%rsp,?%rbp
????0x10c557d7d?<+4>:???pushq??%r15
????0x10c557d7f?<+6>:???pushq??%r14
????0x10c557d81?<+8>:???pushq??%r12
????0x10c557d83?<+10>:??pushq??%rbx
????0x10c557d84?<+11>:??subq???$0x20,?%rsp
????0x10c557d88?<+15>:??movq???%rdi,?%r14
????0x10c557d8b?<+18>:??movq???0x2b463e(%rip),?%rax??????;?(void?*)0x000000011012d070:?__stack_chk_guard
????0x10c557d92?<+25>:??movq???(%rax),?%rax
????0x10c557d95?<+28>:??movq???%rax,?-0x28(%rbp)
????0x10c557d99?<+32>:??xorl???%eax,?%eax
????0x10c557d9b?<+34>:??callq??0x10c55b452???????????????;?NSKeyValueObservingAssertRegistrationLockHeld
????0x10c557da0?<+39>:??movq???%r14,?%rdi
????0x10c557da3?<+42>:??callq??0x10c7752b8???????????????;?symbol?stub?for:?class_getName
????0x10c557da8?<+47>:??movq???%rax,?%r12
????0x10c557dab?<+50>:??movq???%r12,?%rdi
????0x10c557dae?<+53>:??callq??0x10c775ba0???????????????;?symbol?stub?for:?strlen
????0x10c557db3?<+58>:??movq???%rax,?%rbx
????0x10c557db6?<+61>:??addq???$0x10,?%rbx
????0x10c557dba?<+65>:??movq???%rbx,?%rdi
????0x10c557dbd?<+68>:??callq??0x10c775666???????????????;?symbol?stub?for:?malloc
????0x10c557dc2?<+73>:??movq???%rax,?%r15
????0x10c557dc5?<+76>:??leaq???0x29d604(%rip),?%rsi??????;?_NSKVONotifyingCreateInfoWithOriginalClass.notifyingClassNamePrefix
????0x10c557dcc?<+83>:??movq???$-0x1,?%rcx
????0x10c557dd3?<+90>:??movq???%r15,?%rdi
????0x10c557dd6?<+93>:??movq???%rbx,?%rdx
????0x10c557dd9?<+96>:??callq??0x10c77510e???????????????;?symbol?stub?for:?__strlcpy_chk
????0x10c557dde?<+101>:?movq???$-0x1,?%rcx
????0x10c557de5?<+108>:?movq???%r15,?%rdi
????0x10c557de8?<+111>:?movq???%r12,?%rsi
????0x10c557deb?<+114>:?movq???%rbx,?%rdx
????0x10c557dee?<+117>:?callq??0x10c775108???????????????;?symbol?stub?for:?__strlcat_chk
????0x10c557df3?<+122>:?movl???$0x68,?%edx
????0x10c557df8?<+127>:?movq???%r14,?%rdi
????0x10c557dfb?<+130>:?movq???%r15,?%rsi
????0x10c557dfe?<+133>:?callq??0x10c775762???????????????;?symbol?stub?for:?objc_allocateClassPair
????0x10c557e03?<+138>:?movq???%rax,?%rbx
????0x10c557e06?<+141>:?testq??%rbx,?%rbx
????0x10c557e09?<+144>:?je?????0x10c557f17???????????????;?<+414>
????0x10c557e0f?<+150>:?movq???%rbx,?%rdi
????0x10c557e12?<+153>:?callq??0x10c775816???????????????;?symbol?stub?for:?objc_registerClassPair
????0x10c557e17?<+158>:?movq???%r15,?%rdi
????0x10c557e1a?<+161>:?callq??0x10c7754ec???????????????;?symbol?stub?for:?free
????0x10c557e1f?<+166>:?movq???%rbx,?%rdi
????0x10c557e22?<+169>:?callq??0x10c77588e???????????????;?symbol?stub?for:?object_getIndexedIvars
????0x10c557e27?<+174>:?movq???%rax,?%r15
????0x10c557e2a?<+177>:?movq???%r14,?(%r15)
????0x10c557e2d?<+180>:?movq???%rbx,?0x8(%r15)
????0x10c557e31?<+184>:?movq???0x2b4748(%rip),?%rdx??????;?(void?*)0x000000010d7fd1f8:?kCFCopyStringSetCallBacks
????0x10c557e38?<+191>:?xorl???%edi,?%edi
????0x10c557e3a?<+193>:?xorl???%esi,?%esi
????0x10c557e3c?<+195>:?callq??0x10c774778???????????????;?symbol?stub?for:?CFSetCreateMutable
????0x10c557e41?<+200>:?movq???%rax,?0x10(%r15)
????0x10c557e45?<+204>:?movq???0x2b49e4(%rip),?%rcx??????;?(void?*)0x000000010d7f6bb8:?kCFTypeDictionaryValueCallBacks
????0x10c557e4c?<+211>:?xorl???%edi,?%edi
????0x10c557e4e?<+213>:?xorl???%esi,?%esi
????0x10c557e50?<+215>:?xorl???%edx,?%edx
????0x10c557e52?<+217>:?callq??0x10c774454???????????????;?symbol?stub?for:?CFDictionaryCreateMutable
????0x10c557e57?<+222>:?movq???%rax,?0x18(%r15)
????0x10c557e5b?<+226>:?leaq???-0x38(%rbp),?%rbx
????0x10c557e5f?<+230>:?movq???%rbx,?%rdi
????0x10c557e62?<+233>:?callq??0x10c775a3e???????????????;?symbol?stub?for:?pthread_mutexattr_init
????0x10c557e67?<+238>:?movl???$0x2,?%esi
????0x10c557e6c?<+243>:?movq???%rbx,?%rdi
????0x10c557e6f?<+246>:?callq??0x10c775a44???????????????;?symbol?stub?for:?pthread_mutexattr_settype
????0x10c557e74?<+251>:?leaq???0x20(%r15),?%rdi
????0x10c557e78?<+255>:?movq???%rbx,?%rsi
????0x10c557e7b?<+258>:?callq??0x10c775a20???????????????;?symbol?stub?for:?pthread_mutex_init
????0x10c557e80?<+263>:?movq???%rbx,?%rdi
????0x10c557e83?<+266>:?callq??0x10c775a38???????????????;?symbol?stub?for:?pthread_mutexattr_destroy
????0x10c557e88?<+271>:?cmpq???$-0x1,?0x3824a0(%rip)?????;?_NSKVONotifyingCreateInfoWithOriginalClass.onceToken?+?7
????0x10c557e90?<+279>:?jne????0x10c557fa4???????????????;?<+555>
????0x10c557e96?<+285>:?movq???(%r15),?%rdi
????0x10c557e99?<+288>:?movq???0x366528(%rip),?%rsi??????;?"willChangeValueForKey:"
????0x10c557ea0?<+295>:?callq??0x10c7752b2???????????????;?symbol?stub?for:?class_getMethodImplementation
????0x10c557ea5?<+300>:?movb???$0x1,?%cl
????0x10c557ea7?<+302>:?cmpq???0x38248a(%rip),?%rax??????;?_NSKVONotifyingCreateInfoWithOriginalClass.NSObjectWillChange
????0x10c557eae?<+309>:?jne????0x10c557ec9???????????????;?<+336>
????0x10c557eb0?<+311>:?movq???(%r15),?%rdi
????0x10c557eb3?<+314>:?movq???0x366526(%rip),?%rsi??????;?"didChangeValueForKey:"
????0x10c557eba?<+321>:?callq??0x10c7752b2???????????????;?symbol?stub?for:?class_getMethodImplementation
????0x10c557ebf?<+326>:?cmpq???0x38247a(%rip),?%rax??????;?_NSKVONotifyingCreateInfoWithOriginalClass.NSObjectDidChange
????0x10c557ec6?<+333>:?setne??%cl
????0x10c557ec9?<+336>:?movb???%cl,?0x60(%r15)
????0x10c557ecd?<+340>:?movq???0x36715c(%rip),?%rsi??????;?"_isKVOA"
????0x10c557ed4?<+347>:?leaq???0x1ff(%rip),?%rdx?????????;?NSKVOIsAutonotifying
????0x10c557edb?<+354>:?xorl???%ecx,?%ecx
????0x10c557edd?<+356>:?movq???%r15,?%rdi
????0x10c557ee0?<+359>:?callq??0x10c558057???????????????;?NSKVONotifyingSetMethodImplementation
????0x10c557ee5?<+364>:?movq???0x365154(%rip),?%rsi??????;?"dealloc"
????0x10c557eec?<+371>:?leaq???0x1ef(%rip),?%rdx?????????;?NSKVODeallocate
????0x10c557ef3?<+378>:?xorl???%ecx,?%ecx
????0x10c557ef5?<+380>:?movq???%r15,?%rdi
????0x10c557ef8?<+383>:?callq??0x10c558057???????????????;?NSKVONotifyingSetMethodImplementation
????0x10c557efd?<+388>:?movq???0x36519c(%rip),?%rsi??????;?"class"
????0x10c557f04?<+395>:?leaq???0x433(%rip),?%rdx?????????;?NSKVOClass
????0x10c557f0b?<+402>:?xorl???%ecx,?%ecx
????0x10c557f0d?<+404>:?movq???%r15,?%rdi
????0x10c557f10?<+407>:?callq??0x10c558057???????????????;?NSKVONotifyingSetMethodImplementation
????0x10c557f15?<+412>:?jmp????0x10c557f84???????????????;?<+523>
????0x10c557f17?<+414>:?cmpq???$-0x1,?0x382409(%rip)?????;?_NSKVONotifyingCreateInfoWithOriginalClass.kvoLog?+?7
????0x10c557f1f?<+422>:?jne????0x10c557fbc???????????????;?<+579>
????0x10c557f25?<+428>:?movq???0x3823f4(%rip),?%r14??????;?_NSKVONotifyingCreateInfoWithOriginalClass.kvoLog
????0x10c557f2c?<+435>:?movl???$0x10,?%esi
????0x10c557f31?<+440>:?movq???%r14,?%rdi
????0x10c557f34?<+443>:?callq??0x10c7758e2???????????????;?symbol?stub?for:?os_log_type_enabled
????0x10c557f39?<+448>:?testb??%al,?%al
????0x10c557f3b?<+450>:?je?????0x10c557f79???????????????;?<+512>
????0x10c557f3d?<+452>:?movq???%rsp,?%rbx
????0x10c557f40?<+455>:?movq???%rsp,?%rax
????0x10c557f43?<+458>:?leaq???-0x10(%rax),?%r8
????0x10c557f47?<+462>:?movq???%r8,?%rsp
????0x10c557f4a?<+465>:?movl???$0x8200102,?-0x10(%rax)???;?imm?=?0x8200102
????0x10c557f51?<+472>:?movq???%r15,?-0xc(%rax)
????0x10c557f55?<+476>:?leaq???-0x63f5c(%rip),?%rdi
????0x10c557f5c?<+483>:?leaq???0x296c1d(%rip),?%rcx??????;?"KVO?failed?to?allocate?class?pair?for?name?%s,?automatic?key-value?observing?will?not?work?for?this?class"
????0x10c557f63?<+490>:?movl???$0x10,?%edx
????0x10c557f68?<+495>:?movl???$0xc,?%r9d
????0x10c557f6e?<+501>:?movq???%r14,?%rsi
????0x10c557f71?<+504>:?callq??0x10c7751aa???????????????;?symbol?stub?for:?_os_log_error_impl
????0x10c557f76?<+509>:?movq???%rbx,?%rsp
????0x10c557f79?<+512>:?movq???%r15,?%rdi
????0x10c557f7c?<+515>:?callq??0x10c7754ec???????????????;?symbol?stub?for:?free
????0x10c557f81?<+520>:?xorl???%r15d,?%r15d
????0x10c557f84?<+523>:?movq???0x2b4445(%rip),?%rax??????;?(void?*)0x000000011012d070:?__stack_chk_guard
????0x10c557f8b?<+530>:?movq???(%rax),?%rax
????0x10c557f8e?<+533>:?cmpq???-0x28(%rbp),?%rax
????0x10c557f92?<+537>:?jne????0x10c557fd4???????????????;?<+603>
????0x10c557f94?<+539>:?movq???%r15,?%rax
????0x10c557f97?<+542>:?leaq???-0x20(%rbp),?%rsp
????0x10c557f9b?<+546>:?popq???%rbx
????0x10c557f9c?<+547>:?popq???%r12
????0x10c557f9e?<+549>:?popq???%r14
????0x10c557fa0?<+551>:?popq???%r15
????0x10c557fa2?<+553>:?popq???%rbp
????0x10c557fa3?<+554>:?retq
????0x10c557fa4?<+555>:?leaq???0x382385(%rip),?%rdi??????;?_NSKVONotifyingCreateInfoWithOriginalClass.NSObjectIMPLookupOnce
????0x10c557fab?<+562>:?leaq???0x2b9886(%rip),?%rsi??????;?__block_literal_global.8
????0x10c557fb2?<+569>:?callq??0x10c7753d8???????????????;?symbol?stub?for:?dispatch_once
????0x10c557fb7?<+574>:?jmp????0x10c557e96???????????????;?<+285>
????0x10c557fbc?<+579>:?leaq???0x382365(%rip),?%rdi??????;?_NSKVONotifyingCreateInfoWithOriginalClass.onceToken
????0x10c557fc3?<+586>:?leaq???0x2b982e(%rip),?%rsi??????;?__block_literal_global
????0x10c557fca?<+593>:?callq??0x10c7753d8???????????????;?symbol?stub?for:?dispatch_once
????0x10c557fcf?<+598>:?jmp????0x10c557f25???????????????;?<+428>
????0x10c557fd4?<+603>:?callq??0x10c775102???????????????;?symbol?stub?for:?__stack_chk_fail

翻譯成偽代碼如下:

typedef?struct?{
????Class?originalClass;????????????????//?offset?0x0
????Class?KVOClass;?????????????????????//?offset?0x8
????CFMutableSetRef?mset;???????????????//?offset?0x10
????CFMutableDictionaryRef?mdict;???????//?offset?0x18
????pthread_mutex_t?*lock;??????????????//?offset?0x20
????void?*sth1;?????????????????????????//?offset?0x28
????void?*sth2;?????????????????????????//?offset?0x30
????void?*sth3;?????????????????????????//?offset?0x38
????void?*sth4;?????????????????????????//?offset?0x40
????void?*sth5;?????????????????????????//?offset?0x48
????void?*sth6;?????????????????????????//?offset?0x50
????void?*sth7;?????????????????????????//?offset?0x58
????bool?flag;??????????????????????????//?offset?0x60
}?SDTestKVOClassIndexedIvars;
Class?_NSKVONotifyingCreateInfoWithOriginalClass(Class?originalClass)?{
????const?char?*clsName?=?class_getName(originalClass);
????size_t?len?=?strlen(clsName);
????len?+=?0x10;
????char?*newClsName?=?malloc(len);
????const?char?*prefix?=?"NSKVONotifying_";
????__strlcpy_chk(newClsName,?prefix,?len);
????__strlcat_chk(newClsName,?clsName,?len,?-1);
????Class?newCls?=?objc_allocateClassPair(originalClass,?newClsName,?0x68);
????if?(newCls)?{
????????objc_registerClassPair(newCls);
????????SDTestKVOClassIndexedIvars?*indexedIvars?=?object_getIndexedIvars(newCls);
????????indexedIvars->originalClass?=?originalClass;
????????indexedIvars->KVOClass?=?newCls;
????????CFMutableSetRef?mset?=?CFSetCreateMutable(nil,?0,?kCFCopyStringSetCallBacks);
????????indexedIvars->mset?=?mset;
????????CFMutableDictionaryRef?mdict?=?CFDictionaryCreateMutable(nil,?0,?nil,?kCFTypeDictionaryValueCallBacks);
????????indexedIvars->mdict?=?mdict;
????????pthread_mutex_init(indexedIvars->lock);
????????static?dispatch_once_t?onceToken;
????????dispatch_once(&onceToken,?^{
????????????bool?flag?=?true;
????????????IMP?willChangeValueForKeyImp?=?class_getMethodImplementation(indexedIvars->originalClass,?@selector(willChangeValueForKey:));
????????????IMP?didChangeValueForKeyImp?=?class_getMethodImplementation(indexedIvars->originalClass,?@selector(didChangeValueForKey:));
????????????if?(willChangeValueForKeyImp?==?_NSKVONotifyingCreateInfoWithOriginalClass.NSObjectWillChange?&&?didChangeValueForKeyImp?==?_NSKVONotifyingCreateInfoWithOriginalClass.NSObjectDidChange)?{
????????????????flag?=?false;
????????????}
????????????indexedIvars->flag?=?flag;
????????????NSKVONotifyingSetMethodImplementation(indexedIvars,?@selector(_isKVOA),?NSKVOIsAutonotifying,?nil)
????????????NSKVONotifyingSetMethodImplementation(indexedIvars,?@selector(dealloc),?NSKVODeallocate,?nil)
????????????NSKVONotifyingSetMethodImplementation(indexedIvars,?@selector(class),?NSKVOClass,?nil)
????????});
????}?else?{
????????//?錯(cuò)誤處理過程省略......
????????return?nil
????}
????return?newCls;
}

通過_NSKVONotifyingCreateInfoWithOriginalClass 的這段偽代碼你會(huì)發(fā)現(xiàn)我們之前頻繁提到 indexedIvars 原來(lái)就是在這里初始化生成的。

objc_allocateClassPair 在 runtime.h 中的聲明為 Class _Nullable objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes) ,蘋果對(duì) extraBytes 參數(shù)的解釋為“The number of bytes to allocate for indexed ivars at the end of the class and metaclass objects.”

這就是說當(dāng)我們?cè)谕ㄟ^ objc_allocateClassPair 來(lái)生成一個(gè)新的類時(shí)可以通過指定 extraBytes 來(lái)為此類開辟額外的空間用于存儲(chǔ)一些數(shù)據(jù)。系統(tǒng)在生成 KVO 類時(shí)會(huì)額外分配 0x68 字節(jié)的空間,其具體內(nèi)存布局和用途我用一個(gè)結(jié)構(gòu)體描述如下:

typedef?struct?{
???Class?originalClass;????????????????//?offset?0x0
???Class?KVOClass;?????????????????????//?offset?0x8
???CFMutableSetRef?mset;???????????????//?offset?0x10
???CFMutableDictionaryRef?mdict;???????//?offset?0x18
???pthread_mutex_t?*lock;??????????????//?offset?0x20
???void?*sth1;?????????????????????????//?offset?0x28
???void?*sth2;?????????????????????????//?offset?0x30
???void?*sth3;?????????????????????????//?offset?0x38
???void?*sth4;?????????????????????????//?offset?0x40
???void?*sth5;?????????????????????????//?offset?0x48
???void?*sth6;?????????????????????????//?offset?0x50
???void?*sth7;?????????????????????????//?offset?0x58
???bool?flag;??????????????????????????//?offset?0x60
}?SDTestKVOClassIndexedIvars;

3. 如何解決 custom-KVO 導(dǎo)致的 native-KVO Crash

讀到這里相信你對(duì) KVO 實(shí)現(xiàn)細(xì)節(jié)有了大致的了解,然后我們?cè)倩氐阶畛醯膯栴},為什么“先調(diào)用 native-KVO 再調(diào)用 custom-KVO,custom-KVO 運(yùn)行正常,native-KVO 會(huì) crash”呢?我們還以上面提到過的 Test 類為例說明一下:

首先用 Test 類實(shí)例化了一個(gè)實(shí)例 test,然后對(duì) test 的 num 屬性進(jìn)行 native-KVO 操作,這時(shí) test 的 isa 指向了 NSKVONotifying_Test 類。

然后我們?cè)賹?duì) test 進(jìn)行 custom-KVO 操作,這時(shí)我們的 custom-KVO 會(huì)基于 NSKVONotifying_Test 類再生成一個(gè)新的子類 SD_NSKVONotifying_Test_abcd,此時(shí)問題就來(lái)了,如果我們沒有仿照 native-KVO 的做法額外分配 0x68 字節(jié)的空間用于存儲(chǔ) KVO 關(guān)鍵信息,那么當(dāng)我們向 test 發(fā)送 setNum:消息然后 setNum:方法調(diào)用 super 實(shí)現(xiàn)走到了 KVO 的_NSSetIntValueAndNotify 方法時(shí)還按照 SDTestKVOClassIndexedIvars *indexedIvars = object_getIndexedIvars(cls)方式來(lái)獲取 KVO 信息并嘗試獲取從中獲取數(shù)據(jù)時(shí)發(fā)生異常導(dǎo)致 crash。

找到問題的根源之后我們就可以見招拆招,我們可以仿照 native-KVO 的做法在生成 SD_NSKVONotifying_Test_abcd 也額外分配 0x68 自己的空間,然后當(dāng)要進(jìn)行 custom-KVO 操作時(shí)將 NSKVONotifying_Test 的 indexedIvars 拷貝一份到 SD_NSKVONotifying_Test_abcd 即可,代碼實(shí)現(xiàn)如下:

一般情況下在 native-KVO 的基礎(chǔ)上再做 custom-KVO 的話拷貝完 native-KVO 類的 indexedIvars 到 custom-KVO 類上就可以了,而我們的 SDMagicHook 只做到這些還不夠,因?yàn)?SDMagicHook 在生成的新類上以消息轉(zhuǎn)發(fā)的形式來(lái)調(diào)度方法,這樣一來(lái)問題瞬間就變得更為復(fù)雜。舉例說明如下:

  • 由于用到消息轉(zhuǎn)發(fā),我們會(huì)將 SD_NSKVONotifying_Test_abcd 的setNum:對(duì)應(yīng)的實(shí)現(xiàn)指向_objc_msgForward,然后生成一個(gè)新的 SEL__sd_B_abcd_setNum:來(lái)指向其子類的原生實(shí)現(xiàn),在我們這個(gè)例子中就是 NSKVONotifying_TestsetNum:實(shí)現(xiàn)的即void _NSSetIntValueAndNotify(id obj, SEL sel, int number)函數(shù)。
  • 當(dāng) test 實(shí)例收到setNum:消息時(shí)會(huì)先觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制,然后 SDMagicHook 的消息調(diào)度系統(tǒng)會(huì)最終通過向 test 實(shí)例發(fā)送一個(gè)__sd_B_abcd_setNum:消息來(lái)實(shí)現(xiàn)對(duì)被 Hook 的原生方法的回調(diào),而現(xiàn)在__sd_B_abcd_setNum:對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)正是void _NSSetIntValueAndNotify(id obj, SEL sel, int number),所以__sd_B_abcd_setNum:就會(huì)被作為 sel 參數(shù)傳遞到_NSSetIntValueAndNotify函數(shù)。
  • 然后當(dāng)_NSSetIntValueAndNotify函數(shù)內(nèi)部嘗試從 indexedIvars 拿到原始類 Test 然后從 Test 上查找__sd_B_abcd_setNum:對(duì)應(yīng)的方法并調(diào)用時(shí)由于找不到對(duì)應(yīng)函數(shù)實(shí)現(xiàn)而發(fā)生 crash。為解決這個(gè)問題,我們還需要為 Test 類新增一個(gè)__sd_B_abcd_setNum:方法并將其實(shí)現(xiàn)指向setNum:的實(shí)現(xiàn),代碼如下:

至此,“先調(diào)用 native-KVO 再調(diào)用 custom-KVO,custom-KVO 運(yùn)行正常,native-KVO 會(huì) crash”這個(gè)問題就可以順利解決了。

4. 如何解決 native-KVO 導(dǎo)致 custom-KVO 失效的問題

目前還剩下一個(gè)問題“先調(diào)用 native-KVO 再調(diào)用 custom-KVO 再調(diào)用 native-KVO,native-KVO 運(yùn)行正常,custom-KVO 失效,無(wú) crash”。

為什么會(huì)出現(xiàn)這個(gè)問題呢?這次我們依然以 Test 類為例,首先用 Test 類實(shí)例化了一個(gè)實(shí)例 test,然后對(duì) test 的 num 屬性進(jìn)行 native-KVO 操作,這時(shí) test 的 isa 指向了 NSKVONotifying_Test 類。

然后我們?cè)賹?duì) test 進(jìn)行 custom-KVO 操作,這時(shí)我們的 custom-KVO 會(huì)基于 NSKVONotifying_Test 類再生成一個(gè)新的子類 SD_NSKVONotifying_Test_abcd,這時(shí)如果再對(duì) test 的 num 屬性進(jìn)行 native-KVO 操作就會(huì)驚奇地發(fā)現(xiàn) test 的 isa 又重新指向了 NSKVONotifying_Test 類然后 custom-KVO 就全部失效了。

WHY?!!原來(lái) native-KVO 會(huì)持有一個(gè)全局的字典:

_NSKeyValueContainerClassForIsa.NSKeyValueContainerClassPerOriginalClass 以 KVO 操作的原類為 key 和 NSKeyValueContainerClass 實(shí)例為 value 存儲(chǔ) KVO 類信息。

這樣一來(lái),當(dāng)我們?cè)俅螌?duì) test 實(shí)例進(jìn)行 KVO 操作時(shí),native-KVO 就會(huì)以 Test 類為 key 從 NSKeyValueContainerClassPerOriginalClass 中查找到之前存儲(chǔ)的 NSKeyValueContainerClass 并從中直接獲取 KVO 類 NSKVONotifying_Test 然后調(diào)用 object_setclass 方法設(shè)置到 test 實(shí)例上然后 custom-KVO 就直接失效了。

想要解決這個(gè)問題,我想到了兩種思路:

1.修改 NSKVONotifying_Test 相關(guān) KVO 數(shù)據(jù)

2.hook 攔截系統(tǒng)的 setclass 操作。然后仔細(xì)一想方案 1 是不可取的,因?yàn)?NSKVONotifying_Test 的相關(guān)數(shù)據(jù)是被所有 Test 類的實(shí)例在進(jìn)行 KVO 操作時(shí)共享的,任何改動(dòng)都有可能對(duì) Test 類實(shí)例的 KVO 產(chǎn)生全局影響。

所以,我們就需要借助 FishHook 來(lái) hook 系統(tǒng)的 object_setclass 函數(shù),當(dāng)系統(tǒng)以 NSKVONotifying_Test 為參數(shù)對(duì)一個(gè)實(shí)例進(jìn)行 setclass 操作時(shí),我們檢查如果當(dāng)前的 isa 指針是 SD_NSKVONotifying_Test_abcd 且 SD_NSKVONotifying_Test_abcd 繼承自系統(tǒng)的 NSKVONotifying_Test 時(shí)就跳過此次 setclass 操作。

但是這樣做還不夠,因?yàn)?custom-KVO 采用了特殊的消息轉(zhuǎn)發(fā)機(jī)制來(lái)調(diào)度被 hook 的方法,如果先進(jìn)行 custom-KVO 然后在進(jìn)行 native-KVO 就會(huì)導(dǎo)致被觀察屬性被重復(fù)調(diào)用。

所以,我們?cè)趯?duì)一個(gè)實(shí)例進(jìn)行首次 custom-KVO 操作之前先進(jìn)行 native-KVO,這樣一來(lái)就可以保證我們的 custom-KVO 的方法調(diào)度正常工作了。

代碼如下:

總結(jié)

KVO 的本質(zhì)其實(shí)就是基于被觀察的實(shí)例的 isa 生成一個(gè)新的類并在這個(gè)類的 extra 空間中存放各種和 KVO 操作相關(guān)的關(guān)鍵數(shù)據(jù),然后這個(gè)新的類以一個(gè)中間人的角色借助 extra 空間中存放各種數(shù)據(jù)完成復(fù)雜的方法調(diào)度。

系統(tǒng)的 KVO 實(shí)現(xiàn)比較復(fù)雜,很多函數(shù)的調(diào)用層次也比較深,我們一開始不妨從整個(gè)函數(shù)調(diào)用棧的末端層層向前梳理出主要的操作路徑,在對(duì) KVO 操作有個(gè)大致的了解之后再?gòu)娜值慕嵌日蛉娣治龈鱾€(gè)流程和細(xì)節(jié)。我們正是借助這種方式實(shí)現(xiàn)了對(duì) KVO 的快速了解和認(rèn)識(shí)。

至此,一個(gè)良好兼容 native-KVO 的 custom-KVO 就全部完成了。回頭來(lái)看,這個(gè)解決方案其實(shí)還是過于 tricky 了,不過這也只能是在 iOS 系統(tǒng)的各種限制下的無(wú)奈的選擇了。我們不提倡隨意使用類似的 tricky 操作,更多是想要通過這個(gè)例子向大家介紹一下 KVO 的本質(zhì)以及我們分析和解決問題的思路。

如果各位讀者可以從中汲取一些靈感,那么這篇文章“倒也算是不負(fù)恩澤”,倘若大家可以將這篇文章介紹到的思路和方法用于處理自己開發(fā)中的遇到的各種疑難雜癥“那便真真是極好的了”!更多關(guān)于iOS開發(fā)KVO細(xì)節(jié)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論