概要
-
例えば設定を変更した際に、複数のウィンドウのUIをまとめて更新したい場合、
delegate
では限界があります。Q25. NSNotificationCenterとDelegateの使い分けを説明してください。
NotificationCenterはブロードキャスト、Delegateは指定されたクラスへ通知。
- そういうときに便利なのが
NSNotification
です。 -
通知名
をキーとして、通知を監視するクラスと通知を送信するクラス間で情報をやり取りします。 - (欠点としては飛び道具なので、適当に使うと構造が不透明になったり、クラスが密結合になってしまいかねない所でしょうか。)
GitHub
参考
- ヒレガス本
コード全体
メイン画面(通知を受け取る側)
AppDelegate.m
# import "AppDelegate.h"
# import "PreferencesController.h"
@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@property PreferencesController *preferencesController;
// 本来はAppDelegateに書かず、MainMenuウィンドウのためのWindowControllerを建てる
@property (weak) IBOutlet NSTextField *needsSendingMailLabel;
@property (weak) IBOutlet NSTextField *mailAddressLabel;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(handleSendingMailChange:)
name:IKESendingMailChangedNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(handleMailAddressChange:)
name:IKEMailAddressChangedNotification
object:nil];
PreferencesController *preferencesController = [PreferencesController new];
[preferencesController showWindow:self];
_preferencesController = preferencesController;
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
// Notification Receive Methods
- (void)handleSendingMailChange:(NSNotification *)notification {
NSLog(@"(RECEIVE)メール送信有無の変更通知");
BOOL needsSendingMail = [notification.userInfo[IKENeedsSendingMailKey] boolValue];
if (needsSendingMail) {
[_needsSendingMailLabel setStringValue:@"必要"];
} else {
[_needsSendingMailLabel setStringValue:@"不要"];
}
}
- (void)handleMailAddressChange:(NSNotification *)notification {
NSLog(@"(RECEIVE)メールアドレスの変更通知");
[_mailAddressLabel setStringValue:notification.userInfo[IKEMailAddressKey]];
}
@end
設定画面(通知を送る側)
PreferencesController.h
# import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
// 通知名には衝突を避けるためにPrefixをつけると良い。
extern NSString *const IKESendingMailChangedNotification;
extern NSString *const IKEMailAddressChangedNotification;
// userinfo用のキー
extern NSString *const IKENeedsSendingMailKey; // メール有無
extern NSString *const IKEMailAddressKey; // メールアドレス
@interface PreferencesController : NSWindowController
@end
NS_ASSUME_NONNULL_END
PreferencesController.m
# import "PreferencesController.h"
NSString *const IKESendingMailChangedNotification = @"IKESendingMailChanged";
NSString *const IKEMailAddressChangedNotification = @"IKEMailAddressChanged";
NSString *const IKENeedsSendingMailKey = @"needsSending";
NSString *const IKEMailAddressKey = @"mailAddress";
@interface PreferencesController () <NSTextFieldDelegate>
@property (weak) IBOutlet NSTextField *mailAddressTF;
@end
@implementation PreferencesController
- (id)init {
if (self = [super initWithWindowNibName:[self className] owner:self]) {
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
- (IBAction)sendingMailCheckBoxClicked:(NSButton *)sender {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSLog(@"(POST)メール有無の変更通知");
NSNumber *needsSendingMail = [NSNumber numberWithBool: sender.state];
NSDictionary *userinfoDic = @{IKENeedsSendingMailKey : needsSendingMail};
[notificationCenter postNotificationName:IKESendingMailChangedNotification
object:self
userInfo:userinfoDic];
}
// MARK:- NSTextField Notification Methods
- (void)controlTextDidChange:(NSNotification *)aNotification {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSLog(@"(POST)メールアドレスの変更通知");
NSDictionary *userinfoDic = @{IKEMailAddressKey : _mailAddressTF.stringValue};
[notificationCenter postNotificationName:IKEMailAddressChangedNotification
object:self
userInfo:userinfoDic];
}
@end
コード詳細
設定画面(通知を送る側)
PreferencesController.h
// 通知名には衝突を避けるためにPrefixをつけると良い。
extern NSString *const IKESendingMailChangedNotification;
extern NSString *const IKEMailAddressChangedNotification;
// userinfo用のキー
extern NSString *const IKENeedsSendingMailKey; // メール有無
extern NSString *const IKEMailAddressKey; // メールアドレス
- 通知名のグローバル定数を作成する
- 同様に通知内容の
(NSDictionary *)userinfo
を受け取るときのために、キーのグローバル定数を作成する
PreferencesController.m
NSString *const IKESendingMailChangedNotification = @"IKESendingMailChanged";
NSString *const IKEMailAddressChangedNotification = @"IKEMailAddressChanged";
NSString *const IKENeedsSendingMailKey = @"needsSending";
NSString *const IKEMailAddressKey = @"mailAddress";
-
PreferencesController.h
で宣言した定数に具体的な値をつける- 通知名には衝突を避けるためにPrefixをつけると良い。
@interface PreferencesController () <NSTextFieldDelegate>
-
NSTextField
の値変更を監視するためNSTextFieldDelegate
を採用する
- (IBAction)sendingMailCheckBoxClicked:(NSButton *)sender {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSLog(@"(POST)メール有無の変更通知");
NSNumber *needsSendingMail = [NSNumber numberWithBool: sender.state];
NSDictionary *userinfoDic = @{IKENeedsSendingMailKey : needsSendingMail};
[notificationCenter postNotificationName:IKESendingMailChangedNotification
object:self
userInfo:userinfoDic];
}
- 通知を送るときの手順は以下の通り。
-
NSNotificationCenter
オブジェクトを作成する - 通知する際に送りたい情報を
(NSDictionary *)userinfoDic
に登録する -
NSNotificationCenter
オブジェクトのメソッドpostNotificationName
で通知を送信する-
object
に文字列を指定しているコードを見たことがあるが、別にuserinfo
があるので、ここにはself
を指定するのが筋だと思う。 -
object
は例えば、通知先でself
が何か、つまり通知元がどこかを判別するのに使ったりするのではないかと思う。
-
- (void)controlTextDidChange:(NSNotification *)aNotification {
- やっていることは同じなので割愛
メイン画面(通知を受け取る側)
AppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(handleSendingMailChange:)
name:IKESendingMailChangedNotification
object:nil];
- 通知を受け取る設定の手順は以下の通り。
-
NSNotificationCenter
オブジェクトを作成 -
addObserver...
で登録- addObserver:
監視する主体
- selector:
通知があった際に実行する関数
- name:
通知名
- object:
通知送信元の指定
- addObserver:
- 詳細:addObserver:selector:name:object:
// Notification Receive Methods
- (void)handleSendingMailChange:(NSNotification *)notification {
NSLog(@"(RECEIVE)メール送信有無の変更通知");
BOOL needsSendingMail = [notification.userInfo[IKENeedsSendingMailKey] boolValue];
if (needsSendingMail) {
[_needsSendingMailLabel setStringValue:@"必要"];
} else {
[_needsSendingMailLabel setStringValue:@"不要"];
}
}
- 上記の
selector
で指定した関数が、通知を受け取った際に呼ばれる - 通知の際に送った
(NSDictionary *)userinfo
はnotification.userInfo[IKENeedsSendingMailKey]
のように受け取ることができる。