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

时间:2022-09-04 11:20:09

I am trying to write some dynamic code where a user can try calling a method from a specific instance of a class and have it be resolved at runtime. The implementation to retrieve the information exists but the method to access it does not because it is on a per instance basis.

我正在尝试编写一些动态代码,用户可以尝试从类的特定实例调用方法,并在运行时解析它。检索信息的实现存在,但访问它的方法不是因为它基于每个实例。

For example, a user may want to call a method named "getSomething" which doesn't exist in the class:

例如,用户可能想要调用一个名为“getSomething”的方法,该方法在类中不存在:

[someInstance getSomething]

In this situation, I want to have an implementation resolved which has a variable return type that will only apply to the instance being worked on. I was considering using class_addMethod from the Objective-C but I am not 100% sure of its behavior. On the documentation it claims that this can be used to add class or instance methods. Does calling this class add the method to only the specific instance or to the class so that every instance created afterward will have the method on it? I also read that once a method is added you can't remove it.

在这种情况下,我希望有一个实现已解析,它具有一个仅适用于正在处理的实例的变量返回类型。我正在考虑使用Objective-C中的class_addMethod,但我并不是100%肯定它的行为。在文档中,它声称这可以用于添加类或实例方法。调用此类是否仅将方法添加到特定实例或类中,以便之后创建的每个实例都将具有该方法?我还读到,一旦添加了一个方法,你就无法删除它。

Perhaps my approach isn't correct so if any alternatives are known I would appreciate it. I cannot use message forwarding because there is no class that understands the selector already implemented.

也许我的方法不正确,所以如果知道任何替代方案我会很感激。我不能使用消息转发,因为没有类可以理解已经实现的选择器。

3 个解决方案

#1


8  

Another way you could do this is with a dynamic subclass:

另一种方法是使用动态子类:

- (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
}

After doing this, only object will have received the overridden method, because it will be the only thing that's an instance of the special class. Also, this code only overrides instance methods, but it wouldn't be hard to modify it to override class methods.

执行此操作后,只有对象将收到重写方法,因为它将是唯一的特殊类的实例。此外,此代码仅覆盖实例方法,但修改它以覆盖类方法并不困难。

As always, the usual warnings:

一如既往,通常的警告:

  1. Caveat Implementor: this code was typed in a browser
  2. 警告执行者:此代码在浏览器中输入

  3. Caveat Observer: this code does not play well with key-value observing
  4. 警告观察者:这个代码与键值观察不一致

  5. Caveat Threader: this code doesn't look very thread-safe
  6. 警告Threader:此代码看起来不是非常线程安全的

  7. Caveat ARC'er: objc_allocateClassPair() cannot be compiled with ARC.
  8. 警告ARC'er:无法使用ARC编译objc_allocateClassPair()。

  9. Caveat Developer: mucking around with an object's class is a dangerous thing. There are perfectly legitimate uses for this sort of voodoo, but they are very rare. If you think you need to do this, you're probably wrong, and should post a new question here saying: "this is what I think I need to do; do I?"
  10. 警告开发人员:使用对象的类来解决这个问题是一件危险的事情。这种伏都教有完全合法的用途,但它们非常罕见。如果你认为你需要这样做,那你可能错了,并且应该在这里发一个新问题说:“这是我认为我需要做的;是吗?”

#2


2  

class_addMethod() adds an instance method to a class object or a class method to a metaclass object. In other words, you can never add a method just to one instance of a class.

class_addMethod()将类对象或类方法的实例方法添加到元类对象。换句话说,您永远不能只将方法添加到类的一个实例。

Instead, if you really need this behavior, you could implement -forwardInvocation:, where the receiving object can decide if it has enough information to fulfill the message. Note that an implementation of -forwardInvocation: typically requires implementing -methodSignatureForSelector: and -respondsToSelector: as well.

相反,如果您确实需要此行为,则可以实现-forwardInvocation:,其中接收对象可以决定它是否有足够的信息来实现消息。注意-forwardInvocation的实现:通常还需要实现-methodSignatureForSelector:和-respondsToSelector :.

#3


0  

I'm not familiar with class_addMethod, but maybe this can help clarify for you:

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

Remember that in Objective-C you aren't "calling a method" but you're actually sending a message. So it's safe to do: [anyObject anyMethodName] on any instantiated object. This object may or may not respond to the message.

请记住,在Objective-C中,您不是“调用方法”,而是实际上是在发送消息。所以在任何实例化对象上都可以安全地执行:[anyObject anyMethodName]。该对象可能会也可能不会响应该消息。

You can check whether an object will or not by using [anyObject respondsToSelector:@selector(@"anyMethodName")] check, and if that's YES, then go ahead and do [anyObject anyMethodName] call. I can't fully understand your problem description but it sounds like you have a heterogeneous container full of objects that may or may not respond to the call. Doing this "respondsToSelector:" check on each object in the container is a totally normal thing in Objective-C and sounds like good design

您可以使用[anyObject respondsToSelector:@selector(@“anyMethodName”)]检查来检查对象是否存在,如果是,则继续执行[anyObject anyMethodName]调用。我无法完全理解您的问题描述,但听起来您有一个异构容器,其中包含可能会或可能不会响应呼叫的对象。执行此“respondsToSelector:”检查容器中的每个对象在Objective-C中是完全正常的事情,听起来像是好的设计

If every object returns some different type of data, you can handle that using the 'id' generic type. That is, id returnData = [anyObject anyMethodName]; Then, you can either use introspection on the returnData, or you could handle things differently based on what the class of 'anyObject' is, checked by [anyObject class];

如果每个对象都返回一些不同类型的数据,则可以使用“id”泛型类型处理该数据。也就是说,id returnData = [anyObject anyMethodName];然后,你可以在returnData上使用内省,或者你可以根据'anyObject'的类来处理不同的东西,由[anyObject class]检查;

So like,

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

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

I hope this helps answer the question

我希望这有助于回答这个问题

#1


8  

Another way you could do this is with a dynamic subclass:

另一种方法是使用动态子类:

- (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
}

After doing this, only object will have received the overridden method, because it will be the only thing that's an instance of the special class. Also, this code only overrides instance methods, but it wouldn't be hard to modify it to override class methods.

执行此操作后,只有对象将收到重写方法,因为它将是唯一的特殊类的实例。此外,此代码仅覆盖实例方法,但修改它以覆盖类方法并不困难。

As always, the usual warnings:

一如既往,通常的警告:

  1. Caveat Implementor: this code was typed in a browser
  2. 警告执行者:此代码在浏览器中输入

  3. Caveat Observer: this code does not play well with key-value observing
  4. 警告观察者:这个代码与键值观察不一致

  5. Caveat Threader: this code doesn't look very thread-safe
  6. 警告Threader:此代码看起来不是非常线程安全的

  7. Caveat ARC'er: objc_allocateClassPair() cannot be compiled with ARC.
  8. 警告ARC'er:无法使用ARC编译objc_allocateClassPair()。

  9. Caveat Developer: mucking around with an object's class is a dangerous thing. There are perfectly legitimate uses for this sort of voodoo, but they are very rare. If you think you need to do this, you're probably wrong, and should post a new question here saying: "this is what I think I need to do; do I?"
  10. 警告开发人员:使用对象的类来解决这个问题是一件危险的事情。这种伏都教有完全合法的用途,但它们非常罕见。如果你认为你需要这样做,那你可能错了,并且应该在这里发一个新问题说:“这是我认为我需要做的;是吗?”

#2


2  

class_addMethod() adds an instance method to a class object or a class method to a metaclass object. In other words, you can never add a method just to one instance of a class.

class_addMethod()将类对象或类方法的实例方法添加到元类对象。换句话说,您永远不能只将方法添加到类的一个实例。

Instead, if you really need this behavior, you could implement -forwardInvocation:, where the receiving object can decide if it has enough information to fulfill the message. Note that an implementation of -forwardInvocation: typically requires implementing -methodSignatureForSelector: and -respondsToSelector: as well.

相反,如果您确实需要此行为,则可以实现-forwardInvocation:,其中接收对象可以决定它是否有足够的信息来实现消息。注意-forwardInvocation的实现:通常还需要实现-methodSignatureForSelector:和-respondsToSelector :.

#3


0  

I'm not familiar with class_addMethod, but maybe this can help clarify for you:

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

Remember that in Objective-C you aren't "calling a method" but you're actually sending a message. So it's safe to do: [anyObject anyMethodName] on any instantiated object. This object may or may not respond to the message.

请记住,在Objective-C中,您不是“调用方法”,而是实际上是在发送消息。所以在任何实例化对象上都可以安全地执行:[anyObject anyMethodName]。该对象可能会也可能不会响应该消息。

You can check whether an object will or not by using [anyObject respondsToSelector:@selector(@"anyMethodName")] check, and if that's YES, then go ahead and do [anyObject anyMethodName] call. I can't fully understand your problem description but it sounds like you have a heterogeneous container full of objects that may or may not respond to the call. Doing this "respondsToSelector:" check on each object in the container is a totally normal thing in Objective-C and sounds like good design

您可以使用[anyObject respondsToSelector:@selector(@“anyMethodName”)]检查来检查对象是否存在,如果是,则继续执行[anyObject anyMethodName]调用。我无法完全理解您的问题描述,但听起来您有一个异构容器,其中包含可能会或可能不会响应呼叫的对象。执行此“respondsToSelector:”检查容器中的每个对象在Objective-C中是完全正常的事情,听起来像是好的设计

If every object returns some different type of data, you can handle that using the 'id' generic type. That is, id returnData = [anyObject anyMethodName]; Then, you can either use introspection on the returnData, or you could handle things differently based on what the class of 'anyObject' is, checked by [anyObject class];

如果每个对象都返回一些不同类型的数据,则可以使用“id”泛型类型处理该数据。也就是说,id returnData = [anyObject anyMethodName];然后,你可以在returnData上使用内省,或者你可以根据'anyObject'的类来处理不同的东西,由[anyObject class]检查;

So like,

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

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

I hope this helps answer the question

我希望这有助于回答这个问题