Может ли class_addMethod в Objective-C работать только на конкретном экземпляре? - PullRequest
8 голосов
/ 06 мая 2011

Я пытаюсь написать некоторый динамический код, в котором пользователь может попытаться вызвать метод из определенного экземпляра класса и разрешить его во время выполнения. Реализация для извлечения информации существует, но метода для доступа к ней нет, потому что это для каждого экземпляра.

Например, пользователь может захотеть вызвать метод с именем «getSomething», которого нет в классе:

[someInstance getSomething]

В этой ситуации я хочу разрешить реализацию, которая имеет тип возвращаемой переменной, который будет применяться только к экземпляру, с которым выполняется работа. Я подумывал об использовании class_addMethod из Objective-C, но я не уверен на 100% в его поведении. В документации утверждается, что это можно использовать для добавления методов класса или экземпляра. Добавляет ли вызов этого класса метод только к конкретному экземпляру или к классу, чтобы каждый созданный экземпляр имел метод? Я также читал, что после добавления метода его нельзя удалить.

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

Ответы [ 3 ]

8 голосов
/ 26 ноября 2011

Еще один способ сделать это с помощью динамического подкласса:

- (void)addCustomMethodToObject:(id)object {
  Class objectClass = object_getClass(object);
  SEL selectorToOverride = ...; // this is the method name you want to override

  NSString *newClassName = [NSString stringWithFormat:@"Custom_%@", NSStringFromClass(objectClass)];
  Class c = NSClassFromString(newClassName);
  if (c == nil) {
    // this class doesn't exist; create it
    // allocate a new class
    c = objc_allocateClassPair(objectClass, [newClassName UTF8String], 0);
    // get the info on the method we're going to override
    Method m = class_getInstanceMethod(objectClass, selectorToOverride);
    // add the method to the new class
    class_addMethod(c, selectorToOverride, (IMP)myCustomFunction, method_getTypeEncoding(m));
    // register the new class with the runtime
    objc_registerClassPair(c);
  }
  // change the class of the object
  object_setClass(object, c);
}

id myCustomFunction(id self, SEL _cmd, [other params...]) {
  // this is the body of the instance-specific method
  // you may call super to invoke the original implementation
}

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

Как всегда, обычные предупреждения:

  1. Caveat Implementor : этот код был набран в браузере
  2. Caveat Observer : этот код не подходит для наблюдения значения ключа
  3. Caveat Threader : этот код не выглядит очень поточно-ориентированным
  4. Caveat ARC'er : objc_allocateClassPair() не может быть скомпилировано с ARC.
  5. Caveat Developer : возиться с классом объекта опасно. Существуют совершенно законные способы использования этого вида вуду, но они очень редки. Если вы думаете, что вам нужно это сделать, вы, вероятно, ошибаетесь, и должны опубликовать новый вопрос, в котором говорится: «Это то, что я должен сделать, не так ли?»
2 голосов
/ 26 ноября 2011

class_addMethod() добавляет метод экземпляра к объекту класса или метод класса к объекту метакласса .Другими словами, вы никогда не можете добавить метод только к одному экземпляру класса.

Вместо этого, если вам действительно нужно это поведение, вы можете реализовать -forwardInvocation: , где получающий объектможет решить, достаточно ли информации для выполнения сообщения.Обратите внимание, что реализация -forwardInvocation: обычно требует реализации -methodSignatureForSelector: и -respondsToSelector: .

0 голосов
/ 06 мая 2011

Я не знаком с class_addMethod, но, возможно, это поможет вам уточнить:

Помните, что в Objective-C вы не "вызываете метод", но фактически отправляете сообщение.Так что это безопасно сделать: [anyObject anyMethodName] для любого экземпляра объекта.Этот объект может или не может ответить на сообщение.

Вы можете проверить, будет ли объект или нет, с помощью [anyObject responsedsToSelector: @selector (@ "anyMethodName")], и если это ДА, то перейдитевперед и сделайте вызов [anyObject anyMethodName].Я не могу полностью понять ваше описание проблемы, но похоже, что у вас есть гетерогенный контейнер, полный объектов, которые могут или не могут ответить на вызов.Выполнение этого «responsedsToSelector:» проверка на каждом объекте в контейнере является абсолютно обычной вещью в Objective-C и звучит как хороший дизайн

Если каждый объект возвращает какой-либо другой тип данных, вы можете обработать это, используя 'id 'универсальный тип.То есть id returnData = [anyObject anyMethodName];Затем вы можете либо использовать интроспекцию для returnData, либо вы можете обрабатывать вещи по-разному в зависимости от того, что представляет собой класс anyObject, что проверяется [anyObject class];

Так вот,

if([anyObject class] == MyGreatClass) // recast data to MyGreatClassCoolReturnType

Надеюсь, это поможет ответить на вопрос

...