iOS development: Notification and multithreading

Source: Internet
Author: User
Tags sleep thread notification center

Let's take a look at the official document, and that's what it says:

In a multithreaded application, notifications are always delivered to the thread in which the notification is posted, whi CH May is the same thread in which a observer registered itself.

The translation comes from:

In a multithreaded application, the thread in which the notification post is forwarded in which thread, not necessarily in the thread that registers the viewer.

In other words, the notification send and receive processing is in the same thread. To illustrate this point, let's take a look at an example:

Sending and processing of code listing 1:notification

@implementation Viewcontroller

-(void) Viewdidload {

[Super Viewdidload];

NSLog (@ "Current thread =%@", [Nsthread CurrentThread]);

[[Nsnotificationcenter Defaultcenter] addobserver:self selector: @selector (handlenotification:) name:test_ NOTIFICATION Object:nil];

Dispatch_async (Dispatch_get_global_queue (dispatch_queue_priority_default, 0), ^{

[[Nsnotificationcenter Defaultcenter] postnotificationname:test_notification object:nil UserInfo:nil];

});

}

-(void) Handlenotification: (nsnotification *) notification

{

NSLog (@ "Current thread =%@", [Nsthread CurrentThread]);

NSLog (@ "test notification");

}

@end

The output results are as follows:

2015-03-11 22:05:12.856 test[865:45102] Current thread = {Number = 1, name = main}

2015-03-11 22:05:12.857 test[865:45174] Current thread = {Number = 2, name = (NULL)}

2015-03-11 22:05:12.857 test[865:45174] Test notification

As you can see, although we registered the notification observer in the main thread, the notification of post in the global queue is not handled in the main thread. So it's time to be aware that if we want to handle UI-related operations in a callback, we need to make sure that the callback is executed in the main thread.

At this point, there is a problem, if our notification is post in level two thread, how can we handle this notification in the main thread? or in other words, What should we do if we want a notification post thread to be not the same thread as the forwarding thread? Let's look at what the official document says:

For example, if an object running in a background thread are listening for notifications from the user interface, such as a Window closing, you would like to receive the notifications in the background thread instead of the main thread. In this cases, you must capture the notifications as they are the "on" the default thread and delivered to the AP Propriate thread.

"Redirect" is where we capture notifications of these distributions in the default thread where notification resides, and then redirect them to the specified thread.

The idea of a redirect is to customize a notification queue (note, not the Nsnotificationqueue object, but an array), and let this queue maintain the notification that we need to redirect. We are still registering a notification observer as usual, and when notification comes, let's see if the notification thread is the one we expect, and if not, store the notification in our queue. and sends a signal (signal) to the desired thread to tell the thread that it needs to handle a notification. After the specified thread has received the signal, it removes the notification from the queue and processes it.

The official documentation has given the sample code to borrow here to test the actual results:

Code Listing 2: Post and forward a notification on a different thread

@interface Viewcontroller ()

@property (nonatomic) Nsmutablearray *notifications; Notification queue

@property (nonatomic) Nsthread *notificationthread; Expected thread

@property (nonatomic) Nslock *notificationlock; Lock object for locking the notification queue to avoid threading conflicts

@property (nonatomic) Nsmachport *notificationport; The communication port used to send a signal to the desired thread

@end

@implementation Viewcontroller

-(void) Viewdidload {

[Super Viewdidload];

NSLog (@ "Current thread =%@", [Nsthread CurrentThread]);

Class

Self.notifications = [[Nsmutablearray alloc] init];

Self.notificationlock = [[Nslock alloc] init];

Self.notificationthread = [Nsthread CurrentThread];

Self.notificationport = [[Nsmachport alloc] init];

Self.notificationPort.delegate = self;

Add a port source to the current thread's run loop

When a Mach message arrives and the receiving thread's run loop is not running, the kernel saves the message until the next time it enters the run loop

[[Nsrunloop Currentrunloop] AddPort:self.notificationPort

Formode: (__bridge NSString *) kcfrunloopcommonmodes];

[[Nsnotificationcenter Defaultcenter] addobserver:self selector: @selector (processnotification:) name:@ " Testnotification "Object:nil];

Dispatch_async (Dispatch_get_global_queue (dispatch_queue_priority_default, 0), ^{

[[Nsnotificationcenter Defaultcenter] postnotificationname:test_notification object:nil UserInfo:nil];

});

}

-(void) Handlemachmessage: (void *) msg {

[Self.notificationlock Lock];

while ([self.notifications Count]) {

Nsnotification *notification = [Self.notifications objectatindex:0];

[Self.notifications removeobjectatindex:0];

[Self.notificationlock unlock];

[Self processnotification:notification];

[Self.notificationlock Lock];

};

[Self.notificationlock unlock];

}

-(void) Processnotification: (nsnotification *) Notification {

if ([Nsthread CurrentThread]!= _notificationthread) {

Forward the notification to the correct thread.

[Self.notificationlock Lock];

[Self.notifications addobject:notification];

[Self.notificationlock unlock];

[Self.notificationport 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, its output is as follows:

2015-03-11 23:38:31.637 test[1474:92483] Current thread = {Number = 1, name = main}

2015-03-11 23:38:31.663 test[1474:92483] Current thread = {Number = 1, name = main}

2015-03-11 23:38:31.663 test[1474:92483] Process notification

As we can see, the notification we throw in the global dispatch queue is received in the main thread.

This way of implementation of the specific analysis and limitations you can refer to the official document delivering notifications to particular Threads, there is no more explanation. Of course, a better approach might be for us to subclass a nsnotificationcenter, or write a separate class to handle this forwarding.

Thread Security for Nsnotificationcenter

The strategy that Apple takes the notification center to post and forward the same message in the same thread should be considered in terms of thread safety. The official document tells us that Nsnotificationcenter is a thread-safe class, and we can use the same Nsnotificationcenter object without locking in a multithreaded environment. The original text in the Threading Programming Guide, specific as follows:

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

Nsarray

...

Nsnotification

Nsnotificationcenter

We can add/remove notifications from any thread, or post a notification in any thread.

Nsnotificationcenter has done a lot of work on thread safety, does that mean we can sit back and relax? Look at the first example, we'll change it a little bit, 1.1 points:

Common patterns for Code listing 3:nsnotificationcenter

@interface Observer:nsobject

@end

@implementation Observer

-(Instancetype) init

{

self = [super init];

if (self)

{

_poster = [[Poster alloc] init];

[[Nsnotificationcenter Defaultcenter] addobserver:self selector: @selector (handlenotification:) name:test_ NOTIFICATION Object:nil]

}

return self;

}

-(void) Handlenotification: (nsnotification *) notification

{

NSLog (@ "handle notification");

}

-(void) dealloc

{

[[Nsnotificationcenter Defaultcenter] removeobserver:self];

}

@end

Other places

[[Nsnotificationcenter Defaultcenter] postnotificationname:test_notification Object:nil];

The code above is what we usually do: add a notification listener, define a callback, and remove the listener when the owning object is released, and then post a notification somewhere in the program. Simple and straightforward, if all this happens in a thread, or at least the Dealloc method runs in the-postnotificationname: thread (note: Nsnotification post and forwarding are synchronized), OK, There is no thread safety issue. But what happens if the Dealloc method and the-postnotificationname: method does not run on the same thread?

Let's change the code above:

Thread safety issues raised by code listing 4:nsnotificationcenter

#pragma mark-poster

@interface Poster:nsobject

@end

@implementation Poster

-(Instancetype) init

{

self = [super init];

if (self)

{

[Self Performselectorinbackground: @selector (postnotification) Withobject:nil];

}

return self;

}

-(void) postnotification

{

[[Nsnotificationcenter Defaultcenter] 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];

[[Nsnotificationcenter Defaultcenter] 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

{

[[Nsnotificationcenter Defaultcenter] removeobserver:self];

NSLog (@ "Observer dealloc");

}

@end

#pragma mark-viewcontroller

@implementation Viewcontroller

-(void) Viewdidload {

[Super Viewdidload];

__autoreleasing Observer *observer = [[Observer alloc] init];

}

@end

This code is a listener who adds a test_notification notification to the main thread and removes it from the main thread, and our nsnotification is post in the background thread. In the notification handler, we let the thread where the callback is located sleep for 1 seconds before setting the property I value. What happens then? Let's take a look at the output first:

2015-03-14 00:31:41.286 sktest[932:88791] handle notification begin

2015-03-14 00:31:41.291 sktest[932:88713] Observer dealloc

2015-03-14 00:31:42.361 sktest[932:88791] Handle notification end

(LLDB)

"Thread 6:exc_bad_access (Code=exc_i386_gpflt)" was thrown by the program at SELF.I = 10

Classic memory error, the program crashed. In fact, from the output results, we can see exactly what happened. Let us briefly describe:

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

The main thread calls the Dealloc operation to reduce the reference count of the Observer object to 0, when the object is released.

A background thread sends a notification that if observer has not been released at this time, it forwards the message to it and executes the callback method. If the object is freed during the execution of the callback, the above problem occurs.

Of course, the above example is intentional, but it does not rule out that a similar problem would be encountered in the actual coding. Although Nsnotificationcenter is thread-safe, it does not mean that we can guarantee thread safety when we use it, and if we don't pay attention to it, threading problems will occur.

So what do we do? Here are some good tips:

Try to handle notification-related operations in a single thread, and in most cases this will ensure that the notification is working properly. However, we are not sure which thread is going to call the Dealloc method, so this is a bit more difficult.

When registering for listening, use the block based API. This allows us to continue invoking the self's properties or methods in block, which can be handled in a weak-strong manner. Specific you can change the code above to try what is the effect.

Using an object with a security lifecycle, this object can be a single object, and will not be released throughout its 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.