【问题标题】:SudzC ARC version - objc_msgSend call causes EXC_BAD_ACCESS using 64-bit architectureSudzC ARC 版本 - objc_msgSend 调用导致 EXC_BAD_ACCESS 使用 64 位架构
【发布时间】:2013-10-22 01:17:24
【问题描述】:

编辑 - 我已将以下问题跟踪到 64 位与 32 位架构问题...请参阅我发布的答案以了解我的解决方法

我使用 SudzC 为 Web 服务生成 SOAP 代码。他们为您提供了一个示例应用程序,我可以在设备和模拟器上成功使用它。

然后我开始构建我的应用程序。我使用空白应用程序模板(启用了 CoreData 和 ARC)将 SudzC 生成的文件导入到新的 XCode 项目中。

我启动并运行了第一个 SOAP 请求——在模拟器中一切正常——然后我开始在设备(运行 iOS 7.02 的 iPhone 5S)上进行第一次测试。每次运行 SOAP 请求时,设备都会抛出 EXC_BAD_ACCESS 错误。

我已将此追踪到SoapRequest.m 文件,特别是connectionDidFinishLoading 方法。此方法使用objc_msgSend 调用将 SOAP 响应数据发送回另一个类(在本例中为我的视图控制器)中的处理程序方法。代码如下:

SoapRequest.m:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSError* error;
    if(self.logging == YES) {
        NSString* response = [[NSString alloc] initWithData: self.receivedData encoding: NSUTF8StringEncoding];
        NSLog(@"%@", response);
    }

    CXMLDocument* doc = [[CXMLDocument alloc] initWithData: self.receivedData options: 0 error: &error];
    if(doc == nil) {
        [self handleError:error];
        return;
    }

    id output = nil;
    SoapFault* fault = [SoapFault faultWithXMLDocument: doc];

    if([fault hasFault]) {
        if(self.action == nil) {
            [self handleFault: fault];
        } else {
            if(self.handler != nil && [self.handler respondsToSelector: self.action]) {
                objc_msgSend(self.handler, self.action, fault);
            } else {
                NSLog(@"SOAP Fault: %@", fault);
            }
        }
    } else {
        CXMLNode* element = [[Soap getNode: [doc rootElement] withName: @"Body"] childAtIndex:0];
        if(deserializeTo == nil) {
            output = [Soap deserialize:element];
        } else {
            if([deserializeTo respondsToSelector: @selector(initWithNode:)]) {
                element = [element childAtIndex:0];
                output = [deserializeTo initWithNode: element];
            } else {
                NSString* value = [[[element childAtIndex:0] childAtIndex:0] stringValue];
                output = [Soap convert: value toType: deserializeTo];
            }
        }
        if(self.action == nil) { self.action = @selector(onload:); }
        if(self.handler != nil && [self.handler respondsToSelector: self.action]) {
            objc_msgSend(self.handler, self.action, output);
        } else if(self.defaultHandler != nil && [self.defaultHandler respondsToSelector:@selector(onload:)]) {
            [self.defaultHandler onload:output];
        }

    }
    conn = nil;
}

所以objc_msgSend(self.handler, self.action, output); 这行似乎是我的问题所在。 self.handler 指向我的 View Controller,self.action 指向这个方法:

TasksViewController.m:

- (void) findItemHandler: (id) value {

    // Handle errors
    if([value isKindOfClass:[NSError class]]) {
        NSLog(@"%@", value);
        return;
    }

    // Handle faults
    if([value isKindOfClass:[SoapFault class]]) {
        NSLog(@"%@", value);
        return;
    }

    // Do something with the id result
    NSLog(@"FindItem returned the value: %@", value);
}

重新进入这个方法是我崩溃的地方。看起来(id)value 没有从 SoapRequest 类中恢复过来。我认为它正在被 ARC 释放。我已经通过将(id)value 替换为int 来测试调用:

objc_msgSend(self.handler, self.action, 1);

- (void)findItemHandler:(int)value

这行得通。假设问题是 value 变量过早地被破坏,我尝试了一些方法来保持它的保留。我在 SoapRequest.m 中添加了一个属性:

@property (nonatomic, strong) id value;

然后通过了:

self.value = output;
objc_msgSend(self.handler, self.action, self.value);

同样的问题。我也用 SoapRequest 的实例尝试了同样的事情......现在,我能够通过在视图控制器中创建一个属性并将该属性设置为该值来解决这个问题,但我真的很好奇如何使用它来解决这个问题原始代码。我回到从 SudzC 下载的示例应用程序,看看它是如何工作的,结果发现该项目没有启用 ARC(!)。

谁能告诉我:

1) 如果我的假设是正确的,output 正在被释放,导致处理程序方法引用错误的内存地址?

2) 为什么这适用于模拟器?我认为这是因为 sim 有更多可用内存,因此它对 ARC 释放没有那么激进...

3) 假设我想保留objc_msgSend 电话,我该如何解决这个问题?我想了解这是如何/为什么会发生的

4) 如果 SudzC 在此处使用 objc_msgSend 是正确的,据我所知,除非在极少数情况下,否则直接调用它是不好的做法?

谢谢!

【问题讨论】:

    标签: ios objective-c automatic-ref-counting sudzc


    【解决方案1】:

    好的 - 经过更多的研究和研究,我终于意识到这可能是 64 位与 32 位的问题。这是我升级到新 Xcode 后开发的第一个应用程序。我去了构建设置并将架构从“标准架构(包括 64 位)(armv7、armv7s、arm64)”更改为“标准架构(armv7、armv7s)”。这解决了问题!

    然后我回去研究为什么会发生这种情况。我找到了这个 Apple 64 位转换指南:https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaTouch64BitGuide/ConvertingYourAppto64-Bit/ConvertingYourAppto64-Bit.html

    该文件提到以下内容:

    使用方法函数的原型 An 分发 Objective-C 消息 上述转换规则的例外是当您调用时 objc_msgSend 函数或任何其他类似的函数 发送消息的 Objective-C 运行时。虽然原型为 消息函数有一个可变参数形式,方法函数是 Objective-C 运行时调用的不共享相同的原型。 Objective-C 运行时直接调度到函数 实现该方法,因此调用约定不匹配,如 前面描述过。因此,您必须强制转换 objc_msgSend 函数到与被调用的方法函数匹配的原型。

    清单 2-14 显示了将消息发送到 使用低级消息函数的对象。在这个例子中, doSomething:方法采用单个参数并且没有 可变形式。它使用原型转换 objc_msgSend 函数 的方法函数。请注意,方法函数总是需要一个 id 变量和选择器作为其前两个参数。之后 objc_msgSend 函数被强制转换为函数指针,调用是 通过同一个函数指针调度。

    使用此信息,我更改了以下行:

    objc_msgSend(self.handler, self.action, self.value);

    到:

    id (*response)(id, SEL, id) = (id (*)(id, SEL, id)) objc_msgSend;
    response(self.handler, self.action, output);
    

    一切正常!

    希望这对其他人有帮助...

    【讨论】:

    • 谢谢你。这个问题在 64 位模拟器上不出现,太可怕了!
    • 你能看到我的问题吗?我想我做错了什么:stackoverflow.com/questions/21843500/…
    • 非常感谢!!在谷歌上搜索了很长时间:)
    • FWIW,我遇到了这个问题并使用 performSelector 解决了它:[self.handler performSelector:self.action withObject:self.value];
    • 使用此代码同时支持 ARM 64 和 32,因为上面的代码会在 32 位崩溃, ((void(*)(id, SEL, id))objc_msgSend)(self.handler, self.action, 输出);
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多