18

编辑-我已将以下问题跟踪到 64 位与 32 位体系结构问题...请参阅我发布的答案以了解如何解决

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

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

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

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

肥皂请求.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指向我的视图控制器,并self.action指向这个方法:

任务视图控制器.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)valueint

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 释放没有那么激进......

objc_msgSend3)假设我想保持通话,我该如何解决这个问题?我想了解如何/为什么会发生这种情况

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

谢谢!

4

1 回答 1

38

好的 - 所以经过更多的拉扯和研究,我终于意识到这可能是 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

该文件提到以下内容:

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

一切正常!

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

于 2013-10-22T03:02:44.623 回答