KVO removeObserverのタイミング

この辺を参考にKVOでのコールバックの実装をしました。
Abstract Factory パターンを併用してViewを簡単に差し替えできるよう設計を考えていたのですが、removeObserverのタイミングで悩みました。
view関係の初期化はviewDidloadで行っていたので、removeObserverはviewDidUnloadで呼べばいい物かと思いましたが、参考記事を読む限り
viewDidUnloadで呼ぶのは不適切という事がわかりました。

viewDidUnload は呼ばれない(メモリ不足時だけ呼ばれる)
http://cocoadays.blogspot.jp/2010/10/viewdidunload.html

viewDidLoadとviewDidUnloadが対になっていればいいのですが、参考記事を見る限り、対にはなっていません。
そのため検証してみました。
登場人物は
・FirstViewControllerクラス
・SecondViewControllerクラス
・Viewクラス(UIViewのサブクラス)

FirstViewControllerをrootViewControllerとし、
3秒後にSecondViewControllerをプッシュ
SecondViewController内でViewのプロパティ、”actionFlag”を監視。
View初期化、5秒後にactionFlagにYESを代入
SecondViewControllerはViewからKVOでコールバック受ける前にpopViewControllerし、解放されるようにしました。

FirstViewControllerクラス

#import "FirstViewController.h"
#import "SecondViewController.h"

@interface FirstViewController ()

@end

@implementation FirstViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self performSelector:@selector(pushSecondViewController) withObject:nil afterDelay:3];
}

-(void)pushSecondViewController{
    SecondViewController* secondViewController = [[SecondViewController alloc]init];
    [self.navigationController pushViewController:secondViewController animated:YES];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

SecondViewControllerクラス

#import "SecondViewController.h"
#import "View.h"

@interface SecondViewController ()
@property(nonatomic,strong)View* testView;
@end

@implementation SecondViewController{
    //View* testView;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization

    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    NSLog(@"%s||LINE:%d",__func__,__LINE__);
    self.testView = [[View alloc]init];
    [self.testView addObserver:self forKeyPath:@"actionFlag" options:NSKeyValueObservingOptionNew context:nil];

    //シミュレーターのメモリーワーニングエミューレートでは
    //viewDidLoadは複数回呼ばれる事が再現できないのでobsever登録を意図的に2回行う
    [self.testView addObserver:self forKeyPath:@"actionFlag" options:NSKeyValueObservingOptionNew context:nil];
    self.testView.frame = self.view.frame;
    self.testView.backgroundColor = [UIColor redColor];
    self.testView.alpha = 0.5;
    [self.view addSubview:self.testView];
    [self performSelector:@selector(popSecondViewController) withObject:nil afterDelay:3];

}

-(void)viewWillAppear:(BOOL)animated{
    NSLog(@"%s||LINE:%d",__func__,__LINE__);
    [super viewWillAppear:animated];
}
-(void)viewWillDisappear:(BOOL)animated{
    NSLog(@"%s||LINE:%d",__func__,__LINE__);
    [super viewWillDisappear:animated];
}

-(void)dealloc{
    NSLog(@"%s||LINE:%d",__func__,__LINE__);
    [self.testView removeObserver:self forKeyPath:@"actionFlag"];
}

-(void)popSecondViewController{
    NSLog(@"%s||LINE:%d",__func__,__LINE__);
    [self.navigationController popViewControllerAnimated:YES];
}

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if(object == self.testView){
        if ([keyPath isEqualToString:@"actionFlag"]) {
            NSLog(@"%s||LINE:%d",__func__,__LINE__);

        }
    }
}

Viewクラス

#import "View.h"

@implementation View
@synthesize actionFlag;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        [self performSelector:@selector(action) withObject:nil afterDelay:5];
    }
    return self;
}

-(void)action{
    self.actionFlag = YES;
}

予想通り、SecondViewControllerが解放されたあとKVOでコールバックが来ているためアプリが落ちてしまいます。

対策としてはSecondViewControllerクラスの初期化時にTestViewクラスの初期化とKVOの登録を行い
SecondViewControllerのdeallocでremoveObserverでキー監視の解除を行う。
ただし、この場合TestViewはSecondViewControllerのviewDidLoad以降でaddsubviewしなくてはならないので多少煩雑になります。

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization

        //TestViewクラスの初期化とKVOの登録を初期化時に行う
        self.testView = [[View alloc]init];
        [self.testView addObserver:self forKeyPath:@"actionFlag" options:NSKeyValueObservingOptionNew context:nil];
    }
    return self;
}
iOS

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です