对单个实例进行Swizzling,而不是对整个类进行。

19
我在NSObject上有一个类别,它应该做一些事情。 当我在对象上调用它时,我想重写其dealloc方法以进行一些清理。
我想使用方法交换来实现它,但是我无法弄清楚如何做到这一点。 我发现的唯一示例是如何替换整个类的方法实现(在我的情况下,它将覆盖所有NSObjects的dealloc - 这是我不想要的)。
我想覆盖特定NSObject实例的dealloc方法。
@interface NSObject(MyCategory)
-(void)test;
@end

@implementation NSObject(MyCategory)
-(void)newDealloc
{
  // do some cleanup here
  [self dealloc]; // call actual dealloc method
}
-(void)test
{
  IMP orig=[self methodForSelector:@selector(dealloc)];
  IMP repl=[self methodForSelector:@selector(newDealloc)];
  if (...)   // 'test' might be called several times, this replacement should happen only on the first call
  {
     method_exchangeImplementations(..., ...);
  }
}
@end
3个回答

20
由于对象没有它们自己的方法表,因此你无法真正做到这一点。只有类具有方法表,如果更改它们,将影响该类的每个对象。不过,有一个简单的方法可以解决这个问题:在运行时将对象的类更改为动态创建的子类。这种技术也称为 isa-swizzling,被 Apple 用于实现自动 KVO。
这是一种强大的方法,它有其用途。但对于你的情况,有一种更简单的方法使用关联对象。基本上,你使用 objc_setAssociatedObject 将另一个对象关联到第一个对象上,并在其 dealloc 中进行清理。你可以在 Cocoa is my Girlfriend 的这篇博客文章中找到更多详细信息。

谢谢你的回答。基本上,对于每个调用,我都会分配一个新对象(我实现它),并将其设置为关联对象,然后在其dealloc中进行清理? - Gilad Novik
没错。看看那篇博客文章,它甚至有一个很好的类别 NSObject,可以让您注册块,在任何对象的dealloc期间调用。 - Sven

12

方法的选择是基于对象实例的 ,因此方法混淆会影响所有属于同一类的实例,正如您所发现的那样。

但是您可以更改实例的类,但必须小心!这是一个概要,假设您有一个类:

@instance MyPlainObject : NSObject

- (void) doSomething;

@end

如果你只想针对 MyPlainObject 的某些实例更改 doSomething 的行为,那么首先定义一个子类:

<code><code>@instance MyFancyObject: MyPlainObject

- (void) doSomething;

@end
</code></code>

现在你可以清楚地创建MyFancyObject的实例,但我们需要做的是将已经存在的MyPlainObject实例转换为MyFancyObject,以便获得新的行为。为此,我们可以交换类,将以下内容添加到MyFancyObject中:

<code><code>static Class myPlainObjectClass;
static Class myFancyObjectClass;

+ (void)initialize
{
   myPlainObjectClass = objc_getClass("MyPlainObject");
   myFancyObjectClass = objc_getClass("MyFancyObject");
}

+ (void)changeKind:(MyPlainObject *)control fancy:(BOOL)fancy
{
   object_setClass(control, fancy ? myFancyObjectClass : myPlainObjectClass);
}
</code></code>

现在对于任何一个原始的MyPlainClass实例,您都可以切换为以MyFancyClass的方式运行,反之亦然:

<code><code>MyPlainClass *mpc = [MyPlainClass new];

...

// masquerade as MyFancyClass
[MyFancyClass changeKind:mpc fancy:YES]

... // mpc behaves as a MyFancyClass

// revert to true nature
[MyFancyClass changeKind:mpc: fancy:NO];
</code></code>

其中一些注意事项:

只有在子类覆盖或添加方法并添加static(类)变量时,才能这样做。

您还需要为每个要更改行为的类创建子类,不能只有一个可以更改多个不同类行为的类。


但在这种情况下,我必须事先知道该实例的类是什么。在我的情况下,对象可以是任何东西(甚至是SDK本身创建的对象),因此我不能假设我会知道类型。 - Gilad Novik
是的,这是其中一个注意事项!如果需要,可以采用相关对象路线。但是,如果您的目标是特定类,则交换方法比关联对象稍微简单一些(当然有点主观),并且更通用,因为您可以轻松地覆盖任何方法。 - CRD
尽管我已经使用了关联对象作为我的解决方案,但我很感谢你的回答。我确实有一个问题:既然它只有在覆盖/添加方法时才有用,那么这与类别有什么不同呢? - Gilad Novik
一个类别会为一个类的所有实例添加方法。上述方法通过将其变成该类的子类(受限于警告)来改变单个实例的行为。 - CRD

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接