iOS多线程之NSThread详解
在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详解的更多相关文章
- iOS-多线程之NSThread详解
前言 线程是用来执行任务的,线程彻底执行完任务A才能去执行任务B.为了同时执行两个任务,产生了多线程. 我打开一个视频软件,我开辟一个线程A让它执行下载任务,我开辟一个线程B,用来播放视频.我开辟两个 ...
- iOS多线程之GCD详解
GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制.也是目前苹果官方推荐的多线程开发方法.iOS三种多线程开发中GCD是抽象层次最高的.当然用起来也是最简单的. ...
- iOS多线程之NSOperation详解
使用NSOperation和NSOperationQueue进行多线程开发,只要将一个NSOperation(实际开发中需要使用其子类 NSInvocationOperation,NSBlockOpe ...
- ios 多线程之NSThread篇举例详解
这篇博客是接着总篇iOS GCD NSOperation NSThread等多线程各种举例详解写的一个支篇.总篇也包含了此文的链接.本文讲解的知识点有NSThread的开始.取消.在当前线程执行任务. ...
- iOS多线程之NSThread使用
iOS中的多线程技术 我们在iOS开发项目过程中,为了解决UI界面操作不被耗时操作阻塞,我们会使用到多线程技术.在iOS开发中,我们主要会用到三种多线程操作技术:NSThread,NSOperatio ...
- 【原】iOS多线程之NSThread、NSOperationQueue、NSObject和GCD的区别
区别: Thread: 是这几种方式里面相对轻量级的,但也是使用起来最负责的,你需要自己管理thread的生命周期,线程之间的同步.线程共享同一应用程序的部分内存空间, 它们拥有对数据相同的访问权限. ...
- iOS进阶之多线程--NSThread详解
NSThread简介 NSThread是苹果官方提供面向对象操作线程的技术,简单方便,可以直接操作线程对象,不过需要自己控制线程的生命周期.在平时使用很少,最常用到的无非就是 [NSThread cu ...
- iOS开发线程之NSThread
1.初始化 - (instancetype)init API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) NS_DESIGNAT ...
- IOS多线程之NSThread
参考:http://blog.csdn.net/totogo2010/article/details/8010231 1 简介 NSThread: 优点:NSThread 比其他两个轻量级 缺点:需要 ...
随机推荐
- 在Android Stuido中使用Lint
要运行Lint工具,大家首先需要在Android Studio的“Analyze”菜单中选择“Inspect Code…”.当Android Studio完成了对项目的检测之后,它会在窗口底部显示出分 ...
- [Android]使用AdapterTypeRender对不同类型的item数据到UI的渲染
以下内容为原创,转载请注明: 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/3992843.html 本文讲的工具均放在AndroidBucket开源 ...
- iOS开发之图片分辨率与像素对齐
像素对齐的概念 在iOS中,有一个概念叫做像素对齐,如果像素不对齐,那么在GPU渲染时,需要进行插值计算,这个插值计算的过程会有性能损耗. 在模拟器上,有一个选项可以把像素不对齐的部分显示出来.  ...
- 【转】Android NFC学习笔记
一:NFC的tag分发系统 如果想让android设备感应到NFC标签,你要保证两点 1:屏幕没有锁住 2:NFC功能已经在设置中打开 当系统检测到一个NFC标签的时候,他会自动去寻找最合适的acti ...
- C语言printf()输出格式大全
1.转换说明符 %a(%A) 浮点数.十六进制数字和p-(P-)记数法(C99) %c 字符 %d 有符号十 ...
- IOS客户端Coding项目记录导航
IOS客户端Coding项目记录(一) a:UITextField设置出现清除按键 b:绘画一条下划线 表格一些设置 c:可以定义表头跟底部视图(代码接上面) d:隐藏本页的导航栏 e:UIEdge ...
- 【代码笔记】iOS-使图片两边不拉伸,中间拉伸
代码: - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. // ...
- IOS 音效
IOS 音效 音效我们也可以成为短音频通常在程序中播放时间为1~2秒. 在应用程序中起到点缀效果,提升整体用户体验 音效文件只需要加载一次 示例代码: // // ViewController.m / ...
- php底层运行原理
http://www.cnblogs.com/phphuaibei/archive/2011/09/13/2174927.html
- Flex各类型坐标转换(全局、本地、内容坐标间转换)
Flex包含3种坐标:全局坐标.本地坐标.内容坐标 全局坐标:stage级别,坐标原点为舞台的左上角,如MouseEvent的stageX.stageY坐标. 本地坐标:组件级别的坐标系,相对坐标,坐 ...