5

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

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

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

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

任何建议如何在类别上执行此操作?

4

3 回答 3

8

我仍然认为子类化你的类而不弄乱运行时会更好,但如果你确定你需要在一个类别中做,我有一个选项供你参考。它仍然与运行时混淆,但比我认为的 swizzling 更安全。

考虑编写一个辅助类,比如说调用它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 */ ]; }];

您可以实现DeallocHookusing 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

有关关联引用的更多信息,请参阅 Apple 关于Objective-C 运行时的文档(尽管我会说文档对这个主题不是很详细)。

我没有彻底测试过这个,但它似乎工作。只是想我会给你另一个方向去研究。

于 2013-02-10T10:43:45.073 回答
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,因此被dealloc编辑......事实上,我已经dealloc在我为存储而创建的简单存储类中实现了一个方法我的国家价值观。而且,我假设:我的关联对象必须在我的类别实例之前被释放!所以,我可以让我的关联对象在他们意识到他们正在被编辑时通知他们的所有者dealloc!由于我已经拥有了保留的关联对象,我只需要添加一个owner属性(指定为retain!),设置所有者,然后在关联对象的方法中调用所有者的一些dealloc方法。

这是我的类别的 .m 文件的修改部分,其中包含相关位:

#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

我在这里学到的模式(可能在 SO 上)使用工厂方法来获取或创建纪念品对象。现在它将所有者设置在备忘录上,并且备忘录的dealloc方法回调以让所有者知道它正在被dealloc编辑

注意事项:

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

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

于 2013-12-14T22:08:16.787 回答
2

不幸的是,您提出的解决方案不起作用:因为 保留了它的目标,所以在计时器失效之前NSTimer,目标永远不会运行它。dealloc目标的保留计数将始终悬停在 1 或以上,等待计时器释放它。你必须先到计时器 dealloc。(在 ARC 之前,您可以覆盖retainrelease销毁计时器,尽管这确实不是一个好的解决方案。)

NSThread也有这个问题,解决方法很简单:稍微重新设计一下,把线程的控制器从“模型”中分离出来。创建和拥有线程的对象,或者在这种情况下的计时器,不应该也是计时器的目标。然后,不是您当前拥有的保留周期(计时器拥有拥有计时器的对象),而是一条很好的直线:控制器拥有拥有目标的计时器。外部对象只需要与控制器交互:当它被释放时,它可以关闭计时器,而无需您使用覆盖dealloc或其他内存管理方法玩游戏。

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

于 2013-02-11T20:56:42.260 回答