iOS开发中多线程基础
耗时操作演练
代码演练
- 编写耗时方法
- (void)longOperation {
for (int i = 0; i < 10000; ++i) {
NSLog(@"%@ %d", [NSThread currentThread], i);
}
}
- 直接调用耗时方法
// 1> 直接调用耗时方法
[self longOperation];
运行測试效果
- 在后台运行耗时方法
// 2> 在后台运行耗时方法
[self performSelectorInBackground:@selector(longOperation) withObject:nil];
运行測试效果
小结
[NSThread currentThread]
:当前线程对象- 能够在全部的多线程技术中使用!
- 通经常使用来在多线程开发中。Log 代码是否在主线程运行
number
number == 1
主线程number != 1
后台线程- 不要纠结 number 的具体数字
pthread演练
pthread
是POSIX
多线程开发框架,因为是跨平台的 C 语言框架。在苹果的头文件里并没有具体的凝视- 要查阅
pthread
有关资料,能够訪问 http://baike.baidu.com
导入头文件
#import <pthread.h>
pthread演练
// 创建线程。并且在线程中运行 demo 函数
- (void)pthreadDemo {
/**
參数:
1> 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,并且不须要使用 *
2> 用来设置线程属性
3> 线程运行函数的起始地址
4> 运行函数的參数
返回值:
- 若线程创建成功,则返回0
- 若线程创建失败。则返回出错编号
*/
pthread_t threadId = NULL;
NSString *str = @"Hello Pthread";
int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
if (result == 0) {
NSLog(@"创建线程 OK");
} else {
NSLog(@"创建线程失败 %d", result);
}
}
// 后台线程调用函数
void *demo(void *params) {
NSString *str = (__bridge NSString *)(params);
NSLog(@"%@ - %@", [NSThread currentThread], str);
return NULL;
}
小结
- 在 C 语言中,没有
对象
的概念。对象是以结构体
的方式来实现的 - 通常,在 C 语言框架中,对象类型以
_t/Ref
结尾,并且声明时不须要使用*
- C 语言中的
void *
和 OC 中的id
是等价的 - 内存管理
- 在 OC 中,假设是
ARC
开发,编译器会在编译时。依据代码结构,自己主动加入retain
/release
/autorelease
- 可是。
ARC
仅仅负责管理OC
部分的内存管理,而不负责C 语言
代码的内存管理 - 因此,开发过程中。假设使用的
C
语言框架出现retain
/create
/copy
/new
等字样的函数,大多都须要release
,否则会出现内存泄漏
- 在 OC 中,假设是
- 在混合开发时,假设在
C
和OC
之间传递数据,须要使用__bridge
进行桥接,桥接
的目的就是为了告诉编译器怎样管理内存 - 桥接的加入能够借助 Xcode 的辅助功能加入
MRC
中不须要使用桥接
三种创建线程的方法
准备函数
// MARK: - 后台线程调用函数
- (void)longOperation:(id)obj {
NSLog(@"%@ - %@", [NSThread currentThread], obj);
}
1. alloc / init - start
// MARK: - NSThread 演练
- (void)threadDemo1 {
// 1. 实例化线程对象 => alloc(分配内存) / init(初始化)
NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"alloc/init"];
// 2. 启动线程
[t start];
// 3. 当前线程?
NSLog(@"%@", [NSThread currentThread]);
}
演练小结
[t start];
运行后。会在另外一个线程运行demo
方法- 在 OC 中。不论什么一个方法的代码都是从上向下顺序运行的
- 同一个方法内的代码,都是在同样线程运行的(
block
除外)
2. detachNewThreadSelector
- (void)threadDemo2 {
// detach => 分离一个子线程运行 demo: 方法
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"Detach"];
// 2. 当前线程?
NSLog(@"%@", [NSThread currentThread]);
}
演练小结
detachNewThreadSelector
类方法不须要启动,创建线程后自己主动启动线程运行@selector
方法
3. 分类方法
- (void)threadDemo3 {
// 1. 在后台运行 @selector 方法
[self performSelectorInBackground:@selector(longOperation:) withObject:@"category"];
// 2. 当前线程?
NSLog(@"%@", [NSThread currentThread]);
}
performSelectorInBackground
是NSObject
的分类方法- 没有
thread
字眼,会马上在后台线程运行@selector
方法 - 全部
NSObject
都能够使用此方法,在其它线程运行方法!
自己定义对象
Person 类
// MARK: - Person 类
@interface Person : NSObject
/// 姓名
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
/// 使用字典实例化对象
+ (instancetype)personWithDict:(NSDictionary *)dict {
Person *p = [[Person alloc] init];
[p setValuesForKeysWithDictionary:dict];
return p;
}
/// 载入数据
- (void)loadData {
NSLog(@"载入数据 %@ %@", [NSThread currentThread], self.name);
}
@end
Person 类使用分类方法
- (void)threadDemo4 {
Person * p = [Person personWithDict:@{@"name": @"zhangsan"}];
[p performSelectorInBackground:@selector(loadData) withObject:nil];
}
线程状态
演练代码
// MARK: - 线程状态演练
- (void)statusDemo {
NSLog(@"睡会");
[NSThread sleepForTimeInterval:1.0];
for (int i = 0; i < 20; ++i) {
if (i == 8) {
NSLog(@"再睡会");
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
NSLog(@"%@ %d", [NSThread currentThread], i);
if (i == 10) {
NSLog(@"88");
[NSThread exit];
}
}
NSLog(@"能来吗?");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 注意不要在主线程上调用 exit 方法
// [NSThread exit];
// 实例化线程对象(新建)
NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(statusDemo) object:nil];
// 线程就绪(被加入到可调度线程池中)
[t start];
}
堵塞
- 方法运行过程。符合某一条件时,能够利用
sleep
方法让线程进入堵塞
状态
1> sleepForTimeInterval
- 从如今起睡多少
秒
2> sleepUntilDate
- 从如今起睡到指定的日期
死亡
[NSThread exit];
- 一旦强行终止线程,兴许的全部代码都不会被运行
- 注意:在终止线程之前,应该注意释放之前分配的对象!
就绪 -> 运行
线程从
就绪
和运行
状态之间的切换是由CPU
负责的。程序猿无法干预
线程属性
演练代码
// MARK: - 线程属性
- (void)threadProperty {
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 1. 线程名称
t1.name = @"Thread AAA";
// 2. 优先级
t1.threadPriority = 0;
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 1. 线程名称
t2.name = @"Thread BBB";
// 2. 优先级
t2.threadPriority = 1;
[t2 start];
}
- (void)demo {
for (int i = 0; i < 10; ++i) {
// 堆栈大小
NSLog(@"%@ 堆栈大小:%tuK", [NSThread currentThread], [NSThread currentThread].stackSize / 1024);
}
// 模拟崩溃
// 推断是否是主线程
// if (![NSThread currentThread].isMainThread) {
// NSMutableArray *a = [NSMutableArray array];
//
// [a addObject:nil];
// }
}
属性
1. name
- 线程名称
- 在大的商业项目中。通常须要在程序崩溃时。获取程序准确运行所在的线程
2. threadPriority
- 线程优先级
- 优先级,是一个浮点数,取值范围从
0~1.0
1.0
表示优先级最高0.0
表示优先级最低- 默认优先级是
0.5
- 优先级高仅仅是保证 CPU 调度的可能性会高
- 刀哥个人建议,在开发的时候。不要改动优先级
- 多线程的目的:是将耗时的操作放在后台,不堵塞主线程和用户的交互。
- 多线程开发的原则:简单
3. stackSize
- 栈区大小
- 默认情况下,不管是主线程还是子线程。栈区大小都是
512K
- 栈区大小能够设置
[NSThread currentThread].stackSize = 1024 * 1024;
4. isMainThread
- 是否主线程
资源共享-卖票
多线程开发的复杂度相对较高,在开发时能够依照下面套路编写代码:
- 首先确保单个线程运行正确
- 加入线程
卖票逻辑
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
[self saleTickets];
}
/// 卖票逻辑 - 每个售票逻辑(窗体)应该把全部的票卖完
- (void)saleTickets {
while (YES) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"没票了 %@", [NSThread currentThread]);
break;
}
}
}
加入线程
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t1.name = @"售票员 A";
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t2.name = @"售票员 B";
[t2 start];
}
加入休眠
- (void)saleTickets {
while (YES) {
// 模拟休眠
[NSThread sleepForTimeInterval:1.0];
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"没票了 %@", [NSThread currentThread]);
break;
}
}
}
运行測试结果
相互排斥锁
加入相互排斥锁
- (void)saleTickets {
while (YES) {
// 模拟休眠
[NSThread sleepForTimeInterval:1.0];
@synchronized(self) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"没票了 %@", [NSThread currentThread]);
break;
}
}
}
}
相互排斥锁小结
- 保证锁内的代码。同一时间,仅仅有一条线程能够运行!
- 相互排斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差。
- 速记技巧
[[NSUserDefaults standardUserDefaults] synchronize];
相互排斥锁參数
- 能够加锁的随意
NSObject
对象 - 注意:锁对象一定要保证全部的线程都能够訪问
- 假设代码中仅仅有一个地方须要加锁,大多都使用
self
。这样能够避免单独再创建一个锁对象
原子属性
- 原子属性(线程安全)。是针对多线程设计的。是默认属性
- 多个线程在写入原子属性时(调用
setter
方法)。能够保证同一时间仅仅有一个线程运行写入操作 - 原子属性是一种
单(线程)写多(线程)读
的多线程技术 原子属性的效率比相互排斥锁高
,只是可能会出现脏数据
- 在定义属性时。必须显示地指定
nonatomic
演练代码
@interface ViewController ()
@property (atomic, strong) NSObject *obj1;
@property (atomic, strong) NSObject *obj2;
@end
@implementation ViewController
@synthesize obj1 = _obj1;
// 原子属性模拟代码
/// obj1 - getter
- (NSObject *)obj1 {
return _obj1;
}
/// obj1 - setter
- (void)setObj1:(NSObject *)obj1 {
@synchronized(self) {
_obj1 = obj1;
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
long largeNumber = 1000 * 1000;
// 相互排斥锁測试
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; ++i) {
self.obj1 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
// 自旋锁測试
start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; ++i) {
self.obj2 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
}
@end
原子属性内部的锁是
自旋锁
,自旋锁的运行效率比相互排斥锁高
自旋锁 & 相互排斥锁
共同点
- 都能够保证同一时间。仅仅有一条线程运行锁定范围的代码
不同点
相互排斥锁
:假设发现有其它线程正在运行锁定的代码。线程会进入休眠状态
,等待其它线程运行完毕。打开锁之后,线程会被唤醒
自旋锁
:假设发现有其它线程正在运行锁定的代码。线程会以死循环
的方式,一直等待锁定代码运行完毕
结论
- 自旋锁更适合运行很短的代码
- 不管什么锁。都是要付出代价
线程安全
- 多个线程进行读写操作时,仍然能够得到正确结果,被称为线程安全
- 要实现线程安全,必须要用到
锁
- 为了得到更佳的用户体验,
UIKit 不是线程安全的
约定:全部更新 UI 的操作都必须主线程上运行。
- 因此。
主线程
又被称为UI 线程
iOS 开发建议
- 全部属性都声明为
nonatomic
- 尽量避免多线程抢夺同一块资源
- 尽量将加锁、资源抢夺的业务逻辑交给server端处理,减小移动client的压力
线程间通讯
主线程实现
定义属性
/// 根视图是滚动视图
@property (nonatomic, strong) UIScrollView *scrollView;
/// 图像视图
@property (nonatomic, weak) UIImageView *imageView;
/// 网络下载的图像
@property (nonatomic, weak) UIImage *image;
loadView
loadView 方法的作用:
- 载入视图层次结构
- 用纯代码开发应用程序时使用
- 功能和
Storyboard
&XIB
是等价的
假设重写了
loadView
。Storyboard
&XIB
都无效
- (void)loadView {
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.backgroundColor = [UIColor orangeColor];
self.view = self.scrollView;
UIImageView *iv = [[UIImageView alloc] init];
[self.view addSubview:iv];
self.imageView = iv;
}
viewDidLoad
- 视图载入完毕后运行
- 能够做一些数据初始化的工作
- 假设用纯代码开发,不要在此方法中设置界面 UI
- (void)viewDidLoad {
[super viewDidLoad];
// 下载图像
[self downloadImage];
}
下载网络图片
- (void)downloadImage {
// 1. 网络图片资源路径
NSURL *url = [NSURL URLWithString:@"http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg"];
// 2. 从网络资源路径实例化二进制数据(网络訪问)
NSData *data = [NSData dataWithContentsOfURL:url];
// 3. 将二进制数据转换成图像
UIImage *image = [UIImage imageWithData:data];
// 4. 设置图像
self.image = image;
}
设置图片
- (void)setImage:(UIImage *)image {
// 1. 设置图像视图的图像
self.imageView.image = image;
// 2. 依照图像大小设置图像视图的大小
[self.imageView sizeToFit];
// 3. 设置滚动视图的 contentSize
self.scrollView.contentSize = image.size;
}
设置滚动视图的缩放
1> 设置滚动视图缩放属性
// 1> 最小缩放比例
self.scrollView.minimumZoomScale = 0.5;
// 2> 最大缩放比例
self.scrollView.maximumZoomScale = 2.0;
// 3> 设置代理
self.scrollView.delegate = self;
2> 实现代理方法 - 告诉滚动视图缩放哪一个视图
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
3> 跟踪 scrollView
缩放效果
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
NSLog(@"%@", NSStringFromCGAffineTransform(self.imageView.transform));
}
线程间通讯
- 在后台线程下载图像
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
- 在主线程设置图像
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
}
iOS开发中多线程基础的更多相关文章
- 活到老学到老:iOS开发中的基础知识(一)
本文参考 标哥的博客:宝库iOS开发笔试题 进行学习整理.与其说是看面试题,不如说是对自己知识的巩固.工欲善其事必先利其器,基础知识不牢固可能会导致编程中的一些注意不到的问题.总之一句话:活到老,学到 ...
- iOS开发中多线程间关于锁的使用
为什么需要使用锁,当然熟悉多线程的你,自然不会感到陌生. 那你在代码中是否很好的使用了锁的机制呢?你又知道几种实现锁的方法呢? main.m 1 int main(int argc, const ch ...
- IOS开发中多线程的使用
一.创建多线程的五种方式 1.开启线程的方法一 NSThread * thread=[[NSThread alloc] initWithTarget:self selector:@selector(_ ...
- iOS开发中多线程断点下载大文件
主要思想,就是创建一个与目标文件等大小的空白文件,然后分段往这个空白文件中写入数据. 可以通过发送HEAD请求,获得服务器中文件的具体大小,然后再将这样的长度分割成若干等大的数据块,在发送get请求时 ...
- iOS开发之多线程技术
本篇争取一篇讲清讲透,依然将通过四大方面清晰的对iOS开发中多线程的用法进行详尽的讲解: 一.什么是多线程 1)多线程执行原理 2)线程与进程 3)多线程的优缺点 二.我们为什么要用多线程编程技术 三 ...
- ios开发之多线程---GCD
一:基本概念 1:进程:正在运行的程序为进程. 2:线程:每个进程要想执行任务必须得有线程,进程中任务的执行都是在线程中. 3:线程的串行:一条线程里任务的执行都是串行的,假如有一个进程开辟了一条线程 ...
- 多线程在iOS开发中的应用
多线程基本概念 01 进程 进程是指在系统中正在运行的一个应用程序.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内. 02 线程 2-1 基本概念 1个进程要想执行任务,必须得有线程 ...
- iOS开发中各种关键字的区别
1.一些概念 1.浅Copy:指针的复制,只是多了一个指向这块内存的指针,共用一块内存. 深Copy:内存的复制,两块内存是完全不同的, 也就是两个对象指针分别指向不同的内存,互不干涉. 2.atom ...
- IOS开发中UI编写方式——code vs. xib vs.StoryBoard
最近接触了几个刚入门的iOS学习者,他们之中存在一个普遍和困惑和疑问,就是应该如何制作UI界面.iOS应用是非常重视用户体验的,可以说绝大多数的应用成功与否与交互设计以及UI是否漂亮易用有着非常大的关 ...
随机推荐
- 【Linux】用户权限设置,配合FTP访问
转载自: http://blog.csdn.net/fengeh/article/details/16819563 领导需求,需要创建用户,并允许其增删改,却又要求其只能在自己的访问目录内,不能去别的 ...
- java web 解决Form表单乱码问题
JSP和Servlet的六种中文乱码处理方法 一.表单提交时出现乱码: 在进行表单提交的时候,经常提交一些中文,自然就避免不了出现中文乱码的情况,对于表单来说有两种提交方式:get和post提交方式. ...
- 公众号的TOKEN配置PHP代码
1.在后台添加好URL和TOKEN和生成43位随机码 注意域名URL需要备案 2.上传到服务器 3.公众号后台要点提交即可 error_reporting(0); $signature=$_REQU ...
- unity, 导出对象到另一个项目
----更新(2015-6-26): 今天又试了一下,有时候prefabs不用导出直接拷贝过去也能用,但有时候不行.还不太清楚原因. ----旧帖: 一,正确做法: 一个对象包括资源和Hierarch ...
- CodeMirror与jquery UI-Tabs混合使用 注意事项
第一步:.将代码高亮渲染 第二步:jquery Tab输出: 第三步:点击Tab切换时,将代码块刷新: 参考:http://jtmorris.net/2013/06/codemirror-editor ...
- 关于TimeSpan
一秒是1000万个tick TimeSpan ts = * ); Console.WriteLine(ts); Console.Read(); //print 00:00:01 并且在TimeSpan ...
- css语法和JS语法的对比
CSS语法(不区分大小写) JavaScript语法(区分大小写) border border border-bottom borderBottom border-bottom-color bor ...
- Selenium - Switch & Select Api
一.多表单切换 driver.switch_to.frame() iframe :直接将一个html 页面嵌入另一个html 页面中 switch_to.frame() 默认可以直接取表单的id ...
- IOS7 隐藏状态栏 (电池栏)
电池状态栏. //方法一(代码设置): 现在ios7已经更改为透明,并且不占用屏幕高度.其中隐藏及显示的方法如下: 在uiviewcontroller的子类下,调用: if ([self re ...
- 【C语言】21-结构体
C语言的核心部分都说得七七八八了,相信大家已经对C语言的基本数据类型(char\int\float).数组.指针都很熟悉了,今天来学习C语言中另外一种数据类型:结构体.在iOS开发中,结构体是经常用到 ...