iOS上Delegate的懸垂指針問題

文章有點長,寫的過程頗有收穫,但讀的過程不必定有收穫,慎入

【摘要】
 
懸垂指針(dangling pointer)引發的crash問題,是咱們在iOS開發過程中常常會遇到的。其中由delegate引起的此類問題更是常見。本文由一個UIActionSheet引起的delegate懸垂指針問題開始,逐步思索和嘗試解決這類問題的幾種方案並進行比較。
 
【正文】
 
UIActionSheet是一個經常使用的iOS系統控件,用法很簡單,實現UIActionDelegate協議方法,而後經過showInView:等方法彈出。咱們來看一段代碼:(如無特殊說明,本文中的代碼均在ARC條件下書寫)
 
 - (void)popUpActionSheet
{
    UIActionSheet* sheet = [[UIActionSheet alloc] initWithTitle:nil 
                                                       delegate:self 
                                              cancelButtonTitle:NSLocalizedString(@"str_cancel", @"")
                                         destructiveButtonTitle:NSLocalizedString(@"str_delete", @"")
                                               otherButtonTitles:nil];
    [sheet showInView:self.view];
}

 

 
像這樣用一個局部變量彈出actionsheet的代碼喜聞樂見。
 
那麼這樣作是否有問題呢?
 
來看某項目中的一個bug:頁面X,按住區域Y點擊按鈕B,再點擊按鈕C(關閉),鬆開後頁面X退出,但actionsheet A彈出,點擊其中的按鈕,程序crash。
 
從描述中不難看出問題所在:這是個dangling pointer的問題。點擊按鈕B,本應彈出actionsheet A,但因爲某些特殊操做(具體緣由各不相同,這裏是按住區域Y),這個actionsheet的彈出被延遲了,當它彈出的時候,其delegate(一般是一個UIViewController或者一個UIView,這裏是頁面的ViewController X)已經被銷燬了,因而delegate成了一個dangling pointer,點擊按鈕,向delegate發送消息的時候,就出現了crash。
 
爲了防止retain cycle,iOS中大部分的delegate都是不增長對象(X)的引用計數的(弱引用),於是容易出現dangling pointer的問題。對於此類問題,解決方向一般有兩個:
其一,在向delegate發送消息以前,判斷delegate是否仍然有效;
其二,使對象X在dealloc的時候,主動設置全部指向X的delegate爲nil。
 
對於方向一,看上去很美,若是可以在發消息前判斷一個指針是不是dangling pointer,那麼咱們就有了最後一道防線,今後再不會發生此類crash問題。可是,當dangling pointer真出現的時候,咱們更應反思一下代碼設計上是否出現了不合理的地方,而不是簡單以這種方式捕獲並丟棄。
 
相比之下,方向二「銷燬時置空」這個方案顯得更治本,亦是一種良好的編程習慣。推而廣之,不侷限於delegate,全部弱引用指針均可以如此處理。
 
這正是ARC中引入的weak指針的概念,它會在所指對象dealloc的時候自動置爲nil。也就是說,只要全部delegate都是weak類型的,此類dangling pointer問題就不復存在了。本文也能夠到此結束了。
 
可是,現實老是殘酷的。首先,weak指針只有在iOS 5.0及以上的版本中的ARC條件下才能使用,而目前不少項目依然須要支持iOS4.3。固然,隨着iOS7的發佈,這種狀況會有所好轉。但即便全部的用戶都是5.0+,問題仍然沒有解決。爲什麼?
 
咱們自定義的delegate,能夠所有采用weak類型。可是系統控件是什麼狀況呢?好比UIActionSheet,看看iOS7.0版本SDK下的UIActionSheet.h:
 
 @property(nonatomic,assign) id delegate;    // weak reference

 

 
即便是7.0,這些系統控件的delegate仍然不是weak,而是assign,大約是爲了兼容非ARC環境的緣由吧。 也就是說,weak指針並不能解決系統控件delegate的dangling pointer問題。這下腫麼辦?
 
花開兩朵,各表一支。
 
咱們先回過頭來看另一個問題:爲何actionsheet會出現這個dangling pointer的問題?
 
直接緣由是做爲delegate的ViewController X被銷燬了,而此時actionsheet A自己還在顯示。但這個A明明是show在self.view上的,爲何self.view都沒了,它還會存在呢?
 
咱們來看下面一段代碼:
 
 -(void)viewDidAppear:(BOOL)animated
{
    UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"abcde" delegate:self cancelButtonTitle:@"cancel" destructiveButtonTitle:nil otherButtonTitles:nil];

    NSLog(@"application windows:%@", [UIApplication sharedApplication].windows);

    [sheet showInView:self.view];

    NSLog(@"self.window:%@", self.view.window);
    NSLog(@"sheet.window:%@", sheet.window);

    NSLog(@"application windows:%@", [UIApplication sharedApplication].windows);
}

 


主要運行結果(爲iphone上的,狀況在iPad上略有不一樣)以下:

application windows:(
    UIWindow ...
) // actionsheet彈出前,只有1個UIWindow

self.window: UIWindow ...
sheet.window: _UIAlertOverlayWindow ...

application windows:(
    UIWindow ...
    UITextEffectsWindow ...
    _UIAlertOverlayWindow ...
 ) // actionsheet彈出後,有3個UIWindow

 

原來iOS的application並不是只有一個window,actionsheet是彈在另一個內部的window上的(iPad上狀況不一樣,只有一個window,actionsheet是在showInView的superview的一個叫UIDimmingView的subview上),與showInView:方法中指定的view並無持有的關係,因此能在徹底不依賴於後者生命週期的狀況下存在,因而出現dangling pointer delegate一點也不奇怪了。
 
那麼知道了前因後果之後,咱們能夠開始着手解決文章開始時的那個bug了。按照「在一個對象X dealloc的時候,設置全部指向X的delegate爲空」這個「方向二」的中心思想,weak指針是派不上用場了,咱們只能另想辦法。
 
經過分析,咱們知道落實這個「中心思想」的要點就是:
 
怎樣在X dealloc的時候獲取到全部delegate指向X的actionsheet A?
  
因爲文章開始時喜聞樂見的代碼中,actionsheet是局部變量彈出的,在ViewController X dealloc的時候,咱們已經訪問不到那個局部變量,怎麼辦呢?
 
思路1:

改用一個實例變量V保存actionsheet。在X的dealloc方法裏置V.delegate = nil。
 
這毫無疑問是最容易想到的方法,無須贅述。只是要注意一個問題:actionsheet是能夠同時(或相繼)彈出多個的(咱們會看到背景的黑色蒙板隨着彈出actionsheet的數量而疊加,愈來愈深。)這樣一來,咱們要麼改用一個數組來保存actionsheet的指針(們),要麼就要在每彈出一個新的時候,就把舊的處理掉(或者delegate置空,或者乾脆dismiss掉)。
 
這種思路,優勢有二:
          1、思路簡單,代碼添加量少;
          2、若是你是在寫一個iPad app,那反正應付轉屏從新佈局actionsheet也是須要這個實例變量的,一舉多得。

其缺點也有二:
          1、這種方式通用性差,咱們須要針對每個這樣的X都寫一遍這樣的代碼,若是這是一個已經存在的項目,而這個項目裏幾乎全部的actionsheet都是這樣用局部變量彈出的,怎麼辦?咱們須要修改多少代碼?
          2、actionsheet做爲一個系統控件,ViewController多數狀況下只是控制彈出和實現delegate方法,並不作其餘任何操做,這也就是爲何會出現前述喜聞樂見的代碼,由於其餘地方用不着引用這個actionsheet。只爲解決dangling pointer的問題而在類中添加一個實例變量保存指針,甚至要保存一個指針數組,而且這部分代碼還和類自己邏輯的代碼耦合在一塊兒,有潔癖的人看起來總以爲刺眼。
 
理想中解決dangling pointer問題的方法,應該是一個通用的基礎方法,與類的業務邏輯無關,代碼相對獨立。 
 
思路2:

不用實例變量,想辦法在delegate dealloc的時候得到actionsheet的指針。
 
系統的view樹必定是保存了actionsheet的指針的,第一反應是想在actionsheet上打tag,而後利用viewWithTag:方法來獲取。或者,在dealloc的時候遍歷整個view樹來尋找當前存在的actionsheet,這兩種方法本質上是相同的。咱們暫且不討論遍歷view樹的開銷是否值得,只討論方法可行性。剛纔咱們說過,iphone上的actionsheet是從屬於一個內部window的,並不在咱們程序可控的window中,因此上述方法根結點的選取是關鍵。
 
 UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"sheet" delegate:self cancelButtonTitle:@"cancel" destructiveButtonTitle:nil otherButtonTitles:nil];
    [sheet showInView:self.view];

    [sheet setTag:kSheetTag];

    NSLog(@"root(self.view.window):%@", [self.view.window viewWithTag:kSheetTag]);  // null
    NSLog(@"root(internal window):%@", [[UIApplication sharedApplication].windows[2] viewWithTag:kSheetTag]); // actionsheet found!

 


結果情理之中,咱們在當前的window上是遍歷不到這個actionsheet的,須要在以前說的_UIAlertOverlayWindow上遍歷才行。因而咱們能夠先在actionsheet建立時打個tag,而後在X dealloc方法裏這樣寫:(不能應付多個actionsheet彈出的狀況)
 
    
 [[UIApplication sharedApplication].windows enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if (strcmp(class_getName([obj class]),"_UIAlertOverlayWindow") == 0)
        {
            UIActionSheet *theSheet = (UIActionSheet *)[obj viewWithTag:kSheetTag];
            [theSheet setDelegate:nil];
        }
    }];

 

 
也能夠不打tag,直接採用遍歷view樹的方式。(若是是在ipad上,不用使用內部window,直接遍歷本身的self.view.superview的subviews就好了,可自行實驗)

 [[UIApplication sharedApplication].windows enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
         [self traverseView:obj];
    }];
 
// 遍歷view樹
- (void)traverseView:(UIView *)root
{
    [[root subviews] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if ([obj isKindOfClass:[UIActionSheet class]])
        {
            if (((UIActionSheet *)obj).delegate == self)
            {
                ((UIActionSheet *)obj).delegate = nil;
                NSLog(@"enemy spotted!");
            }
        }
        else
        {
            [self traverseView:obj];
        }
    }];
}

 


這樣也解決了問題,其優勢有一:
          1、不用改動類X的業務邏輯部分的代碼,修改範圍縮小到X的dealloc方法中,相對來說便於移植到其餘類中,也能夠經過一些runtime的手段實現自動化。
          2、遍歷的方法,能夠輕易應對彈出多個actionsheet的狀況。
 
其缺點有二:
          1、提起遍歷view樹,開銷問題是確定要考慮的。以某項目爲例,一vc在dealloc的時候,view樹中共有320個view,遍歷尋找UIAcionSheet並置delegate空須要約0.002s,而正常的dealloc方法只須要不到0.0001s,時間提高20倍。實話說,這個開銷並非大到沒法忍受,是否划算視具體狀況而定。
          2、若是想節省這個開銷,那麼就須要利用一些「潛規則」,例如像上面viewWithTag的方法代碼那樣,利用_UIAlertOverlayWindow這個類名來縮小遍歷範圍。潛規則這個東西,若是用,請慎用。它們是毫無文檔保障的,可能下一代iOS,這個實現就被改掉了,那時咱們的代碼就會出問題。譬如經過hack系統imagePicker的方式實現多圖選擇框,算是一個比較常見且「合理」的利用「潛規則」的例子(由於常規用assetslibrary實現的多圖選擇框在iOS5上會有定位權限的提示問題,這是不少產品不肯意接受的事情),可是iOS7中,imagePicker的內部ViewController的名字就被改掉了,原來用這種方式實現多圖選擇框的代碼,就須要跟進修改。
 
思路3:

從思路1和2中咱們能夠獲得這樣的啓發,若是有一個集合,裏面存放了全部delegate指向X的actionsheet A(甚至其它對象實例),那麼,咱們就能在dealloc時遍歷這個集合來置A.delegate = nil。
 
上述這種集合S有以下特徵:
 
一、S可以與一個對象X實現1對1的綁定或對應,並在X dealloc的時候能被訪問到。
二、在合適的時機(好比設置delegate時),可以對S添加或刪除元素
 
咱們先按1和2抽象出一個通用的包含集合S的類結構,取名爲parasite:

 @interface DelegateBaseParasite : NSObject
{
    NSMutableSet *sanctuarySet_; // 集合S
}
 
// 建立並將本身(parasite)綁定(對應)到hostObj X 上
+ (DelegateBaseParasite *)parasitizeIn:(id)hostObj;
 
// 返回已經綁定(對應)到hostObj X上的parasite對象(或nil若未綁定)
+ (DelegateBaseParasite *)getParasiteFromHost:(id)hostObj;

// 添加一個對象object到此parasite的集合S中,當object.delegate = hostObj X的時候
- (void)addToSanctuary:(id)object;
 
// 今後parasite的集合S中移除object,當object.delegate再也不=X的時候
- (void)removeFromSanctuary:(id)object;

// 將全部sanctuary中對象的delegate(此時都指向hostObj)置爲nil
- (void)redemptionAll;
@end

 

大意是:若是每個X都與一個這樣的DelegateBaseParasite P綁定(對應),在設置A.delegate = X的時候,調用addToSanctuary將A添加到P的集合S中(同時經過removeFromSanctuary方法將A從舊delegate綁定parasite的集合S中移除),而且在X dealloc的時候執行redemptionAll方法來清空集合S裏的全部對象的delegate屬性,那麼問題就解決了。
 
對集合S操做的方法沒有什麼複雜的。重點關注的是如何實現對象X與parasite P一對一的綁定。
咱們發現這個parasite對象有以下特色:
一、與宿主的類型和實現徹底無關,沒有調用宿主的任何方法或訪問任何實例變量。
二、只須要在宿主dealloc的時候調用本身的一個方法,而且本身也被銷燬。
 
這讓咱們不由想到了一個叫作associate object( 關聯對象文檔)的東西!不妨將DelegateBaseParasite做爲一個associate object,綁定到X上。按這個思路派生一個DelegateAssociativeParasite類,實現一下綁定相關方法:

 #define kDelegateAssociativeParasiteSanctuaryKey "kDelegateAssociativeParasiteSanctuaryKey"

@implementation DelegateAssociativeParasite

#pragma mark -
#pragma public interface
+ (DelegateAssociativeParasite *)parasitizeIn:(id)hostObj
{
    DelegateAssociativeParasite *parasite = [[DelegateAssociativeParasite alloc] init];
    
    objc_setAssociatedObject(hostObj, &kDelegateAssociativeParasiteSanctuaryKey, parasite, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    return parasite;
}

+ (DelegateAssociativeParasite *)getParasiteFromHost:(id)hostObj
{
    return objc_getAssociatedObject(hostObj, &kDelegateAssociativeParasiteSanctuaryKey);
}

- (void)Dealloc
{
    [self redemptionAll];
}

@end

 

不知不覺,咱們已經成功了一半。也就是說,還有另外一半的問題須要咱們解決:
 
即咱們須要在actionsheet A的setDelegate:方法中刪除綁定於舊delegate的集合S中的元素A,並添加A到綁定於新delegate X的集合S中。還要在A的dealloc方法中調用[self setDelegate:nil]
 
每次調用setDelegate的時候添加代碼手動修改麼?這顯然不是一個好辦法,而且,actionsheet的dealloc時機,並不禁咱們控制,想手動添加代碼都辦不到。那麼有沒有辦法能修改這兩個方法的實現,而且這種修改還可以調用到其原有的方法實現呢?
 
繼承一個DelegateAutoUnregisteredUIActionSheet出來固然能夠辦到,可是將全部UIActionSheet替換掉,仍然要作很多工程,並且,功能上只是在UIActionSheet上打個自動註銷delegate的補丁,不必也不該該採用繼承的方式。
 
能不能用category呢?category複寫主類同名方法會產生warning,屬於apple強烈不推薦的方式,並且就算強行復寫了主類同名方法,也沒法調用原來的實現。
 
那麼怎麼辦呢?能夠用objc的runtime提供的一些方法。先用class_addMethod爲類添加一個新方法,在新方法中調用原有實現,再用method_exchangeImplementation將其與原有實現作交換。( objc runtime文檔
 
按這個思路咱們能夠寫一個輔助類DelegateAutoUnregisterHelper類(代碼見附件示例工程)。

 
這樣一來,另外一半問題也解決了,如今只需在main.m裏簡單調用:
 
 [DelegateAutoUnregisterHelper registerDelegateAutoUnregisterTo:[UIActionSheet class]];

 

就能夠實現actionsheet的delegate自動置空功能了。
 
這個利用associate object和runtime相結合的解法,也有其優缺點。其優勢有二:
     1、向工程中添加的DelegateAssociativeParasite和DelegateAutoUnregisterHelper兩個類是徹底與其它類獨立的代碼,與業務邏輯無關,邏輯清晰。
     2、使用簡單,只須要在main.m中調用一個方法對目標類(UIActionSheet)進行註冊。項目以前「喜聞樂見」的代碼徹底不用作任何修改。
其缺點有一:
     1、廣義的dangling pointer delegate出現最多的場景實際上是多線程。一個線程釋放了delegate對象,而另一個線程剛好在使用它。反觀咱們剛纔寫的代碼,卻徹底沒有考慮任何線程安全的問題。
     
咱們不由要問兩個問題:
     1. 解決UIActionSheet的delegate問題爲何能夠不考慮線程安全?
     2. 這種利用associate object的思路,可否經過鎖/信號量等方式解決線程安全的問題?
 
問題1是由UIActionSheet的使用場景決定的,做爲一個系統的UI控件,在大多數狀況下,其setDelegate、dealloc、showInView等方法,都是在UI線程中調用的。而其delegate通常都是一個UIView或者UIViewController,這兩種對象的銷燬一般也是發生在UI線程裏(實際上,假如咱們發現咱們的某些View或者ViewController的最後一次釋放以至銷燬跑到了非UI線程,咱們應該停下來思考一下是否是設計上出了問題,由於View和VC的釋放頗有可能會涉及到一些在UI線程才能進行的操做。)固然,我說的是大多數狀況,而並不是絕對。於是一般正常使用actionsheet並不會涉及線程安全問題。
 
那麼來到問題2,這種以associate object爲核心的綁定方式,究竟有沒有可能解決線程安全問題呢?
 
一推敲,自然的缺陷就暴露出來了。
 
以前咱們一直刻意模糊了一個概念,即「當X dealloc的時候」。dealloc的時候是何時?是dealloc前仍是dealloc後?
 
對於associate object,其dealloc方法,是在其宿主X的dealloc方法調用完畢之後,也就是宿主X已經被銷燬以後,才調用的。也就是說,delegate的置空是在delegate被銷燬以後。不管之間間隔多麼短,老是有那麼一瞬間,X已經被銷燬了,delegate尚未被置空,dangling pointer出現,若是是在多線程的場景下,就有可能有另外的線程在此時訪問到了這個dangling pointer,程序依然會crash。
 
因此,基於associate object的解決方案,歸根結底是沒法解決線程安全的問題的。
 
那麼怎樣才能作出一個線程安全的dangling pointer delegate問題的解決方案呢?
 
思路4:

既然問題出在associate object上,那咱們就不用它,想一想有沒有其它實現X與P一對一綁定(對應)的方法。這時咱們又想起了weak指針。系統是怎麼作到將object與指向其的weak指針集合綁定(對應)在一塊兒的呢?
 
關於weak指針的實現,咱們能夠在 llvm.org上看到相關的文檔內容( http://clang.llvm.org/docs/AutomaticReferenceCounting.html),可是不夠詳細。更直接的方式是閱讀 http://www.opensource.apple.com/source/objc4/裏面的NSObject和runtime實現的源碼。
 
簡而言之,編譯器實現的weak指針與咱們的中心思想是一致的,即用一種方法綁定對象X和一個指向X的須要監視的指針集合,並在X dealloc之時自動將集合內元素置空。只不過與associate object的方法相比,有兩點不一樣:
 
1. 綁定對象,用的是一個全局的hash table(SideTable),而非associate object。hash table的key對應一個對象X,value爲指針集合。
2. dealloc之時,指的是X的dealloc方法調用過程之中,而非最終銷燬之後,這樣就不存在自然的缺陷,其線程安全問題是能夠經過在hash table上加鎖來解決的。
 
按照這個思路,咱們來派生一個新的DelegateDictParasite類,實現另外一種利用CFDictionary的綁定(對應)的方法:

 @implementation DelegateDictParasite

+ (DelegateDictParasite *)parasitizeIn:(id)hostObj
{
    if (!class_getInstanceMethod([hostObj class], @selector(myHostObjDealloc)))
    {
        [DelegateDictParasite addNewMethodToHost:[hostObj class]];
        [DelegateAutoUnregisterHelper mergeOldSEL:[DelegateAutoUnregisterHelper deallocSelector] NewSEL:@selector(myHostObjDealloc) ForClass:[hostObj class]];
        [DelegateAutoUnregisterHelper mergeOldSEL:[DelegateAutoUnregisterHelper releaseSelector] NewSEL:@selector(myHostObjRelease) ForClass:[hostObj class]];
    }
    
    DelegateDictParasite *parasite;
    
    @synchronized(kDelegateAssociativeParasiteLock)
    {
        if (!delegateHostParasiteHashTable)
        {
            delegateHostParasiteHashTable = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        }
    
        parasite = [[DelegateDictParasite alloc] init];

        CFDictionarySetValue(delegateHostParasiteHashTable, (__bridge const void *)(hostObj), (__bridge const void *)(parasite));
    }
    
    return parasite;
}

+ (DelegateDictParasite *)getParasiteFromHost:(id)hostObj
{
    DelegateDictParasite *parasite;
    
    @synchronized(kDelegateAssociativeParasiteLock)
    {
        if (!delegateHostParasiteHashTable)
        {
            return nil;
        }
        
        parasite = CFDictionaryGetValue(delegateHostParasiteHashTable, (__bridge const void *)(hostObj));
    }
    
    return parasite;
}

@end

 

這裏,因爲沒有了associate object的幫助,X dealloc與parasite dealloc的聯動須要咱們本身觸發,一樣利用runtime,咱們能夠改寫每個X的dealloc方法來完成這種聯動,解除hash table中對X的綁定,從而引起自動置空。
 
另外,經過鎖,咱們能夠解決線程安全問題。從而解決多線程下delegate的dangling pointer問題。(完整代碼見附錄)
 
這種思路,其優勢有二:
     1、沒有了先天缺陷,解決了線程安全問題,從而能夠推廣到廣義的dangling pointer delegate問題上。
     2、用法與思路三同樣,比較簡單。

其缺點有二:
     1、用了全局的一個hash table。通常有潔癖的人看到全局變量會不舒服。
     2、對每個成爲delegate的對象X的類,都會修改其dealloc方法,不像associate object的聯動那麼天然,有點不乾淨。
 
思路5:
 
GitHub上有一個mikeash寫的開源項目MAZeroingWeakRef,目的是在不支持weak的狀況下提供一個weak指針的實現,其實現思想也是與系統weak指針相似,即利用全局hash table來作。與思路4不一樣的是,它修改X的dealloc方法,是經過動態繼承出X的一個子類,而後在子類上addMethod的方式,而不是利用method_exchangeImplementation。
 
這個項目考慮了更多的狀況,好比說對於KVO的支持以及toll-free的CF對象的處理(不過用到了私有API)等等,你們有興趣和時間的話能夠研究一下,再也不贅述。
 
其優勢有二:
     1、考慮了KVO/CF等狀況的支持,更加嚴謹。
     2、動態繼承的方式把dealloc方法修改的範圍縮小到只是使用weak的實例而不是此類的全部實例,解決了思路4的缺點二。
其缺點有二:
     1、動態繼承的方式修改了類的名字。
     2、只是用來在weak不能使用的條件下實現weak指針,能夠解決自定義的delegate的dangling pointer問題,並不能解決文中已經被指定爲assign類型的系統控件delegate的問題。
 
注:本文因爲篇幅所限,實現過程當中一些坑和有意思的地方並未一一說起。例如修改方法實現的時候,須要注意修改的是父類方法仍是子類方法;一些方法實現只能放在非ARC(添加-fno-objc-arc標誌)文件中;等等。
 
 
【總結】
 
本文逐步思考並總結的幾種解決dangling pointer問題的思路各有優缺點,並不存在哪一種必定最好,要具體狀況具體分析。相比之下,思路4是解決多線程delegate的dangling pointer的較爲完整的解決方案。思考和實現過程中還有不少不成熟的地方,歡迎你們一塊兒討論、不正確的地方也歡迎批評指正。
相關文章
相關標籤/搜索