【问题标题】:Trapping Objective C classes捕获 Objective C 类
【发布时间】:2011-08-27 11:03:35
【问题描述】:

我正在尝试捕获/覆盖在我无法访问的对象中调用的某些方法。我希望能够对在系统对象中实例化的对象进行子类化。

例如,当我实例化一个 UIWebView 时,它会在内部实例化一个 UIScrollView。我希望能够对该 UIScrollView 进行子类化,这样我就可以通过对其进行子类化来修改其行为的某些方面。这可能吗?

我一直在尝试通过使用一个类别来做到这一点,@implementation UIScrollView (MyScrollView),然后覆盖 + alloc,并返回我自己的对象,它是 UIScrollView 的子类。但是,我遇到了一些问题。基本上,我似乎无法达到原来的 [UIScrollView alloc] 方法。也许我可以实现我自己的 + alloc 方法来复制 [NSObject alloc] 行为?

有没有更简单的方法来做到这一点?我目前的方法有什么问题?

最后,这样做会通过 Apple 的审核流程吗?我没有打电话给任何无证的东西。

【问题讨论】:

  • 您添加到类别中的方法将替换现有的同名方法;因此,您将无法访问“原始方法”。
  • 是的,我已经收集到了。多么烦人。有没有一种直接的方法来为我的子类对象实现 + alloc 方法?这实际上只是一个内存管理调用,对吧?话又说回来,我如何准确地告诉运行时这是哪种类型的对象??
  • 如果您对私人课程的内幕感到困惑,这通常会受到强烈反对(并可能导致拒绝——您必须向 ADC 寻求明确的答案)。但是,实际上,您不想这样做。保证会导致神秘的崩溃、令人头疼的维护问题,以及在任何给定的软件更新后您的应用是否能正常工作的赌注。

标签: objective-c ios nsobject


【解决方案1】:

类别的问题是,它们不允许正确覆盖,因为您将无法再使用原始方法。

要实现这种(原谅我的法语)肮脏技巧,你可以试试MethodSwizzling。作为最后的手段,这有时很有价值。我过去主要使用它进行调试,也用于逆向工程。

对于您关于 Apple 审批流程的问题,我猜想以如此激进的方式更改 UIWebView 的默认实现时,您遇到麻烦的可能性非常高。

【讨论】:

【解决方案2】:

不要使用类别来破坏现有方法。那就是疯狂。

在您的情况下,我会通过操作视图层次结构而不是分配机制,将滚动视图替换为您的自定义滚动视图子类的实例。

【讨论】:

  • 这远没有 swizzling 脆弱,但 - 仍然 - 一定会以某种方式导致麻烦。
  • 嗯,我会说这是非常冒险的并且不太可能工作,因为实际上可以保证父级 (UIWebView) 将实例指针保持在其 UIScrollView 上,并通过该指针与它进行通信。通过修改视图层次结构(但不告诉 UIWebView),我的替代 UIScrollView 可能不会从 UIWebView 获得它所需要的爱。
  • 任何视图都有一个指向其父视图的指针,当您将其放入视图层次结构时会设置该指针。
  • 没错,但很少有人依赖它与他们的子视图进行通信。因为他们手动创建了该对象,所以他们可能在对象中存储了一个指向它的实例指针,以便于访问——“myScrollView”或其他东西……使用此解决方案,该实例指针将指向错误的 UIScrollView。
【解决方案3】:

@Till 的 Method Swizzling 方法就是您实现这一目标的方式。 Mike Ash 有一个better write-ups 如何做到这一点。我目前更喜欢他的直接覆盖方法,尽管我对其进行了一些修改,如下所示:

#import <objc/runtime.h>

@interface NSObject (RNSwizzle)
+ (IMP)swizzleSelector:(SEL)origSelector withIMP:(IMP)newIMP;
@end

@implementation NSObject (RNSwizzle)

+ (IMP)swizzleSelector:(SEL)origSelector withIMP:(IMP)newIMP {
  Class class = [self class];
  Method origMethod = class_getInstanceMethod(class, origSelector);
  IMP origIMP = method_getImplementation(origMethod);

  if(!class_addMethod(self, origSelector, newIMP,
                      method_getTypeEncoding(origMethod)))
  {
    method_setImplementation(origMethod, newIMP);
  }

  return origIMP;
}

@end

鉴于他的例子,你会这样使用我的方法:

gOrigDrawRect = [UIView swizzleSelector:@selector(drawRect:)
                                withIMP:OverrideDrawRect];

这些都是记录在案的调用,不使用 Apple 私有 API。 Apple 有时确实会拒绝那些对预期 UI 行为做出过大改变的应用程序(不管它们是如何做的),但我已经交付了具有相当戏剧性的幕后 UI 修改的东西,而且没有麻烦,这不依赖于私有API。

这并不意味着 Method Swizzling 是稳定的。这是一种脆弱的技术,确实会让您更加依赖未记录的内部结构。这可能意味着,如果 Apple 做出改变,你就必须更加努力。

(深呼吸)

您可以在此处使用另一种方法,只要您不需要 ivar 存储即可。您可以获取滚动视图并将其转换为您自己的子类。我有没有提到您必须没有自己的 ivar 存储?如果您在此分配 ivar,您将创建的错误和令人费解的问题。 Deusty 在his blog 中写得很好。 (我在他的代码中添加了一个 ivar 并遇到了上面提到的令人难以置信的令人费解的错误。)

同样,这是一种脆弱、疯狂、危险的技术。它有时也是满足您需要的最优雅和可维护的方式。

【讨论】:

    【解决方案4】:

    我还没有真正做到,所以我不是 100% 确定,但我相信你可以做你想做的事,但我不认为你可以用你目前的方法。我相信您将不得不使用 Objective-C 运行时来拦截方法。如果您还没有,请阅读The Objective-C Runtime Reference

    好的,那就是可以现在应该你和你会侥幸逃脱吗?

    您通过这样做完成的任何事情都可能非常脆弱。 Apple 对框架所做的任何未来更改都可能会破坏您的拦截代码。所以就个人而言,我会以“非常可疑”的方式提交此文件,即您是否应该考虑发布依赖于此的产品。

    至于 你会侥幸逃脱吗? 显然,我不能代表 Apple,但我敢打赌不会。您是正确的,严格来说您没有调用未记录的代码,但是您正在干扰私有方法的功能,因此您肯定是在面对 App Store 条款的精神。 p>

    当然,这一切都只是我的看法,你想怎么想就怎么想。

    【讨论】:

      【解决方案5】:

      我找到了一个相对简单、相当安全的解决方案。它并不完美,并非在所有情况下都有效,但在某些情况下可能有用。

      基本上,我正在做的是使用一个类别来捕获我想要的类的 + alloc 调用。然后我实现了我自己的 + alloc 方法,它简单地分配并返回我的 subclassed 类型的对象。然后我可以覆盖我的子类对象中的任何方法,并且它们会从 UIWebView 正确调用。这是一种自动子类化机制:目标类的每个实例(在本例中为 UIScrollView)都会自动子类化,即使是其他人创建的。

      这是我的 + alloc 方法的样子:

      + (id) alloc
      {
          return class_createInstance([MyUIScrollView class], 0);
      }
      

      然后我实现了一个简单的 MyUIScrollView 类,它继承自 UIScrollView 并允许我覆盖我可能需要的任何方法,这使我可以更好地控制父 UIWebView 的行为。酷!

      几个问题:

      • 如果您的应用程序中有多个 UIScrollView,则需要小心,因为它们现在基本上都会变成 M​​yUIScrollView!解决方案是在 MyUIScrollView 中有一个标志,以确保在实例不属于您的特定 UIWebView 的情况下它处于休眠状态(基本上只是将所有内容转发到超类)。这很容易通过子类化 UIWebView 并覆盖其视图层次结构方法(addSubview: 等)来完成,这样一旦 UIWebView 实例化并将其内部 UIScrollView 添加到视图层次结构中,您就可以在子类化实例中设置一个标志告诉它做任何你想做的事情。

      • 这仅适用于在应用程序的任何位置都没有子类化目标类(UIScrollView 等)的应用程序!例如,我可以使用这种技术成功地将自己挤入 UIWebView 的 UIScrollView 并覆盖我想在其中使用的任何方法,但是如果我的应用程序中的某些其他组件是 UIScrollView 的子类(包括我自己的代码),我会遇到麻烦,因为我基本上禁用了这个类的所有子类化。应用程序中的所有 UIScrollView 都将是 MyUIScrollView 类型!

      底线:不是推荐的方法,但它看起来是解决此问题的最简洁的方法。

      【讨论】:

        猜你喜欢
        • 2011-04-26
        • 1970-01-01
        • 2016-05-09
        • 1970-01-01
        • 2020-02-15
        • 2013-10-03
        • 2011-07-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多