如何在类类别中访问dealloc方法?

时间:2013-02-05 13:41:03

标签: ios objective-c cocoa-touch memory-management nstimer

我需要在类别的dealloc方法中执行操作。我尝试过调情,但这不起作用(也不是一个好主意)。

如果有人问,答案是否定的,我不能使用子类,这是专门针对某个类别的。

我想使用[NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:][self performSelector:withObject:afterDelay:]对延迟执行操作,并在dealloc上取消。

第一个问题是NSTimer保留了我不想要的目标。 [self performSelector:withObject:afterDelay:]未保留,但我需要能够使用[NSObject cancelPreviousPerformRequestsWithTarget:selector:object:]方法调用dealloc,否则我们会崩溃。

有关如何在类别上执行此操作的任何建议吗?

3 个答案:

答案 0 :(得分:8)

我仍然认为将类子类化为更好并且不会弄乱运行时,但如果您确定需要在类别中进行操作,我会考虑您的选择。它仍然与运行时混淆,但比我想的更加安全。

考虑编写一个帮助类,比如调用它DeallocHook,它可以附加到任何NSObject,并在释放此NSObject时执行操作。然后你可以做这样的事情:

// Instead of directly messing with your class -dealloc method, attach
// the hook to your instance and do the cleanup in the callback 
[DeallocHook attachTo: yourObject 
             callback: ^{ [NSObject cancelPrevious... /* your code here */ ]; }];

您可以使用DeallocHook

实施objc_setAssociatedObject
@interface DeallocHook : NSObject
@property (copy, nonatomic) dispatch_block_t callback;

+ (id) attachTo: (id) target callback: (dispatch_block_t) block;

@end

实施将是这样的:

#import "DeallocHook.h"
#import <objc/runtime.h>

// Address of a static global var can be used as a key
static void *kDeallocHookAssociation = &kDeallocHookAssociation;

@implementation DeallocHook

+ (id) attachTo: (id) target callback: (dispatch_block_t) block
{
    DeallocHook *hook = [[DeallocHook alloc] initWithCallback: block];

    // The trick is that associations are released when your target
    // object gets deallocated, so our DeallocHook object will get
    // deallocated right after your object
    objc_setAssociatedObject(target, kDeallocHookAssociation, hook, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    return hook;
}


- (id) initWithCallback: (dispatch_block_t) block
{
    self = [super init];

    if (self != nil)
    {
        // Here we just copy the callback for later
        self.callback = block;
    }
    return self;
}


- (void) dealloc
{
    // And we place our callback within the -dealloc method
    // of your helper class.
    if (self.callback != nil)
        dispatch_async(dispatch_get_main_queue(), self.callback);
}

@end

有关关联参考的详细信息,请参阅有关Objective-C runtime的Apple文档(尽管我说文档在此主题方面并不十分详细)。

我没有彻底测试过,但它似乎有效。我以为我会给你另一个方向来研究。

答案 1 :(得分:4)

我偶然发现了一个我以前从未见过的解决方案,似乎有效......

我有一个类别 - 正如人们经常做的那样 - 需要一些状态变量,因此我使用objc_setAssociatedObject,如下所示:

Memento *m = [[[Memento alloc] init] autorelease];
objc_setAssociatedObject(self, kMementoTagKey, m, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

而且,我需要知道我的类别扩展的实例何时被dealloc编辑。在我的情况下,这是因为我在self上设置观察者,并且必须在某个时刻删除那些观察者,否则我会收到NSKVODeallocateBreak泄漏警告,这可能导致不好的事情。

突然间我突然意识到,因为我的关联对象被retain编辑(因为使用了OBJC_ASSOCIATION_RETAIN_NONATOMIC),所以它们也必须release d,因此{{1}实际上我在我创建的用于存储状态值的简单存储类中实现了dealloc方法。并且,我假设:我的关联对象必须在我的类别的实例之前被解除分配!因此,我可以让我的关联对象在他们意识到他们的时候通知他们的所有者{{ 1}}版!由于我已经有了保留的关联对象,我只需要添加一个dealloc属性(指定为dealloc!),设置所有者,然后调用一些关联对象的owner方法中所有者的方法。

这是我的类别的.m文件的修改部分,带有相关位:

retain

我在某处学习的模式(可能在S.O.上)使用工厂方法来获取或创建纪念品对象。现在它将所有者设置在纪念品上,而memento的dealloc方法会回叫让所有者知道它正在#import <objc/runtime.h> // So we can use objc_setAssociatedObject, etc. #import "TargetClass+Category.h" @interface TargetClass_CategoryMemento : NSObject { GLfloat *_coef; } @property (nonatomic) GLfloat *coef; @property (nonatomic, assign) id owner; @end @implementation TargetClass_CategoryMemento -(id)init { if (self=[super init]) { _coef = (GLfloat *)malloc(sizeof(GLfloat) * 15); } return self; }; -(void)dealloc { free(_coef); if (_owner != nil && [_owner respondsToSelector:@selector(associatedObjectReportsDealloc)]) { [_owner associatedObjectReportsDealloc]; } [super dealloc]; } @end @implementation TargetClass (Category) static NSString *kMementoTagKey = @"TargetClass+Category_MementoTagKey"; -(TargetClass_CategoryMemento *)TargetClass_CategoryGetMemento { TargetClass_CategoryMemento *m = objc_getAssociatedObject(self, kMementoTagKey); if (m) { return m; } // else m = [[[TargetClass_CategoryMemento alloc] init] autorelease]; m.owner = self; // so we can let the owner know when we dealloc! objc_setAssociatedObject(self, kMementoTagKey, m, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return m; } -(void) doStuff { CCSprite_BlurableMemento *m = [self CCSprite_BlurableGetMemento]; // do stuff you needed a category for, and store state values in m } -(void) associatedObjectReportsDealloc { NSLog(@"My associated object is being dealloced!"); // do stuff you need to do when your category instances are dealloced! } @end 编辑

注意事项:

  • 显然,您必须将关联的对象设置为dealloc,否则它将不会自动保留和释放。
  • 如果您的纪念品/状态相关对象在其他情况下获得dealloc而不是所有者OBJC_ASSOCIATION_RETAIN_NONATOMIC编辑,则会变得更加棘手......但您可以训练一个对象或另一个对象来忽略该事件
  • dealloc属性不能声明为dealloc,否则您将真正创建一个强大的参考循环,这两个对象都不会被owner编辑!
  • 我不知道在所有者完全retain之前,dealloc关联对象必须OBJC_ASSOCIATION_RETAIN_NONATOMIC来记录,但它似乎是这样发生的,几乎必须是这种情况,至少是直观的。
  • 我不知道{/ 1>}在之前是之后是 {@ 1}}的dealloc方法 - 这个可能很重要!如果它之后运行,如果您尝试访问release的成员对象,您将崩溃!我的猜测是它之后。

这有点乱,因为你是双重链接你的对象,这要求你非常小心地保持这些引用。但是,它不涉及调动或对运行时的其他干扰 - 这只是依赖于运行时的某种行为。如果您已经有一个关联的对象,它似乎是一个方便的解决方案。在某些情况下,创建一个只是为了抓住你自己的dealloc s!

是值得的

答案 2 :(得分:2)

遗憾的是,您建议的解决方案无效:因为 NSTimer保留其目标,目标将永远不会运行其dealloc,直到计时器失效。目标的保留计数将始终悬停在1或更高,等待计时器释放它。你必须在 dealloc之前到达计时器。 (在ARC之前,你可能覆盖retainrelease并销毁计时器,尽管这确实不是一个好的解决方案。)

NSThread also has this problem,解决方案很简单:一些重新设计将线程的控制器与“模型”分开。在这种情况下,创建和拥有线程或计时器的对象也不应该是计时器的目标。然后,代替你当前拥有的保留周期(计时器拥有拥有计时器的对象),你有一个很好的直线:控制器拥有拥有目标的计时器。外部对象只需要与控制器交互:当它被解除分配时,它可以关闭计时器,而不必使用覆盖dealloc或其他内存管理方法来玩游戏。

这是处理此问题的最佳方法。如果由于某种原因你不能这样做 - 你说的是类别覆盖,所以显然你没有代表计时器目标的类的代码(但你仍然可以做一个控制器,即使在那种情况下) - 你可以使用弱引用。不幸的是,我不知道如何让NSTimer对其目标进行弱引用,但GCD会通过dispatch_after()给你一个合理的近似值。获取对目标的弱引用,并在您传递的块中专门使用它。 Block不会通过弱引用(NSTimer的方式)保留对象,如果在Block运行之前对象已被释放,则弱引用当然是nil,因此您可以安全地写下你喜欢的任何信息。