15

私は簡単なことをしようとしています。インターネットから画像を読み取り、それを iPhone のアプリのドキュメント ディレクトリに保存し、そのファイルから読み戻して、後で他の処理を実行できるようにします。ファイルの書き込みは正常に機能しますが、読み戻そうとすると、GDB で EXC_BAD_ACCESS エラーが発生し、解決方法がわかりません。私のコードは基本的に次のようになります。

-(UIImage *) downloadImageToFile {
NSURL * url = [[NSURL alloc] initWithString: self.urlField.text];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsDirectory = [paths objectAtIndex:0];

[paths release]
NSString * path = [documentsDirectory stringByAppendingString:@"/testimg.png"];

NSData * data = [[NSData alloc] initWithContentsOfURL:url];

[data writeToFile:path atomically:YES];

return [[UIImage alloc] initWithContentsOfFile:path];
}

ファイルから UIImage を初期化しようとすると、return ステートメントでコードが失敗します。何か案は?

編集:もともとコードの問題だったリリースを追加することを怠った。

4

8 に答える 8

47

注:これは、特に非ARCメモリー管理に適用されます。

これは非常に多くの見解を持っており、チェックされた回答は「CodeはObjective-Cでのメモリ管理の仕組みに関する知識の深刻な欠如を示しています」と適切に述べているので、特定のエラーを指摘している人はいないので、それらに触れた答え。

メソッドの呼び出しについて覚えておく必要のあるベースラインレベルのルール:

  • メソッド呼び出しにallocnewcopyretainという単語が含まれている場合、作成されたオブジェクトの所有権があります。¹オブジェクトの所有権がある場合、それを解放するのは私たちの責任です。

  • メソッド呼び出しにこれらの単語が含まれていない場合、作成されたオブジェクトの所有権はありません。¹オブジェクトの所有権がない場合、オブジェクトを解放することは私たちの責任ではないため、絶対に行わないでください。

OPのコードの各行を見てみましょう。

-(UIImage *) downloadImageToFile {

新しい方法を始めました。そうすることで、作成された各オブジェクトが存在する新しいコンテキストを開始しました。これを少し覚えておいてください。次の行:

    NSURL * url = [[NSURL alloc] initWithString: self.urlField.text];

私たちが所有している:そこにあるallocurlという単語は、私たちがオブジェクトの所有権を持っており、自分でそれを解放する必要があることを示しています。そうしないと、コードがメモリリークを起こします。

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

私たちは所有していませんpaths:4つの魔法の言葉を使用していないので、私たちは所有権を持っておらず、自分でそれを解放してはなりません。

    NSString *documentsDirectory = [paths objectAtIndex:0];

私たちは所有していませんdocumentsDirectory:魔法の言葉はありません=所有権はありません。

    [paths release]

数行前に戻ると、パスを所有していないことがわかります。したがって、このリリースでは、存在しなくなったものにアクセスしようとすると、EXC_BAD_ACCESSがクラッシュします。

    NSString * path = [documentsDirectory stringByAppendingString:@"/testimg.png"];

私たちは所有していませんpath:魔法の言葉はありません=所有権はありません。

    NSData * data = [[NSData alloc] initWithContentsOfURL:url];

私たちが所有している:そこにあるallocdataという単語は、私たちがオブジェクトの所有権を持っており、自分でそれを解放する必要があることを示しています。そうしないと、コードがメモリリークを起こします。

次の2行は、何も作成または解放しません。次に、最後の行があります。

}

メソッドが終了したため、変数のコンテキストは終了しました。コードを見ると、との両方を所有していることがわかりますが、urlどちらdataもリリースしていません。その結果、このメソッドが呼び出されるたびに、コードがメモリリークを起こします。

NSURLオブジェクトurlはそれほど大きくないので、リークに気付かない可能性がありますが、それでもクリーンアップする必要がありますが、リークする理由はありません。

NSDataオブジェクトはpng画像であり、data非常に大きくなる可能性があります。このメソッドが呼び出されるたびに、オブジェクトのサイズ全体がリークされます。テーブルセルが描画されるたびにこれが呼び出されたと想像してください。アプリ全体がクラッシュするのにそれほど時間はかかりません。

では、問題を解決するために何をする必要がありますか?非常に簡単です。オブジェクトが不要になったらすぐに解放する必要があります。通常は、最後に使用した直後にオブジェクトを解放する必要があります。

-(UIImage *) downloadImageToFile {

    // We own this object due to the alloc
    NSURL * url = [[NSURL alloc] initWithString: self.urlField.text];

    // We don't own this object
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    // We don't own this object
    NSString *documentsDirectory = [paths objectAtIndex:0];

    //[paths release] -- commented out, we don't own paths so can't release it

    // We don't own this object
    NSString * path = [documentsDirectory stringByAppendingString:@"/testimg.png"];

    // We own this object due to the alloc
    NSData * data = [[NSData alloc] initWithContentsOfURL:url];

    [url release]; //We're done with the url object so we can release it

    [data writeToFile:path atomically:YES];

    [data release]; //We're done with the data object so we can release it

    return [[UIImage alloc] initWithContentsOfFile:path];

    //We've released everything we owned so it's safe to leave the context
}

メソッドの最後でコンテキストが閉じる直前に、すべてを一度にリリースすることを好む人もいます。その場合、両方[url release];とが閉じ中括弧[data release];の直前に表示されます。}できるだけ早くそれらをリリースすると、コードがより明確になり、後でオブジェクトを処理した場所を正確に確認するときに明確になります。

alloc要約すると、メソッド呼び出しでnew、、、copyまたはで作成されたオブジェクトを所有retainしているため、コンテキストが終了する前にオブジェクトを解放する必要があります。私たちは他に何も所有しておらず、決して解放してはなりません。


¹4つの単語には実際に魔法のようなものは何もありません。これらは、問題のメソッドを作成したAppleの人々によって一貫して使用されているリマインダーにすぎません。独自のクラスに対して独自の初期化またはコピーメソッドを作成する場合、適切なメソッドにalloc、new、copy、またはretainという単語を含めるのは私たちの責任であり、名前にそれらを使用しない場合は、所有権が渡されたかどうかを自分で覚えておく必要があります。

于 2011-01-02T23:42:11.640 に答える
10

objc_exception_throw にブレークポイントを設定すると、非常に役立ちます。例外がスローされようとしているときはいつでも、このブレークポイントにヒットし、スタック チェーンをデバッグしてバックアップできます。iPhone プロジェクトでは、このブレークポイントを常に有効にしておくだけです。

これを行うには、xcode で左ペインの下部近くにある [グループとファイル] に移動し、[ブレークポイント] を見つけます。それを開いて [Project Breakpoints] をクリックすると、詳細ペイン (上部) に [Double-Click for Symbol] というラベルの付いた青いフィールドが表示されます。それをダブルクリックして、「objc_exception_throw」と入力します。

次に例外をスローすると、停止し、デバッガーでスタック チェーンを遡って、例外の原因となったコードに戻ることができます。

于 2009-03-05T20:43:03.627 に答える
7

あなたのコードは、Objective-C でメモリ管理がどのように機能するかについての知識が著しく不足していることを示しています。受信している EXC_BAD_ACCESS エラーに加えて、不適切なメモリ管理もメモリ リークを引き起こし、iPhone のような小さなデバイスでは、ランダムなクラッシュにつながる可能性があります。

これを徹底的に読むことをお勧めします。

Cocoaのメモリ管理プログラミングガイドの紹介

于 2009-03-03T17:50:26.617 に答える
1

必ずメモリ管理ルールを簡単に確認してください。取得しているエラーの原因となるものは何も飛び出しませんが、割り当てたすべてのオブジェクトをリークしています。保持/解放パターンを理解していない場合、オブジェクトを適切に保持していない別の場所がコードにある可能性があり、それが EXC_BAD_ACCESS エラーの原因です。

また、NSString にはファイルシステム パスを処理するためのメソッドがあることに注意してください。区切り文字について自分で心配する必要はありません。

于 2009-03-03T17:52:54.393 に答える
1

ただし、一般的には、コードで EXC_BAD_ACCESS を取得していて、一生その理由を理解できない場合は、NSZombie を使用してみてください (冗談ではありません)。

Xcode で、左側の [実行可能ファイル] セクションを展開します。プロジェクトと同じ名前のリストをダブルクリックします (これが唯一のはずです)。ポップアップ ウィンドウで [引数] に移動し、下部にあるプラス ボタンをクリックします。名前はNSZombieEnabledで、値はYESに設定する必要があります

このようにして、解放されたオブジェクトにアクセスしようとすると、より良い結果が得られます。バグを見つけたら、値をNOに設定してください。

これが誰かを助けることを願っています!

于 2011-01-02T22:13:57.523 に答える
0

これらのエラーは、メモリを誤って管理している場合に発生します (つまり、オブジェクトが時期尚早に解放されているなど)。

次のようなことをしてみてください..

UIImage *myImage = [[UIImage alloc] initWithContentsOfFile:path];
return [myImage autorelease];

release/autorelease の概念を理解するために多くの時間を実験に費やしました。場合によっては、retain キーワードも再生する必要があります (ただし、この場合はおそらくそうではありません)。

別のオプションとして、単にパスが存在しないか、またはパスを読み取ることができませんか?

于 2009-03-03T17:00:51.400 に答える
-1

おそらく、initWithContentsOfFile はパス引数を取らないのでしょうか? UIImage のさまざまな init メソッドを参照してください。パスを受け入れるための別のメソッドがあると思います。

パスを作成するためにしなければならない、より手の込んだ何かがあるかもしれません。「バンドル」で何かをしたことを覚えていますか?漠然としていて申し訳ありませんが、私が覚えているのはそれがすべてです。

于 2009-03-03T17:08:43.503 に答える
-2

パスからスラッシュを取り除き、それがプロジェクト内にあることを確認してください。そのディレクトリにあるかどうかは関係ありませんが、アクセスするにはプロジェクトに追加する必要があります。

于 2009-03-03T17:12:57.173 に答える