18

編集 - 以下の問題を 64 ビット対 32 ビットのアーキテクチャの問題に追跡しました...どのように解決したかについては、投稿された回答を参照してください

SudzCを使用して Web サービスの SOAP コードを生成しました。彼らは、デバイスとシミュレーターの両方で、私が正常に使用できたサンプル アプリケーションを提供します。

その後、アプリの構築を開始しました。空のアプリケーション テンプレート (CoreData と ARC が有効になっている) を使用して、SudzC で生成されたファイルを新しい XCode プロジェクトにインポートしました。

最初の SOAP リクエストを起動して実行しました。すべてがシミュレーターで動作します。次に、デバイス (iOS 7.02 を実行する iPhone 5S) で最初のテストを行いました。EXC_BAD_ACCESSSOAP 要求が実行されるたびに、デバイスはエラーをスローします。

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)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 のインスタンスでも同じことを試しました...今、View Controller でプロパティを作成し、そのプロパティを値に設定することで問題を回避できましたが、これを使用してこれを修正する方法に本当に興味があります元のコード。SudzC からダウンロードしたサンプル アプリに戻って、それがどのように機能しているかを確認したところ、そのプロジェクトでは ARC が有効になっていないことがわかりました (!)。

誰か教えてください:

1)割り当てが解除されているという私の仮定が正しい場合output、ハンドラーメソッドが不正なメモリアドレスを参照する原因になりますか?

2) これがシミュレーターで機能するのはなぜですか? これは、シムに使用可能なメモリが多く、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 メッセージをディスパッチする 上記のキャスト規則の例外は、メッセージを送信する Objective-C ランタイムで objc_msgSend 関数またはその他の同様の関数を呼び出す場合です。メッセージ関数のプロトタイプは可変長形式ですが、Objective-C ランタイムによって呼び出されるメソッド関数は同じプロトタイプを共有しません。Objective-C ランタイムは、メソッドを実装する関数に直接ディスパッチするため、前述のように、呼び出し規約が一致しません。したがって、objc_msgSend 関数を、呼び出されるメソッド関数と一致するプロトタイプにキャストする必要があります。

リスト 2-14 は、低レベルのメッセージ関数を使用してオブジェクトにメッセージをディスパッチする適切な形式を示しています。この例では、doSomething: メソッドは 1 つのパラメーターを取り、可変長形式を持ちません。メソッド関数のプロトタイプを使用して、objc_msgSend 関数をキャストします。メソッド関数は常に最初の 2 つのパラメーターとして 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 に答える