Objective-C中的class_addMethod方法只能在特定实例上工作吗?

8
我正在尝试编写一些动态代码,用户可以尝试从特定类的实例中调用方法,并在运行时解析它。检索信息的实现已经存在,但访问它的方法不存在,因为它是基于每个实例的。例如,用户可能想调用一个名为“getSomething”的方法,该方法在类中不存在:
[someInstance getSomething]

在这种情况下,我想要实现一个具有可变返回类型的实例,该类型仅适用于正在处理的实例。我考虑使用 Objective-C 中的class_addMethod,但我不能确定其行为是否完全正确。文档中声称可以将其用于添加类或实例方法。调用此类会将方法添加到特定实例还是类中,以便每个创建后的实例都将在其中包含该方法?我还读到过一旦添加了方法,就无法删除它的内容。
也许我的方法不正确,如果您知道任何其他替代方案,我将不胜感激。由于没有已经实现选择器的类,因此无法使用消息转发。
3个回答

8
您可以使用动态子类的方式来实现这一点:
- (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. 实现警告:此代码是在浏览器中输入的。
  2. 观察者警告:此代码不适用于键值观察。
  3. 线程安全警告:此代码看起来不太线程安全。
  4. ARC 警告objc_allocateClassPair() 无法与 ARC 编译。
  5. 开发者警告:深入探究对象的类是危险的事情。这种巫术有完全合法的用途,但非常罕见。如果您认为需要这样做,则可能错了,并应在此发布新问题,问:“我认为我需要这样做;我需要吗?”

1
这里还涉及到一些时间/空间的权衡。如果每个实例可能支持不同的方法,运行时动态类的数量可能会变得难以控制。相反,如果真的只有一个方法是某些实例具有而其他实例没有的,那么方法转发可能会产生无法接受的速度成本,而动态子类可能会更快。 - Justin Spahr-Summers

2

class_addMethod()可以向一个类对象添加实例方法,或者向元类对象添加类方法。换句话说,您永远不能仅向类的一个实例添加方法。

如果您真的需要这种行为,可以实现-forwardInvocation:,其中接收对象可以决定是否有足够的信息来执行消息。请注意,实现-forwardInvocation:通常需要同时实现-methodSignatureForSelector:-respondsToSelector:


0

我不熟悉class_addMethod,但也许这可以帮助你理解:

请记住,在Objective-C中,你并不是“调用一个方法”,而是发送一条消息。因此,在任何实例化的对象上执行[anyObject anyMethodName]是安全的。这个对象可能会或者不会响应这个消息。

你可以使用[anyObject respondsToSelector:@selector(@"anyMethodName")]检查对象是否会响应该消息。如果是,则继续执行[anyObject anyMethodName]调用。我无法完全理解你的问题描述,但听起来你有一个充满了可能会或者不会响应该调用的对象的异构容器。在Objective-C中,对容器中的每个对象进行“respondsToSelector:”检查是一件非常正常的事情,也是很好的设计。

如果每个对象返回一些不同类型的数据,你可以使用'id'泛型类型来处理。也就是说,id returnData = [anyObject anyMethodName]; 然后,你可以根据returnData的类别进行内省,或者根据'anyObject'的类别进行不同的处理,通过[anyObject class]进行检查。

就像这样:

if([anyObject class] == MyGreatClass) // 将数据重新转换为MyGreatClassCoolReturnType

希望这能回答你的问题


感谢您抽出时间回复我的问题。虽然这并不是我正在寻找的答案,但它确实让我对其他一些不清楚的领域有了一些了解。 - gtaborga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接