2010-12-30 9 views
3

UPDATE |パネルを使用してサンプルプロジェクトをアップロードしました。ここでは、http://w3style.co.uk/~d11wtq/BlocksCrash.tar.gz(「選択...」ボタンは何もしません。まだ実装されていません)。ブロックを呼び出すEXC_BAD_ACCESS

更新日2 |ちょうど私がちょうど文句でそれを使用する必要があるクラッシュを引き起こすためにnewFilePanelの何かを呼び出す必要はないことを発見しました。

これもクラッシュが発生します、そして時には、この「dyld_stub_objc_msgSend_stretを分解することができません。」:コンソールにダンプ最後のものは時々これです表示されます

[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) { 
    newFilePanel; // Do nothing, just use the variable in an expression 
}]; 

「アドレスは0xaでメモリにアクセスすることはできません」 。

自分自身のシート(NSPanelサブクラス)を作成しました。NSOpenPanel/NSSavePanelに似たAPIを提供しようとしています。シートとして表示され、完了するとブロックが呼び出されます。

// 
// EDNewFilePanel.h 
// MojiBaker 
// 
// Created by Chris Corbyn on 29/12/10. 
// Copyright 2010 Chris Corbyn. All rights reserved. 
// 

#import <Cocoa/Cocoa.h> 

@class EDNewFilePanel; 

@interface EDNewFilePanel : NSPanel <NSTextFieldDelegate> { 
    BOOL allowsRelativePaths; 

    NSTextField *filenameInput; 

    NSButton *relativePathSwitch; 

    NSTextField *localPathLabel; 
    NSTextField *localPathInput; 
    NSButton *chooseButton; 

    NSButton *createButton; 
    NSButton *cancelButton; 
} 

@property (nonatomic) BOOL allowsRelativePaths; 

+(EDNewFilePanel *)newFilePanel; 

-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler; 
-(void)setFileName:(NSString *)fileName; 
-(NSString *)fileName; 
-(void)setLocalPath:(NSString *)localPath; 
-(NSString *)localPath; 
-(BOOL)isRelative; 

@end 

と実装内部の主なメソッド:

-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler { 
    [NSApp beginSheet:self 
     modalForWindow:aWindow 
     modalDelegate:self 
     didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) 
      contextInfo:(void *)[handler retain]]; 
} 

-(void)dismissSheet:(id)sender { 
    [NSApp endSheet:self returnCode:([sender tag] == 1) ? NSOKButton : NSCancelButton]; 
} 

-(void)sheetDidEnd:(NSWindow *)aSheet returnCode:(NSInteger)result contextInfo:(void *)contextInfo { 
    ((void (^)(NSUInteger result))contextInfo)(result); 
    [self orderOut:self]; 
    [(void (^)(NSUInteger result))contextInfo release]; 
} 

このすべての作品は私のブロックが空のボディを持つだけで何もしません提供

はここインターフェースです。シートが解除されると、ブロックが呼び出されます。

EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel]; 
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]]; 
[newFilePanel setLocalPath:@"~/"]; 
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) { 
    NSLog(@"I got invoked!"); 
}]; 

しかし、ブロック内からパネルにアクセスしようとすると、EXC_BAD_ACCESSでクラッシュします。たとえば、次のようにクラッシュします。

EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel]; 
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]]; 
[newFilePanel setLocalPath:@"~/"]; 
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) { 
    NSLog(@"I got invoked and the panel is %@!", newFilePanel); 
}]; 

原因はデバッガからはわかりません。スタック上の最初の項目(ゼロ0)は "??"リストには何もありません。

スタック内の次の項目(1と2)はそれぞれ-endSheet:returnCode:-dismissSheet:の呼び出しです。デバッガの変数を調べると、範囲外のものはありません。

パネルがリリースされている(自動リリースされているので)と思っていましたが、作成した直後に-retainと呼んでもそれは役に立ちません。

私はこれを間違って実装していますか?

答えて

12

あるメソッドのパラメータがretainであり、そのオブジェクトがインスタンス変数でない場合は、別のメソッドのパラメータが少し奇妙です。

beginSheetcompletionHandlerビットをインスタンス変数にすることをおすすめします。とにかく一度に複数回シートを表示できるようではないので、この方法ではよりクリーンになります。

また、EXC_BAD_ACCESSは、beginSheet:メソッドで[handler retain]コールから来ている可能性が最も高いです。その場合は、あなたは-copy必見、

[myObject doThingWithCompletionHandler:^{ NSLog(@"done!"); }]; 

ブロックを代わりにそれを保持:あなたは、おそらく(簡潔にするため)のようなもので、このメソッドを呼び出します。上にタイプしたブロックは、スタック上に存在します。しかし、そのスタックフレームが実行スタックからポップされると、そのブロックは消えます。 poofブロックに後でアクセスしようとすると、もはや存在しないコードを実行しようとしているため、ガーベージに置き換えられたため、クラッシュする可能性があります。そのため、ブロック上でcopyを起動してヒープに移動する必要があります。ヒープには、作成されたスタックフレームの存続期間を超えて存続することができます。

+0

華麗な、ありがとう!コピーはすべてを解決します。また、私が保持している/解放している場所に関する入力にも感謝します。それは私にとっても奇妙に感じましたが、正しく思い出したら(NSNumberを保持していてリリースしていた)リンゴの文書のどこかにパターンをたどっていました。一時的な象牙は恐らく危険性が低いです。 – d11wtq

+0

追加するのを忘れてしまったあなたの答えは非常にはっきりしていました。パネルが非同期に呼び出され、スタックが終了するので、このケースを回避するためにコピーする必要があります。 – d11wtq

+0

Block_copyとBlock_releaseも使用することをお勧めします –

-1

__block修飾子を使用してEDNewFilePanelを定義してみてください。

__block EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel]; 

ブロックが呼び出されたとき、これはパネルのオブジェクトが解放された後とすることができる、オブジェクトを保持しなければなりません。無関係の副作用として、ブロックスコープ内で変更可能にします。

+0

それを試しましたが、それは違いはありませんでしたが、それはまだクラッシュします。ちなみに、NSOpenPanelは '__block'を使用する必要はありません。ありがとう(PS:質問のサンプルプロジェクトへのリンクを追加しました)。 – d11wtq

+2

__blockは、あなたが記述したものとまったく逆です。 – bbum

関連する問題