簡體   English   中英

何時釋放相關對象?

[英]When does an associated object get released?

我通過關聯引用將對象B附加到對象A.對象B觀察對象A到KVO的一些屬性。

問題是對象B似乎對象A 之后被釋放,這意味着它太遲了以將其自身移除為對象A的KVO觀察者。我知道這是因為我得到NSKVODeallocateBreak異常,然后EXEC_BAD_ACCESS在對象B的dealloc中崩潰。

有沒有人知道為什么對象B在具有OBJC_ASSOCIATION_RETAIN的對象A之后被釋放? 解除分配后是否釋放相關對象? 他們是否被自動釋放? 有誰知道改變這種行為的方法?

我試圖通過類別向類添加一些東西,所以我不能覆蓋任何現有的方法(包括dealloc),而且我並不特別想要混亂。 在對象A被釋放之前,我需要一些方法來解除關聯並釋放對象B.

編輯 - 這是我正在努力工作的代碼。 如果在完全取消分配UIImageView之前釋放了關聯的對象,那么這一切都可以正常工作。 我所看到的唯一解決方案是在我自己的dealloc方法中調整,並調回原來以調用它。 但這真的很混亂。

ZSPropertyWatcher類的要點是KVO需要一個標准的回調方法,我不想替換UIImageView,以防它自己使用它。

的UIImageView + Loading.h

@interface UIImageView (ZSShowLoading)
@property (nonatomic)   BOOL    showLoadingSpinner;
@end

的UIImageView + Loading.m

@implementation UIImageView (ZSShowLoading)

#define UIIMAGEVIEW_SPINNER_TAG 862353453
static char imageWatcherKey;
static char frameWatcherKey;

- (void)zsShowSpinner:(BOOL)show {
    if (show) {
        UIActivityIndicatorView *spinnerView = (UIActivityIndicatorView *)[self viewWithTag:UIIMAGEVIEW_SPINNER_TAG];
        if (!spinnerView) {
            spinnerView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
            spinnerView.tag = UIIMAGEVIEW_SPINNER_TAG;
            [self addSubview:spinnerView];
            [spinnerView startAnimating];
        }

        [spinnerView setEvenCenter:self.boundsCenter];
    } else {
        [[self viewWithTag:UIIMAGEVIEW_SPINNER_TAG] removeFromSuperview];
    }
}

- (void)zsFrameChanged {
    [self zsShowSpinner:!self.image];
}

- (void)zsImageChanged {
    [self zsShowSpinner:!self.image];
}

- (BOOL)showLoadingSpinner {
    ZSPropertyWatcher *imageWatcher = (ZSPropertyWatcher *)objc_getAssociatedObject(self, &imageWatcherKey);
    return imageWatcher != nil;
}

- (void)setShowLoadingSpinner:(BOOL)aBool {
    ZSPropertyWatcher *imageWatcher = nil;
    ZSPropertyWatcher *frameWatcher = nil;

    if (aBool) {
        imageWatcher = [[[ZSPropertyWatcher alloc] initWithObject:self keyPath:@"image" delegate:self callback:@selector(zsImageChanged)] autorelease];
        frameWatcher = [[[ZSPropertyWatcher alloc] initWithObject:self keyPath:@"frame" delegate:self callback:@selector(zsFrameChanged)] autorelease];

        [self zsShowSpinner:!self.image];
    } else {
        // Remove the spinner
        [self zsShowSpinner:NO];
    }

    objc_setAssociatedObject(
        self,
        &imageWatcherKey,
        imageWatcher,
        OBJC_ASSOCIATION_RETAIN
    );

    objc_setAssociatedObject(
        self,
        &frameWatcherKey,
        frameWatcher,
        OBJC_ASSOCIATION_RETAIN
    );
}

@end

ZSPropertyWatcher.h

@interface ZSPropertyWatcher : NSObject {
    id          delegate;
    SEL         delegateCallback;

    NSObject    *observedObject;
    NSString    *keyPath;
}

@property (nonatomic, assign)   id      delegate;
@property (nonatomic, assign)   SEL     delegateCallback;

- (id)initWithObject:(NSObject *)anObject keyPath:(NSString *)aKeyPath delegate:(id)aDelegate callback:(SEL)aSelector;

@end

ZSPropertyWatcher.m

@interface ZSPropertyWatcher ()

@property (nonatomic, assign)   NSObject    *observedObject;
@property (nonatomic, copy)     NSString    *keyPath;

@end

@implementation ZSPropertyWatcher

@synthesize delegate, delegateCallback;
@synthesize observedObject, keyPath;

- (id)initWithObject:(NSObject *)anObject keyPath:(NSString *)aKeyPath delegate:(id)aDelegate callback:(SEL)aSelector {
    if (!anObject || !aKeyPath) {
        // pre-conditions
        self = nil;
        return self;
    }

    self = [super init];
    if (self) {
        observedObject = anObject;
        keyPath = aKeyPath;
        delegate = aDelegate;
        delegateCallback = aSelector;

        [observedObject addObserver:self forKeyPath:keyPath options:0 context:nil];
    }
    return self;
}

- (void)dealloc {
    [observedObject removeObserver:self forKeyPath:keyPath];

    [keyPath release];

    [super dealloc];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    [self.delegate performSelector:self.delegateCallback];
}

@end

甚至比你的-dealloc問題還要大:

UIKit不符合KVO標准

沒有努力使UIKit類的鍵值可觀察。 如果它們中的任何一個,它完全是巧合,並且在Apple的心血來潮中受到打破。 是的,我在UIKit框架上為Apple工作。

這意味着您將不得不尋找另一種方法來實現此目的,可能是稍微改變您的視圖布局。

此相關問題的已接受答案解釋了對象的釋放時間線。 結果是:在原始對象的dealloc方法完成釋放關聯對象。

我認為你的情況是這樣的:

1)對象A在其保留計數變為0后接收-dealloc調用;

2)關聯機制確保在某個時刻釋放對象B(與解除分配不同)。

也就是說,我們並不確切知道在哪一點,但似乎我認為這種語義差異是對象A在對象A之后被解除分配的原因; 對象-dealloc選擇器無法-dealloc關聯,因此當調用它的最后一個版本時,執行-dealloc ,並且只有在此之后關聯機制才能向對象B發送-release ...

還看看這篇文章

它還指出:

現在,當取消分配objectToBeDeallocated時,objectWeWantToBeReleasedWhenThatHappens將自動發送一個-release消息。

我希望這有助於解釋您的體驗。 至於其余的,我幫不了多少......

編輯:在DougW的評論之后繼續進行如此有趣的猜測......

如果關聯機制在釋放對象A時被“破壞”(繼續你的例子),我會看到存在一種循環依賴的風險。

  1. 如果從釋放方法(而不是dealloc)執行與關聯相關的代碼,則對於每個版本,您將檢查“擁有”對象(對象A)是否具有保留計數1; 實際上,在這種情況下,你知道減少它的保留計數會觸發dealloc,所以在這之前,你首先要釋放相關的對象(你的例子中的對象B);

  2. 但是如果對象B又“擁有”了第三個對象,比如C,會發生什么? 會發生的情況是,在對象B上調用釋放時,當對象B保留計數為1時,C將被釋放;

  3. 現在,考慮對象C“擁有”該序列的第一個對象A的情況。如果,當接收上述版本時,C的保留計數為1,它將首先嘗試釋放其關聯對象,是A;

    1. 但是A的釋放次數仍為1,因此另一個版本將發送給B,其保留計數仍為1; 等等,循環播放。

另一方面,如果您從-dealloc發送釋放,則這種循環依賴似乎不可能。

這是非常做作的,我不確定我的推理是否正確,所以隨時評論它...

objc_getAssociatedObject()用於OBJC_ASSOCIATION_RETAIN關聯返回一個自動釋放的對象。 你可以在同一個runloop循環/自動釋放池范圍內調用它,因為對象A被釋放了嗎? (您可以通過將關聯更改為NONATOMIC來快速測試)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM