Cowboy Tech

iOS设计模式-观察者

通知中心业务逻辑

  1. 订阅中心是单例模式,只有一个
  2. 订阅中心维护书刊的信息,书刊号维护用户的信息
  3. 用户信息是某种集合(NSDictionary, NSSet, NSArray)
  4. 观察者(用户)是被动接收消息的,不会影响到订阅中心的发布

抽象接口设计

按照业务逻辑,和涉及的对象,将逻辑理顺,不需要考虑如何实现,先设计接口。例如,通知中心可以这样:

维护订阅信息

//创建订阅号
+ (void)createSubscriptionNumber:(NSString *)subscriptionNumber;

//移除订阅号
+ (void)removeSubscriptionNumber:(NSString *)subscriptionNumber;

维护客户信息

//添加客户到具体的订阅号当中
+ (void)addCustomer:(id <SubscriptionServiceCenterProtocol>)customer withSubscriptionNumber:(NSString *)subscriptionNumber;

//从具体的订阅号当中移除客户
+ (void)removeCustomer:(id <SubscriptionServiceCenterProtocol>)customer withSubscriptionNumber:(NSString *)subscriptionNumber;

发送消息

//发送消息到具体的订阅号当中
+ (void)sendMessage:(id)message toSubscriptionNumber:(NSString *)subscriptionNumber;

通知中心与用户的消息传递(协议)

消息传递,就让它遵循某类协议,例如订阅的用户

id <SubscriptionServiceCenterProtocol>

订阅中心实例

  1. NSHashTable对持有的对象是weak引用。这里的例子用来存储”订阅杂志的用户集合”
  2. NSSet, NSArray, NSDictionary中对持有对象是strong引用
  3. NSParameterAssert判断不能为nil,否则程序会奔溃

订阅中心创建发行杂志的集合

发行杂志的集合:NSMutableDictionary

+ (void)initialize {

if (self == [SubscriptionServiceCenter class]) {

    _subscriptionDictionary = [NSMutableDictionary dictionary];
}
}

获得某订阅号的用户集合

用户集合:NSHashTable

+ (NSHashTable *)existSubscriptionNumber:(NSString *)subscriptionNumber {

return [_subscriptionDictionary objectForKey:subscriptionNumber];
}

创建订阅号

创建订阅号,实质上是创建它的用户集合

+ (void)createSubscriptionNumber:(NSString *)subscriptionNumber {

NSParameterAssert(subscriptionNumber);

//如果这杂志的用户集合不存在,则创建
NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];

if (hashTable == nil) {
    hashTable = [NSHashTable weakObjectsHashTable];
    [_subscriptionDictionary setObject:hashTable forKey:subscriptionNumber];
}
}

移除订阅号

移除订阅号,实质上是移除它的用户集合

+ (void)removeSubscriptionNumber:(NSString *)subscriptionNumber {

NSParameterAssert(subscriptionNumber);

NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];
if (hashTable) {

    [_subscriptionDictionary removeObjectForKey:subscriptionNumber];
}
}

添加客户到具体的订阅号当中

根据订阅号,取出用户集合,并添加至NSHashTable集合中

+ (void)addCustomer:(id <SubscriptionServiceCenterProtocol>)customer withSubscriptionNumber:(NSString *)subscriptionNumber {

NSParameterAssert(customer);
NSParameterAssert(subscriptionNumber);


NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];
[hashTable addObject:customer];
}

从具体的订阅号当中移除客户

根据订阅号,取出用户集合,从NSHashTable集合中删除,这里customer可以为nil,因为该customer可能本来就没有订阅。所以不需要NSParameterAssert判断

+ (void)removeCustomer:(id <SubscriptionServiceCenterProtocol>)customer withSubscriptionNumber:(NSString *)subscriptionNumber {

NSParameterAssert(subscriptionNumber);

NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];
[hashTable removeObject:customer];
}

订阅中心发行期刊

从用户集合中,遍历取出每个用户,只要它实现了协议,就可以获得消息

+ (void)sendMessage:(id)message toSubscriptionNumber:(NSString *)subscriptionNumber {

NSParameterAssert(subscriptionNumber);

NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];
if (hashTable) {

    NSEnumerator *enumerator = [hashTable objectEnumerator];
    id <SubscriptionServiceCenterProtocol> object = nil;
    while (object = [enumerator nextObject]) {

        if ([object respondsToSelector:@selector(subscriptionMessage:subscriptionNumber:)]) {

            [object subscriptionMessage:message subscriptionNumber:subscriptionNumber];
        }
    }
}
}

订阅中心的工作过程

// 创建订阅号SCIENCE
[SubscriptionServiceCenter createSubscriptionNumber:SCIENCE];

// 添加订阅的用户到指定的刊物
[SubscriptionServiceCenter addCustomer:self(这里是viewcontroller,只要实现了协议,任何对象都可以)
                withSubscriptionNumber:SCIENCE];

// 发行机构发送消息
[SubscriptionServiceCenter sendMessage:@"V1.0" toSubscriptionNumber:SCIENCE];

用户接收消息的实现

- (void)subscriptionMessage:(id)message subscriptionNumber:(NSString *)subscriptionNumber {
NSLog(@"%@  %@", message, subscriptionNumber);
}

框架级别的观察者模式1:NSNotificationCenter

// 添加客户到指定的订阅号中
[[NSNotificationCenter defaultCenter] addObserver:self  (用户)
                                         selector:@selector(notificationCenterEvent:)
                                             name:SCIENCE(订阅号)
                                           object:nil];

// 发送信息到指定的订阅号当中
[[NSNotificationCenter defaultCenter] postNotificationName:SCIENCE
                                                    object:@"V1.0"];



//通知中心方法
- (void)notificationCenterEvent:(id)sender {
NSLog(@"%@", sender);
}

- (void)dealloc {

// 移除通知中心
[[NSNotificationCenter defaultCenter] removeObserver:self
                                          forKeyPath:SCIENCE];
                                      }

框架级别的观察者模式2:KVO

kVO(key value obeserveing)是一种非常重要的机制,他允许监听对象属性的变化

// 创建订阅中心
self.model = [Model new];

// 客户添加了订阅中心的"name"服务
[self.model addObserver:self (用户)
             forKeyPath:@"name" (订阅号)
                options:NSKeyValueObservingOptionNew (当订阅号改变时,需要传递什么值给监听器(枚举类型))
                context:nil];

// 订阅中心发送消息(通过修改属性值)
self.model.name = @"V1.0";

//KVO方法
- (void)observeValueForKeyPath:(NSString *)keyPath 
        ofObject:(id)object 
        change:(NSDictionary *)change 
        context:(void *)context {

NSLog(@"%@", change);
}


- (void)dealloc {
// 移除KVO
[self.model removeObserver:self forKeyPath:@"name"];

}

其他

强引用和弱引用的叙述(转载)

一个对象:类比为一条狗,释放对象:类比为狗要跑掉

  1. strong类型的指针就像是栓住的狗,只要你用绳子拴住狗,那么狗就不会跑掉.——- (一个对象new过以后,不会自动的释放)
  2. 如果有5个人都牵着这一条狗(5条绳子栓一只狗)——- (5个strong类型指针指向一个对象.)
  3. 除非5个绳子都脱落,否则狗是不会跑掉的———— 5个strong指针都=nil,则该对象释放
  4. weak型指针就像是一个小孩子指着狗喊道:“看,有一只狗在那里”,只要狗一直被拴着,那么小孩子就能看到狗(weak指针)会一直指向它,
  5. 只要狗的绳子脱落,那么狗就会跑掉,不管有多少的小孩在看着它。
  6. 只要最后一个strong型指针不再指向对象,那么对象就会被释放,同时所有的weak型指针都将会被清除。

NSEnumerator遍历

NSEnumerator *enumerator = [hashTable objectEnumerator];
id object = nil
while (object = [enumerator nextObject]) {

}

NSParameterAssert

判断不能为nil,否则程序会奔溃

判断对象存在与否

//对象存在
if (hashTable) {...}

//对象不存在
if (hashTable == nil) {....}

initialize

每个对象都具有的初始化方法,只执行一次

+ (void)initialize {...}    

KVC (key value coding)

使用valueforKey:获取Student对象的name。

NSString *name = [Student valueForKey :@"name"];

使用setValue:ForKey:设置student对象的name。

[Student setVlue:@"zhangjl" forKey:@"name"];

支持通过键路径(key path)进行访问和赋值。比如:利用键路径对Student对象的Card对象的no属性进行访问和赋值

[Student setValue:@"1234" forKeyPath: @"card.no"];
[Student valueForKeyPath:@"card.no"]