
線程間通信能不用就不用
好像兩輛行駛的車之間交換內(nèi)容
用performSelector靜好就是晴天
一、了解NSPort
- NSPort是描述通信通道的抽象類。在兩個(gè)NSPort對(duì)象之間通過(guò)
- (BOOL)sendBeforeDate:(NSDate *)limitDate msgid:(NSUInteger)msgID components:(nullable NSMutableArray *)components from:(nullable NSPort *)receivePort reserved:(NSUInteger)headerSpaceReserved;發(fā)送消息和handlePortMessage:代理接受NSPortMessage對(duì)象,來(lái)實(shí)現(xiàn)線程(或進(jìn)程或應(yīng)用)之間的通信。 - NSPort對(duì)象必須作為輸入源添加到NSRunLoop對(duì)象中,來(lái)實(shí)現(xiàn)線程不退出。
- NSPort有3個(gè)子類NSMachPort、NSMessagePort和NSSocketPort。
- CFRunLoopSource中source1事件源采用port,來(lái)監(jiān)聽(tīng)系統(tǒng)端口和通過(guò)內(nèi)核和其他線程發(fā)送消息。
二、線程間通信思路
- 在主線程創(chuàng)建一個(gè)NSPort的實(shí)例A并添加主線程的NSRunLoop中。
- 再創(chuàng)建一個(gè)線程S將A作為參數(shù)發(fā)送到線程中。
- 線程中創(chuàng)建一個(gè)本地NSPort實(shí)例B,也添加到NSRunLoop中。
- 從B向A發(fā)送消息后,將線程S的runloop運(yùn)行。
- 主線程收到線程S發(fā)送的消息。
- 主線程向線程S發(fā)送消息。
- 通過(guò)
handlePortMessage:代理方法接受消息。
三、NSPort實(shí)例
網(wǎng)上找了很久就看到一篇關(guān)于NSPort實(shí)例的文章,然后發(fā)現(xiàn)現(xiàn)實(shí)有錯(cuò)誤。我使用的是Xcode 12.3和iOS14SDK。
在一個(gè)VC中實(shí)現(xiàn)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"viewDidLoad thread is %@.", [NSThread currentThread]);
self.myPort = [NSMachPort port];
self.myPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
self.work = [[MyWorkerClass alloc] init];
[NSThread detachNewThreadSelector:@selector(launchThreadWithPort:) toTarget:self.work withObject:self.myPort];
}
- (void)handlePortMessage:(NSMessagePort *)message
{
NSLog(@"接受到子線程傳遞的消息。%@", message);
NSUInteger msgId = [[message valueForKeyPath:@"msgid"] integerValue];
NSMachPort *localPort = [message valueForKeyPath:@"localPort"];
NSMachPort *remotePort = [message valueForKeyPath:@"remotePort"];
NSMutableArray *componts = [message valueForKey:@"components"];
for (NSData *data in componts) {
NSLog(@"data is %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
if (msgId == kMsg1) {
[remotePort sendBeforeDate:[NSDate date] msgid:kMsg2 components:nil from:localPort reserved:0];
}
}
在VC的頭部引入自定義線程類
#import "MyWorkerClass.h"
#define kMsg1 100
#define kMsg2 101
自定義線程類MyWorkerClass的h文件暴露方法
- (void)launchThreadWithPort:(NSPort *)port;
m文件實(shí)現(xiàn)
#define kMsg1 100
#define kMsg2 101
@interface MyWorkerClass () <NSMachPortDelegate>
@property (nonatomic, strong) NSPort *remotePort;
@property (nonatomic, strong) NSPort *myPort;
@end
@implementation MyWorkerClass
- (void)launchThreadWithPort:(NSPort *)port
{
@autoreleasepool {
self.remotePort = port;
[[NSThread currentThread] setName:@"MyWorkerClass"];
NSLog(@"launchThreadWithPort thread is %@.", [NSThread currentThread]);
self.myPort = [NSMachPort port];
self.myPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
[self sendPortMessage];
[[NSRunLoop currentRunLoop] run];
}
}
- (void)sendPortMessage
{
NSData *data1 = [@"wang" dataUsingEncoding:NSUTF8StringEncoding];
NSData *data2 = [@"yinan" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:@[@"1", @"2"]];
[self.remotePort sendBeforeDate:[NSDate date] msgid:kMsg1 components:array from:self.myPort reserved:0];
}
- (void)handlePortMessage:(NSMessagePort *)message
{
NSLog(@"接受到父類的消息。。。%@。", message);
}
運(yùn)行打印為
MyDream[11004:357602] viewDidLoad thread is <NSThread: 0x600003820100>{number = 1, name = main}.
MyDream[11004:357779] launchThreadWithPort thread is <NSThread: 0x60000384d440>{number = 8, name = MyWorkerClass}.
MyDream[11004:357602] 接受到子線程傳遞的消息。<NSPortMessage: 0x600003874d80>
MyDream[11004:357602] data is wang
MyDream[11004:357602] data is yinan
MyDream[11004:357779] 接受到父類的消息。。。<NSPortMessage: 0x600003838900>。
四、注意點(diǎn)
1. handlePortMessage方法
查看NSMachPort的delegate屬性為NSMachPortDelegate,而NSPort的delegate屬性為NSPortDelegate。其中NSMachPortDelegate繼承NSPortDelegate。只要實(shí)現(xiàn)其中一個(gè)就可以。
@protocol NSPortDelegate <NSObject>
@optional
- (void)handlePortMessage:(NSPortMessage *)message;
// This is the delegate method that subclasses should send
// to their delegates, unless the subclass has something
// more specific that it wants to try to send first
@end
有一個(gè)問(wèn)題是NSPortMessage是MacOS上使用了,所以在iOS上開(kāi)發(fā)的時(shí)候就一直報(bào)錯(cuò)。最后修改為- (void)handlePortMessage:(NSMessagePort *)message,使用NSMessagePort來(lái)接受消息。
2. 子線程運(yùn)行RunLoop
調(diào)用[[NSRunLoop currentRunLoop] run];的時(shí)機(jī)不同,就決定是否子線程能夠通過(guò)NSPort接受到主線程的消息。也就是必須將[[NSRunLoop currentRunLoop] run];一定要放到[self sendPortMessage];之后。
3. components的類型
在- (BOOL)sendBeforeDate:(NSDate *)limitDate msgid:(NSUInteger)msgID components:(nullable NSMutableArray *)components from:(nullable NSPort *)receivePort reserved:(NSUInteger)headerSpaceReserved;發(fā)送消息時(shí)進(jìn)行傳遞參數(shù)components。
當(dāng)components的內(nèi)容為字符串時(shí),報(bào)警告
2021-01-27 09:57:24.521514+0800 MyDream[10747:331651] *** D.O. message send ignoring unknown component type '__NSCFConstantString'
2021-01-27 09:57:24.522004+0800 MyDream[10747:331651] *** D.O. message send ignoring unknown component type '__NSCFConstantString'
而當(dāng)components的內(nèi)容為class時(shí),報(bào)警告
2021-01-27 09:41:22.425767+0800 MyDream[10541:318874] *** D.O. message send ignoring unknown component type 'MyWorkerData'
2021-01-27 09:41:22.425931+0800 MyDream[10541:318874] *** D.O. message send ignoring unknown component type 'MyWorkerData'
一頭霧水又度娘無(wú)解的時(shí)候,看到了
// The components array consists of a series of instances
// of some subclass of NSData, and instances of some
// subclass of NSPort; since one subclass of NSPort does
// not necessarily know how to transport an instance of
// another subclass of NSPort (or could do it even if it
// knew about the other subclass), all of the instances
// of NSPort in the components array and the 'receivePort'
// argument MUST be of the same subclass of NSPort that
// receives this message. If multiple DO transports are
// being used in the same program, this requires some care.
按蘋(píng)果的注釋習(xí)慣應(yīng)該放在方法的上面的,這個(gè)竟然是在下方,導(dǎo)致一致被忽略了。尷尬尷尬。主要意思就是components需要是NSData組成。將內(nèi)容修改為NSData后

傳遞參數(shù)成功。
五、performSelector實(shí)現(xiàn)線程間通信
NSThread中提供了
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
直接指定線程來(lái)執(zhí)行方法,方便簡(jiǎn)潔。
寫(xiě)一個(gè)demo來(lái)show一把。將上面的修改一把,VC中的代碼不用修改,MyWorkerClass的m文件修改如下:
#import "MyWorkerClass.h"
#define kMsg1 100
#define kMsg2 101
@interface MyWorkerClass () <NSMachPortDelegate>
@property (nonatomic, strong) NSPort *remotePort;
@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) NSThread *thread;
@end
@implementation MyWorkerClass
- (void)launchThreadWithPort:(NSPort *)port
{
@autoreleasepool {
self.remotePort = port;
self.thread = [NSThread currentThread];
[[NSThread currentThread] setName:@"MyWorkerClass"];
NSLog(@"launchThreadWithPort thread is %@.", [NSThread currentThread]);
self.myPort = [NSMachPort port];
self.myPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
[self sendPortMessage];
[[NSRunLoop currentRunLoop] run];
}
}
- (void)sendPortMessage
{
[self performSelectorOnMainThread:@selector(runInMainThread:) withObject:@"往往" waitUntilDone:YES];
}
- (void)runInMainThread:(NSString *)name
{
NSLog(@"runInMainThread thread is %@.", [NSThread currentThread]);
NSLog(@"runInMainThread name is %@.", name);
// [self performSelector:@selector(runInThread:) onThread:self.thread withObject:@"牛牛汪汪" waitUntilDone:NO];
[self performSelector:@selector(runInThread:) onThread:self.thread withObject:@"牛牛汪汪" waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
}
- (void)runInThread:(NSString *)name
{
NSLog(@"runInThread thread is %@.", [NSThread currentThread]);
NSLog(@"runInThread name is %@.", name);
}
@end
打印為:
MyDream[11613:411566] viewDidLoad thread is <NSThread: 0x600000864580>{number = 1, name = main}.
MyDream[11613:411624] launchThreadWithPort thread is <NSThread: 0x6000008123c0>{number = 7, name = MyWorkerClass}.
MyDream[11613:411566] runInMainThread thread is <NSThread: 0x600000864580>{number = 1, name = main}.
MyDream[11613:411566] runInMainThread name is 往往.
MyDream[11613:411624] runInThread thread is <NSThread: 0x6000008123c0>{number = 7, name = MyWorkerClass}.
MyDream[11613:411624] runInThread name is 牛牛汪汪.
實(shí)現(xiàn)子線程和主線程互相通信,前提是子線程需要?;睢?/p>
參考:
NSPort知識(shí)
iOS線程通信和進(jìn)程通信的例子(NSMachPort和NSTask,NSPipe)