【问题标题】:Method parameters are nil when called using IMP in Release configuration在 Release 配置中使用 IMP 调用时方法参数为零
【发布时间】:2017-02-28 09:38:50
【问题描述】:

我们的 Swift 应用程序需要一些较低级别的 C/Objective-C 代码,因此我们添加了一个动态库来简化与应用程序的集成。

该库有一个单一的、共享的控制器实例,但回调的样式不适用于闭包,因此我们采用了协议。然而,由于多个类需要使用这个控制器,它需要有多个委托。因此,每个类都将自己注册为委托,当调用协议方法时,它会遍历每个委托,获取选择器的 IMP,然后调用它。

在调试构建时这工作正常,直到我们使用 Release 配置,我们才注意到这些函数的参数在协议方法的实现中为 nil,即使它们在调用时不是 nil。

这就是我们的协议方法的调用方式:

- (void) delegateCall:(SEL)sel withObject:(id)object {
    for (id delegate in self.delegates) {
        if ([delegate respondsToSelector:sel]) {
            IMP imp = [delegate methodForSelector:sel];
            void (*func)(__strong id,SEL,...) = (void (*)(__strong id, SEL, ...))imp;
            func(delegate, sel, object);
        }
    }
}

让我们使用示例协议方法:- (void) blah:(NSNumber * _Null_unspecified)aNumber;

如果我们调用[self delegateCall:@selector(blah:) withObject:@32];,则在blah的实现中对象将为零:

func blah(_ aNumber: NSNumber) {
    if aNumber == nil {
        print("The number is nil somehow?!?!?!") // <-- Release
    } else {
        print("The number is: \(aNumber.intValue)") // <-- Debug, prints 32
    }
}

如果我们在委托的代码中调用方法(而不是使用 IMP),则不会发生问题:

for (id delegate in self.delegates) {
    [delegate blah:@32];
}

【问题讨论】:

  • 作为一个实验,您可以尝试从函数声明中删除可变参数(在-delegateCall:withObject: 中),特别是因为您知道调用(至少在这种情况下)将使用单个争论;可能是这样的:void (*func)(__strong id, SEL, id) = (void (*)(_strong id, SEL, id))imp; func(delegate, sel, object); 我不知道这会改变行为,特别是因为它在调试版本中工作,但也许值得一试,看看运行时是否存在问题,当符号是时确定参数的正确顺序剥离。
  • 增加保留计数func(delegate, sel, (__bridge id)(__bridge_retained void *)object); 可以解决问题吗?
  • @fullofsquirrels 成功了!我也喜欢乔希回答的安全性。随意回答问题,我会接受。
  • 我的建议实际上可能会导致泄漏! :) 我只是想检查内存管理是否存在问题。
  • fullofsquirrels 的想法应该正确修复内存管理。

标签: ios objective-c swift


【解决方案1】:

从未尝试将IMP 实例转换为具有可变参数的函数,我不能确定它会/应该如何工作(例如,它可能涉及解析va_list),但既然你知道您只有一个参数,我认为您应该能够通过在将 IMP 实例转换为函数指针时消除使用可变参数来解决这个特定问题:

- (void) delegateCall:(SEL)sel withObject:(id)object {
    for (id delegate in self.delegates) {
        if ([delegate respondsToSelector:sel]) {
            IMP imp = [delegate methodForSelector:sel];
            void (*func)(__strong id, SEL, id) = (void (*)(_strong id, SEL, id))imp;
            func(delegate, sel, object);
        }
    }
}

既然你知道参数已经是一个id,这应该是一个非常安全的替换。

至于为什么您的原始实现在调试版本中有效,但在发布版本中无效,我只能猜测;这可能与发布版本通常在链接期间剥离所有符号这一事实有关,并且运行时可能能够利用符号(如果存在)来猜测调用时正确的参数顺序?也许编译器在生成对具有固定参数占用空间但 invoked 具有可变参数的函数的调用时,在发布配置中使用了错误的调用约定?如果有人对调试/发布问题有更明确的答案,我会对更多信息感兴趣。

请参阅关于调用约定 here 的讨论,了解使用 reinterpret_cast 的可能替代方案,如果实际上您的问题是由于调用约定不匹配造成的。

【讨论】:

    猜你喜欢
    • 2012-12-19
    • 2017-05-01
    • 1970-01-01
    • 2012-07-31
    • 1970-01-01
    • 2017-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多