iOS通过MachPort向特定线程发送通知

NSNotification在iOS开发中常用到,使用起来很简单,但你是不是真的完全掌握了呢?上一篇文章主要讲多重代理的实现,那这篇文章就来看看NSNotification有哪些值得研究的东西。

NSNotification

通知封装了一个事件的信息,比如窗口获取了焦点或者网络连接关闭事件。需要知道一个事件的对象(例如,一个文件时,需要知道它的窗口即将关闭)注册通知中心,当这个事件发生时对象得到通知。当这个事件确实发生了,通知中心发出通知,立刻广播通知到所有注册的对象。或者,通知被放到通知队列中,这个通知延迟指定的通知并且根据指定的标准聚合类似的通知,然后会发布通知到通知中心。

NSNotification原理

对象间传递信息的标准方法是 消息传递――一个对象调用另一个对象中方法。但是消息传递机制中,发送消息的对象需要知道接收消息的对象以及接收对象要相应什么方法。有时,这种两个对象的紧密耦合是不可取的――尤其是两个原本独立的子系统因此联系起来。由于这些情况,引入了广播模型:一个对象发布通知,通知通过NSNotification对象被派到相应的观察者去,或者只是送到通知中心。一个NSNotification对象(称为通知)包含一个名字,一个对象,和一个可选的字典。名字是识别通知的标记。对象是任何发布通知的对象,典型的,就是发布通知本身的对象。字典包含了事件的额外信息。

MachPort向特定线程发送通知

以下是我们假定你已经掌握了MachPort的一些基本使用,如果你还没接触过,建议你看看我之前的文章深入理解RunLoop,里面有举例怎样使用MachPort配合RunLoop进行线程保活。

通常通知在通知被发布的线程上传递通知。分布式通知中心在主线程传递通知。有时候,你也许希望在你希望的线程上传递通知。例如,如果一个运行在后台的对象正在监听用户界面的通知,比如窗口关闭,这时候你希望在后台线程接收通知而不是在主线程。在这种情况下,你必须在默认线程截住通知,然后发送到合适的线程。
MachPort的工作方式其实是将NSMachPort的对象添加到一个线程所对应的RunLoop中,并给NSMachPort对象设置相应的代理。在其他线程中调用该MachPort对象发消息时会在MachPort所关联的线程中执行相关的代理方法。

为了实现这个技术,你的观察者对象需要以下值的实例变量:一个保存通知的可变数组,一个通知正确线程的MachPort,一个防止通知数组内多线程处理冲突的锁,一个标示正确线程(一个NSThread对象的值。你也需要方法去设置变量,去处理通知,去接收MachPort消息。

lock是对通知队列加锁,避免多个线程同时操作该队列所出现的数据不一致问题

    /**
     notifications    存储子线程发出的通知的队列
     thread           处理通知事件的预期线程
     lock             用于对通知队列加锁的锁对象,避免线程冲突
     port             用于向期望线程发送信号的通信端口
     */
    var notifications: [NSNotification]!
    var thread: Thread!
    var lock: NSLock!
    var port: NSMachPort!

在注册通知之前,你需要初始化这些属性,下面的方法初始化了队列和锁对象,保持了一个对当前线程对象的引用,以及创建了一个MachPort,它将被添加到当前线程的RunLoop中。

/**
 对相关的成员属性进行初始
 */
func setUpThreadingSupport() {
    
    guard notifications == nil else { return }
    
    notifications = [NSNotification]()
    thread = Thread.current
    lock = NSLock()
    port = NSMachPort()
    port.setDelegate(self)
    
    RunLoop.current.add(port, forMode: .commonModes)
}

在这个方法运行之后,任何发送到notificationPort的消息将会在首先运行这个方法的线程的RunLoop中被收到。如果MachPort消息到达时,接收者线程的RunLoop没有运行,内核被保持这个消息直到下次RunLoop进入。接收者线程的RunLoop发送未到达的消息到端口代理的handleMachMessage方法。

在这个实现中,发送到notificationPort的消息没有包含任何信息。 相反,通知数组包含线程间传递的信息。当Mach信息到达时,handleMachMessage方法忽略了消息的内容,只是检查通知数组中需要处理的通知。通知从数组中移除,然后传到真正处理通知的方法中。因为如果同时发送太多端口信息,信息可能会丢失。handleMachMessage方法会遍历数组,直到它为空。当访问通知数组时必须获取一个锁,以防止一个线程添加通知而另一个线程把通知从数组移除。

    /**
     从子线程收到MachPort发出的消息后所执行的方法
     在该方法中从队列中获取子线程中发出的NSNotification
     然后使用当前线程来处理该通知
     
     RunLoop收到MacPort发出的消息时所执行的回调方法。
     */
    func handleMachMessage(_ msg: UnsafeMutableRawPointer) {
        
        lock.lock()
        while notifications.count > 0 {
            let notification = notifications[0]
            notifications.remove(at: 0)
            lock.unlock()
            processNotification(notification)
            lock.lock()
        }
        lock.unlock()
    }
    

当通知传递给对象时,接收通知的方法必须确定它是否正在正确的线程中运行。 如果它是正确的线程,通知正常处理。 如果它是错误的线程,则将通知添加到队列中,并通知通知端口。


func processNotification(_ notification: NSNotification) {
        
        if Thread.current == thread {
            //处理出队列中的通知
            
        }else{//将通知转发到正确的线程
            lock.lock()
            //将其他线程中发过来的通知不做处理,入队列暂存
            notifications.append(notification)
            lock.unlock()
            //通过MachPort给处理通知的线程发送通知,使其处理队列中所暂存的队列
            port.send(before: Date(), msgid: 100, components:nil, from: nil, reserved: 0)
        }
        
    } 

最后,要注册想要在当前线程上传递的通知,无论它可能在哪个线程中发布,都必须通过调用setUpThreadingSupport来初始化对象的通知属性,然后正常注册通知,指定特殊通知处理方法作为selector。

self.setUpThreadingSupport()
        
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.processNotification(note:)), name: NSNotification.Name(rawValue: "NotificationName"), object: nil)

这个实现在几个方面是有限的

  1. 由该对象处理的所有线程通知都必须通过相同的方法processNotification
  2. 每个对象都必须提供自己的实现和通信端口

如果以后有时间并且能力所及,会尝试通过封装来改善这些缺陷

最后最后如果还不知道怎么使用,请看Demo DeliverNotificationToThread

参考文章
iOS开发之线程间的MachPort通信与子线程中的Notification转发
Delivering Notifications To Particular Threads

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

推荐阅读更多精彩内容