iOS开发 - 多线程间通信

本文探讨了iOS应用中线程间通信的重要性,详细介绍了如何通过子线程避免阻塞主线程执行,以及实现线程间通信的两种方式:在GCD内部再次调用主线程执行操作和使用NSThread类方法。同时,文章还讨论了开启子线程执行操作的细节,包括使用NSOperationQueue和`performSelectorInBackground`方法,并提供了关键代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、线程间通信的意义

       一个iOS程序运行时对应一个进程, 该进程至少包含一个主线程, 实际的程序通常是多线程运行的, 而多个线程之间常常涉及到通信问题, 有时候需要开启一个子线程来完成一些耗时操作, 但是子线程执行完后又需要回到主线程更新UI界面, 相当于子线程执行完后, 通知主线程更新UI界面。

二、阻塞主线程的情况

      主线程是顺序执行的, 如果耗时操作放在z主线程执行, 势必影响主线程执行后面的其他操作。
      测试1: 耗时操作全部放在主线程执行, 会阻塞主线程的执行
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *bigImageView;
@property (weak, nonatomic) IBOutlet UIImageView *smallImageView;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [self downloadAndSetupIconImageView];
}

- (void)downloadAndSetupIconImageView
{
    NSString *urlStr = @"http://localhost:8080/iOSServer/picture/pic01.png";
    NSURL *url = [NSURL URLWithString:urlStr];
    // 线程的编号为1, 所以是在主线程执行
    NSLog(@"%@", [NSThread currentThread]);
    // 假定要下载4000张图片
    for(int i = 0; i< 4000; i++)
    {
        NSData *data = [NSData dataWithContentsOfURL:url];
    }
    self.bigImageView.image = [UIImage imageWithData:data];
    self.smallImageView.image = [UIImage imageNamed:@"009.png"];
}
@end
<span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">        </span><span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">效果: 程序顺序执行, 等待几秒钟后bigImageView和smallImageView开始显示;</span>

三、创建子线程防止阻塞主线程  

       有没有方法可以让图片的下载不影响smallImageView的显示呢?
       可以将图片的下载放在子线程里面去执行。

<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">- (void)downloadAndSetupIconImageView</span>
{
    NSString *urlStr = @"http://localhost:8080/iOSServer/picture/pic01.png";
    NSURL *url = [NSURL URLWithString:urlStr];
    __block  NSData *data = nil;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          // 线程的编号=2, 在子线程执行
          NSLog(@"%@", [NSThread currentThread]);
          for (int i = 0; i< 2000; i++) {
                   NSData *data = [NSData dataWithContentsOfURL:url];
          }
    });
    self.bigImageView.image = [UIImage imageWithData:data];
    self.smallImageView.image = [UIImage imageNamed:@"009.png"];
}
    
    分析: 程序是顺序执行, 在开启子线程后, 程序会顺序往下执行, 此时bigImageView不显示(data = nil),
             但是smallImageView立即显示。也就是说, 阻塞的问题得到了解决, 但是出现了新的问题。
              新手学习多线程可能会犯以上低级错误。

四、线程间通信的方式

       子线程执行完后, 应该通知主线程更新UI, 这就是线程间通信

       方式1: 直接在GCD内部再次调用主线程执行操作

- (void)downloadAndSetupIconImageView
{
    NSString *urlStr = @"http://localhost:8080/iOSServer/picture/pic01.png";
    NSURL *url = [NSURL URLWithString:urlStr];
    // 下载图片, 耗时操作
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSData *data = nil;
        for (int i = 0; i< 2000; i++) {
           data = [NSData dataWithContentsOfURL:url];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            self.bigImageView.image = [UIImage imageWithData:data];
        });
    });
    self.smallImageView.image = [UIImage imageNamed:@"009.png"];
}
       效果: 程序启动后, 立即显示smallImageView, 等待数秒后, 显示bigImageView

      

 
   

   方式2: 调用NSThread类的方法

   - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
   - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

- (void)downloadAndSetupIconImageView
{

    NSString *urlStr = @"http://localhost:8080/iOSServer/picture/pic01.png";
    NSURL *url = [NSURL URLWithString:urlStr];
   
    // 创建子线程下载图片, 耗时操作
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSData *data = nil;
        for (int i = 0; i< 2000; i++) {
           data = [NSData dataWithContentsOfURL:url];
        }
        UIImage *image = [UIImage imageWithData:data];

      [self performSelector:@selector(setBigImageView:)
       onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
       
    });
    self.smallImageView.image = [UIImage imageNamed:@"009"];
}

- (void)setBigImageView:(UIImage *)image
{
   self.bigImageView1.image = image;
}

五、细节

       细节1: 因为NSOperationQueue基于GCD, 所以GCD的线程间通信方式也适用于NSOperationQueue
- (void)downloadAndSetupIconImageView
{
    NSString *urlStr = @"http://localhost:8080/iOSServer/picture/pic01.png";
    NSURL *url = [NSURL URLWithString:urlStr];

    // 子线程
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        NSData *data = nil;
        for (int i = 0; i< 2000; i++) {
            data = [NSData dataWithContentsOfURL:url];
        }
        // 主线程
        NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
        [mainQueue addOperationWithBlock:^{
            self.bigImageView1.image  = [UIImage imageWithData:data];
        }];
    }];

    self.smallImageView.image = [UIImage imageNamed:@"009"];
}

    细节2: 以下方法也可以开启子线程执行操作
    - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)argNS_AVAILABLE(10_5, 2_0);
    范例:
- (void)downloadAndSetupIconImageView
{
   [self performSelectorInBackground:@selector(download) withObject:nil];
   self.smallImageView.image = [UIImage imageNamed:@"009"];
}

/*
 开启子线程进行图片批量下载
 */
- (void)download
{
    NSLog(@"dowload - %@", [NSThread currentThread]);
    NSString *urlStr = @"http://localhost:8080/iOSServer/picture/pic01.png";
    NSURL *url = [NSURL URLWithString:urlStr];
    NSData *data = nil;
    for (int i = 0; i< 2000; i++) {
        data = [NSData dataWithContentsOfURL:url];
    }
    UIImage *image = [UIImage imageWithData:data];
    [self performSelector:@selector(setBigImageView:)
          onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];  
}

- (void)setBigImageView:(UIImage *)image
{
   self.bigImageView1.image = image;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值