Ответ 1
Принятый ответ на этот связанный вопрос объясняет временную шкалу освобождения объектов. Результатом является: Связанные объекты освобождаются после, метод dealloc
исходного объекта завершен.
Я привязываю объект B через ассоциативную ссылку на объект A. Объект B наблюдает некоторые свойства объекта A через KVO.
Проблема в том, что объект B, по-видимому, освобождается после объекта A, что означает его слишком поздно, чтобы удалить себя как наблюдателя KVO объекта A. Я знаю это, потому что получаю исключения NSKVODeallocateBreak, за которым следуют аварийные ситуации EXEC_BAD_ACCESS в объекте B dealloc.
Кто-нибудь знает, почему объект B освобождается после объекта A с OBJC_ASSOCIATION_RETAIN? Выпускаются ли связанные объекты после освобождения? Получают ли они автореализацию? Кто-нибудь знает способ изменить это поведение?
Я пытаюсь добавить некоторые вещи в класс по категориям, поэтому я не могу переопределить какие-либо существующие методы (включая dealloc), и я не особо хочу возиться с swizzling. Мне нужен способ де-ассоциировать и освободить объект B до того, как объект A будет освобожден.
EDIT. Вот код, который я пытаюсь получить. Если связанные объекты были выпущены до того, как UIImageView будет полностью освобожден, все это будет работать. Единственное решение, которое я вижу, - это swizzle в моем собственном методе dealloc и swizzle назад оригинал, чтобы вызвать его. Это становится действительно грязным, хотя.
Точка класса 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
исходного объекта завершен.
Даже больше, чем ваша проблема -dealloc
:
UIKit не соответствует KVO
Не было сделано никаких усилий, чтобы сделать доступным значение ключа класса UIKit. Если какой-либо из них, он полностью случайный и может быть нарушен при приходе Apple. И да, я работаю для Apple в рамках UIKit.
Это означает, что вам придется найти другой способ сделать это, возможно, слегка изменив макет представления.
то, что я думаю, происходит в вашем случае:
1) объект A получает вызов -dealloc
после того, как его счетчик удержания переместился в 0;
2) механизм ассоциации гарантирует, что в какой-то момент объект B будет освобожден (что отличает его от деллаоцита).
i.e, мы точно не знаем, в какой точке, но мне кажется, что подобная семантическая разность является причиной того, что объект B освобождается после объекта A; объект A -dealloc
селектор не может знать об ассоциации, поэтому, когда вызывается последняя версия на нем, выполняется -dealloc
, и только после этого механизм ассоциации может отправить объект -release
на объект B...
также посмотрите этот пост.
он также указывает:
Теперь, когда objectToBeDeallocated освобождается, objectWeWantToBeReleasedWhenThatHappens будет автоматически отправлено сообщение -release.
Я надеюсь, что это поможет объяснить, что вы испытываете. Что касается остальных, я не могу помочь...
EDIT: просто продолжайте с такой интересной спекуляцией после комментария DougW...
Я вижу риск наличия какой-либо циклической зависимости, если механизм ассоциации был "сломан" при выпуске объекта A (чтобы продолжить работу с вашим примером).
если связанный с ассоциацией код был выполнен из метода release (вместо dealloc), для каждой версии вы должны проверить, имеет ли объект "владеющий" (объект A) счетчик сохранения 1; на самом деле, в таком случае вы знаете, что уменьшение его количества удержания вызовет dealloc, поэтому перед этим вы сначала отпустите связанный объект (объект B в вашем примере);
но что произойдет, если объект B также будет в свою очередь "владеть" третьим объектом, скажем, C? произойдет то, что в момент, когда релиз вызывается для объекта B, когда объект B сохраняет счет 1, C будет выпущен;
теперь рассмотрим случай, когда объект C "владел" самой первой из этой последовательности, объект A. Если, получив вышеприведенный релиз, C имел значение удержания 1, он сначала попытался бы и отпустите свой связанный объект, который равен A;
Если вы, с другой стороны, отправляете выпуск из -dealloc, такая циклическая зависимость не представляется возможной.
Это довольно надуманно, и я не уверен, что мои рассуждения верны, поэтому не стесняйтесь комментировать его...
objc_getAssociatedObject()
для ассоциации OBJC_ASSOCIATION_RETAIN
возвращает объект с автореализацией. Возможно, вы вызываете его раньше в той же области runloop cycle/autorelease pool, когда объект A освобождается? (Вероятно, вы можете проверить это быстро, изменив связь на NONATOMIC
).