模擬實現(xiàn)通知中心 iOS

一锰蓬、概述

系統(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末筛婉,一起剝皮案震驚了整個濱河市簇爆,隨后出現(xiàn)的幾起案子癞松,更是在濱河造成了極大的恐慌爽撒,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件响蓉,死亡現(xiàn)場離奇詭異硕勿,居然都是意外死亡,警方通過查閱死者的電腦和手機枫甲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門源武,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人想幻,你說我怎么就攤上這事粱栖。” “怎么了脏毯?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵闹究,是天一觀的道長。 經(jīng)常有香客問我食店,道長渣淤,這世上最難降的妖魔是什么赏寇? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮价认,結(jié)果婚禮上嗅定,老公的妹妹穿的比我還像新娘。我一直安慰自己用踩,他們只是感情好渠退,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捶箱,像睡著了一般智什。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丁屎,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天荠锭,我揣著相機與錄音,去河邊找鬼晨川。 笑死证九,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的共虑。 我是一名探鬼主播愧怜,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妈拌!你這毒婦竟也來了拥坛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤尘分,失蹤者是張志新(化名)和其女友劉穎猜惋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體培愁,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡著摔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了定续。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谍咆。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖私股,靈堂內(nèi)的尸體忽然破棺而出摹察,到底是詐尸還是另有隱情,我是刑警寧澤倡鲸,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布供嚎,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏查坪。R本人自食惡果不足惜寸宏,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望偿曙。 院中可真熱鬧氮凝,春花似錦、人聲如沸望忆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽启摄。三九已至稿壁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歉备,已是汗流浹背傅是。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蕾羊,地道東北人喧笔。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像龟再,于是被迫代替她去往敵國和親书闸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,089評論 1 32
  • 一利凑、概述 系統(tǒng)的通知中心NSNotificationCenter相信大家都比較熟悉浆劲,也就是一種觀察者模式,當我關(guān)心...
    ztlight000閱讀 1,979評論 3 3
  • 1.設(shè)計模式是什么? 你知道哪些設(shè)計模式日丹,并簡要敘述走哺?設(shè)計模式是一種編碼經(jīng)驗蚯嫌,就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,136評論 0 12
  • 7月11日踐行分享 今天大寶幼兒園畢業(yè)哲虾,接下來就是輕松愉快的假期,是老媽不知足择示,所以逼著她睡完午覺就開始做慣例表束凑。...
    丑樹閱讀 82評論 0 0
  • 190114 日更第44天: 自從網(wǎng)絡(luò)、特別是移動互聯(lián)出現(xiàn)后扒寄,出點啥事鱼鼓,網(wǎng)上熱炒一波接一波,泥沙俱下该编,假新聞大行其...
    德萬托阿閱讀 274評論 0 7