在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程。由于iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面。iOS多线程的使用并不复杂,关键是如何控制好各个线程的执行顺序,处理好资源竞争问题。常用的多线程开发有三种形式:1.NSThread 2:NSOperation 3:GCD 这篇博客主要讲解NSThread。

NSThread是轻量级的多线程开发,使用起来也并不复杂,但是使用NSThread需要自己管理线程的生命周期,可以使用对象方法+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument直接将操作添加到线程中并启动,也可以使用- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument ,创建一个多线程对象,然后使用start方法,启动线程

解决线程阻塞问题

在资源下载过程中,由于网络原因有时候很难保证下载时间,如果不使用多线程可能用户完成一个下载操作需要长时间的等待,这个过程中无法进行其他操作。下面演示一个采用多线程下载图片的过程,在这个示例中点击按钮会启动一个线程去下载图片,下载完成后使用UIImageView将图片显示到界面中。可以看到用户点击完下载按钮后,不管图片是否下载完成都可以继续操作界面,不会造成阻塞。

@interface ViewController (){
UIImageView *_imageView;
} @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; [self layoutUI];
} - (void)layoutUI{
_imageView = [[UIImageView alloc] initWithFrame:[UIScreen mainScreen].bounds];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.view addSubview:_imageView]; UIButton *button = [UIButton buttonWithType:];
button.frame = CGRectMake((self.view.frame.size.width - ) / , self.view.frame.size.height - , , );
[button setTitle:@"加载图片" forState:UIControlStateNormal];
[button addTarget:self action:@selector(loadImageWithThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button]; }
#pragma mark - 多线程下载图片
- (void)loadImageWithThread{
//方法一 使用对象方法开辟线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImage) object:nil];
//启动线程 启动一个线程并非就一定立即执行 而是出于就绪状态 当系统调度时才真正执行
[thread start]; //使用类方法开辟线程
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil]; //block方法
[NSThread detachNewThreadWithBlock:^{
[self loadImage];
}];
} #pragma mark -下载图片
- (void)loadImage{
//请求数据
NSData *imageData = [self requestData]; /*
请求到数据 回到主线程更新UI
每个对象都有 performSelectorOnMainThread: withObject: waitUntilDone:方法 它调用的selector方法是当前调用控件的方法,例如使用UIImageView调用的时候selector就是UIImageView的方法
Object:代表调用方法的参数,不过只能传递一个参数(如果有多个参数请使用对象进行封装)
waitUntilDone:是否线程任务完成执行
*/
[self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];
} - (void)updateImage:(NSData *)imageData{
UIImage *image = [UIImage imageWithData:imageData];
_imageView.image = image;
} - (NSData *)requestData{
@autoreleasepool {
NSURL *url = [NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
return data;
}
}

多个线程并发

上面这个演示并没有演示多个子线程操作之间的关系,现在不妨在界面中多加载几张图片,每个图片都来自远程请求。

大家应该注意到不管是使用+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 方法还是使用- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait方法都只能传一个参数,由于更新图片需要传递UIImageView的索引和图片数据,因此这里不妨定义一个类保存图片索引和图片数据以供后面使用。

代码如下:

#import <Foundation/Foundation.h>

@interface TLFImageData : NSObject

@property (nonatomic,strong) NSData *data;

@property (nonatomic,assign) int index;

@end
#import "ViewController.h"
#import "TLFImageData.h" #define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10 @interface ViewController (){
NSMutableArray *_imageViews;
} @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; [self layoutUI];
} - (void)layoutUI{
_imageViews = [NSMutableArray array];
for (int r = ; r < ROW_COUNT; r++) {
for (int c = ; c < COLUMN_COUNT; c++) {
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
[self.view addSubview:imageView];
[_imageViews addObject:imageView];;
}
} UIButton *button = [UIButton buttonWithType:];
button.frame = CGRectMake((self.view.frame.size.width - ) / , self.view.frame.size.height - , , );
[button setTitle:@"加载图片" forState:UIControlStateNormal];
[button addTarget:self action:@selector(loadImageWithThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button]; } #pragma mark - 多线程下载图片
- (void)loadImageWithThread{
for (int i=; i<ROW_COUNT*COLUMN_COUNT; ++i){
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
thread.name = [NSString stringWithFormat:@"myThread:%i",i];
[thread start];
}
} #pragma mark -下载图片
- (void)loadImage:(NSNumber *)index{
//请求数据
//currentThread方法可以取得当前操作线程
NSLog(@"current thread:%@",[NSThread currentThread]);
int i = (int)[index integerValue];
NSData *Data = [self requestData:i]; /*
请求到数据 回到主线程更新UI
每个对象都有 performSelectorOnMainThread: withObject: waitUntilDone:方法 它调用的selector方法是当前调用控件的方法,例如使用UIImageView调用的时候selector就是UIImageView的方法
Object:代表调用方法的参数,不过只能传递一个参数(如果有多个参数请使用对象进行封装)
waitUntilDone:是否线程任务完成执行
*/
TLFImageData *imageData = [[TLFImageData alloc] init];
imageData.data = Data;
imageData.index = i;
[self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];
} - (void)updateImage:(TLFImageData *)imageData{
UIImage *image = [UIImage imageWithData:imageData.data];
UIImageView *imageView = _imageViews[imageData.index]; imageView.image = image;
} - (NSData *)requestData:(int)i{
@autoreleasepool {
NSURL *url = [NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
return data;
}
}

通过NSThread的currentThread可以取得当前操作的线程,其中会记录线程名称name和编号number,需要注意主线程编号永远为1。多个线程虽然按顺序启动,但是实际执行未必按照顺序加载照片(loadImage:方法未必依次创建,可以通过在loadImage:中打印索引查看),因为线程启动后仅仅处于就绪状态,实际是否执行要由CPU根据当前状态调度。

从上面的运行效果大家不难发现,图片并未按顺序加载,原因有两个:第一,每个线程的实际执行顺序并不一定按顺序执行(虽然是按顺序启动);第二,每个线程执行时实际网络状况很可能不一致。当然网络问题无法改变,只能尽可能让网速更快,但是可以改变线程的优先级,让15个线程优先执行某个线程。线程优先级范围为0~1,值越大优先级越高,每个线程的优先级默认为0.5。修改图片下载方法如下,改变最后一张图片加载的优先级,这样可以提高它被优先加载的几率,但是它也未必就第一个加载。因为首先其他线程是先启动的,其次网络状况我们没办法修改:

- (void)loadImageWithThread{
for (int i=; i<ROW_COUNT*COLUMN_COUNT; ++i){
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
thread.name = [NSString stringWithFormat:@"myThread:%i",i];
/*
改变线程的优先级 优先级的范围在0-1 值越大优先级越高 每个线程默认的优先级是0.5
*/
if (i == ) {
thread.threadPriority = 1.0;
}else {
thread.threadPriority = 0.0;
} [thread start];
}
}

在线程操作过程中可以让某个线程休眠等待,优先执行其他线程操作,而且在这个过程中还可以修改某个线程的状态或者终止某个指定的线程。为了解决一些需要依靠其他方法执行的方法的问题。我们可以让依靠其他方法的线程休眠。等待其他线程执行完,再执行。

- (NSData *)requestData:(int)i{
@autoreleasepool {
if (i != (ROW_COUNT*COLUMN_COUNT-)) {
[NSThread sleepForTimeInterval:2.0];
}
NSURL *url = [NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
return data;
}
}

线程状态分为isExecuting(正在执行)、isFinished(已经完成)、isCancellled(已经取消)三种。其中取消状态程序可以干预设置,只要调用线程的cancel方法即可。但是需要注意在主线程中仅仅能设置线程状态,并不能真正停止当前线程,如果要终止线程必须在线程中调用exist方法,这是一个静态方法,调用该方法可以退出当前线程。

使用NSThread在进行多线程开发过程中操作比较简单,但是要控制线程执行顺序并不容易(前面万不得已采用了休眠的方法),另外在这个过程中如果打印线程会发现循环几次就创建了几个线程,这在实际开发过程中是不得不考虑的问题,因为每个线程的创建也是相当占用系统开销的。

扩展--NSObject分类扩展方法

为了简化多线程开发过程,苹果官方对NSObject进行分类扩展(本质还是创建NSThread),对于简单的多线程操作可以直接使用这些扩展方法。

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

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

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait:在主线程上执行一个方法(前面已经使用过)。

例如前面加载图多个图片的方法,可以改为后台线程执行:

-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT; for (int i=; i<count; ++i) {
[self performSelectorInBackground:@selector(loadImage:) withObject:[NSNumber numberWithInt:i]];
}
}

iOS多线程之NSThread详解的更多相关文章

  1. iOS-多线程之NSThread详解

    前言 线程是用来执行任务的,线程彻底执行完任务A才能去执行任务B.为了同时执行两个任务,产生了多线程. 我打开一个视频软件,我开辟一个线程A让它执行下载任务,我开辟一个线程B,用来播放视频.我开辟两个 ...

  2. iOS多线程之GCD详解

    GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制.也是目前苹果官方推荐的多线程开发方法.iOS三种多线程开发中GCD是抽象层次最高的.当然用起来也是最简单的. ...

  3. iOS多线程之NSOperation详解

    使用NSOperation和NSOperationQueue进行多线程开发,只要将一个NSOperation(实际开发中需要使用其子类 NSInvocationOperation,NSBlockOpe ...

  4. ios 多线程之NSThread篇举例详解

    这篇博客是接着总篇iOS GCD NSOperation NSThread等多线程各种举例详解写的一个支篇.总篇也包含了此文的链接.本文讲解的知识点有NSThread的开始.取消.在当前线程执行任务. ...

  5. iOS多线程之NSThread使用

    iOS中的多线程技术 我们在iOS开发项目过程中,为了解决UI界面操作不被耗时操作阻塞,我们会使用到多线程技术.在iOS开发中,我们主要会用到三种多线程操作技术:NSThread,NSOperatio ...

  6. 【原】iOS多线程之NSThread、NSOperationQueue、NSObject和GCD的区别

    区别: Thread: 是这几种方式里面相对轻量级的,但也是使用起来最负责的,你需要自己管理thread的生命周期,线程之间的同步.线程共享同一应用程序的部分内存空间, 它们拥有对数据相同的访问权限. ...

  7. iOS进阶之多线程--NSThread详解

    NSThread简介 NSThread是苹果官方提供面向对象操作线程的技术,简单方便,可以直接操作线程对象,不过需要自己控制线程的生命周期.在平时使用很少,最常用到的无非就是 [NSThread cu ...

  8. iOS开发线程之NSThread

    1.初始化 - (instancetype)init API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) NS_DESIGNAT ...

  9. IOS多线程之NSThread

    参考:http://blog.csdn.net/totogo2010/article/details/8010231 1 简介 NSThread: 优点:NSThread 比其他两个轻量级 缺点:需要 ...

随机推荐

  1. 编写高质量的Objective-C代码

    点标记语法 属性和幂等方法(多次调用和一次调用返回的结果相同)使用点标记语法访问,其他的情况使用方括号标记语法.   良好的风格: view.backgroundColor = [UIColor or ...

  2. Android 样式和主题(style & theme)

    Android 样式 android中的样式和CSS样式作用相似,都是用于为界面元素定义显示风格,它是一个包含一个或者多个view控件属性的集合.如:需要定义字体的颜色和大小. 在CSS中是这样定义的 ...

  3. 苹果IPSW文件提取软件

    ipsw文件 提取系统文件 方法总结 由于修改运营商文件造成我的有锁4S无法使用移动卡了,在网上苦寻一番还是没有结果,最后萌生了从固件中提取文件的想法,于是便开始在网上搜集资料,最后文件终于提取成功并 ...

  4. IOS block 循环引用的解决

    在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...

  5. JQuery制作简单的网页导航特效

    使用JQuery中hover()方法,使其根据鼠标的移动简单的改变背景颜色; hover();用于模拟鼠标指针悬停事件,当鼠标指针移动到元素上时,会触发指定的第一个函数,当鼠标指针移除这个元素时,会触 ...

  6. Play Framework安装和配置

    安装环境: jdk 1.7; play 1.3.1; eclipse 安装指南:http://play-framework.herokuapp.com/zh/install 安装Play Framew ...

  7. PHP 替换标签和标签内的内容

    $filter_arr=array('/#(.*?)#/','/\$(.*?)\$/','/\^(.*?)\^/');//要替换的标签 $content=$data['Monthlys']['cont ...

  8. 说一下output子句

    Output子句日常灰常有用,而且用的地方也挺多,但是确好多时候被我们忽视,今天我就也简单扫盲一下这个语句的用法. Output子句 返回受 INSERT.UPDATE.DELETE 或 MERGE ...

  9. apache指定的网络名不再可用

    如果Apache的error.log还是出现大量的:Sat Dec 24 17:21:28 2006] [warn] (OS 64)指定的网络名不再可 用. : winnt_accept: Async ...

  10. setTimeout()与setInterval()——走马灯效果

    JavaScript中的setTimeout()与setInterval()都是指延时执行某一操作. 但setInterval()指每隔指定时间执行某操作,会循环不断地执行该操作:setTimeout ...