Извилистый единственный экземпляр, а не класс - PullRequest
17 голосов
/ 29 февраля 2012

У меня есть категория на 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

Ответы [ 3 ]

19 голосов
/ 29 февраля 2012

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

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

10 голосов
/ 29 февраля 2012

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

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

@instance MyPlainObject : NSObject

- (void) doSomething;

@end

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

@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 (класс) переменные.

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

1 голос
/ 28 октября 2013

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

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

...