<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一次遍歷導(dǎo)致的崩潰

          共 942字,需瀏覽 2分鐘

           ·

          2022-03-05 15:39

          ????關(guān)注后回復(fù)?“進(jìn)群”?,拉你進(jìn)程序員交流群????


          作者丨搜狐新聞-Augus

          來源丨搜狐技術(shù)產(chǎn)品(ID:sohu-tech

          ?

          本文字?jǐn)?shù):10920

          預(yù)計(jì)閱讀時(shí)間:28分鐘

          一次遍歷導(dǎo)致的崩潰

          題記:用最通俗的語言,描述最難懂的技術(shù)

          ?

          本文是作者在對項(xiàng)目進(jìn)行調(diào)試某靜態(tài)庫的功能進(jìn)行單元測試發(fā)現(xiàn)的問題的記錄,如果有哪些論述模糊或者不準(zhǔn)確,請聯(lián)系[email protected]

          目錄表

          • 故事背景
          • 問題定位
          • 解決方案
          • 原理
            • copy是什么
            • copy如何實(shí)現(xiàn)
            • copy底層實(shí)現(xiàn)
          • 延展之深淺拷貝
            • 集合類對象
            • 非集合類對象
          • 參考文檔
          • 結(jié)束語

          故事背景

          環(huán)境及場景:

          編譯環(huán)境Xcode 12.5.1

          2021年8月的某一天,Augus正在調(diào)試項(xiàng)目需求A,因?yàn)锳要求需要接入一個(gè)SDK進(jìn)行實(shí)現(xiàn)某些采集功能

          操作流程

          • 在程序啟動的最開始地方,初始化SDK,并分配內(nèi)存空間

          • 在某次的啟動中就出現(xiàn)了以下錯(cuò)誤

            Trapped uncaught exception 'NSGenericException', reason: '*** Collection <__NSSetM: 0x2829f9740> was mutated while being enumerated.'

          初步猜測

          開始的時(shí)候,我先排除自己代碼的原因(畢竟代碼自己寫的,還是求穩(wěn)一些),因?yàn)檎{(diào)試模式下沒有開全局?jǐn)帱c(diǎn),所以本次的崩潰就這么被錯(cuò)失機(jī)會定位

          為了下一次的復(fù)現(xiàn)

          • 首先進(jìn)行了NSMutableSet某些方法的hook
          • 開啟全局?jǐn)帱c(diǎn)

          最后定位

          項(xiàng)目中引入SDK導(dǎo)致的崩潰

          問題定位

          問題原因

          被引入第三方的SDK在某個(gè)邏輯中使用的NSMutableSet遍歷中對原可變集合進(jìn)行同時(shí)讀寫的操作

          復(fù)現(xiàn)同樣崩潰的場景,Let's do it

          ?NSMutableSet?*mutableSet?=?[NSMutableSet?setWithObjects:@"1",@"2",@"3",?nil];
          ????
          ?for?(NSString?*item?in?mutableSet)?{
          ?????if?([item?integerValue]?3)?{
          ?????????[mutableSet?removeObject:item];
          ??????}
          ??}

          控制臺日志

          很好,現(xiàn)在已經(jīng)知道了問題的原因,那么接下來解決問題就很容易了,讓我們繼續(xù)

          解決方案

          問題原因總結(jié)

          不能在一個(gè)可變集合,包括NSMutableArray,NSMutableDictionary等類似對象遍歷的同時(shí)又對該對象進(jìn)行添加或者移除操作

          解決問題

          把遍歷中的對象進(jìn)行一次copy操作


          其實(shí)其中的道理很簡單,我現(xiàn)在簡而概括

          你在內(nèi)存中已經(jīng)初始化一塊區(qū)域,而且分配了地址,那么系統(tǒng)在這次的遍歷中會把這次遍歷包裝成原子操作,因?yàn)闀赡軙L問壞內(nèi)存或者越界的問題,當(dāng)然這也是出于安全原因,不同的系統(tǒng)下的實(shí)現(xiàn)方式不同,但是底層的原理是一致的,都是為了保護(hù)對象在操作過程中不受可變因素的更新

          那問題來了

          • copy是什么?
          • copy在底層如何實(shí)現(xiàn)?
          • copy有哪些需要注意的?
          ?

          帶著這些疑問,我們繼續(xù)下面的閱讀,相信你讀完肯定會柳暗花明又一村...

          原理

          copy是什么

          copyObjective-C編程語言下的屬性修飾關(guān)鍵詞,比如修飾Block orNS*開頭的對象

          copy如何實(shí)現(xiàn)

          對需要實(shí)現(xiàn)的類遵守NSCopying協(xié)議

          實(shí)現(xiàn)NSCopying協(xié)議,該協(xié)議只有一個(gè)方法

          -?(id)copyWithZone:(NSZone?*)zone;

          舉例說明,首先我們新建一個(gè)Perosn類進(jìn)行說明,下面是示例代碼

          //?The?person.h?file
          #import?

          NS_ASSUME_NONNULL_BEGIN

          @interface?Person?:?NSObject<NSCopying>

          -?(instancetype)initWithName:(NSString?*)name;

          @property(nonatomic,?copy)?NSString?*name;


          ///?To?update?internal?mutabl?set?for?adding?a?person
          ///?@param?person?A?instance?of?person
          -?(void)addPerson:(Person?*)person;


          ///?To?update?internal?mutbable?set?for?removing?a?person
          ///?@param?person?A?instance?of?person
          -?(void)removePerson:(Person?*)person;


          @end

          NS_ASSUME_NONNULL_END
          ??
          //?The?person.m?file
          #import?"Person.h"

          @interface?Person?()

          @property(nonatomic,?strong)?NSMutableSet?*friends;

          @end

          @implementation?Person

          #pragma?mark?-?Initalizaiton?Methods

          -?(instancetype)initWithName:(NSString?*)name?{
          ????self?=?[super?init];
          ????if?(!self)?{
          ????????return?nil;
          ????}
          ???if(!name?||?name.length?1)?{
          ???????name?=?@"Augus";
          ????}
          ????_name?=?name;
          ????
          ????//?Warn:?Do?not?self.persons?way?to?init.?But?do?u?know?reason?
          ????_friends?=?[NSMutableSet?set];
          ????return?self;
          }


          #pragma?mark?-?Private?Methods

          -?(void)addPerson:(Person?*)person?{
          ????
          ????//?Check?param?safe
          ????if?(!person)?{
          ????????return;
          ????}
          ????
          ????[self.friends?addObject:person];
          ????
          ????
          }

          -?(void)removePerson:(Person?*)person?{
          ????
          ????if?(!person)?{
          ????????return;
          ????}
          ????
          ????[self.friends?removeObject:person];
          }


          #pragma?mark?-?Copy?Methods

          -?(id)copyWithZone:(NSZone?*)zone?{
          ????
          ????//?need?copy?object
          ????Person?*copy?=?[[Person?allocWithZone:zone]?initWithName:_name];
          ????
          ????return?copy;
          }

          -?(id)deepCopy?{
          ????Person?*copy?=?[[[self?class]?alloc]?initWithName:_name];
          ????copy->_persons?=?[[NSMutableSet?alloc]?initWithSet:_friends?copyItems:YES];
          ????return?copy;
          }


          #pragma?mark?-?Lazy?Load

          -?(NSMutableSet?*)friends?{
          ????if?(!_friends)?{
          ????????_friends?=?[NSMutableSet?set];
          ????}
          ????return?_friends;
          }

          @end

          類的功能很簡單,初始化的時(shí)候需要外層傳入name進(jìn)行初始化,如果name非法則進(jìn)行默認(rèn)值的處理

          • 類內(nèi)部維護(hù)了一個(gè)可變集合用來存放好友
          • 外部提供了新增和移除的兩個(gè)方法
          • - (id)copyWithZone:(NSZone *)zone;中的實(shí)現(xiàn)就是簡單的一個(gè)copy功能
          • deepCopy是對可變集合的深層復(fù)制,至于原因,我們會在延展中舉例說明,這里先擱置

          copy底層實(shí)現(xiàn)

          之前的文檔中說過,想要看底層的實(shí)現(xiàn)那就用clang -rewrite-objc main.m看源碼

          為了方便測試和查看,我們新建一個(gè)TestCopy的類繼承NSObject,然后在TestCopy.m中只加如下代碼

          #import?"TestCopy.h"

          @interface?TestCopy?()

          @property(nonatomic,?copy)?NSString?*augusCopy;

          @end

          @implementation?TestCopy

          @end

          然后在終端執(zhí)行$ clang -rewrite-objc TestCopy.m命令

          接下來我們進(jìn)行源碼分析

          //?augusCopy's?getter?function
          static?NSString?*?_I_TestCopy_augusCopy(TestCopy?*?self,?SEL?_cmd)?{?return?(*(NSString?**)((char?*)self?+?OBJC_IVAR_$_TestCopy$_augusCopy));?}

          //?augusCopy's?setter?function
          static?void?_I_TestCopy_setAugusCopy_(TestCopy?*?self,?SEL?_cmd,?NSString?*augusCopy)?{?objc_setProperty?(self,?_cmd,?__OFFSETOFIVAR__(struct?TestCopy,?_augusCopy),?(id)augusCopy,?0,?1);?}

          總結(jié):copygetter是根據(jù)地址偏移找到對應(yīng)的實(shí)例變量進(jìn)行返回,那么objc_setProperty又是怎么實(shí)現(xiàn)的呢?

          objc_setProperty.cpp中沒有找到,在[Apple源碼](鏈接附文后)中找到了答案,我們來看下

          //?self:?The?current?instance
          //?_cmd:?The?setter's?function?name
          //?offset:?The?offset?for?self?that?find?the?instance?property
          //?newValue:?The?new?value?that?outer?input
          //?atomic:?Whether?atomic?or?nonatomic,it?is?nonatomic?here
          //?shouldCopy:?Whether?should?copy?or?not
          void?
          objc_setProperty(id?self,?SEL?_cmd,?ptrdiff_t?offset,?id?newValue,?
          ?????????????????BOOL?atomic,?signed?char?shouldCopy)
          ?
          {
          ????objc_setProperty_non_gc(self,?_cmd,?offset,?newValue,?atomic,?shouldCopy);
          }

          void?objc_setProperty_non_gc(id?self,?SEL?_cmd,?ptrdiff_t?offset,?id?newValue,?BOOL?atomic,?signed?char?shouldCopy)?
          {
          ????bool?copy?=?(shouldCopy?&&?shouldCopy?!=?MUTABLE_COPY);
          ????bool?mutableCopy?=?(shouldCopy?==?MUTABLE_COPY);
          ????reallySetProperty(self,?_cmd,?newValue,?offset,?atomic,?copy,?mutableCopy);
          }

          看到內(nèi)部又調(diào)用了objc_setProperty_non_gc方法,這里主要看下這個(gè)方法內(nèi)部的實(shí)現(xiàn),前五個(gè)參數(shù)和開始的傳入一致,最后的兩個(gè)參數(shù)是由shouldCopy決定,shouldCopy在這里是0 or 1,我們現(xiàn)考慮當(dāng)前的情況,

          • 如果shouldCopy=0,那么copy=NO,mutableCopy=NO
          • 如果shouldCopy=1,那么copy=YES,mutableCopy=NO

          下面繼續(xù)reallySetProperty的實(shí)現(xiàn)

          static?inline?void?reallySetProperty(id?self,?SEL?_cmd,?id?newValue,?ptrdiff_t?offset,?bool?atomic,?bool?copy,?bool?mutableCopy)
          {
          ????id?oldValue;
          ????id?*slot?=?(id*)?((char*)self?+?offset);

          ????if?(copy)?{
          ????????newValue?=?[newValue?copyWithZone:NULL];
          ????}?else?if?(mutableCopy)?{
          ????????newValue?=?[newValue?mutableCopyWithZone:NULL];
          ????}?else?{
          ????????if?(*slot?==?newValue)?return;
          ????????newValue?=?objc_retain(newValue);
          ????}

          ????if?(!atomic)?{
          ????????oldValue?=?*slot;
          ????????*slot?=?newValue;
          ????}?else?{
          ????????spin_lock_t?*slotlock?=?&PropertyLocks[GOODHASH(slot)];
          ????????_spin_lock(slotlock);
          ????????oldValue?=?*slot;
          ????????*slot?=?newValue;????????
          ????????_spin_unlock(slotlock);
          ????}

          ????objc_release(oldValue);
          }

          基于本例子中的情況,copy=YES,最后還是調(diào)用了newValue = [newValue copyWithZone:NULL];,如果copy=NO and mutableCopy=NO,那么最后會調(diào)用newValue = objc_retain(newValue);

          objc_retain的實(shí)現(xiàn)

          id?objc_retain(id?obj)?{?return?[obj?retain];?}

          總結(jié):用copy修飾的屬性,賦值的時(shí)候,不管本身是可變與不可變,賦值給屬性之后的都是不可變的

          延展之深淺拷貝

          非集合類對象

          在iOS下我們經(jīng)常聽到深拷貝(內(nèi)容拷貝)或者淺拷貝(指針拷貝),對于這些操作,我們將針對集合類對象和非集合類對象進(jìn)行copymutableCopy實(shí)驗(yàn)

          類簇:Class Clusters

          • an architecture that groups a number of private, concrete subclasses under a public, abstract superclass. (一個(gè)在共有的抽象超類下設(shè)置一組私有子類的架構(gòu))

          • Class cluster 是 Apple 對抽象工廠設(shè)計(jì)模式的稱呼。使用抽象類初始化返回一個(gè)具體的子類的模式的好處就是讓調(diào)用者只需要知道抽象類開放出來的API的作用,而不需要知道子類的背后復(fù)雜的邏輯。驗(yàn)證結(jié)論過程的類簇對應(yīng)關(guān)系請看這篇 [Class Clusters 文檔](鏈接附文后)。

          NSString

          NSString?*str?=?@"augusStr";
          NSString?*copyAugus?=?[str?copy];
          NSString?*mutableCopyAugus?=?[str?mutableCopy];
          ????
          NSLog(@"str:(%@<%p>:?%p):?%@",[str?class],&str,str,str);
          NSLog(@"copyAugus?str:(%@<%p>:?%p):?%@",[copyAugus?class],©Augus,copyAugus,copyAugus);
          NSLog(@"mutableCopyAugus?str:(%@<%p>:?%p):?%@",[mutableCopyAugus?class],&mutableCopyAugus,mutableCopyAugus,mutableCopyAugus);

          //?控制臺輸出
          2021-09-03?14:51:49.263571+0800?TestBlock[4573:178396]?augus?str(__NSCFConstantString<0x7ffee30a1008>:?0x10cb63198):?augusStr
          2021-09-03?14:51:49.263697+0800?TestBlock[4573:178396]?copyAugus?str(__NSCFConstantString<0x7ffee30a1000>:?0x10cb63198):?augusStr
          2021-09-03?14:51:49.263808+0800?TestBlock[4573:178396]?mutableCopyAugus?str(__NSCFString<0x7ffee30a0ff8>:?0x6000036bcfc0):?augusStr
          ?

          __NSCFConstantString是字符串常量類,可看作NSString__NSCFString是字符串類,可看作NSMutableString

          結(jié)論:strcopyAugus打印出來的內(nèi)存地址是一樣的,都是0x10cb63198且類名相同都是__NSCFConstantString,表明都是淺拷貝,都是NSString;變量mutableCopyAugus打印出來的內(nèi)存地址和類名都不一致,所以是生成了新的對象

          類名操作新對象拷貝類型元素拷貝新類名
          NSStringcopyNO淺拷貝NONSString

          mutableCopyYES深拷貝NONSMutableString

          NSMutableString

          NSMutableString?*str?=?[NSMutableString?stringWithString:@"augusMutableStr"];
          NSMutableString?*copyStr?=?[str?copy];
          NSMutableString?*mutableCopyStr?=?[str?mutableCopy];


          NSLog(@"str:(%@<%p>:?%p):?%@",[str?class],&str,str,str);
          NSLog(@"copyStr:?(%@<%p>:?%p):?%@",[copyStr?class],©Str,copyStr,copyStr);
          NSLog(@"mutableCopyStr:?(%@<%p>:?%p):?%@",[mutableCopyStr?class],&mutableCopyStr,mutableCopyStr,mutableCopyStr);

          //?控制臺輸出
          2021-09-03?15:31:56.105642+0800?TestBlock[4778:198224]?str:(__NSCFString<0x7ffeeaa34008>:?0x600001a85fe0):?augusMutableStr
          2021-09-03?15:31:56.105804+0800?TestBlock[4778:198224]?copyStr:?(__NSCFString<0x7ffeeaa34000>:?0x600001a86400):?augusMutableStr
          2021-09-03?15:31:56.105901+0800?TestBlock[4778:198224]?mutableCopyStr:?(__NSCFString<0x7ffeeaa33ff8>:?0x600001a86070):?augusMutableStr

          結(jié)論:strcopyStrmutableCopyStr打印出來的內(nèi)存地址都不一樣的,但是生成的類簇都是__NSCFString,也就是NSMutableString

          類名操作新對象拷貝類型元素拷貝新類名
          NSMutableStringcopyYES深拷貝NONSMutableString

          mutableCopyYES深拷貝NONSMutableString

          集合類對象

          ?

          因?yàn)楸疚膶?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">NSMutableSet展開討論,所以只對該類進(jìn)行測試,其余的NSArray&NSMutableArrayNSDictionary&NSMutableDictionary本質(zhì)是一樣的,請小伙伴自行參考測試就行

          NSSet

          Person?*p1?=?[[Person?alloc]?init];
          Person?*p2?=?[[Person?alloc]?init];
          Person?*p3?=?[[Person?alloc]?init];

          NSSet?*set?=?[[NSSet?alloc]?initWithArray:@[p1,p2,p3]];
          NSSet?*copySet?=?[set?copy];
          NSSet?*mutableCopySet?=?[set?mutableCopy];

          NSLog(@"set:(%@<%p>:?%p):?%@",[set?class],&set,set,set);
          NSLog(@"copySet:?(%@<%p>:?%p):?%@",[copySet?class],©Set,copySet,copySet);
          NSLog(@"mutableCopySet:?(%@<%p>:?%p):?%@",[mutableCopySet?class],&mutableCopySet,mutableCopySet,mutableCopySet);
          ????
          //?控制臺輸出
          2021-09-03?16:11:36.590338+0800?TestBlock[4938:219837]?set:(__NSSetI<0x7ffeef3f7fd0>:?0x6000007322b0):?{(
          ????0x600000931e00>,
          ????0x600000931e20>,
          ????0x600000932000>
          )}
          2021-09-03?16:11:36.590479+0800?TestBlock[4938:219837]?copySet:?(__NSSetI<0x7ffeef3f7fc8>:?0x6000007322b0):?{(
          ????0x600000931e00>,
          ????0x600000931e20>,
          ????0x600000932000>
          )}
          2021-09-03?16:11:36.590614+0800?TestBlock[4938:219837]?mutableCopySet:?(__NSSetM<0x7ffeef3f7fc0>:?0x600000931fa0):?{(
          ????0x600000931e00>,
          ????0x600000932000>,
          ????0x600000931e20>
          )}
          ?

          __NSSetI是不可變?nèi)ブ責(zé)o序集合的子類,即NSSet,__NSSetM是可變?nèi)ブ責(zé)o序集合的子類,即NSMutableSet

          結(jié)論:setcopySet打印出來的內(nèi)存地址是一致的0x6000007322b0,類簇都是__NSSetI說明是淺拷貝,沒有生成新對象,也都屬于類 NSSetmutableCopySet的內(nèi)存地址和類簇都不同,所以是深拷貝,生成了新的對象,屬于類NSMutablSet;集合里面的元素地址都是一樣的

          類名操作新對象拷貝類型元素拷貝新類名
          NSSetcopyNO淺拷貝NONSSet

          mutableCopyYES深拷貝NONSMutablSet

          NSMutableSet

          NSMutableSet?*set?=?[[NSMutableSet?alloc]?initWithArray:@[p1,p2,p3]];
          NSMutableSet?*copySet?=?[set?copy];
          NSMutableSet?*mutableCopySet?=?[set?mutableCopy];

          NSLog(@"set:(%@<%p>:?%p):?%@",[set?class],&set,set,set);
          NSLog(@"copySet:?(%@<%p>:?%p):?%@",[copySet?class],©Set,copySet,copySet);
          NSLog(@"mutableCopySet:?(%@<%p>:?%p):?%@",[mutableCopySet?class],&mutableCopySet,mutableCopySet,mutableCopySet);
          ?
          ?//?控制臺輸出
          2021-09-03?16:33:35.573557+0800?TestBlock[5043:232294]?set:(__NSSetM<0x7ffeefb78fd0>:?0x600002b99640):?{(
          ????0x600002b99620>,
          ????0x600002b99600>,
          ????0x600002b995e0>
          )}
          2021-09-03?16:33:35.573686+0800?TestBlock[5043:232294]?copySet:?(__NSSetI<0x7ffeefb78fc8>:?0x6000025e54a0):?{(
          ????0x600002b99620>,
          ????0x600002b99600>,
          ????0x600002b995e0>
          )}
          2021-09-03?16:33:35.573778+0800?TestBlock[5043:232294]?mutableCopySet:?(__NSSetM<0x7ffeefb78fc0>:?0x600002b99680):?{(
          ????0x600002b99620>,
          ????0x600002b99600>,
          ????0x600002b995e0>
          )}

          結(jié)論:setcopySetmutableCopySet的內(nèi)存地址都不一樣,說明操作都是深拷貝;集合里面的元素地址都是一樣的

          類名操作新對象拷貝類型元素拷貝新類名
          NSMutableSetcopyYES深拷貝NONSSet

          mutableCopyYES深拷貝NONSMutablSet

          結(jié)論分析

          • NSMutable*開頭的類不要用copy屬性去修飾,因?yàn)槊看钨x值操作拷貝出來的都是不可變集合類
          • 集合類的copymutableCopy操作,對象里面的元素不會發(fā)生拷貝,只會對容器層面拷貝,也稱之為單層深拷貝

          參考文檔

          • 文檔0:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html#//apple_ref/doc/uid/TP40010162-SW8
          • 文檔1:https://gist.github.com/Catfish-Man/bc4a9987d4d7219043afdf8ee536beb2
          • 文檔2:https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-accessors.mm.auto.html
          • Apple源碼:https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-accessors.mm.auto.html
          • Class Clusters 文檔:https://gist.github.com/Catfish-Man/bc4a9987d4d7219043afdf8ee536beb2

          結(jié)束語

          一次崩潰定位,一次源碼之旅,一系列拷貝操作,基本可以把文中提到的問題說清楚;遇到問題不要怕刨根問底,因?yàn)閱柕椎谋M頭就是無盡的光明

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?面試題?資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 53
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99爱精品视频在线观看 | 影音先锋成人女优资源 | 国产美穴| 日本操美女 | japαnese老熟女老熟妇 |