Подтвердить что ты не робот

Swizzling один экземпляр, а не класс

У меня есть категория на NSObject, которая должна так много работать. Когда я вызываю его на объект, я хотел бы переопределить его метод dealloc, чтобы сделать некоторые очистки.

Я хотел сделать это, используя метод swizzling, но не мог понять, как это сделать. Единственные примеры, которые я нашел, - это о том, как заменить реализацию метода для всего класса (в моем случае он будет переопределять dealloc для ВСЕХ NSObjects, чего я не хочу).

Я хочу переопределить метод dealloc конкретных экземпляров NSObject.

@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
4b9b3361

Ответ 1

Вы не можете этого сделать, поскольку объекты не имеют собственных таблиц методов. Только классы имеют таблицы методов, и если вы измените их, это повлияет на каждый объект этого класса. В этом есть простой способ: изменить класс вашего объекта во время выполнения на динамически созданный подкласс. Эта технология, также называемая isa-swizzling, используется Apple для реализации автоматического KVO.

Это мощный метод, и он использует его. Но для вашего случая есть более простой метод, использующий связанные объекты. В основном вы используете objc_setAssociatedObject для связывания другого объекта с вашим первым объектом, который выполняет очистку в dealloc. Вы можете найти более подробную информацию в этот пост в блоге на Cocoa является моей подругой.

Ответ 2

Выбор метода основан на классе экземпляра объекта, поэтому метод swizzling затрагивает все экземпляры одного и того же класса - как вы обнаружили.

Но вы можете изменить класс экземпляра, но вы должны быть осторожны! Вот схема, предположим, что у вас есть класс:

@instance MyPlainObject : NSObject

- (void) doSomething;

@end

Теперь, если для некоторых экземпляров MyPlainObject вы хотите изменить поведение doSomething, вы сначала определите подкласс:

@instance MyFancyObject: MyPlainObject

- (void) doSomething;

@end

Теперь вы можете явно создавать экземпляры MyFancyObject, но нам нужно сделать предварительный экземпляр MyPlainObject и превратить его в MyFancyObject, чтобы мы получили новое поведение. Для этого мы можем swizzle класса, добавьте следующее в 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 (class).

Вам также нужен подкласс для класса, для которого вы хотите изменить поведение, вы не можете иметь один класс, который может изменить поведение многих разных классов.

Ответ 3

Я сделал swizzling API, который также имеет особенность, зависящую от конкретного случая. Я думаю, что это именно то, что вы ищете: https://github.com/JonasGessner/JGMethodSwizzler

Он работает, создавая динамический подкласс для конкретного экземпляра, который вы запускаете во время выполнения.