iOS 多线程之NSThread

栏目: IOS · 发布时间: 7年前

内容简介:级别: ★★☆☆☆标签:「iOS」「多线程」「NSThread」作者:dac_1033

级别: ★★☆☆☆

标签:「iOS」「多线程」「NSThread」

作者:dac_1033

审校:QiShare团队

iOS多线程系列计划3篇,分别为:NSThread、NSOperation、GCD。

本篇是系列文章第一篇。

1. 线程的概念

首先简单叙述一下这两个概念,我们在电脑上单独运行的每个程序就是一个独立的进程,通常进程之间是相互独立存在的,进程是系统分配资源的最小单元。进程中的最小执行单位就是线程,并且一个进程中至少有一个线程,进程中的所有线程共用这个进程的资源,线程也是系统进行调度的最小单元。

1.1 多线程在多核CPU中处理任务

iOS 多线程之NSThread

1.2 线程状态的切换

iOS 多线程之NSThread

1.3 线程安全问题

线程安全问题是在多个线程处理任务的情况下产生。例如多个线程在同事执行下面的任务时,如果多个线程可以同时执行这段代码(任务),当多个线程同时在getTicket方法中执行count--时,count的结果就会出现不准确的情况,这个处理过程就不是线程安全的。

NSInteger ticketCount = 100;
- (void)getTicket() {

       count--;
       NSLog(@"剩余票数 = %d", ticketCount);
   }
复制代码

1.4 iOS 中的多线程

在iOS中每个app启动后都会建立一个主线程,也称UI线程。由于除了主线程的其他子线程都是独立于Cocoa Touch的,所以一般只使用主线程来更新UI界面。iOS中的多线程有三种方式: NSThread、NSOperation、GCD,其中GCD是目前苹果比较推荐的方式。对于这篇文章,我们主要了解一下NSThread。

2. NSThread简介

NSThread是轻量级的多线程开发,优点是我们可以直接实例化一个NSThread对象并直接操作这个线程对象,但是使用NSThread需要自己管理线程生命周期。iOS开发过程中,NSThread最常用到的方法就是 [NSThread currentThread]获取当前线程,其他常用属性及方法如下:

// 线程字典
@property (readonly, retain) NSMutableDictionary *threadDictionary;
// 线程名称
@property (nullable, copy) NSString *name;
// 优先级
@property double threadPriority ; 
// 是否为主线程
@property (readonly) BOOL isMainThread
// 读取线程状态
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;


// 直接将操作添加到线程中并启动
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument

// 创建一个线程对象
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 

// 启动
- (void)start;

// 撤销
- (void)cancel;

// 退出
+ (void)exit;

// 休眠
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
复制代码

在NSObject(NSThreadPerformAdditions)类中的几个常用方法,实现了在特定线程上执行任务的功能,该分类也定义在NSThread.h中:

// 在主线程上执行一个方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

// 在指定的线程上执行一个方法,需要用户创建一个线程对象
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

// 在后台执行一个操作,本质就是重新创建一个线程执行当前方法
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
复制代码

2.1 NSThread的使用过程

例如在app从网络下载图片时,由于网络原因可能需要较长时间,这时如果只在主线程中进行下载,则这个过程中用户将无法进行其他操作,直到网络图片下载完成之前界面都处于卡死状态(线程阻塞)。我们在主线程中另起一个新线程来单独下载即可解决这一问题,不管资源是否下载完成都可以继续操作界面,不会造成阻塞。示例代码如下:

@interface MultiThread_NSThread ()

// 显示图片
@property (nonatomic, strong) UIImageView *imgView;

@end

@implementation MultiThread_NSThread

- (void)viewDidLoad {
    
    [super viewDidLoad];
    [self setTitle:@"NSThread"];
    [self.view setBackgroundColor:[UIColor whiteColor]];
    self.edgesForExtendedLayout = UIRectEdgeNone;
    
    [self layoutViews];
}

- (void)layoutViews {
    
    CGSize size = self.view.frame.size;
    
    _imgView =[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, size.width, 300)];
    [self.view addSubview:_imgView];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    button.frame = CGRectMake(15, CGRectGetMaxY(_imgView.frame) + 30, size.width - 15 * 2, 45);
    [button setTitle:@"点击加载" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}


#pragma mark - 多线程下载图片

- (void)loadImageWithMultiThread {
    
    ////1. 对象方法
    //NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImage) object:nil];
    //[thread start];
    
    //2. 类方法
    [NSThread detachNewThreadSelector:@selector(downloadImg) toTarget:self withObject:nil];
}


#pragma mark - 加载图片

- (void)downloadImg {
    
    // 请求数据
    NSData *data = [self requestData];
    // 回到主线程更新UI
    [self performSelectorOnMainThread:@selector(updateImg:) withObject:data waitUntilDone:YES];
}


#pragma mark - 请求图片数据

- (NSData *)requestData {
    
    NSURL *url = [NSURL URLWithString:@"https://store.storeimages.cdn-apple.com/8756/as-images.apple.com/is/image/AppleInc/aos/published/images/a/pp/apple/products/apple-products-section1-one-holiday-201811?wid=2560&hei=1046&fmt=jpeg&qlt=95&op_usm=0.5,0.5&.v=1540576114151"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    return data;
}


#pragma mark - 将图片显示到界面

- (void)updateImg:(NSData *)imageData {
    
    UIImage *image = [UIImage imageWithData:imageData];
    _imgView.image = image;
}

@end
复制代码

在请求数据的代码上打一个断点,可以看出NSThread是对pthread的封装:

iOS 多线程之NSThread

2.2 使用NSThread实现多线程并发

下面我们使用NSThread实现多线程加载多张网络图片,来了解NSThread多线程处理任务的过程。示例代码如下:

@implementation NSThreadImage

@end

#define ColumnCount    4
#define RowCount       5
#define Margin         10

@interface MultiThread_NSThread1 ()

// imgView数组
@property (nonatomic, strong) NSMutableArray *imgViewArr;
// thread数组
@property (nonatomic, strong) NSMutableArray *threadArr;

@end

@implementation MultiThread_NSThread1

- (void)viewDidLoad {
    
    [super viewDidLoad];
    [self setTitle:@"NSThread1"];
    [self.view setBackgroundColor:[UIColor whiteColor]];
    self.edgesForExtendedLayout = UIRectEdgeNone;
    
    [self layoutViews];
}

- (void)layoutViews {
    
    CGSize size = self.view.frame.size;
    CGFloat imgWidth = (size.width - Margin * (ColumnCount + 1)) / ColumnCount;
    
    _imgViewArr = [NSMutableArray array];
    for (int row=0; row<RowCount; row++) {
        for (int colomn=0; colomn<ColumnCount; colomn++) {
            UIImageView *imageView=[[UIImageView alloc] initWithFrame:CGRectMake(Margin + colomn * (imgWidth + Margin), Margin + row * (imgWidth + Margin), imgWidth, imgWidth)];
            imageView.backgroundColor=[UIColor cyanColor];
            [self.view addSubview:imageView];
            [_imgViewArr addObject:imageView];
        }
    }
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    button.frame = CGRectMake(15, (imgWidth + Margin) * RowCount + Margin, size.width - 15 * 2, 45);
    [button addTarget:self action:@selector(loadImgWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"点击加载" forState:UIControlStateNormal];
    [self.view addSubview:button];
}


#pragma mark - 多线程下载图片

- (void)loadImgWithMultiThread {
    
    _threadArr = [NSMutableArray array];
    for (int i=0; i<RowCount*ColumnCount; ++i) {
        NSThreadImage *threadImg = [[NSThreadImage alloc] init];
        threadImg.index = i;
        NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(loadImg:) object:threadImg];
        thread.name = [NSString stringWithFormat:@"myThread%i",i];
        //// 优先级
        //thread.threadPriority = 1.0;
        [thread start];
        [_threadArr addObject:thread];
    }
}


#pragma mark - 加载图片

- (void)loadImg:(NSThreadImage *)threadImg {
    
    //// 休眠
    //[NSThread sleepForTimeInterval:2.0];
    //// 撤销(停止加载图片)
    //[[NSThread currentThread] cancel];
    //// 退出当前线程
    //[NSThread exit];
    
    // 请求数据
    threadImg.imgData =  [self requestData];
    // 回到主线程更新UI
    [self performSelectorOnMainThread:@selector(updateImg:) withObject:threadImg waitUntilDone:YES];
    
    // 打印当前线程
    NSLog(@"current thread: %@", [NSThread currentThread]);
}


#pragma mark - 请求图片数据

- (NSData *)requestData{
    
    NSURL *url = [NSURL URLWithString:@"https://store.storeimages.cdn-apple.com/8756/as-images.apple.com/is/image/AppleInc/aos/published/images/a/pp/apple/products/apple-products-section1-one-holiday-201811?wid=2560&hei=1046&fmt=jpeg&qlt=95&op_usm=0.5,0.5&.v=1540576114151"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    return data;
}


#pragma mark - 将图片显示到界面

- (void)updateImg:(NSThreadImage *)threadImg {
    
    UIImage *image = [UIImage imageWithData:threadImg.imgData];
    UIImageView *imageView = _imgViewArr[threadImg.index];
    imageView.image = image;
}


//#pragma mark 停止加载网络图片
//
//- (void)stopLoadingImgs {
//
//    for (int i=0; i<RowCount*ColumnCount; ++i) {
//
//        NSThread *thread = _threadArr[i];
//        if (!thread.isFinished) {
//            [thread cancel];
//        }
//    }
//}

@end
复制代码

3. 关于NSThread线程状态的说明

NSThread类型的对象可以获取到线程的三种状态属性isExecuting(正在执行)、isFinished(已经完成)、isCancellled(已经撤销),其中撤销状态是可以在代码中调用线程的cancel方法手动设置的(在主线程中并不能真正停止当前线程)。isFinished属性标志着当前线程上的任务是否执行完成,cancel一个线程只是撤销当前线程上任务的执行,监测到isFinished = YES或调用cancel方法都不能代表立即退出了这个线程,而调用类方法exit方法才可立即退出当前线程。

例如在加载多张网络图片时,中途停止加载动作的执行:

#pragma mark 停止加载网络图片

- (void)stopLoadingImgs {
    
    for (int i=0; i<RowCount*ColumnCount; ++i) {
        
        NSThread *thread = _threadArr[i];
        if (!thread.isFinished) {
            [thread cancel];
        }
    }
}
复制代码

PS:

  1. 更新UI需回到主线程中操作;
  2. 线程处于就绪状态时会处于等待状态,不一定立即执行;
  3. 区分线程三种状态的不同,尤其是撤销和退出两种状态的不同;
  4. 在线程死亡之后,再次点击屏幕尝试重新开启线程,则程序会挂;
  5. NSThread可以设置对象的优先级thread.threadPriority,threadPriority取值范围是0到1;
  6. NSThread并没有提供设置线程间的依赖关系的方法,也就不能单纯通过NSThread来设置任务处理的先后顺序,但是我们可以通过设置NSThread的休眠或优先级来尽量优化任务处理的先后顺序;
  7. 在自己试验的工程中,虽然NSThread实例的数量理论上不受限制,但是正常的处理过程中需要控制线程的数量。

工程源码GitHub地址

小编微信:可加并拉入《QiShare技术交流群》。

iOS 多线程之NSThread

关注我们的途径有:

QiShare(简书)

QiShare(掘金)

QiShare(知乎)

QiShare(GitHub)

QiShare(CocoaChina)

QiShare(StackOverflow)

QiShare(微信公众号)


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Form Design

Web Form Design

Luke Wroblewski / Rosenfeld Media / 2008-5-2 / GBP 25.00

Forms make or break the most crucial online interactions: checkout, registration, and any task requiring information entry. In Web Form Design, Luke Wroblewski draws on original research, his consider......一起来看看 《Web Form Design》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具