日々精進

新しく学んだことを書き留めていきます

NSBeginAlertSheetで表示したアラートのボタンをクリックしたらEXC_BAD_ACCESSが出る

アラートが消える時に実行させる処理をブロックで渡していたが、そのブロックが実行される時にはもうdelegate先のオブジェクトが解放されているため起こっていた。
クラス変数から参照を張ってdidDismissが実行されるまではdelegate先のオブジェクトが解放されないようにした。

@implementation BlockAlertSheet
@interface BlockAlertSheet()
@property(nonatomic, strong) NSString *title;
@property(nonatomic, strong) NSString *message;
@property(nonatomic, strong) NSWindow *docWindow;
@property(nonatomic, assign) AlertBlock didEnd;
@property(nonatomic, assign) AlertBlock didDismiss;
@property(nonatomic, assign) void *contextInfo;
@property(nonatomic, strong) NSString *defaultButtonTitle;
@property(nonatomic, strong) NSString *alternateButtonTitle;
@property(nonatomic, strong) NSString *otherButtonTitle;
@property(nonatomic, strong) NSString *id;
@end

@implementation BlockAlertSheet
static NSMutableDictionary *retainer;
+ (void)initialize {
    retainer = [NSMutableDictionary dictionary];
}

- (id)initWithTitle:(NSString *)title message:(NSString *)message docWindow:(NSWindow *)docWindow didEnd:(AlertBlock)didEnd didDismiss:(AlertBlock)didDismiss contextInfo:(void *)contextInfo defaultButtonTitle:(NSString *)defaultButtonTitle alternateButtonTitle:(NSString *)alternateButtonTitle otherButtonTitle:(NSString *)otherButtonTitle {
    self = [super init];
    if (self) {
        self.title = title;
        self.message = message;
        self.docWindow = docWindow;
        self.didEnd = didEnd;
        self.didDismiss = didDismiss;
        self.contextInfo = contextInfo;
        self.defaultButtonTitle = defaultButtonTitle;
        self.alternateButtonTitle = alternateButtonTitle;
        self.otherButtonTitle = otherButtonTitle;
        self.id = [NSString stringWithFormat:@"%p", self];
    }
    return self;
}

- (void)show {
    // didEndなどのブロックが実行された時にselfが解放されていてエラーになるのを防ぐために
    // retainしておく
    [retainer setObject:self forKey:self.id];
    NSBeginAlertSheet(self.title, self.defaultButtonTitle, self.alternateButtonTitle, self.otherButtonTitle, self.docWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:), @selector(sheetDidDismiss:returnCode:contextInfo:), self.contextInfo, self.message);
}

- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void  *)contextInfo {
    if (self.didEnd) {
        self.didEnd(sheet, returnCode, contextInfo);
    }
}

- (void)sheetDidDismiss:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void  *)contextInfo {
    if (self.didDismiss) {
        self.didDismiss(sheet, returnCode, contextInfo);
    }
    // TODO:なぜかオブジェクトが削除されないことがある。メモリリークしている。
    [retainer removeObjectForKey:self.id];
}
@end