总结ios开发中一些知识,有错或者有问题的欢迎交流。
这些资料也是看其他的一些文章总结的,首先把别人文章地址贴出来。本文直接介绍pthread和NSThread,其他多线程方式请看本人其他文章或者其他地方了解。
一 pthread
基本介绍
pthread 是 POSIX 多线程开发框架。跨平台,适用于多种操作系统,可移植性强,是一套纯C语言的通用API,且线程的生命周期需要程序员自己管理。
ARC 只负责管理 OC 部分的内存管理,而不负责 C 语言 代码的内存管理。因此,开发过程中,如果使用的 C 语言框架出现retain/create/copy/new 等字样的函数,大多都需要 release,否则会出现内存泄漏。在混合开发时,如果在 C 和 OC 之间传递数据,需要使用 __bridge 进行桥接,桥接的目的就是为了告诉编译器如何管理内存。
桥接的添加可以借助 Xcode 的辅助功能添加。
MRC 中不需要使用桥接。
使用方法
使用pthread 要引入头文件
#import <pthread.h>
创建线程,并在线程中执行demo
/** pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
参数:
1> 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *
2> 用来设置线程属性
3> 线程运行函数的起始地址
4> 运行函数的参数
返回值: - 若线程创建成功,则返回0 - 若线程创建失败,则返回出错编号
*/
pthread_t threadId = NULL; //一个结构体,具有指向了当前线程的指针
NSString *str = @"Hello Pthread";
// 这边的demo函数名作为第三个参数写在这里可以在其前面加一个&,也可以不加,因为函数名就代表了函数的地址。
int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
if (result == 0) {
NSLog(@"创建线程 OK");
} else {
NSLog(@"创建线程失败 %d", result);
}
// pthread_detach:设置子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
pthread_detach(threadId);
回调函数
// 后台线程调用函数
void *demo(void *params) {
NSString *str = (__bridge NSString *)(params);
NSLog(@"%@ - %@", [NSThread currentThread], str);
return NULL;
}
函数介绍
pthread_create的函数原型为
int pthread_create(pthread_t * __restrict, const pthread_attr_t * __restrict, void *(*)(void *), void * __restrict);
第一个参数pthread_t * __restrict
由于c语言没有对象的概念,所以pthread_t实际是一个结构体
所以创建的thread是一个指向当前新建线程的指针
typedef __darwin_pthread_t pthread_t;
typedef struct _opaque_pthread_t *__darwin_pthread_t;
struct _opaque_pthread_t {
long __sig;
struct __darwin_pthread_handler_rec *__cleanup_stack;
char __opaque[__PTHREAD_SIZE__];
};
第二个参数const pthread_attr_t * __restrict
同样是一个结构体,这里是用来设置线程属性的
typedef __darwin_pthread_attr_t pthread_attr_t;
typedef struct _opaque_pthread_attr_t __darwin_pthread_attr_t;
struct _opaque_pthread_attr_t {
long __sig;
char __opaque[__PTHREAD_ATTR_SIZE__];
};
第三个参数void ()(void *)这里给出了一个函数指针,指向的是一个函数的起始地址,所以是线程开启后的回调函数
第四个参数是回调函数所用的参数
这里只是介绍了pthread的基本用法和其参数类型,和其他多线程方式比除了跨平台还有什么优点,苹果提供pthread具体在项目中怎么使用,希望有做过的大神留言沟通,我也会继续更新相关内容。
二 NSThread 原文在这里
通过NSThread我们具体讨论一些线程相关的问题,包括如下内容:
使用NSThread创建线程
线程状态
线程间通信
线程安全
1. 使用NSThread创建线程
使用NSThread创建线程有以下几种方式:
使用NSThread的init方法显式创建
使用NSThread类方法显式创建并启动线程
隐式创建并启动线程
具体的代码实现在下面已经给出了,这里提醒大家注意一点。只有使用NSThread的init方法创建的线程才会返回具体的线程实例。也就是说如果想要对线程做更多的控制,比如添加线程的名字、更改优先级等操作,要使用第一种方式来创建线程。但是此种方法需要使用start方法来手动启动线程。
/** * 隐式创建并启动线程 */
- (void)createThreadWithImplicit {
// 隐式创建并启动线程
[self performSelectorInBackground:@selector(threadMethod3:) withObject:@"implicitMethod"];
}
/** * 使用NSThread类方法显式创建并启动线程 */
- (void)createThreadWithClassMethod {
// 使用类方法创建线程并自动启动线程
[NSThread detachNewThreadSelector:@selector(threadMethod2:) toTarget:self withObject:@"fromClassMethod"];
}
/** * 使用init方法显式创建线程 */
- (void)createThreadWithInit {
// 创建线程
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod1) object:nil];
// 设置线程名
[thread1 setName:@"thread1"];
// 设置优先级 优先级从0到1 1最高
[thread1 setThreadPriority:0.9];
// 启动线程
[thread1 start];
}
2:线程状态
线程状态分为:启动线程,阻塞线程,结束线程
启动线程:
- (void)start;
阻塞线程:
// 线程休眠到某一时刻
+ (void)sleepUntilDate:(NSDate *)date;
// 线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
结束线程:
+ (void)exit;
大家在看官方api的时候可能会有一个疑问,api里明明有cancel方法,为什么使用cancel方法不能结束线程?
当我们使用cancel方法时,只是改变了线程的状态标识,并不能结束线程,所以我们要配合isCancelled方法进行使用。具体实现如下:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 创建线程
[self createThread];
}
- (void)createThread {
// 创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod) object:nil];
thread.name = @"i'm a new thread";
// 启动线程
[thread start];
}
/** * 线程方法 */
- (void)threadMethod {
NSLog(@"thread is create -- the name is: \"%@\"", [NSThread currentThread].name);
// 线程阻塞 -- 延迟到某一时刻 --- 这里的时刻是3秒以后
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(@"sleep end");
NSLog(@"sleep again"); // 线程阻塞 -- 延迟多久 -- 这里延迟2秒
[NSThread sleepForTimeInterval:2];
NSLog(@"sleep again end");
for (int i = 0 ; i < 100; i++) {
NSLog(@"thread working");
if(30 == i) {
NSLog(@"thread will dead");
[[NSThread currentThread] cancel];
}
if([[NSThread currentThread] isCancelled]) {
// 结束线程 //
[NSThread exit]; return;
} } }
3:线程间通讯
线程间通信我们最常用的就是开启子线程进行耗时操作,操作完毕后回到主线程,进行数据赋值以及刷新主线程UI。在这里,用一个经典的图片下载demo进行简述。首先我们先了解一下api给出的线程间通信的方法:
//与主线程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes //
与其他子线程通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
以下是demo
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 下载图片
[self downloadImage];
}
/** * 下载图片 */
- (void)downloadImage {
// 创建线程下载图片
[NSThread detachNewThreadSelector:@selector(downloadImageInThread) toTarget:self withObject:nil];
}
/** * 线程中下载图片操作 */
- (void)downloadImageInThread {
NSLog(@"come in sub thread -- %@", [NSThread currentThread]);
// 获取图片url
NSURL *url = [NSURL URLWithString:@"http://img.ycwb.com/news/attachement/jpg/site2/20110226/90fba60155890ed3082500.jpg"];
// 计算耗时
NSDate *begin = [NSDate date];
// 使用CoreFoundation计算耗时 CFDate
CFTimeInterval beginInCF = CFAbsoluteTimeGetCurrent();
// 从url读取数据(下载图片) -- 耗时操作
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSDate *end = [NSDate date];
CFTimeInterval endInCF= CFAbsoluteTimeGetCurrent();
// 计算时间差
NSLog(@"time difference -- %f", [end timeIntervalSinceDate:begin]);
NSLog(@"time difference inCF -- %f", endInCF - beginInCF);
// 通过二进制data创建image
UIImage *image = [UIImage imageWithData:imageData];
// 回到主线程进行图片赋值和界面刷新
[self performSelectorOnMainThread:@selector(backToMainThread:) withObject:image waitUntilDone:YES];
// 这里也可以使用imageView的set方法进行操作 //
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}
/** * 回到主线程的操作 */
- (void)backToMainThread:(UIImage *)image {
NSLog(@"back to main thread --- %@", [NSThread currentThread]);
// 赋值图片到imageview
self.imageView.image = image;
}
在demo中已经把注释写的比较清晰了,需要补充的有三点:
1.performSelectorOnMainThread:withObject:waitUntilDone:方法这里是回到了主线程进行操作,同样也可以使用
[selfperformSelector:@selector(backToMainThread:) onThread:[NSThreadmainThread] withObject:image waitUntilDone:YES];
回到主线程,或者进入其他线程进行操作。
2.在实际项目中我们可能会分析耗时操作所花费时间或者分析用户行为的时候要计算用户在当前页面所耗时间,所以在demo中加入了时间的两种计算方式,分别是CoreFoundation和Foundation中的。
// 计算耗时
NSDate *begin = [NSDate date];
// 使用CoreFoundation计算耗时 CFDate
CFTimeInterval beginInCF = CFAbsoluteTimeGetCurrent();
// 从url读取数据(下载图片) -- 耗时操作
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSDate *end = [NSDate date];
CFTimeInterval endInCF= CFAbsoluteTimeGetCurrent();
// 计算时间差 NSLog(@"time difference -- %f", [end timeIntervalSinceDate:begin]);
NSLog(@"time difference inCF -- %f", endInCF - beginInCF);
3.如果自己写的项目无法运行,可能是因为Xcode7 创建HTTP请求报错导致,具体解决方案请点击这里。
4:线程安全
因为是多线程操作,所以会存在一定的安全隐患。原因是多线程会存在不同线程的资源共享,也就是说我们可能在同一时刻两个线程同时操作了某一个变量的值,但是线程的对变量的操作不同,导致变量的值出现误差。下面是一个存取钱的demo片段:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 初始化状态 [self initStatus];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 启动线程 [self startThread];
}
/** * 初始化状态 */
- (void)initStatus {
// 设置存款
self.depositMoney = 5000;
// 创建存取钱线程
self.saveThread = [[NSThread alloc] initWithTarget:self selector:@selector(saveAndDraw) object:nil];
self.saveThread.name = @"save";
self.drawThread = [[NSThread alloc] initWithTarget:self selector:@selector(saveAndDraw) object:nil];
self.drawThread.name = @"draw";
}
/** * 开启线程 */
- (void)startThread {
// 开启存取钱线程
[self.saveThread start];
[self.drawThread start];
}
/** * 存取钱操作 */
- (void)saveAndDraw {
while(1) {
if(self.depositMoney > 3000) {
// 阻塞线程,模拟操作花费时间
[NSThread sleepForTimeInterval:0.05];
if([[NSThread currentThread].name isEqualToString:@"save"]) {
self.depositMoney += 100;
} else {
self.depositMoney -= 100;
}
NSLog(@"currentThread: %@, depositMoney: %d", [NSThread currentThread].name, self.depositMoney);
} else {
NSLog(@"no money"); return;
} } }
在上面的demo中我们发现,存取钱的线程是同时开启的,而存取钱的钱数相同,所以每一次存取操作结束后,存款值应该不会改变。大家可以运行demo进行查看结果。
所以需要在线程操作中加入锁:
/** * 存取钱操作 */
- (void)saveAndDraw {
while(1) {
// 互斥锁
@synchronized (self) {
if(self.depositMoney > 3000) {
// 阻塞线程,模拟操作花费时间
[NSThread sleepForTimeInterval:0.05];
if([[NSThread currentThread].name isEqualToString:@"save"]) {
self.depositMoney += 100;
} else {
self.depositMoney -= 100;
}
NSLog(@"currentThread: %@, depositMoney: %d", [NSThread currentThread].name, self.depositMoney);
} else {
NSLog(@"no money"); return;
} } } }
线程安全解决方案 这边有介绍关于锁的一些文章可以看看
* 互斥锁 @synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对锁住的对象进行修改。
* 互斥锁使用格式: @synchronized (锁对象) { // 需要锁定的代码 }
* 互斥锁的优缺点:
优点: 防止多线程对共享资源进行抢夺造成的数据安全问题
缺点: 需要消耗大量cpu资源
atomic属性内部的锁称为 自旋锁
互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。
自旋锁的效率高于互斥锁。
NSThread头文件中的相关信息请通过command + 鼠标左键的方式查看
总结
pthread的线程方式虽然平时没用到,但是也有其自己特点,希望了解的大佬能留言沟通。
NSThread平时开发中,时常用来进行线程间通讯,AFN中也用来开启一个常驻线程。
还没来得及自己去实验总结 如果有错误希望能指出来。
网友评论