iOS 观察者模式和发布订阅模式

一、观察者模式和发布订阅模式简介

1.1 观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

image

1.2 发布订阅模式

发布订阅模式理念和观察者模式相同,但是处理方式上不同:订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。

image

1.3 两者之间的差异

1、在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

2、发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

二、观察者模式和发布订阅模式优缺点

2.1 优点

共同有点:

1、都可以一对多

2、程序便于扩展

观察者模式:

单向解耦,发布者不需要清楚订阅者何时何地订阅,只需要维护订阅队列,发送消息即可

发布订阅模式:

双向解耦,发布者和订阅者都不用清楚对方,全部由订阅中心做处理

2.2 缺点

1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

4、发布者不知道订阅者是否收到发布的消息。

5、订阅者不知道自己是否收到了发布者发出的所有消息。

6、发送者不能获知订阅者的执行情况。

7、没人知道订阅者何时开始收到消息。

8、发布订阅模式,中心任务过重,一旦崩溃,所有订阅者都会受到影响。

三、应用场景

1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。

2、一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。

3、一个对象必须通知其他对象,而并不知道这些对象是谁。

4、需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者/订阅发布创建一种链式触发机制。

四、实现

4.1 观察者模式

目标:

 // 将观察者添加到数组中
- (void)addObserver:(NSObject *)obj selector:(SEL)aSelector {
    
    NSDictionary *dict = @{@"object":obj,@"sel":NSStringFromSelector(aSelector)};
    [self.observers addObject:dict];
}
//发送通知给观察者
- (void)notify {
    
    if (_observers) {
        for (NSDictionary *dict in _observers) {
            
            if ([dict isKindOfClass:[NSDictionary class]]) {
                
                NSObject *observer = dict[@"object"];
                SEL aSelector = NSSelectorFromString(dict[@"sel"]);
                
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                
                [observer performSelector:aSelector];
#pragma clang diagnostic pop
            }

            
        }
    }
}
//创建观察者数组
- (NSMutableArray *)observers {
    if (!_observers) {
        _observers = [NSMutableArray array];
    }
    
    return _observers;
}

观察者:

// 接收到消息通知
- (void)update {
    
    NSLog(@"我观察的目标发生了变化,我接收到了新的信息,%@",self.name);
}

4.2 发布订阅模式
目标:

// 发送消息给通知中心
- (void)change {
    
    [[NotifyCenter shared] notify:@"change"];
}

通知中心:

//创建通知中心单例
+ (NotifyCenter *)shared {
    static NotifyCenter *shared;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        shared = [[self alloc] init];
    });
    
    return shared;
}
// 添加观察者
- (void)addObserver:(NSObject *)object selector:(SEL)aSelector name:(NSString *)aName {
    
    [self.set addObject:aName];
    NSDictionary *dict = @{@"object":object,@"sel":NSStringFromSelector(aSelector)};
    
    NSMutableDictionary *observer = [NSMutableDictionary dictionary];
    observer[aName] = dict;
    
    [self.observers addObject:observer];
}
// 通知观察者
- (void)notify:(NSString *)name {
    
    if ([_set containsObject:name]) {
        [_observers enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            NSDictionary *dict = obj[name];
            if ([dict isKindOfClass:[NSDictionary class]]) {
                
                NSObject *observer = dict[@"object"];
                SEL aSelector = NSSelectorFromString(dict[@"sel"]);
                
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                
                [observer performSelector:aSelector];
#pragma clang diagnostic pop
            }
        }];
    }
}

观察者:

// 接收到消息通知
- (void)update {
    
    NSLog(@"我观察的目标发生了变化,我接收到了新的信息,%@",self.name);
}

4.4 调用

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //观察者模式
    
    //创建目标
    self.subject = [[Subject alloc] init];

    //创建观察者1
    Observer *one = [[Observer alloc] init];
    one.name = @"one";

    //向目标订阅通知
    [_subject addObserver:one selector:@selector(update)];

    //创建观察者2
    Observer *two = [[Observer alloc] init];
    two.name = @"two";

    //向目标订阅通知
    [_subject addObserver:two selector:@selector(update)];
        



    
    //发布订阅模式

    //创建目标
    self.subscribe = [[Subscribe alloc] init];
    
    //创建观察者3
    Observer *third = [[Observer alloc] init];
    third.name = @"third";
    
    //观察者向通知中心订阅消息
    [[NotifyCenter shared] addObserver:third selector:@selector(update) name:@"change"];
    
    //创建观察者4
    Observer *four = [[Observer alloc] init];
    four.name = @"four";

    //观察者向通知中心订阅消息
    [[NotifyCenter shared] addObserver:four selector:@selector(update) name:@"change"];

    //按钮触发目标发送消息
    UIButton *testBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 100, 100, 40)];
    testBtn.backgroundColor = [UIColor lightGrayColor];
    [testBtn addTarget:self action:@selector(testButton) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:testBtn];

}

- (void)testButton {
    
    NSLog(@"差不多11:45分了,到点吃饭了");
    NSLog(@"观察者模式发送消息");
    [_subject notify];
    
    NSLog(@"发布-订阅模式发送消息");
    [_subscribe change];
}
image

五、总结

1、虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。

2、两种模式都可以用于松散耦合,改进代码管理和潜在的复用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容