NSPort線程間通信實(shí)例(Xcode12.3)

NSPort,搜圖片都指向了Sport

線程間通信能不用就不用
好像兩輛行駛的車之間交換內(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ā)送消息。

二、線程間通信思路

  1. 在主線程創(chuàng)建一個(gè)NSPort的實(shí)例A并添加主線程的NSRunLoop中。
  2. 再創(chuàng)建一個(gè)線程S將A作為參數(shù)發(fā)送到線程中。
  3. 線程中創(chuàng)建一個(gè)本地NSPort實(shí)例B,也添加到NSRunLoop中。
  4. 從B向A發(fā)送消息后,將線程S的runloop運(yùn)行。
  5. 主線程收到線程S發(fā)送的消息。
  6. 主線程向線程S發(fā)送消息。
  7. 通過(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后


components

傳遞參數(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)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容