区块链技术博客
www.b2bchain.cn

iOS面试题目解析22 – KVO和通知求职学习资料

本文介绍了iOS面试题目解析22 – KVO和通知求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

KVO

KVO 的本质

  1. 利用 runtime 的 API 动态生成一个名为 NSKVONotifying_XXX 的子类,并且让 instance 对象的 isa 指针指向这个全新的子类。
  2. 当修改 instance 对象的属性时,就会调用 Foundation 的 _NSSetXXXValueAndNotify 函数,该函数又会调用如下方法:
    1. willChangeValueForKey
    2. 父类原来的 setter 方法
    3. didChangeValueForKey:
  3. didChangeValueForKey:方法内部会触发监听器(Oberser)的监听方法 observeValueForKeyPath:ofObject:change:context:

KVO 的存储结构

两个NSMapTable

KVO 的优缺点

优点:

  • 使用简单,可以使用 KVO 来检测对象属性的变化、快速做出响应,这能够为我们在开发强交互、响应式应用以及实现视图和模型的双向绑定时提供大量的帮助。

缺点:

  • string 类型的 key
  • 无法指定响应 KVO 的 selector
  • KVO 消息是隐式的
  • context 很鸡肋

Facebook 提供了一个开源的框架 facebook/KVOController,一句话添加 KVO 并监听属性修改,使用 Block 或者 selecter 的形式观察对象

self.person = [[Person alloc] init]; [self.KVOController observe:self.person                     keyPath:@"age"                     options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld                       block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString    *,id> * _Nonnull change) {                           NSLog(@"%@", change);                       }];

KVO 的防护

有两类需要防护的原因:

  1. 不匹配的移除和添加关系:

    • 移除了未注册的观察者,导致崩溃。
    • 重复移除多次,移除次数多于添加次数,导致崩溃。
    • 重复添加多次,虽然不会崩溃,但是发生改变时,也同时会被观察多次。
  2. 观察者和被观察者释放的时候没有及时断开观察者关系。
    • 添加或者移除时 keypath == nil,导致崩溃。
    • 添加了观察者,但未实现 observeValueForKeyPath:ofObject:change:context: 方法,导致崩溃。

防护方案:在观察者和被观察者之间建立代理对象,维护KVO相关信息,对添加移除操作做防护,hook dealloc方法,做连接断开处理。参考iOS 开发:『Crash 防护系统』(二)KVO 防护

KVO 的相关题目

1. 如何手动触发 KVO?

手动调用 willChangeValueForKey:didChangeValueForKey: 方法

2. 直接修改成员变量会触发 KVO 吗?

不会触发 KVO。
必须使用属性才会触发 KVO。根据 KVO 的本质,必须要调用 setter 方法才能够触发 KVO。 比如直接调用成员变量 self->_name,是不会触发KVO 的。


Notification

通知底层原理

通知的存储结构:

// 根容器,NSNotificationCenter 持有 typedef struct NCTbl {   Observation        *wildcard;  /* 链表结构,保存既没有name也没有object的通知 */   GSIMapTable        nameless;   /* 存储没有name但是有object的通知 */   GSIMapTable        named;      /* 存储带有name的通知,不管有没有object  */     ... } NCTable;  // Observation 存储观察者和响应结构体,基本的存储单元 typedef    struct  Obs {   id        observer;   /* 观察者,接收通知的对象  */   SEL        selector;   /* 响应方法     */   struct Obs    *next;      /* Next item in linked list.    */   ... } Observation;
  1. 通知在设计结构上实际上是用了 name 和 object 两个维度来记录和查找通知,内存使用一个链表存储没有name和object的通知,使用 一个 MapTable 存储有name 但是没有 object 的通知,使用另一个 MapTable 存储有 name 的通知。
  2. 当发送通知的时候,是通过 name 和 object 查到到所有的 observer 对象,放到一个数组中,在通过 performSelector 逐一调用 selector 去执行通知方法,所以是同步的。

添加通知的详细过程

  1. 创建一个 Observation 对象,持有观察者和 SEL
  2. 判断是否有 name,如果有,则以 name 作为 key, 从 named 字典中获取对应的 MapTable,然后以 object 为key,从 MapTable 中取出对应的值,这个值就是 Observation 类型的链表,然后把刚开始创建的 Observation 对象存储进去
  3. 如果没有 name,但有 object,则以 object 为 key,从 nameless 字典中取出对应的 value,value 是个链表结构,然后把创建的 Observation 类型的对象存储到链表中
  4. 如果 name 和 object 都没有,则存储到 wildcard 链表中

发送通知的详细过程

  1. 通过 name 和 object 查找到所有的 Observation 对象(保存了observer 和 SEL),放到数组中
  2. 通过 performSelector:逐一调用 SEL,这是个同步操作
  3. 释放 Notification 对象

通知和线程

通知的发送是同步还是异步的?

同步的
通知在设计结构上实际上是用了 name 和 object 两个维度来记录和查找通知,当发送通知的时候,是通过 name 和 object 查到到所有的observer对象,放到一个数组中,在通过performSelector 逐一调用 selector 去执行通知方法,所以是同步的。

如何异步发送通知?

使用 NSNotificationQueue
NSNotificationQueue 和 Runloop 的关系:
依赖runloop,所以如果在其他子线程使用 NSNotificationQueue,需要开启 Runloop 最终还是通过 NSNotificationCenter 进行发送通知,所以这个角度讲它还是同步的。所谓异步,指的是非实时发送而是在合适的时机发送,并没有开启异步线程

NSPostingStyle 发送通知的方式,三个值:

  • NSPostWhenIdle,在空闲时发送, 当本线程的runloop空闲时即发送通知到通知中心
  • NSPostASAP,ASAP即as soon as possible,就是说尽可能快。当前通知或者timer的回调执行完毕时发送通知到通知中心。
  • NSPostNow, 多个相同的通知合并之后马上发送。

coalesceMask 多个通知的合并方式,三个值:

  • NSNotificationNoCoalescing,不管是否重复,不合并。
  • NSNotificationCoalescingOnName, 按照通知的名字,如果名字重复,则移除重复的。
  • NSNotificationCoalescingOnSender,按照发送方,如果多个通知的发送方是一样的,则只保留一个。
    [[NSNotificationCenter defaultCenter] addObserver:self                                              selector:@selector(handleNotificationAction:)                                                  name:@"MyTestNotification"                                                object:nil];      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{         NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];         NSNotification *notification = [NSNotification notificationWithName:@"MyTestNotification" object:nil];          [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {             // 该log 会在通知发送后打印             NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);         }];          // 尽快发送         [queue enqueueNotification:notification postingStyle:NSPostASAP];          // 子线程需要手动开启runloop         [[NSRunLoop currentRunLoop] run];     });

如何保证通知接收的线程在主线程?

由于通知是同步的,异步线程发送通知则响应函数也是在异步线程,如果执行UI刷新相关的话就会出问题,保证通知在主线程响应的方式有两种:

方法1 – 指定在 Main Queue 响应

KVO

KVO 的本质

  1. 利用 runtime 的 API 动态生成一个名为 NSKVONotifying_XXX 的子类,并且让 instance 对象的 isa 指针指向这个全新的子类。
  2. 当修改 instance 对象的属性时,就会调用 Foundation 的 _NSSetXXXValueAndNotify 函数,该函数又会调用如下方法:
    1. willChangeValueForKey
    2. 父类原来的 setter 方法
    3. didChangeValueForKey:
  3. didChangeValueForKey:方法内部会触发监听器(Oberser)的监听方法 observeValueForKeyPath:ofObject:change:context:

KVO 的存储结构

两个NSMapTable

KVO 的优缺点

优点:

  • 使用简单,可以使用 KVO 来检测对象属性的变化、快速做出响应,这能够为我们在开发强交互、响应式应用以及实现视图和模型的双向绑定时提供大量的帮助。

缺点:

  • string 类型的 key
  • 无法指定响应 KVO 的 selector
  • KVO 消息是隐式的
  • context 很鸡肋

Facebook 提供了一个开源的框架 facebook/KVOController,一句话添加 KVO 并监听属性修改,使用 Block 或者 selecter 的形式观察对象

self.person = [[Person alloc] init]; [self.KVOController observe:self.person                     keyPath:@"age"                     options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld                       block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString    *,id> * _Nonnull change) {                           NSLog(@"%@", change);                       }];

KVO 的防护

有两类需要防护的原因:

  1. 不匹配的移除和添加关系:

    • 移除了未注册的观察者,导致崩溃。
    • 重复移除多次,移除次数多于添加次数,导致崩溃。
    • 重复添加多次,虽然不会崩溃,但是发生改变时,也同时会被观察多次。
  2. 观察者和被观察者释放的时候没有及时断开观察者关系。
    • 添加或者移除时 keypath == nil,导致崩溃。
    • 添加了观察者,但未实现 observeValueForKeyPath:ofObject:change:context: 方法,导致崩溃。

防护方案:在观察者和被观察者之间建立代理对象,维护KVO相关信息,对添加移除操作做防护,hook dealloc方法,做连接断开处理。参考iOS 开发:『Crash 防护系统』(二)KVO 防护

KVO 的相关题目

1. 如何手动触发 KVO?

手动调用 willChangeValueForKey:didChangeValueForKey: 方法

2. 直接修改成员变量会触发 KVO 吗?

不会触发 KVO。
必须使用属性才会触发 KVO。根据 KVO 的本质,必须要调用 setter 方法才能够触发 KVO。 比如直接调用成员变量 self->_name,是不会触发KVO 的。


Notification

通知底层原理

通知的存储结构:

// 根容器,NSNotificationCenter 持有 typedef struct NCTbl {   Observation        *wildcard;  /* 链表结构,保存既没有name也没有object的通知 */   GSIMapTable        nameless;   /* 存储没有name但是有object的通知 */   GSIMapTable        named;      /* 存储带有name的通知,不管有没有object  */     ... } NCTable;  // Observation 存储观察者和响应结构体,基本的存储单元 typedef    struct  Obs {   id        observer;   /* 观察者,接收通知的对象  */   SEL        selector;   /* 响应方法     */   struct Obs    *next;      /* Next item in linked list.    */   ... } Observation;
  1. 通知在设计结构上实际上是用了 name 和 object 两个维度来记录和查找通知,内存使用一个链表存储没有name和object的通知,使用 一个 MapTable 存储有name 但是没有 object 的通知,使用另一个 MapTable 存储有 name 的通知。
  2. 当发送通知的时候,是通过 name 和 object 查到到所有的 observer 对象,放到一个数组中,在通过 performSelector 逐一调用 selector 去执行通知方法,所以是同步的。

添加通知的详细过程

  1. 创建一个 Observation 对象,持有观察者和 SEL
  2. 判断是否有 name,如果有,则以 name 作为 key, 从 named 字典中获取对应的 MapTable,然后以 object 为key,从 MapTable 中取出对应的值,这个值就是 Observation 类型的链表,然后把刚开始创建的 Observation 对象存储进去
  3. 如果没有 name,但有 object,则以 object 为 key,从 nameless 字典中取出对应的 value,value 是个链表结构,然后把创建的 Observation 类型的对象存储到链表中
  4. 如果 name 和 object 都没有,则存储到 wildcard 链表中

发送通知的详细过程

  1. 通过 name 和 object 查找到所有的 Observation 对象(保存了observer 和 SEL),放到数组中
  2. 通过 performSelector:逐一调用 SEL,这是个同步操作
  3. 释放 Notification 对象

通知和线程

通知的发送是同步还是异步的?

同步的
通知在设计结构上实际上是用了 name 和 object 两个维度来记录和查找通知,当发送通知的时候,是通过 name 和 object 查到到所有的observer对象,放到一个数组中,在通过performSelector 逐一调用 selector 去执行通知方法,所以是同步的。

如何异步发送通知?

使用 NSNotificationQueue
NSNotificationQueue 和 Runloop 的关系:
依赖runloop,所以如果在其他子线程使用 NSNotificationQueue,需要开启 Runloop 最终还是通过 NSNotificationCenter 进行发送通知,所以这个角度讲它还是同步的。所谓异步,指的是非实时发送而是在合适的时机发送,并没有开启异步线程

NSPostingStyle 发送通知的方式,三个值:

  • NSPostWhenIdle,在空闲时发送, 当本线程的runloop空闲时即发送通知到通知中心
  • NSPostASAP,ASAP即as soon as possible,就是说尽可能快。当前通知或者timer的回调执行完毕时发送通知到通知中心。
  • NSPostNow, 多个相同的通知合并之后马上发送。

coalesceMask 多个通知的合并方式,三个值:

  • NSNotificationNoCoalescing,不管是否重复,不合并。
  • NSNotificationCoalescingOnName, 按照通知的名字,如果名字重复,则移除重复的。
  • NSNotificationCoalescingOnSender,按照发送方,如果多个通知的发送方是一样的,则只保留一个。
    [[NSNotificationCenter defaultCenter] addObserver:self                                              selector:@selector(handleNotificationAction:)                                                  name:@"MyTestNotification"                                                object:nil];      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{         NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];         NSNotification *notification = [NSNotification notificationWithName:@"MyTestNotification" object:nil];          [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {             // 该log 会在通知发送后打印             NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);         }];          // 尽快发送         [queue enqueueNotification:notification postingStyle:NSPostASAP];          // 子线程需要手动开启runloop         [[NSRunLoop currentRunLoop] run];     });

如何保证通知接收的线程在主线程?

由于通知是同步的,异步线程发送通知则响应函数也是在异步线程,如果执行UI刷新相关的话就会出问题,保证通知在主线程响应的方式有两种:

方法1 – 指定在 Main Queue 响应

KVO

KVO 的本质

  1. 利用 runtime 的 API 动态生成一个名为 NSKVONotifying_XXX 的子类,并且让 instance 对象的 isa 指针指向这个全新的子类。
  2. 当修改 instance 对象的属性时,就会调用 Foundation 的 _NSSetXXXValueAndNotify 函数,该函数又会调用如下方法:
    1. willChangeValueForKey
    2. 父类原来的 setter 方法
    3. didChangeValueForKey:
  3. didChangeValueForKey:方法内部会触发监听器(Oberser)的监听方法 observeValueForKeyPath:ofObject:change:context:

KVO 的存储结构

两个NSMapTable

KVO 的优缺点

优点:

  • 使用简单,可以使用 KVO 来检测对象属性的变化、快速做出响应,这能够为我们在开发强交互、响应式应用以及实现视图和模型的双向绑定时提供大量的帮助。

缺点:

  • string 类型的 key
  • 无法指定响应 KVO 的 selector
  • KVO 消息是隐式的
  • context 很鸡肋

Facebook 提供了一个开源的框架 facebook/KVOController,一句话添加 KVO 并监听属性修改,使用 Block 或者 selecter 的形式观察对象

self.person = [[Person alloc] init]; [self.KVOController observe:self.person                     keyPath:@"age"                     options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld                       block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString    *,id> * _Nonnull change) {                           NSLog(@"%@", change);                       }];

KVO 的防护

有两类需要防护的原因:

  1. 不匹配的移除和添加关系:

    • 移除了未注册的观察者,导致崩溃。
    • 重复移除多次,移除次数多于添加次数,导致崩溃。
    • 重复添加多次,虽然不会崩溃,但是发生改变时,也同时会被观察多次。
  2. 观察者和被观察者释放的时候没有及时断开观察者关系。
    • 添加或者移除时 keypath == nil,导致崩溃。
    • 添加了观察者,但未实现 observeValueForKeyPath:ofObject:change:context: 方法,导致崩溃。

防护方案:在观察者和被观察者之间建立代理对象,维护KVO相关信息,对添加移除操作做防护,hook dealloc方法,做连接断开处理。参考iOS 开发:『Crash 防护系统』(二)KVO 防护

KVO 的相关题目

1. 如何手动触发 KVO?

手动调用 willChangeValueForKey:didChangeValueForKey: 方法

2. 直接修改成员变量会触发 KVO 吗?

不会触发 KVO。
必须使用属性才会触发 KVO。根据 KVO 的本质,必须要调用 setter 方法才能够触发 KVO。 比如直接调用成员变量 self->_name,是不会触发KVO 的。


Notification

通知底层原理

通知的存储结构:

// 根容器,NSNotificationCenter 持有 typedef struct NCTbl {   Observation        *wildcard;  /* 链表结构,保存既没有name也没有object的通知 */   GSIMapTable        nameless;   /* 存储没有name但是有object的通知 */   GSIMapTable        named;      /* 存储带有name的通知,不管有没有object  */     ... } NCTable;  // Observation 存储观察者和响应结构体,基本的存储单元 typedef    struct  Obs {   id        observer;   /* 观察者,接收通知的对象  */   SEL        selector;   /* 响应方法     */   struct Obs    *next;      /* Next item in linked list.    */   ... } Observation;
  1. 通知在设计结构上实际上是用了 name 和 object 两个维度来记录和查找通知,内存使用一个链表存储没有name和object的通知,使用 一个 MapTable 存储有name 但是没有 object 的通知,使用另一个 MapTable 存储有 name 的通知。
  2. 当发送通知的时候,是通过 name 和 object 查到到所有的 observer 对象,放到一个数组中,在通过 performSelector 逐一调用 selector 去执行通知方法,所以是同步的。

添加通知的详细过程

  1. 创建一个 Observation 对象,持有观察者和 SEL
  2. 判断是否有 name,如果有,则以 name 作为 key, 从 named 字典中获取对应的 MapTable,然后以 object 为key,从 MapTable 中取出对应的值,这个值就是 Observation 类型的链表,然后把刚开始创建的 Observation 对象存储进去
  3. 如果没有 name,但有 object,则以 object 为 key,从 nameless 字典中取出对应的 value,value 是个链表结构,然后把创建的 Observation 类型的对象存储到链表中
  4. 如果 name 和 object 都没有,则存储到 wildcard 链表中

发送通知的详细过程

  1. 通过 name 和 object 查找到所有的 Observation 对象(保存了observer 和 SEL),放到数组中
  2. 通过 performSelector:逐一调用 SEL,这是个同步操作
  3. 释放 Notification 对象

通知和线程

通知的发送是同步还是异步的?

同步的
通知在设计结构上实际上是用了 name 和 object 两个维度来记录和查找通知,当发送通知的时候,是通过 name 和 object 查到到所有的observer对象,放到一个数组中,在通过performSelector 逐一调用 selector 去执行通知方法,所以是同步的。

如何异步发送通知?

使用 NSNotificationQueue
NSNotificationQueue 和 Runloop 的关系:
依赖runloop,所以如果在其他子线程使用 NSNotificationQueue,需要开启 Runloop 最终还是通过 NSNotificationCenter 进行发送通知,所以这个角度讲它还是同步的。所谓异步,指的是非实时发送而是在合适的时机发送,并没有开启异步线程

NSPostingStyle 发送通知的方式,三个值:

  • NSPostWhenIdle,在空闲时发送, 当本线程的runloop空闲时即发送通知到通知中心
  • NSPostASAP,ASAP即as soon as possible,就是说尽可能快。当前通知或者timer的回调执行完毕时发送通知到通知中心。
  • NSPostNow, 多个相同的通知合并之后马上发送。

coalesceMask 多个通知的合并方式,三个值:

  • NSNotificationNoCoalescing,不管是否重复,不合并。
  • NSNotificationCoalescingOnName, 按照通知的名字,如果名字重复,则移除重复的。
  • NSNotificationCoalescingOnSender,按照发送方,如果多个通知的发送方是一样的,则只保留一个。
    [[NSNotificationCenter defaultCenter] addObserver:self                                              selector:@selector(handleNotificationAction:)                                                  name:@"MyTestNotification"                                                object:nil];      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{         NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];         NSNotification *notification = [NSNotification notificationWithName:@"MyTestNotification" object:nil];          [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {             // 该log 会在通知发送后打印             NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);         }];          // 尽快发送         [queue enqueueNotification:notification postingStyle:NSPostASAP];          // 子线程需要手动开启runloop         [[NSRunLoop currentRunLoop] run];     });

如何保证通知接收的线程在主线程?

由于通知是同步的,异步线程发送通知则响应函数也是在异步线程,如果执行UI刷新相关的话就会出问题,保证通知在主线程响应的方式有两种:

方法1 – 指定在 Main Queue 响应

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » iOS面试题目解析22 – KVO和通知求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们