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
Ответы
Ответ 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
Он работает, создавая динамический подкласс для конкретного экземпляра, который вы запускаете во время выполнения.