【问题标题】:Swizzling a single instance, not a class混合单个实例,而不是类
【发布时间】:2012-03-19 05:55:47
【问题描述】:

我在 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

【问题讨论】:

    标签: objective-c ios methods categories swizzling


    【解决方案1】:

    您不能真正做到这一点,因为对象没有自己的方法表。只有类有方法表,如果您更改它们,它将影响该类的每个对象。但是有一个直接的方法:在运行时将对象的类更改为动态创建的子类。这种技术也称为 isa-swizzling,被 Apple 用于实现自动 KVO。

    这是一个强大的方法,它有它的用途。但是对于您的情况,有一种使用关联对象的更简单方法。基本上,您使用objc_setAssociatedObject 将另一个对象关联到您的第一个对象,该对象在其dealloc 中进行清理。您可以在this blog post on Cocoa is my Girlfriend找到更多详细信息。

    【讨论】:

    • 感谢您的回答。所以基本上,对于每个调用 - 我分配一个新对象(我实现)并将其设置为关联对象,然后我在它的 dealloc 中进行清理?
    • 没错。看看那篇博文,它甚至在NSObject 上有一个不错的类别,让您注册要在任何对象的释放期间调用的块。
    【解决方案2】:

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

    但是你可以改变一个实例的类,但是你必须小心!这是大纲,假设你有一个类:

    @instance MyPlainObject : NSObject
    
    - (void) doSomething;
    
    @end
    

    现在,如果您想要更改 MyPlainObject 的某些实例,您首先要定义一个子类:

    @instance MyFancyObject: MyPlainObject
    
    - (void) doSomething;
    
    @end
    

    现在您可以清楚地创建MyFancyObject 的实例,但我们需要做的是获取MyPlainObject预先存在的实例并将其变为MyFancyObject,这样我们就得到了新行为。为此,我们可以调整类,将以下内容添加到MyFancyObject

    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);
    }
    

    现在对于MyPlainClass 的任何原始 实例,您可以切换为MyFancyClass,反之亦然:

    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];
    

    (一些)注意事项:

    可以在子类覆盖或添加方法并添加static(类)变量的情况下执行此操作。

    你还需要一个子类来改变你想要改变行为的类,你不能有一个类可以改变许多不同类的行为。

    【讨论】:

    • 但在这种情况下,我必须提前知道该实例的类是什么。在我的例子中,对象可以是任何东西(甚至是 SDK 本身创建的对象),所以我不能假设我会知道类型。
    • 是的,这是警告之一!如果需要,请转到关联的对象路线。但是,如果您针对的是特定类,则 swizzling 比关联对象更简单(当然是主观的),并且更通用,因为您可以轻松地覆盖任何方法。
    • 尽管我使用了关联对象作为我的解决方案,但我仍感谢您的回答。我确实有一个问题:因为它只对覆盖/添加方法有用 - 这和类别有什么不同?
    • 一个类别将方法添加到一个类的所有实例。上述方法通过将类的单个实例变为该类的子类来更改该类的单个实例的行为(受警告)。
    【解决方案3】:

    我制作了一个 swizzling API,它还具有特定于实例的 swizzling。我认为这正是您正在寻找的:https://github.com/JonasGessner/JGMethodSwizzler

    它的工作原理是为您在运行时调动的特定实例创建一个动态子类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-21
      • 1970-01-01
      • 2013-06-17
      相关资源
      最近更新 更多