iOS面试题+基础知识

iOS NSNotification相关

2021-08-17  本文已影响0人  朝雨晚风

1、通知实现原理(结构设计、通知如何存储的、name&observer&SEL之间的关系等)

通知是结构体通过双向链表进行数据存储

// 根容器,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;
namelsee name

主要是以key value的形式存储,这里需要重点强调一下 通知以 name和object两个纬度来存储相关通知内容,也就是我们添加通知的时候传入的两个不同的方法.
简单理解name&observer&SEL之间的关系就是name作为key, observer作为观察者对象,当合适时机触发就会调用observer的SEL.

2、通知的添加方式

[[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(receiceNotification:)
                                                 name:@"JKRNO"
                                               object:nil];

addObserver:接收通知的对象
selector:接收通知的对象接收到通知调用的方法
name:通知的名字
object:接收哪个对象发送的通知

@property (nonatomic, strong) NSObject *observer;
...
    self.observer = [[NSNotificationCenter defaultCenter]
                    addObserverForName:@"JKRSEC"
                    object:self 
                    queue:[NSOperationQueue new]
                    usingBlock:^(NSNotification * _Nonnull note) {
                        /// 接收到通知回调的block
                    }];

返回值:通知实际添加到的observer,移除通知要移除这个对象
name参数:通知的名字
object:接收哪个对象发送的通知
queue:接收到通知的回调在哪个线程中调用,如果传mainQueue则通知在主线程回调,否则在子线程回调
usingBlock:接收到通知回调的block

3、通知的移除 页面销毁时不移除通知会崩溃吗?

  1. addObserver添加的通知在iOS 9.0之前,通知中心对观察者对象进行unsafe_unretained 引用,当被引用的对象释放时不会自动置为nil,,也就是成了野指针,需要在dealloc手动移除。
    iOS 9.0之后通知中心对观察者做了弱引用,当被添加通知的对象销毁的时候,通知会自动被移除。。
  2. 但 addObserverForName,被系统 retain,手动移除通知,同时这个 block类型参数也需注意避免循环引用。最明显的体现就是,就算你的ViewController被释放了,走了dealloc,第二次进入VC中会执行两次block中的代码块。

4、通知的发送时同步的,还是异步的?

- (void)textNotifation {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyHandle) name:@"NotificationTestName" object:nil];
    NSLog(@"即将发出通知");
    [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationTestName" object:nil];
    NSLog(@"处理发出通知的下一条代码");
}

- (void)notifyHandle {
    sleep(10);
    NSLog(@"通知处理结束");
}

以上示例证明通知的发送和接收和同步的,即通知发送后,在通知接收方法完成之前,通知发送之后的代码会等待执行
默认在哪个线程发送通知,就在哪个线程接收到。

5、如何让通知异步的方法?

1、将通知的发送放到子线程中

 NSLog(@"即将发出通知");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationTestName" object:nil];
    });
    NSLog(@"处理发出通知的下一条代码");

2、将通知的处理方法放到子线程中

- (void)notifyHandle {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(10);
        NSLog(@"通知处理结束");
    });
}

3、通知的发送可以添加到NSNotificationQueue异步通知缓冲队列中

    NSLog(@"即将发出通知");
    NSNotification *notification = [NSNotification notificationWithName:XYNotificationTestName object:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
    NSLog(@"处理发出通知的下一条代码");

NSNoticicationQueue是一个通知缓冲队列,以FIFO(先进先出)的规则维护通知队列的发送。
向通知队列中添加通知有三种枚举类型:
1,NSPostWhenIdle:runloop空闲的时候回调通知方法
2,NSPostASAP:runloop能够调用的时候就回调通知方法
3,NSPostNow:runloop立即回调通知方法

6、下面的方式能接收到通知吗?多次添加同一个通知会是什么结果?多次移除通知呢?

// 监听通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
// 发送通知
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];

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

dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName: @"NotificationTestName"  object:self];
            });

- (void)notifyHandle {
    dispatch_async(dispatch_get_main_queue(), ^{
        sleep(10);
        NSLog(@"通知处理结束");
    });
}

如果在子线程接受通知并更新UI 会造成crash Main Thread Checker: UI API called on a background thread: -[UIButton setTitle:forState:],要么回到主线程update UI,要么在主线程发送通知

8、NSNotificationQueue和runloop的关系?

为了验证通知和runloop的关系,在主线程添加runloop的状态监听:
postringStyle参数就是定义通知调用和runloop状态之间关系。
该参数的三个可选参数:
1,NSPostWhenIdle:runloop空闲的时候回调通知方法
2,NSPostASAP:runloop能够调用的时候就回调通知方法
3,NSPostNow:runloop立即回调通知方法

- (void)runLoopNotification {
    CFRunLoopRef runloop = CFRunLoopGetCurrent();
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"进入runLoop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"处理timer事件");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"处理source事件");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"进入睡眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"被唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"退出");
                break;
            default:
                break;
        }
    });
    CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
    CFRelease(observer);
    [self addObserverForNotify];
}
- (void)addObserverForNotify {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(receiceNotification:)
                                                 name:@"JKRNO"
                                               object:nil];
    [self postNotification];
}
- (void)postNotification {
    NSLog(@"1");
    NSNotification *notification = [NSNotification notificationWithName:@"JKRNO" object:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle];
    NSLog(@"3");
}

- (void)receiceNotification:(NSNotification *)notification {
    sleep(3);
    NSLog(@"2");
}

用第一个参数NSPostWhenIdle的时候,通知发送的时候runloop和通知方法调用的顺序如下:可以看到,通知回调方法是等待到当下线程runloop进入睡眠状态才会调用。

2021-08-17 14:27:46.237979+0800 exercise[30888:2524325] 1
2021-08-17 14:27:46.238169+0800 exercise[30888:2524325] 3
2021-08-17 14:27:46.264124+0800 exercise[30888:2524325] 处理timer事件
2021-08-17 14:27:46.264326+0800 exercise[30888:2524325] 处理source事件
2021-08-17 14:27:46.264705+0800 exercise[30888:2524325] 处理timer事件
2021-08-17 14:27:46.264828+0800 exercise[30888:2524325] 处理source事件
2021-08-17 14:27:46.265067+0800 exercise[30888:2524325] 进入睡眠
2021-08-17 14:27:49.266005+0800 exercise[30888:2524325] 2
2021-08-17 14:27:49.267331+0800 exercise[30888:2524325] 被唤醒

用第二个参数NSPostASAP的时候,通知发送的时候runloop和通知方法调用的顺序:可以看到,通知回调方法是等待到当下线程runloop开始接收事件源的时候就会调用。

2021-08-17 14:26:37.309870+0800 exercise[30867:2523325] 1
2021-08-17 14:26:37.310111+0800 exercise[30867:2523325] 3
2021-08-17 14:26:37.337183+0800 exercise[30867:2523325] 处理timer事件
2021-08-17 14:26:40.337853+0800 exercise[30867:2523325] 2
2021-08-17 14:26:40.338126+0800 exercise[30867:2523325] 处理source事件
2021-08-17 14:26:40.338555+0800 exercise[30867:2523325] 处理timer事件
2021-08-17 14:26:40.338684+0800 exercise[30867:2523325] 处理source事件
2021-08-17 14:26:40.339368+0800 exercise[30867:2523325] 处理timer事件
2021-08-17 14:26:40.339502+0800 exercise[30867:2523325] 处理source事件
2021-08-17 14:26:40.339652+0800 exercise[30867:2523325] 进入睡眠

用第三个参数NSPostNow的时候,通知发送的时候runloop和通知方法调用的顺序:其实和直接用默认的通知中心添加通知是一样的,通知马上调用回调方法。

2021-08-17 14:28:48.991694+0800 exercise[30907:2525314] 1
2021-08-17 14:28:51.992326+0800 exercise[30907:2525314] 2
2021-08-17 14:28:51.992535+0800 exercise[30907:2525314] 3
2021-08-17 14:28:52.022365+0800 exercise[30907:2525314] 处理timer事件
2021-08-17 14:28:52.022576+0800 exercise[30907:2525314] 处理source事件
2021-08-17 14:28:52.023646+0800 exercise[30907:2525314] 处理timer事件
2021-08-17 14:28:52.023812+0800 exercise[30907:2525314] 处理source事件
2021-08-17 14:28:52.023986+0800 exercise[30907:2525314] 进入睡眠
上一篇下一篇

猜你喜欢

热点阅读