一锰蓬、概述
系統(tǒng)的通知中心NSNotificationCenter相信大家都比較熟悉房轿,也就是一種觀察者模式浓冒,當我關(guān)心某件事的時候我就注冊這個通知栽渴,在收到通知時做相應(yīng)的處理,是一種多對多機制稳懒,非常方便的實現(xiàn)多個對象的聯(lián)動操作闲擦,那通知中心是怎么實現(xiàn)的呢?
第一反應(yīng)可能用一個單例維護一個數(shù)組场梆,注冊的時候?qū)斍皩ο蠹拥綌?shù)組中墅冷,當postNotification時去數(shù)組中遍歷然后調(diào)用相應(yīng)方法,確實可以或油,但是也許會遇到一個問題寞忿,就是對象加入數(shù)組后,引用計數(shù)會加一顶岸,拿控制器來說就是當我們pop的時候?qū)⒉荒茕N毀這個控制器腔彰,自然不會走dealloc方法
我們將用三種形式討論通知中心的可行性,至于有什么作用辖佣,可能更多的是一種思想上的借鑒吧霹抛。gitHub地址:Demo
二、實現(xiàn)原理
至于哪三種方式呢卷谈,思路其實都是一樣的杯拐,通過單例維護注冊的對象集合,發(fā)出通知的時候去遍歷并調(diào)用相應(yīng)的方法世蔗,這里我們采用三種方式去維護這些對象集合:
1端逼、通過數(shù)組形式
2、通過OC模擬鏈表形式
3污淋、通過C語言鏈表形式
接下來就是實現(xiàn)代碼顶滩,首先看一下.h中的方法:
NS_ASSUME_NONNULL_BEGIN
@interface ZLNotificationCenter :NSObject
+ (ZLNotificationCenter *)defaultCenter;
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullableNSString*)aName object:(nullableid)anObject;
- (void)postNotification:(ZTNotification*)notification;
- (void)postNotificationName:(NSString*)aName object:(nullableid)anObject;
- (void)postNotificationName:(NSString*)aName object:(nullableid)anObject userInfo:(nullableNSDictionary*)aUserInfo;
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullableNSString*)aName object:(nullableid)anObject;
- (id)addObserverForName:(nullableNSString*)name object:(nullableid)obj queue:(nullableNSOperationQueue*)queue usingBlock:(void(^)(ZTNotification*note))blockNS_AVAILABLE(10_6,4_0);
@end
NS_ASSUME_NONNULL_END
可以說完全照搬系統(tǒng)NSNotificationCenter中的方法,本來就是要實現(xiàn)他嘛寸爆。
而要想達到通知的目的诲祸,有三個屬性是必不可少的:
- 注冊的對象observer、
- 要執(zhí)行的方法selector
- 注冊的通知名notificationName
由此我們創(chuàng)建了一個保存這些信息的對象ZLObserverModel而昨,看下有哪些屬性:
typedefvoid(^OperationBlock)(ZTNotification*notification);
@interfaceZTObserverModel :NSObject
@property (nonatomic, weak) id observer;
@property (nonatomic, assign) SEL selector;
@property(nonatomic, copy) NSString*notificationName;
@property (nonatomic, weak) id object;
@property (nonatomic, strong) NSOperationQueue *operationQueue;
@property (nonatomic, copy) OperationBlock block;
前四個是注冊時的參數(shù)救氯,最后兩個意思是如果對象指定了隊列則在隊列中執(zhí)行block。同時定義這個對象也是為了解決強引用加一不釋放的問題歌憨,我們并沒有直接將對象加入數(shù)組或鏈表中着憨,而是使用自定義的一個對象,其中observer使用的是weak务嫡,這就避免了強引用加一問題甲抖,不釋放問題就解決了漆改。
接下來看下三種方式的具體代碼:
第一種數(shù)組:注冊時就是把對象加到數(shù)組中,接收到通知的時候去數(shù)組中遍歷
//注冊方法
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString*)aName object:(id)anObject {
ZTObserverModel *observerModel = [[ZTObserverModel alloc] init];
observerModel.observer = observer;
observerModel.selector = aSelector;
observerModel.notificationName = aName;
observerModel.object = anObject;
[self.observers addObject:observerModel]; //采用數(shù)組形式
}
- (void)postNotification:(ZTNotification*)notification {
//采用數(shù)組形式
[self.observers enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
ZTObserverModel *observerModel = obj;
id observer = observerModel.observer;
SEL selector = observerModel.selector;
if (![notification.name isEqualToString:observerModel.notificationName]) {
return;
}
if (!observerModel.operationQueue) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[observer performSelector:selector withObject:notification];
#pragma clang diagnostic pop
} else {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
observerModel.block(notification);
}];
NSOperationQueue *operationQueue = observerModel.operationQueue;
[operationQueue addOperation:operation];
}
}];
}
第二種OC模擬鏈表:首先我們需要先定義一個節(jié)點和一個鏈表
節(jié)點:
@interface ZTNode : NSObject
@property id data; //數(shù)據(jù)域
@property ZTNode *next; //指針域准谚,指向下個節(jié)點
- (id)initWithData:(id)data;//結(jié)點初始化
@end
鏈表:
@interface ZTList : NSObject
@property ZTNode *head; //定義鏈表的頭
+ (void)insert:(id)data linkList:(ZTList*)linkList; //插入結(jié)點
+ (id)listWithData:(id)data; //工廠方法
@end
看下鏈表中的實現(xiàn):插入方法用的是尾插法
@implementation ZTList
- (instancetype)initWithData:(id)data {
self= [super init];
if(self) {
self.head = [[ZTNode alloc] init];
self.head.data = data;
self.head.next = nil;
}
return self;
}
+ (id)listWithData:(id)data {
ZTList *firstNode = [[ZTList alloc] initWithData:data];
return firstNode;
}
+ (void)insert:(id)data linkList:(ZTList*)linkList {
ZTNode *p = linkList.head;
while(p.next) {
p = p.next;
}
ZTNode *node = [[ZTNode alloc] init];
node.data = data;
node.next = p.next;
p.next = node;
}
@end
有了鏈表我們存儲和查找就比較簡單了挫剑,存儲時只需將加入數(shù)組的代碼改成
[ZTList insert:observerModel linkList:_ztList];
就行了,查找時代碼如下:
//采用oc模擬鏈表
ZTNode *p = _ztList.head;
while (p) {
p = p.next;
ZTObserverModel *observerModel = p.data;
id observer = observerModel.observer;
SEL selector = observerModel.selector;
if (![notification.name isEqualToString:observerModel.notificationName]) {
return;
}
if (!observerModel.operationQueue) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[observer performSelector:selector withObject:notification];
#pragma clang diagnostic pop
} else {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
observerModel.block(notification);
}];
NSOperationQueue *operationQueue = observerModel.operationQueue;
[operationQueue addOperation:operation];
}
}
第三種C鏈表:C語言中我們使用結(jié)構(gòu)體定義鏈表柱衔,看下.h文件:**
typedef struct Node {
void *data;
struct Node *next;
} Node;
typedef struct Node *LinkList;
@interface LinkListC :NSObject
+ (LinkList)insert:(id)data linkList:(LinkList)linkList;//插入結(jié)點
+ (LinkList)listInit;
@end
.m中主要看下insert中的數(shù)據(jù)轉(zhuǎn)換
#import "LinkListC.h"
@implementation LinkListC
//單鏈表的初始化
LinkList LinkedListInit() {
Node*L;
L = (Node*)malloc(sizeof(Node)); //申請結(jié)點空間
if(L == NULL) {//判斷是否有足夠的內(nèi)存空間
printf("申請內(nèi)存空間失敗\n");
}
L->next = NULL; //將next設(shè)置為NULL,初始長度為0的單鏈表
return L;
}
- (instancetype)initWithData:(id)data {
self= [super init];
if(self) {
LinkedListInit();
}
return self;
}
+ (LinkList)listInit {
LinkList list = LinkedListInit();
return list;
}
+ (LinkList)insert:(id)data linkList:(LinkList)linkList {
Node*head;
head = linkList;
Node*p;
//插入的結(jié)點為p
p = (Node*)malloc(sizeof(Node));
p->data = (__bridge_retained void *)data;
p->next = head->next;
head->next = p;
return linkList;
}
@end
定義過程中void *data標示C的任意類型樊破,
p->data = (__bridge_retained void *)data
這里通過橋接將OC數(shù)據(jù)轉(zhuǎn)成C數(shù)據(jù),插入方法這里使用的是頭插法唆铐。
接下來注冊的時候
[LinkListC insert:observerModel linkList:_linkListC]
就會將對象插入到了鏈表中哲戚,這里接受到通知時還是要注意C跟OC數(shù)據(jù)轉(zhuǎn)換的問題,看下代碼:
- (void)postNotification:(ZTNotification*)notification {
//采用C鏈表
Node*p;
p = _linkListC;
while(p->next) {
p = p->next;
ZTObserverModel *observerModel = (__bridge id)p->data;
id observer = observerModel.observer;
SEL selector = observerModel.selector;
if(![notification.name isEqualToString:observerModel.notificationName]) {
return;
}
if(!observerModel.operationQueue) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Warc-performSelector-leaks"
[observerperformSelector:selectorwithObject:notification];
#pragma clang diagnostic pop
}else{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
observerModel.block(notification);
}];
NSOperationQueue*operationQueue = observerModel.operationQueue;
[operationQueue addOperation:operation];
}
}
}
這樣我們?nèi)N方式就說完了艾岂,話說簡書自動取消空格好煩人顺少,整的格式都不對了。
三王浴、使用方法
注冊:
ZTNotificationCenter *notificationCenter = [ZTNotificationCenter defaultCenter];
[notificationCenter addObserver:selfselector:@selector(test1:)name:@"TextFieldValueChanged"object:nil];
發(fā)通知:
ZTNotification *notification = [ZTNotification notificationWithName:@"TextFieldValueChanged" object:self.textField];
ZTNotificationCenter *notificationCenter = [ZTNotificationCenter defaultCenter];
[notificationCenter postNotification:notification];
四脆炎、補充
在以上代碼中會用到一個ZTNotification對象,這個系統(tǒng)的也是有的氓辣,是通知的對象秒裕,.h中的方法也是從系統(tǒng)的NSNotification中完全拿過來的。代碼可以看下:
NS_ASSUME_NONNULL_BEGIN
/**************** Notifications ****************/
@class NSString, NSDictionary, NSOperationQueue;
@interfaceZTNotification :NSObject
@property (readonly, copy) NSString *name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;
- (instancetype)initWithName:(NSString*)name object:(nullableid)object userInfo:(nullableNSDictionary*)userInfoNS_AVAILABLE(10_6,4_0) ;
- (nullableinstancetype)initWithCoder:(NSCoder*)aDecoder ;
+ (instancetype)notificationWithName:(NSString*)aName object:(nullableid)anObject;
+ (instancetype)notificationWithName:(NSString*)aName object:(nullableid)anObject userInfo:(nullableNSDictionary*)aUserInfo;
@end
NS_ASSUME_NONNULL_END