iOS Developer程序员iOS Developer

ReactiveCocoa 前奏 KVC. KVO. NSNot

2017-04-01  本文已影响54人  漂泊海上的大土豆

KVC

KVC (key-value observing) 是基于 NSKeyValueCoding 的一个非正式 Protocol,它提供了一种无需通过SetterGetter间接访问对象属性的方式。

常用的方法:

- (id)valueForKey:(NSString *)key;

- (void)setValue:(id)value 
          forKey:(NSString *)key;

Tips:


KVO

KVOkey-value observing) 键值观察,观察一个对象属性的变化,观察者在键值改变时会得到通知。

1.当一个 object 有观察者时,动态创建这个 object 的类的子类。
2.对于每个被观察的 property ,重写其 set 方法。
3.在重写的set方法中调用 - willChangeValueForKey:- didChangeValueForKey: 通知观察者。
4.当一个 property 没有观察者时,删除重写的方法。
5.当没有 observer 观察任何一个 property 时,删除动态创建的子类。

Apple 官方解释

常用方法:

1.注册一个观察者

- (void)addObserver:(NSObject *)anObserver
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context

2.当观察者发生变化会得到此方法的响应

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context

3.然后你还要记得移除观察者

- (void)removeObserver:(NSObject *)anObserver
            forKeyPath:(NSString *)keyPath

如果我们观察很多个键的话,开销可能会变得明显,如果我们在实现一个类的时候把它自己注册为观察者的话,一定要传入一个这个类唯一的 context。比如:

static int const PrivateKVOContext;

可以写在这个类 .m 文件的顶端,然后我们像这样调用 API 并传入 PrivateKVOContext 的指针:

[otherObject addObserver:self 
              forKeyPath:@"someKey" 
                 options:someOptions 
                 context:&PrivateKVOContext];
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == &PrivateKVOContext) {
        // 这里写相关的观察代码
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

这将确保我们写的子类都是正确的。如此一来,子类和父类都能安全的观察同样的键值而不会冲突。否则我们将会碰到难以 debug 的奇怪行为

Tips:

KVO 是同步的,并且发生与所观察的值发生变化的同样的线程上。没有队列或者 Run-loop 的处理。手动或者自动调用 -didChange... 会触发 KVO 通知。

只要我们在单一线程上面运行, KVO 能保证所有 exchangeRate 的观察者在 setter 方法返回前被通知到。 其次,如果某个键被观察的时候附上了 NSKeyValueObservingOptionPrior 选项,直到 -observe... 被调用之前, exchangeRateaccessor 方法都会返回同样的值。


NSNotification

一个 NSNotificationCenter 对象提供了在程序中广播消息的机制,它实质上就是一个通知分发表。这个分发表负责维护为各个通知注册的观察者,并在通知到达时,去查找相应的观察者,将通知转发给他们进行处理。

使用方式:

每一个程序都有一个默认的通知中心,并提供了单例方法来获取它,如果不是出于必要,使用默认的就很方便。

+ (NSNotificationCenter *)defaultCenter

通知中心添加观察者

- (void)addObserver:(id)notificationObserver     // 通知的观察者
           selector:(SEL)notificationSelector    // 处理通知的回调
               name:(NSString *)notificationName // 通知名
             object:(id)notificationSender       // 通知的发送对象

举个🌰

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleNotification:)
                                                 name:nil
                                               object:nil];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"post"
                                                        object:nil];
}
- (void)handleNotification:(NSNotification *)notification {
    NSLog(@"notification = %@", notification.name);
}
@end

1.notificationObserver 不能为 nil。
notificationSelector 回调方法有且只有一个参数(NSNotification对象)。

2.如果 notificationName 为 nil,则会接收所有的通知(如果 notificationSender 不为空,则接收所有来自于 notificationSender 的所有通知)。

3.如果 notificationSender 为 nil,则会接收所有 notificationName 定义的通知。否则,接收由notificationSender发送的通知。

4.监听同一条通知的多个观察者,在通知到达时,它们执行回调的顺序是不确定的,所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。

在 iOS 4.0 之后 NSNotificationCenter 有了 block 的添加观察者方式

使用方式:

- (id<NSObject>)addObserverForName:(NSString *)name        // 通知名
                            object:(id)obj                 // 通知发送的对象
                             queue:(NSOperationQueue *)queue // 接收通知的线程
                        usingBlock:(void (^)(NSNotification *note))block

举个🌰

[[NSNotificationCenter defaultCenter] addObserverForName:@"postOne"
                                                      object:nil
                                                       queue:[NSOperationQueue mainQueue]
                                                  usingBlock:^(NSNotification * _Nonnull note) {
                                                      NSLog(@"receive thread = %@", [NSThread currentThread]);
                                                  }];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"post thread = %@", [NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"postOne"
                                                            object:nil];
    });

前者使用一个现存的对象作为观察者,而这个方法会创建一个匿名的对象作为观察者(即方法返回的id<NSObject>对象),这个匿名对象会在指定的队列(queue)上去执行我们的 block。

1.name 和 obj 为 nil 时的情形与前面一个方法是相同的。

2.如果 queue 为 nil,则消息是默认在 post 线程中同步处理,即通知的 post 与转发是在同一线程中。

3.block 块会被通知中心拷贝一份(执行 copy 操作),以在堆中维护一个 block 对象,直到观察者被从通知中心中移除。所以,应该特别注意在 block 中使用外部对象,避免出现对象的循环引用。

4.如果一个给定的通知触发了多个观察者的 block 操作,则这些操作会在各自的 Operation Queue 中被并发执行。所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。

5.该方法会返回一个表示观察者的对象,记得在不用时释放这个对象。

无论使用哪种方式, 一定要记得移除观察者,block 要一如既往地注意循环引用问题

- (void)removeObserver:(id)notificationObserver
- (void)removeObserver:(id)notificationObserver name:(NSString *)notificationName object:(id)notificationSender

前一个方法会将 notificationObserver 从通知中心中移除,这样 notificationObserver 就无法再监听任何消息。而后一个会根据三个参数来移除相应的观察者。

1.由于注册观察者时(不管是哪个方法),通知中心会维护一个观察者的弱引用,所以在释放对象时,要确保移除对象所有监听的通知。否则,可能会导致程序崩溃或一些莫名其妙的问题。

2.对于第二个方法,如果 notificationName 为 nil,则会移除所有匹配 notificationObserver 和notificationSender 的通知,同理 notificationSender 也是一样的。而如果 notificationName 和notificationSender 都为 nil,则其效果就与第一个方法是一样的了。

3.–removeObserver: 适合于在类的 dealloc 方法中调用,这样可以确保将对象从通知中心中清除;
而在 viewWillDisappear: 这样的方法中,则适合于使用 -removeObserver:name:object: 方法,以避免不知情的情况下移除了不应该移除的通知观察者。

4.每次调用 addObserver 时,都会在通知中心重新注册一次,即使是同一对象监听同一个消息,而不是去覆盖原来的监听,如果同一对象多次注册了这个通知的观察者,则会收到多个通知。

Post postNotification

使用方式:

- postNotification:
– postNotificationName:object:
– postNotificationName:object:userInfo:

1.通知的发送与处理是同步的,在某个地方post一个消息时,会等到所有观察者对象执行完处理操作后,才回到post的地方,继续执行后面的代码。

2.可以根据需要指定通知的发送者(object)并附带一些与通知相关的信息(userInfo)。

    • postNotification: 的参数不能为空,否则会引发一个异常

举个🌰

 [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleNotification:)
                                                 name:nil
                                               object:nil];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:@"post"
                                                        object:nil];
    
    NSLog(@"continue");
2017-03-24 10:51:45.145 simpleRacDemo[11432:6734903] notification = post
2017-03-24 10:51:45.145 simpleRacDemo[11432:6734903] continue

Tips


Delegate

Delegate 本身就是一种设计模式,简而言之就是委托者将一件事情交给被委托者来做。

使用方式:

代理的使用在 iOS 开发中 与 protocol(协议)密不可分,通常在协议中定义好你期望委托对象实现的方法,@required 是被委托者必须实现的, @optional 则是可选择的。

直接举个🌰

这是一个个人中心的 headerView,定义了很多需要委托对象可选择来实现的方法。

@property (nonatomic, weak) id <MyCenterTableHeaderViewDelegate> delegate;这里对 delegate 一定要 weak弱引用,避免互相持有循环引用的问题。

@class MyCenterTableHeaderView;

@protocol MyCenterTableHeaderViewDelegate <NSObject>

@required // 必须实现
@optional // 可选择实现
- (void)myCenterTableHeaderViewChatBtnClicked:(MyCenterTableHeaderView *)view;      // 聊天按钮点击
- (void)myCenterTableHeaderViewFollowTopBtnClicked:(MyCenterTableHeaderView *)view; // 关注用户按钮点击
- (void)myCenterTableHeaderViewFollowBtnClicked:(MyCenterTableHeaderView *)view;    // 关注数点击
- (void)myCenterTableHeaderViewSnsBtnClicked:(MyCenterTableHeaderView *)view;       // 动态数点击
- (void)myCenterTableHeaderViewFansBtnClicked:(MyCenterTableHeaderView *)view;      // 粉丝数点击
- (void)myCenterTableHeaderViewAvatarBtnClicked:(MyCenterTableHeaderView *)view;    // 头像点击
- (void)myCenterTableHeaderViewEditBtnClicked:(MyCenterTableHeaderView *)view;      // 编辑信息点击

@end



@interface MyCenterTableHeaderView : UIView

@property (nonatomic, weak) id <MyCenterTableHeaderViewDelegate> delegate;

@end

方法的实现,加强了安全性判断,养成良好的代码习惯,防止调用没有实现的方法而崩溃。

#pragma mark - Control Events
- (void)clickedAvatarImgV:(UITapGestureRecognizer *)sender {
    if ([self.delegate respondsToSelector:@selector(myCenterTableHeaderViewAvatarBtnClicked:)]) {
        [self.delegate myCenterTableHeaderViewAvatarBtnClicked:self];
    }
}

接下来轮到被委托者来实现代理

首先实现代理对应的协议

@interface MyCenterViewController () <MyCenterTableHeaderViewDelegate>

...

@end

这是 MyCenterTableHeaderView 的懒加载方式

我们需要关注的是这一行代码 [_mainTableViewHeader setDelegate:self]; 被委托者就是 MyCenterViewController 它来实现 MyCenterTableHeaderView 的委托。

- (MyCenterTableHeaderView *)mainTableViewHeader {
    if (_mainTableViewHeader == nil) {
        ...
        [_mainTableViewHeader setDelegate:self];
    }
    
    return _mainTableViewHeader;
}

部分方法的实现,这里我们将委托者本身 MyCenterTableHeaderView 作为参数传递了出来,当然也可以根据具体的业务逻辑传递你期望的参数。

#pragma mark   MyCenterTableHeaderViewDelegate
- (void)myCenterTableHeaderViewAvatarBtnClicked:(MyCenterTableHeaderView *)view {// 头像
    ...
}

Tips:


参考资料:

上一篇下一篇

猜你喜欢

热点阅读