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

Jim*_*Jim 18 objective-c ios sudzc automatic-ref-counting

编辑 - 我已将以下问题跟踪到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响应数据发送回另一个类(在本例中为我的视图控制器)中的处理程序方法.这是代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

所以这条线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);
}
Run Code Online (Sandbox Code Playgroud)

重新进入这种方法是我崩溃的地方.它似乎(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);
Run Code Online (Sandbox Code Playgroud)

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

有人能告诉我:

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

2)为什么这在模拟器上有效?我认为这是因为sim有更多的内存可用,所以它不像ARC解除分配那么激进......

3)我怎么能解决这个问题,假设我想保持objc_msgSend通话?我想了解这是怎么发生的

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

谢谢!

Jim*_*Jim 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);
Run Code Online (Sandbox Code Playgroud)

一切正常!

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

  • 这次真是万分感谢.这个问题没有出现在64位模拟器上,这太可怕了! (4认同)
  • FWIW,我遇到了这个问题并通过使用performSelector来解决它:```[self.handler performSelector:self.action withObject:self.value];``` (2认同)
  • 使用此代码支持ARM 64和32,因为上面的代码将以32位崩溃,((void(*)(id,SEL,id))objc_msgSend)(self.handler,self.action,output); (2认同)