原理
iOS中消息通知是以notificationName來做為標(biāo)識(shí)坷牛,發(fā)通知時(shí),監(jiān)聽了同一個(gè)notificationName的實(shí)例會(huì)收到通知溜在。
而這里所說的方式是以protocol為標(biāo)識(shí)陌知,發(fā)通知時(shí),以protocol+selector掖肋,來判斷要發(fā)送給哪些實(shí)例仆葡。
做法如下:
將需要通知的方法定義成protocol,通常是將一組有關(guān)聯(lián)的方法定義在同個(gè)protocol中志笼。
以protocol為key沿盅,監(jiān)聽該protocol的實(shí)例(可能有多個(gè))為value,組成映射關(guān)系纫溃,即protocol->observer list腰涧。
防止強(qiáng)引用實(shí)例,使用包裝對(duì)象紊浩,用weak屬性存儲(chǔ)該實(shí)例窖铡。
發(fā)送通知時(shí),根據(jù)protocol+selector坊谁,拿到對(duì)應(yīng)的實(shí)例费彼,進(jìn)行調(diào)用,同時(shí)傳遞參數(shù)呜袁。
看圖會(huì)比較清晰:
相比自帶的消息通知敌买,好處是:
- 自帶的消息通知,添加同一個(gè)notificationName多次阶界,會(huì)收到多個(gè)通知虹钮,而這種方式會(huì)去重聋庵。
- 參數(shù)靈活,可直接傳遞芙粱,不用包裝成dict和解dict祭玉。
- 避免移除系統(tǒng)通知的問題
- 實(shí)例對(duì)象以weak修飾,其釋放后春畔,會(huì)自動(dòng)置為nil脱货。避免野指針問題。
添加觀察者
// 可在內(nèi)部判斷該protocol對(duì)應(yīng)的object是否重復(fù)添加
#define ADD_SERVICE_CLIENT(protocolName, object)
[[NotificationServiceCenter defaultCenter] addServiceClient:object withKey:@protocol(protocolName)]
添加時(shí)律姨,需帶上要監(jiān)聽的protocol振峻。
發(fā)送通知
#define NOTIFY_SERVICE_CLIENT(protocolName, selector, func) \
{ \
NSArray *__clients__ = [[NotificationServiceCenter defaultCenter] serviceClientsWithKey:@protocol(protocolName)]; \
for (NotificationClient *client in __clients__) \
{ \
id obj = client.object; \
if ([obj respondsToSelector:selector]) \
{ \
[obj func]; \
} \
} \
發(fā)送時(shí)需要加上selector和func。selector用來判斷實(shí)例是否實(shí)現(xiàn)了該selector择份,func用來直接調(diào)用方法扣孟。
if ([obj respondsToSelector:selector])
{
[obj func];
}
Example:
NOTIFY_SERVICE_CLIENT(UserProtocol, @selector(userLogin:), userLogin:19000);
有個(gè)比較巧妙的地方,就是方法的調(diào)用和參數(shù)的傳遞荣赶。使用宏替換的特性凤价,直接寫成[obj func],在編譯時(shí)不會(huì)報(bào)錯(cuò)拔创。比如上面的例子中利诺,func指userLogin:19000,展開調(diào)用就是[obj userLogin:900]剩燥。如果我們要將NOTIFY_SERVICE_CLIENT寫成方法慢逾,是會(huì)編譯不過的,因?yàn)檫@里的func根本就不是一種類型躏吊。所以采用宏氛改,比較tricky的解決這個(gè)問題。
另外比伏,如果在處理通知過程中,又調(diào)用NOTIFY_SERVICE_CLIENT給同一個(gè)protocol+selector發(fā)送通知消息疆导,是會(huì)引起死循環(huán)的赁项。
比如A監(jiān)聽了ProtocolA的test方法,在test方法中調(diào)用NOTIFY_SERVICE_CLIENT(ProtocolA, @selector(test), test)澈段,A又會(huì)收到通知悠菜,循環(huán)往復(fù),就會(huì)造成死循環(huán)败富。所以悔醋,需要注意這種情況。
// 在A中
- (void)addClient {
ADD_SERVICE_CLIENT(ProtocolA, self)
}
#pragma mark - ProtocolA
// 收到通知
- (void)test {
NSLog("receive ProtocolA test");
// 會(huì)引起死循環(huán)
NOTIFY_SERVICE_CLIENT(ProtocolA, @selector(test), test)
}
移除觀察者
#define REMOVE_SERVICE_CLIENT(protocolName, object)
[[NotificationServiceCenter defaultCenter] removeServiceClient:object withKey:@protocol(protocolName)]
#define REMOVE_ALL_SERVICE_CLIENT(object)
[[NotificationServiceCenter defaultCenter] removeServiceClient:object]
移除有兩種方式:移除單個(gè)protocol和移除該object監(jiān)聽的所有protocol兽叮。
使用
定義protocol
@protocol UserProtocol <NSObject>
@optional
- (void)logout;
- (void)userLogin:(NSUInteger)userID;
@end
添加監(jiān)聽芬骄,實(shí)現(xiàn)通知的方法猾愿,移除監(jiān)聽。注意需讓其遵循協(xié)議账阻,因?yàn)樵贏DD_SERVICE_CLIENT會(huì)判斷object是否遵循了protocol蒂秘。
@interface ViewController ()<UserProtocol>
@end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 添加監(jiān)聽
ADD_SERVICE_CLIENT(UserProtocol, self);
}
- (void)dealloc {
// 移除監(jiān)聽
REMOVE_ALL_SERVICE_CLIENT(self);
}
#pragma mark - UserProtocol
// 通知過來后的具體實(shí)現(xiàn)
- (void)logout {
NSLog(@"logout");
}
- (void)userLogin:(NSUInteger)userId {
NSLog(@"loginSuccess");
}
發(fā)送通知:
- (IBAction)login:(id)sender {
NOTIFY_SERVICE_CLIENT(UserProtocol, @selector(userLogin:), userLogin:19000);
}
- (IBAction)logout:(id)sender {
NOTIFY_SERVICE_CLIENT(UserProtocol, @selector(logout), logout);
}
如果只關(guān)心protocol的某個(gè)方法通知,只需要實(shí)現(xiàn)該方法即可淘太,不需要實(shí)現(xiàn)protocol中的所有方法姻僧。