SudzC ARC版本 - objc_msgSend调用使用64位架构导致EXC_BAD_ACCESS

时间:2013-10-22 01:17:24

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

编辑 - 我已将以下问题跟踪到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

这很有效。假设问题是值变量过早被破坏,我尝试了一些事情来保持它。我在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的使用是正确的,据我所知,除非在极少数情况下直接调用它是不好的做法?

谢谢!

1 个答案:

答案 0 :(得分:38)

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

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

该文件提到以下内容:

  

使用方法函数的原型发送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);

一切正常!

希望这会帮助别人......