IOS development: Notification and Multithreading

Source: Internet
Author: User
Tags notification center

IOS development: Notification and Multithreading

Let's take a look at the official documentation, which is written in this way:

In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.

Translated:

In multi-threaded applications, the thread in which Notification is post is forwarded, not necessarily in the thread where the observer is registered.

That is to say, the sending and receiving processes of Notification are in the same thread. To illustrate this, let's look at an example:

Code List 1: Send and process Notification

@ Implementation ViewController

-(Void) viewDidLoad {

[Super viewDidLoad];

NSLog (@ "current thread = % @", [NSThread currentThread]);

[[Nsicationcenter center defacenter center] addObserver: self selector: @ selector (handleNotification :) name: TEST_NOTIFICATION object: nil];

Dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {

[[Nsicationcenter center defacenter center] postNotificationName: TEST_NOTIFICATION object: nil userInfo: nil];

});

}

-(Void) handleNotification :( NSNotification *) notification

{

NSLog (@ "current thread = % @", [NSThread currentThread]);

NSLog (@ "test notification ");

}

@ End

The output result is as follows:

22:05:12. 856 test [865: 45102] current thread = {number = 1, name = main}

22:05:12. 857 test [865: 45174] current thread = {number = 2, name = (null )}

22:05:12. 857 test [865: 45174] test notification

We can see that although we have registered the Notification observer in the main thread, the post Notification in the global queue is not processed in the main thread. Therefore, we need to note that if we want to process UI-related operations in the callback, we need to ensure that the callback is executed in the main thread.

Now there is a problem. If our Notification is post in the second-level thread, how can we process this Notification in the main thread? Or, if we want the post thread of a Notification to be different from the forwarding thread, what should we do? Let's take a look at what the official documentation says:

For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you wowould like to receive the notifications in the background thread instead of the main thread. in these cases, you must capture the limits as they are delivered on the default thread and redirect them to the appropriate thread.

Here we talk about "redirection", that is, we capture these distributed notifications in the default thread where the Notification is located, and then redirect them to the specified thread.

One way to implement redirection is to customize a Notification Queue (note that it is not an NSNotificationQueue object, but an array), so that this queue can maintain the notifications we need to redirect. We are still registering a Notification observer as usual. When the Notification comes, first check whether the post Notification thread is the thread we expect. If not, store the Notification in our queue and send a signal (signal) to the expected thread to tell the thread to process a Notification. After receiving the signal, the specified Thread removes the Notification from the queue and processes it.

The official document provides sample code, which is used here to test the actual results:

Code List 2: post and forward a Notification in different threads

@ Interface ViewController ()

@ Property (nonatomic) NSMutableArray * notifications; // notification queue

@ Property (nonatomic) NSThread * notificationThread; // The expected thread

@ Property (nonatomic) NSLock * icationicationlock; // The lock object used to lock the notification queue to avoid thread conflicts.

@ Property (nonatomic) NSMachPort * notificationPort; // The communication port used to send signals to the expected thread

@ End

@ Implementation ViewController

-(Void) viewDidLoad {

[Super viewDidLoad];

NSLog (@ "current thread = % @", [NSThread currentThread]);

// Initialization

Self. configurications = [[NSMutableArray alloc] init];

Self. icationicationlock = [[NSLock alloc] init];

Self. notificationThread = [NSThread currentThread];

Self. icationicationport = [[NSMachPort alloc] init];

Self. icationicationport. delegate = self;

// Add a port source to the run loop of the current thread

// When the Mach message arrives and the run loop of the receiving thread does not run, the kernel will save the message until the next time it enters the run loop

[[Nsunloop currentRunLoop] addPort: self. icationicationport

ForMode :( _ bridge NSString *) kCFRunLoopCommonModes];

[[Nsicationcenter center defacenter center] addObserver: self selector: @ selector (processNotification :) name: @ "TestNotification" object: nil];

Dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {

[[Nsicationcenter center defacenter center] postNotificationName: TEST_NOTIFICATION object: nil userInfo: nil];

});

}

-(Void) handleMachMessage :( void *) msg {

[Self. icationicationlock];

While ([self. Events count]) {

NSNotification * notification = [self. configurications objectAtIndex: 0];

[Self. configurications removeObjectAtIndex: 0];

[Self. icationicationlock unlock];

[Self processNotification: notification];

[Self. icationicationlock];

};

[Self. icationicationlock unlock];

}

-(Void) processNotification :( NSNotification *) notification {

If ([NSThread currentThread]! = _ Icationicationthread ){

// Forward the notification to the correct thread.

[Self. icationicationlock];

[Self. configurications addObject: notification];

[Self. icationicationlock unlock];

[Self. icationicationport sendBeforeDate: [NSDate date]

Components: nil

From: nil

Reserved: 0];

}

Else {

// Process the notification here;

NSLog (@ "current thread = % @", [NSThread currentThread]);

NSLog (@ "process notification ");

}

}

@ End

After running, the output is as follows:

23:38:31. 637 test [1474: 92483] current thread = {number = 1, name = main}

23:38:31. 663 test [1474: 92483] current thread = {number = 1, name = main}

23:38:31. 663 test [1474: 92483] process notification

We can see that the Notification we throw in the global dispatch queue is received in the main thread as expected.

For details about this implementation method and its limitations, please refer To the official documentation Delivering configurations To particle Threads, which will not be explained here. Of course, a better way is to subclass an nsicationicationcenter, or write a class to process such forwarding.

Thread security of nsicationicationcenter

The reason why Apple uses the notification center to post and forward the same message in the same thread should be taken into consideration from the thread security perspective. The official document tells us that nsicationicationcenter is a thread security class. We can use the same NSNotificationCenter object in a multi-threaded environment without locking. The original Article is in the Threading Programming Guide. The details are as follows:

The following classes and functions are generally considered to be thread-safe. You can use the same instance from multiple threads without first acquiring a lock.

NSArray

...

NSNotification

NSNotificationCenter

We can add/Delete the notification observer in any thread, or post a notification in any thread.

Nsicationicationcenter has done a lot of work on thread security. Does that mean we can rest assured? Let's take a look at the first example. Let's make a slight transformation. We will come at 1.1:

Code List 3: General mode of nsicationicationcenter

@ Interface Observer: NSObject

@ End

@ Implementation Observer

-(Instancetype) init

{

Self = [super init];

If (self)

{

_ Poster = [[Poster alloc] init];

[[Nsicationcenter center defacenter center] addObserver: self selector: @ selector (handleNotification :) name: TEST_NOTIFICATION object: nil]

}

Return self;

}

-(Void) handleNotification :( NSNotification *) notification

{

NSLog (@ "handle notification ");

}

-(Void) dealloc

{

[[Nsicationcenter center defacenter center] removeObserver: self];

}

@ End

// Other places

[[Nsicationcenter center defacenter center] postNotificationName: TEST_NOTIFICATION object: nil];

The above code is what we usually do: Add a notification listener, define a callback, remove the listener when the object is released, and post a notification somewhere in the program. It is simple and clear. If all this happens in a thread, or at least the dealloc method runs in the thread of-postNotificationName: (Note: The post and forwarding of NSNotification are synchronized ), so all are OK, there is no thread security problem. But what if the dealloc method and-postNotificationName: method are not running in the same thread?

Let's rebuild the above Code:

Code list 4: thread security problems caused by nsicationicationcenter

# Pragma mark-Poster

@ Interface Poster: NSObject

@ End

@ Implementation Poster

-(Instancetype) init

{

Self = [super init];

If (self)

{

[Self defined mselectorinbackground: @ selector (postNotification) withObject: nil];

}

Return self;

}

-(Void) postNotification

{

[[Nsicationcenter center defacenter center] postNotificationName: TEST_NOTIFICATION object: nil];

}

@ End

# Pragma mark-Observer

@ Interface Observer: NSObject

{

Poster * _ poster;

}

@ Property (nonatomic, assign) NSInteger I;

@ End

@ Implementation Observer

-(Instancetype) init

{

Self = [super init];

If (self)

{

_ Poster = [[Poster alloc] init];

[[Nsicationcenter center defacenter center] addObserver: self selector: @ selector (handleNotification :) name: TEST_NOTIFICATION object: nil];

}

Return self;

}

-(Void) handleNotification :( NSNotification *) notification

{

NSLog (@ "handle notification begin ");

Sleep (1 );

NSLog (@ "handle notification end ");

Self. I = 10;

}

-(Void) dealloc

{

[[Nsicationcenter center defacenter center] removeObserver: self];

NSLog (@ "Observer dealloc ");

}

@ End

# Pragma mark-ViewController

@ Implementation ViewController

-(Void) viewDidLoad {

[Super viewDidLoad];

_ Autoreleasing Observer * observer = [[Observer alloc] init];

}

@ End

This Code adds a listener for the TEST_NOTIFICATION notification to the main thread and removes it from the main thread. Our NSNotification is post in the background thread. In the notification processing function, we sleep the thread where the callback is located for 1 second, and then set the attribute I value. What will happen at this time? Let's take a look at the output:

00:31:41. 286 SKTest [932: 88791] handle notification begin

00:31:41. 291 SKTest [932: 88713] Observer dealloc

00:31:42. 361 SKTest [932: 88791] handle notification end

(Lldb)

// The program throws "Thread 6: EXC_BAD_ACCESS (code = exc_i1__gpflt)" at self. I = 10 )"

The typical memory error caused the program to crash. In fact, from the output results, we can see what happened. Let's briefly describe:

When we register an observer, the notification center holds a weak reference from the observer to ensure that the observer is available.

When the main thread calls the dealloc operation, the reference count of the Observer object will be reduced to 0, and the object will be released.

The background thread sends a notification. If the Observer is not released yet, the message is forwarded to it and the callback method is executed. If the object is released during callback execution, the above problem occurs.

Of course, the above example is intentional, but it is not ruled out that similar problems will occur in actual encoding. Although nsicationicationcenter is thread-safe, it does not mean that we can guarantee thread security during use. If we do not pay attention to it, there will still be thread problems.

What should we do? Here are some good suggestions:

Handle notification-related operations in one thread as much as possible. In most cases, this can ensure the normal operation of the notification. However, it is difficult to determine which thread will call the dealloc method.

When you register all listeners, use the block-based API. In this way, we can use weak-strong to process the attributes or methods of self in the block. For details, you can try the above Code.

Use objects with a secure life cycle, which is no longer suitable for an object singleton object, and will not be released throughout the application lifecycle.

Use a proxy.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.