寫給本身看的源碼系列: iOS中的通知機制

本文從源碼角度對iOS中的通知進行了解析,並對通知中心的一些特性進行了相應的解讀。node

Swift版本

數據結構

NSNotification

NSNotification理所固然要包含通知name、object,且使用userInfo用於傳遞參數。git

open class NSNotification: NSObject, NSCopying, NSCoding {
    public struct Name : RawRepresentable, Equatable, Hashable {
        public private(set) var rawValue: String

        public init(_ rawValue: String) {
            self.rawValue = rawValue
        }

        public init(rawValue: String) {
            self.rawValue = rawValue
        }
    }

    private(set) open var name: Name
    
    private(set) open var object: Any?
    
    private(set) open var userInfo: [AnyHashable : Any]?
}
複製代碼

這裏封裝了一個結構體Name,而非直接使用字符串。因此,咱們一般使用的話,須要這樣寫 ***NotificationCenter.default.post(name: NSNotification.Name(rawValue: kNotificationCLLocationDidUpdated), object: nil)***。github

基於Swift的特色,咱們能夠對Swift項目中的相關Notification使用進行一些優雅的改進。swift

更加Swift化的通知

強烈建議採用Alamofire中這樣的寫法:api

extension Notification.Name {
    /// Used as a namespace for all `URLSessionTask` related notifications.
    public struct Task {
        /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`.
        public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume")

        /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`.
        public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend")

        /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`.
        public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel")

        /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`.
        public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete")
    }
}
複製代碼

則使用方式就很簡單了,且經過相似 Notification.Name.Task 這樣的寫法來進行了業務的區分。數組

NotificationCenter.default.post(
                name: Notification.Name.Task.DidComplete,
                object: strongSelf,
                userInfo: userInfo
            )
複製代碼

NSNotificationReceiver

NSNotificationReceiver用於封裝通知的基本結構,name、block、sender都包含在裏邊。這裏的name使用的其實就是Notification.Name對象,sender即爲發送通知的對象。緩存

private class NSNotificationReceiver : NSObject {
    fileprivate var name: Notification.Name?
    fileprivate var block: ((Notification) -> Void)?
    fileprivate var sender: AnyObject?
    fileprivate var queue: OperationQueue?
}
複製代碼

NotificationCenter中使用_observers來存儲NSNotificationReceiver對象。數據結構

private var _observers: [AnyHashable /* Notification.Name */ : [ObjectIdentifier /* object */ : [ObjectIdentifier /* notification receiver */ : NSNotificationReceiver]]]
複製代碼

簡化一下就是多線程

private var _observers: [AnyHashable : [ObjectIdentifier : [ObjectIdentifier : NSNotificationReceiver]]]
複製代碼

爲啥要使用這麼複雜的結構?而不是直接使用 [Name: [NSNotificationReceiver]]這樣的結構,緣由在於區分通知的不只僅是name,還有發送對象即object。一個通知名(String)能夠對應於多個通知Receiver。app

_observers的key是一個可hash的對象,value是一個字典。該value字典的key是ObjectIdentifier,而value則是[ObjectIdentifier : NSNotificationReceiver],又是一個字典。

後續會講到ObjectIdentifier,這裏僅簡單理解爲惟一標記一個對象(通知的sender)便可。

addObserver

addObserver將通知加到通知中心。

open func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol {
    let newObserver = NSNotificationReceiver()
    newObserver.name = name
    newObserver.block = block
    newObserver.sender = __SwiftValue.store(obj)
    newObserver.queue = queue
    
    let notificationNameIdentifier: AnyHashable = name.map({ AnyHashable($0) }) ?? _nilHashable
    let senderIdentifier: ObjectIdentifier = newObserver.sender.map({ ObjectIdentifier($0) }) ?? _nilIdentifier
    let receiverIdentifier: ObjectIdentifier = ObjectIdentifier(newObserver)

    _observersLock.synchronized({
        _observers[notificationNameIdentifier, default: [:]][senderIdentifier, default: [:]][receiverIdentifier] = newObserver
    })
    
    return newObserver
}
複製代碼

先封裝一個NSNotificationReceiver對象,注意這裏對發送通知的對象obj使用了__SwiftValue.store(obj)操做,存到了sender中。

將name轉換爲notificationNameIdentifier,將newObserver.sender轉爲爲ObjectIdentifier對象senderIdentifier。

注意爲nil的時候,分別轉換成了_nilHashable和_nilIdentifier,即:

private lazy var _nilIdentifier: ObjectIdentifier = ObjectIdentifier(_observersLock)
private lazy var _nilHashable: AnyHashable = AnyHashable(_nilIdentifier)
複製代碼

開發者文檔是這樣解釋的:

name: The name of the notification for which to register the observer; that is, only notifications with this name are used to add the block to the operation queue. If you pass nil, the notification center doesn’t use a notification’s name to decide whether to add the block to the operation queue.

obj:The object whose notifications the observer wants to receive; that is, only notifications sent by this sender are delivered to the observer. If you pass nil, the notification center doesn’t use a notification’s sender to decide whether to deliver it to the observer.

name用於惟一標識一個通知,obj爲發送通知的對象。若是name和obj都爲nil,則addObserver會註冊一個observer,對全部的通知進行響應。如:

[[NSNotificationCenter defaultCenter] addObserverForName:nil object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
    NSLog(@"block 3 %@", note.name);
    sleep(2);
}];
複製代碼

以上代碼會對全部通知進行響應,打印結果以下:

2020-04-14 23:11:40.027720+0800 DemoRunloop[15128:4831118] block 3 NSThreadWillExitNotification
2020-04-14 23:11:41.188560+0800 DemoRunloop[15128:4830986] block 3 1234567
2020-04-14 23:11:43.189717+0800 DemoRunloop[15128:4830986] block 3 UIApplicationDidFinishLaunchingNotification
2020-04-14 23:11:45.171794+0800 DemoRunloop[15128:4831116] block 3 NSThreadWillExitNotification
2020-04-14 23:11:45.190932+0800 DemoRunloop[15128:4830986] block 3 UIApplicationSuspendedNotification
2020-04-14 23:11:47.191469+0800 DemoRunloop[15128:4830986] block 3 _UIWindowContentWillRotateNotification
2020-04-14 23:11:47.244987+0800 DemoRunloop[15128:4831122] block 3 NSThreadWillExitNotification
2020-04-14 23:11:49.192743+0800 DemoRunloop[15128:4830986] block 3 UIDeviceOrientationDidChangeNotification
2020-04-14 23:11:51.194071+0800 DemoRunloop[15128:4830986] block 3 _UIApplicationDidRemoveDeactivationReasonNotification
複製代碼

繼續往下看,就是將封裝好的NSNotificationReceiver對象存儲到_observers中。

_observers[notificationNameIdentifier, default: [:]][senderIdentifier, default: [:]][receiverIdentifier] = newObserver
複製代碼

對照着定義:

private var _observers: [AnyHashable : [ObjectIdentifier : [ObjectIdentifier : NSNotificationReceiver]]]
複製代碼

Swift中的Dictionary可使用default來指定默認值。

removeObserver

removeObserver即根據_observers執行相應的移除操做。

open func removeObserver(_ observer: Any, name aName: NSNotification.Name?, object: Any?) {
    guard let observer = observer as? NSNotificationReceiver,
        // These 2 parameters would only be useful for removing notifications added by `addObserver:selector:name:object:`
        aName == nil || observer.name == aName,
        object == nil || observer.sender === __SwiftValue.store(object)
    else {
        return
    }

    let notificationNameIdentifier: AnyHashable = observer.name.map { AnyHashable($0) } ?? _nilHashable
    let senderIdentifier: ObjectIdentifier = observer.sender.map { ObjectIdentifier($0) } ?? _nilIdentifier
    let receiverIdentifier: ObjectIdentifier = ObjectIdentifier(observer)
    
    _observersLock.synchronized({
        _observers[notificationNameIdentifier]?[senderIdentifier]?.removeValue(forKey: receiverIdentifier)
        if _observers[notificationNameIdentifier]?[senderIdentifier]?.count == 0 {
            _observers[notificationNameIdentifier]?.removeValue(forKey: senderIdentifier)
        }
    })
}
複製代碼

postNotification

postNotification的目的很簡單,就是遍歷存儲的全部NSNotificationReceiver對象,找到符合條件的,將通知發送到Receiver便可。

open func post(name aName: NSNotification.Name, object anObject: Any?, userInfo aUserInfo: [AnyHashable : Any]? = nil) {
    let notification = Notification(name: aName, object: anObject, userInfo: aUserInfo)
    post(notification)
}
複製代碼

先構建一個Notification對象,而後調用post函數:

open func post(_ notification: Notification) {
    let notificationNameIdentifier: AnyHashable = AnyHashable(notification.name)
    // 轉換成ObjectIdentifier
    let senderIdentifier: ObjectIdentifier? = notification.object.map({ ObjectIdentifier(__SwiftValue.store($0)) })
    
    let sendTo: [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values] = _observersLock.synchronized({
        var retVal = [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values]()
        (_observers[_nilHashable]?[_nilIdentifier]?.values).map({ retVal.append($0) })
        senderIdentifier.flatMap({ _observers[_nilHashable]?[$0]?.values }).map({ retVal.append($0) })
        (_observers[notificationNameIdentifier]?[_nilIdentifier]?.values).map({ retVal.append($0) })
        senderIdentifier.flatMap({ _observers[notificationNameIdentifier]?[$0]?.values}).map({ retVal.append($0) })
        
        return retVal
    })

    sendTo.forEach { observers in
        observers.forEach { observer in
            guard let block = observer.block else {
                return
            }
            
            if let queue = observer.queue, queue != OperationQueue.current {
                queue.addOperation { block(notification) }
                queue.waitUntilAllOperationsAreFinished()
            } else {
                block(notification)
            }
        }
    }
}
複製代碼

原理其實很簡單:根據通知name和sender來查找對應的Receiver,而後調用其響應便可。然而,查找的過程看着至關累。。。

這句代碼 let senderIdentifier: ObjectIdentifier? = notification.object.map({ ObjectIdentifier(__SwiftValue.store($0)) }) 要與addObserver中的 newObserver.sender = __SwiftValue.store(obj) 結合起來看,就是根據Notification的object對象(也就是通知的sender),轉換爲一個ObjectIdentifier惟一表示。而且,再一次強調一下 _observers 這個變態的字典。

private var _observers: [AnyHashable : [ObjectIdentifier : [ObjectIdentifier : NSNotificationReceiver]]]
複製代碼

下邊的一句更變態,sendTo其實是一個數組,其值就是字典的Values,即sendTo是NSNotificationReceiver組成的數組。添加一些註釋:

let sendTo: [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values] = _observersLock.synchronized({
    // 初始化一個空數組
    var retVal = [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values]()
    // name爲nil的observer,經過_nilHashable爲key來查找,即 _observers[_nilHashable]? 結果爲 [ObjectIdentifier : [ObjectIdentifier : NSNotificationReceiver]]
    // name爲nil且object爲nil的observer,經過 _nilIdentifier爲key來查找,即 _observers[_nilHashable]?[_nilIdentifier]? 結果爲 [ObjectIdentifier : NSNotificationReceiver]
    // 不區分name和object的observer,都會收到該通知
    (_observers[_nilHashable]?[_nilIdentifier]?.values).map({ retVal.append($0) })
    // 沒有name的observer(即_observers[_nilHashable]?),若ObjectIdentifier爲senderIdentifier,則會收到該通知
    senderIdentifier.flatMap({ _observers[_nilHashable]?[$0]?.values }).map({ retVal.append($0) })
    // name符合的observer(即_observers[notificationNameIdentifier]?),若object爲nil(即_observers[notificationNameIdentifier]?[_nilIdentifier]?),則會受到該通知
    (_observers[notificationNameIdentifier]?[_nilIdentifier]?.values).map({ retVal.append($0) })
    // name和object均符合的observer,則會收到該通知
    senderIdentifier.flatMap({ _observers[notificationNameIdentifier]?[$0]?.values}).map({ retVal.append($0) })
    
    return retVal
})
複製代碼

使用Swift的高階函數,寫了這麼大一堆代碼,實際上就是爲了過濾出知足條件的NSNotificationReceiver而已:

  1. name和object均爲nil
  2. name爲nil,object符合
  3. name符合,object爲nil
  4. name和object均符合

我我的以爲這種寫法給我帶來了很多困惑,直接一個for循環加上判斷不就解決了麼?想更加Swift的話,使用 _observers.filter({ 篩選出符合條件的observer }) 不就能夠了麼?何須如此複雜。

總之,獲得了NSNotificationReceiver數組sendTo以後,就是執行observer任務的時候了:

sendTo.forEach { observers in
  observers.forEach { observer in
      guard let block = observer.block else {
          return
      }
      
      if let queue = observer.queue, queue != OperationQueue.current {
          queue.addOperation { block(notification) }
          queue.waitUntilAllOperationsAreFinished()
      } else {
          block(notification)
      }
  }
}
複製代碼

若是observer指定了queue,且與當前post的queue一致,則將任務加到queue,調用 queue.waitUntilAllOperationsAreFinished() 會將當前的任務執行完畢,纔會對下一個observer任務執行addOperation。

若是沒有指定queue,則直接執行observer的任務。因此observer的任務執行的線程,依然與當前post的保持一致。這也是iOS中通知中心很是重要的一點!!!

遺留的問題

ObjectIdentifier

/// A unique identifier for a class instance or metatype.
///
/// In Swift, only class instances and metatypes have unique identities. There
/// is no notion of identity for structs, enums, functions, or tuples.
複製代碼

用於惟一表示一個類的實例或者metatype。

__SwiftValue

// TODO: Making this a SwiftObject subclass would let us use Swift refcounting,
// but we would need to be able to emit __SwiftValue's Objective-C class object
// with the Swift destructor pointer prefixed before it.
//
// The layout of `__SwiftValue` is:
// - object header,
// - `SwiftValueHeader` instance,
// - the payload, tail-allocated (the Swift value contained in this box).
//
// NOTE: older runtimes called this _SwiftValue. The two must
// coexist, so it was renamed. The old name must not be used in the new
// runtime.
@interface __SwiftValue : NSObject <NSCopying>

- (id)copyWithZone:(NSZone *)zone;

@end
複製代碼

關於__SwiftValue,能夠參考 奇怪的AnyObject和背後的SwiftValue

Objective-C版本

鑑於Objective-C版本與Swift版本徹底不同,這裏也一併進行解析。這裏採用GNUStep的源碼。

數據結構

NSNotification

結構基本相似:

@interface NSNotification : NSObject <NSCopying, NSCoding>
- (NSString*) name;
- (id) object;
- (NSDictionary*) userInfo;
@end

@implementation NSNotification

static Class	abstractClass = 0;
static Class	concreteClass = 0;

+ (void) initialize
{
  if (concreteClass == 0)
    {
      abstractClass = [NSNotification class];
      concreteClass = [GSNotification class];
    }
}

+ (NSNotification*) notificationWithName: (NSString*)name
				  object: (id)object
			        userInfo: (NSDictionary*)info
{
  return [concreteClass notificationWithName: name
				      object: object
				    userInfo: info];
}
@end
複製代碼

看GSNotification:

/**
 * Concrete class implementing NSNotification.
 */
@interface	GSNotification : NSNotification
{
@public
  NSString	*_name;
  id		_object;
  NSDictionary	*_info;
}
@end

@implementation GSNotification
+ (NSNotification*) notificationWithName: (NSString*)name
				  object: (id)object
			        userInfo: (NSDictionary*)info
{
  GSNotification	*n;

  n = (GSNotification*)NSAllocateObject(self, 0, NSDefaultMallocZone());
  n->_name = [name copyWithZone: [self zone]];
  n->_object = TEST_RETAIN(object);
  n->_info = TEST_RETAIN(info);
  return AUTORELEASE(n);
}
@end
複製代碼

初始化的通知對象是GSNotification,包含了name、object、info。

Observation

Observation用於封裝觀察者。

typedef	struct Obs {
  id		observer;	/* Object to receive message. */
  SEL		selector;	/* Method selector. */
  struct Obs *next;		/* Next item in linked list. */
  int		retained;	/* Retain count for structure. */
  struct NCTbl *link;		/* Pointer back to chunk table */
} Observation;
複製代碼

Observation封裝了observer、selector,經過next指針構成了一個鏈表結構。至關於Swift版本的NSNotificationReceiver。

NCTable

NCTable存儲了全部的Observation對象。

typedef struct NCTbl {
  Observation		*wildcard;	/* Get ALL messages. */
  GSIMapTable		nameless;	/* Get messages for any name. */
  GSIMapTable		named;		/* Getting named messages only. */
  unsigned		lockCount;	/* Count recursive operations. */
  NSRecursiveLock	*_lock;		/* Lock out other threads. */
  Observation		*freeList;
  Observation		**chunks;
  unsigned		numChunks;
  GSIMapTable		cache[CACHESIZE];
  unsigned short	chunkIndex;
  unsigned short	cacheIndex;
} NCTable;
複製代碼

nameless是負責沒有name的通知,而named則是有name的。

  1. wildcard存儲name和object都爲nil的observer
  2. nameless存儲name爲nil,但object不爲nil的observer
  3. named存儲name不爲nil的observer

freeList、chunks、cache用於查找observer時的緩存機制,提升查找效率。

addObserver

GSNotificationObserver

@interface GSNotificationObserver : NSObject
{
	NSOperationQueue *_queue;
	GSNotificationBlock _block;
}
@end
複製代碼

GSNotificationObserver對象包含了block。若是使用了 addObserverForName:object:queue:usingBlock: 接口,會先封裝一個GSNotificationObserver對象,而後再調用 addObserver:selector:name:object: 接口,傳入的selector即爲 @selector(didReceiveNotification:)

- (id) addObserverForName: (NSString *)name 
                   object: (id)object 
                    queue: (NSOperationQueue *)queue 
               usingBlock: (GSNotificationBlock)block
{
	GSNotificationObserver *observer = 
		[[GSNotificationObserver alloc] initWithQueue: queue block: block];

	[self addObserver: observer 
	         selector: @selector(didReceiveNotification:) 
	             name: name 
	           object: object];

	return observer;
}
複製代碼

didReceiveNotification以下,

- (void) didReceiveNotification: (NSNotification *)notif
{
	if (_queue != nil)
	{
		GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc] 
			initWithNotification: notif block: _block];

		[_queue addOperation: op];
	}
	else
	{
		CALL_BLOCK(_block, notif);
	}
}
複製代碼

若是指定了queue,則將NSNotification對象和GSNotificationObserver對象的_block包裝成了GSNotificationBlockOperation,而後加入到queue中,由queue來調度執行。若是未指定queue,則直接 CALL_BLOCK(_block, notif); 來調用。

GSNotificationBlockOperation的任務,其實也就是 ***CALL_BLOCK(_block, _notification);***。

@implementation GSNotificationBlockOperation
- (id) initWithNotification: (NSNotification *)notif 
                      block: (GSNotificationBlock)block
{
	self = [super init];
	if (self == nil)
		return nil;

	ASSIGN(_notification, notif);
	_block = Block_copy(block);
	return self;

}

- (void) main
{
	CALL_BLOCK(_block, _notification);
}
@end
複製代碼

CALL_BLOCK即爲調用block,傳入指定參數而已。

/** * Calls a block. Works irrespective of whether the compiler supports blocks. */
#define CALL_BLOCK(block, args, ...) block(args, ## __VA_ARGS__)
複製代碼

addObserver:selector:name:object:

- (void) addObserver: (id)observer
	        selector: (SEL)selector
                name: (NSString*)name
	      object: (id)object
{
  Observation	*list;
  Observation	*o;
  GSIMapTable	m;
  GSIMapNode	n;

  if (observer == nil)
    [NSException raise: NSInvalidArgumentException
		format: @"Nil observer passed to addObserver ..."];

  if (selector == 0)
    [NSException raise: NSInvalidArgumentException
		format: @"Null selector passed to addObserver ..."];

  if ([observer respondsToSelector: selector] == NO)
    {
      [NSException raise: NSInvalidArgumentException
        format: @"[%@-%@] Observer '%@' does not respond to selector '%@'",
        NSStringFromClass([self class]), NSStringFromSelector(_cmd),
        observer, NSStringFromSelector(selector)];
    }

  lockNCTable(TABLE);

  o = obsNew(TABLE, selector, observer);

  /*
   * Record the Observation in one of the linked lists.
   *
   * NB. It is possible to register an observer for a notification more than
   * once - in which case, the observer will receive multiple messages when
   * the notification is posted... odd, but the MacOS-X docs specify this.
   */

  if (name)
    {
      /*
       * Locate the map table for this name - create it if not present.
       */
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
      if (n == 0)
	{
	  m = mapNew(TABLE);
	  /*
	   * As this is the first observation for the given name, we take a
	   * copy of the name so it cannot be mutated while in the map.
	   */
	  name = [name copyWithZone: NSDefaultMallocZone()];
	  GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
	  GS_CONSUMED(name)
	}
      else
	{
	  m = (GSIMapTable)n->value.ptr;
	}

      /*
       * Add the observation to the list for the correct object.
       */
      n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
      if (n == 0)
	{
	  o->next = ENDOBS;
	  GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
	}
      else
	{
	  list = (Observation*)n->value.ptr;
	  o->next = list->next;
	  list->next = o;
	}
    }
  else if (object)
    {
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      if (n == 0)
	{
	  o->next = ENDOBS;
	  GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
	}
      else
	{
	  list = (Observation*)n->value.ptr;
	  o->next = list->next;
	  list->next = o;
	}
    }
  else
    {
      o->next = WILDCARD;
      WILDCARD = o;
    }

  unlockNCTable(TABLE);
}
複製代碼

經過obsNew函數,將observer、selector組成一個Observation對象,存儲到_table中。根據name和object是否爲nil的狀況,將不一樣的observer對象,分別存儲到不一樣的地方(WILDCARD、NAMELESS、NAMED)。這一點與前邊Swift的實現一致。

#define TABLE ((NCTable*)_table)
#define WILDCARD (TABLE->wildcard)
#define NAMELESS (TABLE->nameless)
#define NAMED (TABLE->named)
#define LOCKCOUNT (TABLE->lockCount)

_table = newNCTable();
複製代碼

obsNew函數,並不是每次都新建一個Observation對象,而是從NCTable的freeList中取出空閒的對象來使用。

static Observation * obsNew(NCTable *t, SEL s, id o) {
  Observation	*obs;

  /* Generally, observations are cached and we create a 'new' observation * by retrieving from the cache or by allocating a block of observations * in one go. This works nicely to both hide observations from the * garbage collector (when using gcc for GC) and to provide high * performance for situations where apps add/remove lots of observers * very frequently (poor design, but something which happens in the * real world unfortunately). */
  if (t->freeList == 0)
    {
      Observation	*block;

      if (t->chunkIndex == CHUNKSIZE)
	{
	  unsigned	size;

	  t->numChunks++;

	  size = t->numChunks * sizeof(Observation*);
	  t->chunks = (Observation**)NSReallocateCollectable(
	    t->chunks, size, NSScannedOption);

	  size = CHUNKSIZE * sizeof(Observation);
	  t->chunks[t->numChunks - 1]
	    = (Observation*)NSAllocateCollectable(size, 0);
	  t->chunkIndex = 0;
	}
      block = t->chunks[t->numChunks - 1];
      t->freeList = &block[t->chunkIndex];
      t->chunkIndex++;
      t->freeList->link = 0;
    }
  obs = t->freeList;
  t->freeList = (Observation*)obs->link;
  obs->link = (void*)t;
  obs->retained = 0;
  obs->next = 0;

  obs->selector = s;
  obs->observer = o;

  return obs;
}
複製代碼

removeObserver

/**
 * Deregisters observer for notifications matching name and/or object.  If
 * either or both is nil, they act like wildcards.  The observer may still
 * remain registered for other notifications; use -removeObserver: to remove
 * it from all.  If observer is nil, the effect is to remove all registrees
 * for the specified notifications, unless both observer and name are nil, in
 * which case nothing is done.
 */
- (void) removeObserver: (id)observer
		   name: (NSString*)name
                 object: (id)object
{
  if (name == nil && object == nil && observer == nil)
      return;

  /*
   *	NB. The removal algorithm depends on an implementation characteristic
   *	of our map tables - while enumerating a table, it is safe to remove
   *	the entry returned by the enumerator.
   */

  lockNCTable(TABLE);

  if (name == nil && object == nil)
    {
      WILDCARD = listPurge(WILDCARD, observer);
    }

  if (name == nil)
    {
      GSIMapEnumerator_t	e0;
      GSIMapNode		n0;

      /*
       * First try removing all named items set for this object.
       */
      e0 = GSIMapEnumeratorForMap(NAMED);
      n0 = GSIMapEnumeratorNextNode(&e0);
      while (n0 != 0)
	{
	  GSIMapTable		m = (GSIMapTable)n0->value.ptr;
	  NSString		*thisName = (NSString*)n0->key.obj;

	  n0 = GSIMapEnumeratorNextNode(&e0);
	  if (object == nil)
	    {
	      GSIMapEnumerator_t	e1 = GSIMapEnumeratorForMap(m);
	      GSIMapNode		n1 = GSIMapEnumeratorNextNode(&e1);

	      /*
	       * Nil object and nil name, so we step through all the maps
	       * keyed under the current name and remove all the objects
	       * that match the observer.
	       */
	      while (n1 != 0)
		{
		  GSIMapNode	next = GSIMapEnumeratorNextNode(&e1);

		  purgeMapNode(m, n1, observer);
		  n1 = next;
		}
	    }
	  else
	    {
	      GSIMapNode	n1;

	      /*
	       * Nil name, but non-nil object - we locate the map for the
	       * specified object, and remove all the items that match
	       * the observer.
	       */
	      n1 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
	      if (n1 != 0)
		{
		  purgeMapNode(m, n1, observer);
		}
	    }
	  /*
	   * If we removed all the observations keyed under this name, we
	   * must remove the map table too.
	   */
	  if (m->nodeCount == 0)
	    {
	      mapFree(TABLE, m);
	      GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName);
	    }
	}

      /*
       * Now remove unnamed items
       */
      if (object == nil)
	{
	  e0 = GSIMapEnumeratorForMap(NAMELESS);
	  n0 = GSIMapEnumeratorNextNode(&e0);
	  while (n0 != 0)
	    {
	      GSIMapNode	next = GSIMapEnumeratorNextNode(&e0);

	      purgeMapNode(NAMELESS, n0, observer);
	      n0 = next;
	    }
	}
      else
	{
	  n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
	  if (n0 != 0)
	    {
	      purgeMapNode(NAMELESS, n0, observer);
	    }
	}
    }
  else
    {
      GSIMapTable		m;
      GSIMapEnumerator_t	e0;
      GSIMapNode		n0;

      /*
       * Locate the map table for this name.
       */
      n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      if (n0 == 0)
	{
	  unlockNCTable(TABLE);
	  return;		/* Nothing to do.	*/
	}
      m = (GSIMapTable)n0->value.ptr;

      if (object == nil)
	{
	  e0 = GSIMapEnumeratorForMap(m);
	  n0 = GSIMapEnumeratorNextNode(&e0);

	  while (n0 != 0)
	    {
	      GSIMapNode	next = GSIMapEnumeratorNextNode(&e0);

	      purgeMapNode(m, n0, observer);
	      n0 = next;
	    }
	}
      else
	{
	  n0 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
	  if (n0 != 0)
	    {
	      purgeMapNode(m, n0, observer);
	    }
	}
      if (m->nodeCount == 0)
	{
	  mapFree(TABLE, m);
	  GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));
	}
    }
  unlockNCTable(TABLE);
}
複製代碼

postNotification

postNotification最終會調用_postAndRelease函數:

/**
 * Private method to perform the actual posting of a notification.
 * Release the notification before returning, or before we raise
 * any exception ... to avoid leaks.
 */
- (void) _postAndRelease: (NSNotification*)notification
{
  Observation	*o;
  unsigned	count;
  NSString	*name = [notification name];
  id		object;
  GSIMapNode	n;
  GSIMapTable	m;
  GSIArrayItem	i[64];
  GSIArray_t	b;
  GSIArray	a = &b;

  if (name == nil)
    {
      RELEASE(notification);
      [NSException raise: NSInvalidArgumentException
		  format: @"Tried to post a notification with no name."];
    }
  object = [notification object];

  /*
   * Lock the table of observations while we traverse it.
   *
   * The table of observations contains weak pointers which are zeroed when
   * the observers get garbage collected.  So to avoid consistency problems
   * we disable gc while we copy all the observations we are interested in.
   * We use scanned memory in the array in the case where there are more
   * than the 64 observers we allowed room for on the stack.
   */
  GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
  lockNCTable(TABLE);

  /*
   * Find all the observers that specified neither NAME nor OBJECT.
   */
  for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next)
    {
      GSIArrayAddItem(a, (GSIArrayItem)o);
    }

  /*
   * Find the observers that specified OBJECT, but didn't specify NAME.
   */
  if (object)
    {
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      if (n != 0)
	{
	  o = purgeCollectedFromMapNode(NAMELESS, n);
	  while (o != ENDOBS)
	    {
	      GSIArrayAddItem(a, (GSIArrayItem)o);
	      o = o->next;
	    }
	}
    }

  /*
   * Find the observers of NAME, except those observers with a non-nil OBJECT
   * that doesn't match the notification's OBJECT).
   */
  if (name)
    {
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      if (n)
	{
	  m = (GSIMapTable)n->value.ptr;
	}
      else
	{
	  m = 0;
	}
      if (m != 0)
	{
	  /*
	   * First, observers with a matching object.
	   */
	  n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
	  if (n != 0)
	    {
	      o = purgeCollectedFromMapNode(m, n);
	      while (o != ENDOBS)
		{
		  GSIArrayAddItem(a, (GSIArrayItem)o);
		  o = o->next;
		}
	    }

	  if (object != nil)
	    {
	      /*
	       * Now observers with a nil object.
	       */
	      n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
	      if (n != 0)
		{
	          o = purgeCollectedFromMapNode(m, n);
		  while (o != ENDOBS)
		    {
		      GSIArrayAddItem(a, (GSIArrayItem)o);
		      o = o->next;
		    }
		}
	    }
	}
    }

  /* Finished with the table ... we can unlock it,
   */
  unlockNCTable(TABLE);

  /*
   * Now send all the notifications.
   */
  count = GSIArrayCount(a);
  while (count-- > 0)
    {
      o = GSIArrayItemAtIndex(a, count).ext;
      if (o->next != 0)
	{
          NS_DURING
            {
              [o->observer performSelector: o->selector
                                withObject: notification];
            }
          NS_HANDLER
            {
              NSLog(@"Problem posting notification: %@", localException);
            }
          NS_ENDHANDLER
	}
    }
  lockNCTable(TABLE);
  GSIArrayEmpty(a);
  unlockNCTable(TABLE);

  RELEASE(notification);
}
複製代碼

步驟:查找name和object符合條件的observer,執行其任務便可。執行任務的代碼即爲:***[o->observer performSelector: o->selector withObject: notification];***。

而單純的 performSelector:withObject: 並不會跟runloop什麼的扯上關係,實際上就是objc_msgSend調用,iOS的performSelector是如何實現的?

因此,OC版本的通知,同樣是同步發送全部通知到observer的。

若是是對於GSNotificationObserver,則 ***[o->observer performSelector: o->selector withObject: notification];***,其實是調用GSNotificationObserver對象的@selector(didReceiveNotification),繼而執行對應的block。

NSNotificationQueue

NSNotificationQueue使得通知的發送能夠在一個queue中進行。實際是將通知存入queue,而後由queue等待合適的時機進行發送,而發送實際上仍是通過NSNotificationCenter。

enqueueNotification

enqueueNotification是將通知放入隊列,而NSNotificationQueue的使用會依賴runloop,默認是DefaultMode。

經過兩個參數決定了queue發送該通知的策略,postingStyle爲發送時機,coalesceMask用於合併通知。

enum {
  NSPostWhenIdle = 1, // post when runloop is idle
  NSPostASAP = 2,     // post soon
  NSPostNow = 3       // post synchronously
};
// Posting styles into notification queue
typedef NSUInteger NSPostingStyle;

enum {
  NSNotificationNoCoalescing = 0,       // don't combine
  NSNotificationCoalescingOnName = 1,   // combine all registered with same name
  NSNotificationCoalescingOnSender = 2  // combine all registered with same object
};
// Enumeration of possible ways to combine notifications when dealing with [NSNotificationQueue]:
typedef NSUInteger NSNotificationCoalescing;
複製代碼

enqueueNotification以下:

/** * Sets notification to be posted to notification center at time dependent on * postingStyle, which may be either <code>NSPostNow</code> (synchronous * post), <code>NSPostASAP</code> (post soon), or <code>NSPostWhenIdle</code> * (post when runloop is idle). coalesceMask determines whether this * notification should be considered same as other ones already on the queue, * in which case they are removed through a call to * -dequeueNotificationsMatching:coalesceMask: . The modes argument * determines which [NSRunLoop] mode notification may be posted in (nil means * NSDefaultRunLoopMode). */
- (void) enqueueNotification: (NSNotification*)notification
		postingStyle: (NSPostingStyle)postingStyle
		coalesceMask: (NSUInteger)coalesceMask
		    forModes: (NSArray*)modes
{
  if (modes == nil)
    {
      modes = defaultMode;
    }
  if (coalesceMask != NSNotificationNoCoalescing)
    {
      [self dequeueNotificationsMatching: notification
			    coalesceMask: coalesceMask];
    }
  switch (postingStyle)
    {
      case NSPostNow:
	{
	  NSString	*mode;

	  mode = [[NSRunLoop currentRunLoop] currentMode];
	  if (mode == nil || [modes indexOfObject: mode] != NSNotFound)
	    {
	      [_center postNotification: notification];
	    }
	}
	break;

      case NSPostASAP:
	add_to_queue(_asapQueue, notification, modes, _zone);
	break;

      case NSPostWhenIdle:
	add_to_queue(_idleQueue, notification, modes, _zone);
	break;
    }
}
複製代碼

若是選擇了合併通知,則會調用dequeueNotificationsMatching:coalesceMask進行合併操做。

若是設置爲NSPostNow,則當即調用postNotification方法。不然,調用add_to_queue將通知放入不一樣的queue。

static void
add_to_queue(NSNotificationQueueList *queue, NSNotification *notification,
  NSArray *modes, NSZone *_zone)
{
  NSNotificationQueueRegistration	*item;

  item = NSZoneCalloc(_zone, 1, sizeof(NSNotificationQueueRegistration));
  if (item == 0)
    {
      [NSException raise: NSMallocException
      		  format: @"Unable to add to notification queue"];
    }

  item->notification = RETAIN(notification);
  item->name = [notification name];
  item->object = [notification object];
  item->modes = [modes copyWithZone: [modes zone]];

  item->next = NULL;
  item->prev = queue->tail;
  queue->tail = item;
  if (item->prev)
    {
      item->prev->next = item;
    }
  if (!queue->head)
    {
      queue->head = item;
    }
}
複製代碼

_asapQueue和_idleQueue兩個queue其實是NSNotificationQueueList

typedef struct _NSNotificationQueueList {
  struct _NSNotificationQueueRegistration *head;
  struct _NSNotificationQueueRegistration *tail;
} NSNotificationQueueList;
複製代碼

NSNotificationQueue中保存的實際上NSNotificationQueueRegistration對象構成的鏈表。

runloop如何發送通知的?

runloop會經過調用下邊三個函數,分別將不一樣的queue中的通知發送出來。而這三個函數在runloop中的 acceptInputForMode:beforeDate: 函數中會觸發,即:

GSPrivateNotifyASAP(_currentMode); 或 GSPrivateNotifyIdle(_currentMode);
[self acceptInputForMode: mode beforeDate: d];
- (BOOL) runMode: (NSString*)mode beforeDate: (NSDate*)date
複製代碼
/* Function used by the NSRunLoop and friends for processing
 * queued notifications which should be processed at the first safe moment.
 */
void GSPrivateNotifyASAP(NSString *mode) GS_ATTRIB_PRIVATE;

/* Function used by the NSRunLoop and friends for processing
 * queued notifications which should be processed when the loop is idle.
 */
void GSPrivateNotifyIdle(NSString *mode) GS_ATTRIB_PRIVATE;

/* Function used by the NSRunLoop and friends for determining whether
 * there are more queued notifications to be processed.
 */
BOOL GSPrivateNotifyMore(NSString *mode) GS_ATTRIB_PRIVATE;
複製代碼
/*
 *	The following code handles sending of queued notifications by
 *	NSRunLoop.
 */

void
GSPrivateNotifyASAP(NSString *mode)
{
  NotificationQueueList	*item;

  GSPrivateCheckTasks();

  for (item = currentList(); item; item = item->next)
    {
      if (item->queue)
	{
	  notify(item->queue->_center,
	    item->queue->_asapQueue,
	    mode,
	    item->queue->_zone);
	}
    }
}

void
GSPrivateNotifyIdle(NSString *mode)
{
  NotificationQueueList	*item;

  for (item = currentList(); item; item = item->next)
    {
      if (item->queue)
	{
	  notify(item->queue->_center,
	    item->queue->_idleQueue,
	    mode,
	    item->queue->_zone);
	}
    }
}

BOOL
GSPrivateNotifyMore(NSString *mode)
{
  NotificationQueueList	*item;

  for (item = currentList(); item; item = item->next)
    {
      if (item->queue != nil)
	{
          NSNotificationQueueRegistration	*r;

	  r = item->queue->_idleQueue->head;
	  while (r != 0)
	    {
	      if (mode == nil || [r->modes indexOfObject: mode] != NSNotFound)
		{
		  return YES;
		}
	      r = r->next;
	    }
	}
    }
  return NO;
}
複製代碼

發送操做則是notify函數。

static void
notify(NSNotificationCenter *center, NSNotificationQueueList *list,
  NSString *mode, NSZone *zone)
{
  BOOL					allocated = NO;
  void					*buf[100];
  void					**ptr = buf;
  unsigned				len = sizeof(buf) / sizeof(*buf);
  unsigned				pos = 0;
  // 取出鏈表頭元素
  NSNotificationQueueRegistration	*item = list->head;

  /* Gather matching items into a buffer.
   */
  while (item != 0)
    {
      if (mode == nil || [item->modes indexOfObject: mode] != NSNotFound)
	{
	  if (pos == len)
	    {
	      unsigned	want;

	      want = (len == 0) ? 2 : len * 2;
	      if (NO == allocated)
		{
		  void		*tmp;
		  
		  tmp = NSZoneMalloc(NSDefaultMallocZone(),
		    want * sizeof(void*));
		  memcpy(tmp, (void*)ptr, len * sizeof(void*));
		  ptr = tmp;
		  allocated = YES;
		}
	      else
		{
		  ptr = NSZoneRealloc(NSDefaultMallocZone(),
		    ptr, want * sizeof(void*));
		}
	      len = want;
	    }
	  ptr[pos++] = item;
	}
      item = item->next;	// head --> tail uses next link
    }
  len = pos;	// Number of items found

  /* Posting a notification catches exceptions, so it's OK to use
   * retain/release of objects here as we won't get an exception
   * causing a leak.
   */
  if (len > 0)
    {
      /* First, we make a note of each notification while removing the
       * corresponding list item from the queue ... so that when we get
       * round to posting the notifications we will not get problems
       * with another notif() trying to use the same items.
       */
      for (pos = 0; pos < len; pos++)
	{
	  item = ptr[pos];
	  ptr[pos] = RETAIN(item->notification);
	  remove_from_queue(list, item, zone);
	}

      /* Now that we no longer need to worry about r-entrancy,
       * we step through our notifications, posting each one in turn.
       */
      for (pos = 0; pos < len; pos++)
	{
	  NSNotification	*n = (NSNotification*)ptr[pos];

	  [center postNotification: n];
	  RELEASE(n);
	}

      if (allocated)
	{
	  NSZoneFree(NSDefaultMallocZone(), ptr);
	}
    }
}
複製代碼

發送操做實際上就是一個for循環,遍歷全部NSNotification,調用NSNotificationCenter的postNotification函數即完成了通知發送。

dequeueNotification

dequeueNotification操做也會根據通知合併策略(coalesceMask)來決定出隊操做。

/**
 * Immediately remove all notifications from queue matching notification on
 * name and/or object as specified by coalesce mask, which is an OR
 * ('<code>|</code>') of the options
 * <code>NSNotificationCoalescingOnName</code>,
 * <code>NSNotificationCoalescingOnSender</code> (object), and
 * <code>NSNotificationNoCoalescing</code> (match only the given instance
 * exactly).  If both of the first options are specified, notifications must
 * match on both attributes (not just either one).  Removed notifications are
 * <em>not</em> posted.
 */
- (void) dequeueNotificationsMatching: (NSNotification*)notification
			 coalesceMask: (NSUInteger)coalesceMask
{
  NSNotificationQueueRegistration	*item;
  NSNotificationQueueRegistration	*prev;
  id					name   = [notification name];
  id					object = [notification object];

  if ((coalesceMask & NSNotificationCoalescingOnName)
    && (coalesceMask & NSNotificationCoalescingOnSender))
    {
      /*
       * find in ASAP notification in queue matching both
       */
      for (item = _asapQueue->tail; item; item = prev)
	{
          prev = item->prev;
          //PENDING: should object comparison be '==' instead of isEqual?!
          if ((object == item->object) && [name isEqual: item->name])
	    {
              remove_from_queue(_asapQueue, item, _zone);
	    }
	}
      /*
       * find in idle notification in queue matching both
       */
      for (item = _idleQueue->tail; item; item = prev)
	{
          prev = item->prev;
          if ((object == item->object) && [name isEqual: item->name])
	    {
              remove_from_queue(_idleQueue, item, _zone);
	    }
	}
    }
  else if ((coalesceMask & NSNotificationCoalescingOnName))
    {
      /*
       * find in ASAP notification in queue matching name
       */
      for (item = _asapQueue->tail; item; item = prev)
	{
          prev = item->prev;
          if ([name isEqual: item->name])
	    {
              remove_from_queue(_asapQueue, item, _zone);
	    }
	}
      /*
       * find in idle notification in queue matching name
       */
      for (item = _idleQueue->tail; item; item = prev)
	{
          prev = item->prev;
          if ([name isEqual: item->name])
	    {
              remove_from_queue(_idleQueue, item, _zone);
	    }
	}
    }
  else if ((coalesceMask & NSNotificationCoalescingOnSender))
    {
      /*
       * find in ASAP notification in queue matching sender
       */
      for (item = _asapQueue->tail; item; item = prev)
	{
          prev = item->prev;
          if (object == item->object)
	    {
              remove_from_queue(_asapQueue, item, _zone);
	    }
	}
      /*
       * find in idle notification in queue matching sender
       */
      for (item = _idleQueue->tail; item; item = prev)
	{
          prev = item->prev;
          if (object == item->object)
	    {
              remove_from_queue(_idleQueue, item, _zone);
	    }
	}
    }
}
複製代碼

注意事項

區分通知有兩個維度

基於name和object兩個維度,涉及的數據結構,是通知中心的關鍵所在。添加、移除、發送均是基於該數據結構進行了相應的操做。

postNotification是同步的

將notification發送到全部的observer,且observer的對應任務都執行完畢,纔算通知發送成功。這個邏輯其實從上邊的源碼已經看出來了。

Another good thing to know is that postNotificationName: posts notifications synchronously.

看下邊這段代碼:

static NSString *const kNotificationName = @"1234567";

- (void)postNotificationSync {
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNotification) name:kNotificationName object:nil];
  [[NSNotificationCenter defaultCenter] addObserverForName:kNotificationName
                                                    object:nil
                                                     queue:[NSOperationQueue mainQueue]
                                                usingBlock:^(NSNotification * _Nonnull note) {
      NSLog(@"block 1");
      sleep(2);
  }];
  [[NSNotificationCenter defaultCenter] addObserverForName:kNotificationName
                                                    object:nil
                                                     queue:[NSOperationQueue mainQueue]
                                                usingBlock:^(NSNotification * _Nonnull note) {
      NSLog(@"block 2");
      sleep(2);
  }];

  NSLog(@"postNotificationName %@", [NSThread currentThread]);
  [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil];
  NSLog(@"postNotificationName done");
}

- (void)onNotification {
  // 默認,postNotificationName和onNotification在同一個線程中執行的。
  NSLog(@"onNotification %@", [NSThread currentThread]);
  sleep(2);
}
複製代碼

輸出結果爲:

2020-04-14 21:51:28.425951+0800 DemoRunloop[11894:4768945] postNotificationName <NSThread: 0x600003a705c0>{number = 1, name = main}
2020-04-14 21:51:28.426169+0800 DemoRunloop[11894:4768945] onNotification <NSThread: 0x600003a705c0>{number = 1, name = main}
2020-04-14 21:51:30.427355+0800 DemoRunloop[11894:4768945] block 1
2020-04-14 21:51:32.428547+0800 DemoRunloop[11894:4768945] block 2
2020-04-14 21:51:34.429006+0800 DemoRunloop[11894:4768945] postNotificationName done
複製代碼

postNotificationName done這一句是在全部observer對應的任務執行完畢,纔打印出來的。且三個任務是按照addObserver時候的順序依次執行的,且sleep都有效。

默認postNotification和observer的任務是在同一線程

- (void)testBackgroundNotification {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNotification) name:kNotificationName object:nil];
    self.myQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(self.myQueue, ^{
        NSLog(@"postNotificationName %@", [NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil];
    });    
}

- (void)onNotification {
    // 默認,postNotificationName和onNotification在同一個線程中執行的。
    NSLog(@"onNotification %@", [NSThread currentThread]);
    sleep(2);
}
複製代碼

輸出結果爲:

2020-04-14 21:57:27.723143+0800 DemoRunloop[12073:4773960] postNotificationName <NSThread: 0x60000239b200>{number = 5, name = (null)}
2020-04-14 21:57:27.723352+0800 DemoRunloop[12073:4773960] onNotification <NSThread: 0x60000239b200>{number = 5, name = (null)}
複製代碼

若是想要指定隊列執行通知的任務,可使用 addObserverForName:object:queue:usingBlock: 的接口。

避免多線程引起的死鎖

若是跨線程使用通知,要注意避免出現死鎖的場景。好比,使用下邊的代碼,能夠產生一次死鎖。

@implementation MyObject
+ (instancetype)sharedInstance {
    static MyObject *sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyObject alloc] init];
        
        NSLog(@"postNotificationName %@", [NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationName object:nil];
        NSLog(@"postNotificationName done");
    });
    return sharedInstance;
}
@end

- (void)testLockDispatchOnce {
    [[NSNotificationCenter defaultCenter] addObserverForName:kNotificationName
                                                      object:nil
                                                       queue:[NSOperationQueue mainQueue]
                                                  usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"block");
    }];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        [MyObject sharedInstance];
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [MyObject sharedInstance];
    });
}
複製代碼

死鎖的緣由在於:

  1. 子線程先執行了dispatch_once的邏輯,而後發送了通知。等待observer執行完任務,該通知纔算發送完成,dispatch_once的代碼段才能結束。
  2. 主線程執行了sharedInstance,遇到dispatch_once,要等待上一次調用dispatch_once返回,才能繼續。而上一次子線程調用的dispatch_once,卻由於observer的任務要在mainQueue執行,因此會等待mainQueue裏邊的sharedInstance調用結束。
  3. 這樣就造成了死鎖。因此,在dispatch_once中要避免跨線程操做。

參考資料

相關文章
相關標籤/搜索