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];

运行測试效果

小结

  1. [NSThread currentThread]:当前线程对象

    • 能够在全部的多线程技术中使用!
    • 通经常使用来在多线程开发中。Log 代码是否在主线程运行
  2. number
    • number == 1 主线程
    • number != 1 后台线程
    • 不要纠结 number 的具体数字

pthread演练

  • pthreadPOSIX 多线程开发框架,因为是跨平台的 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;
}

小结

  1. 在 C 语言中,没有对象的概念。对象是以结构体的方式来实现的
  2. 通常,在 C 语言框架中,对象类型以 _t/Ref 结尾,并且声明时不须要使用 *
  3. C 语言中的 void * 和 OC 中的 id 是等价的
  4. 内存管理
    • 在 OC 中,假设是 ARC 开发,编译器会在编译时。依据代码结构,自己主动加入 retain/release/autorelease
    • 可是。ARC 仅仅负责管理 OC 部分的内存管理,而不负责 C 语言 代码的内存管理
    • 因此,开发过程中。假设使用的 C 语言框架出现 retain/create/copy/new 等字样的函数,大多都须要 release,否则会出现内存泄漏
  5. 在混合开发时,假设在 COC 之间传递数据,须要使用 __bridge 进行桥接,桥接的目的就是为了告诉编译器怎样管理内存
  6. 桥接的加入能够借助 Xcode 的辅助功能加入
  7. 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]);
}

演练小结

  1. [t start];运行后。会在另外一个线程运行 demo 方法
  2. 在 OC 中。不论什么一个方法的代码都是从上向下顺序运行的
  3. 同一个方法内的代码,都是在同样线程运行的(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]);
}
  1. performSelectorInBackgroundNSObject 的分类方法
  2. 没有 thread 字眼,会马上在后台线程运行 @selector 方法
  3. 全部 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 - 是否主线程

资源共享-卖票

多线程开发的复杂度相对较高,在开发时能够依照下面套路编写代码:

  1. 首先确保单个线程运行正确
  2. 加入线程

卖票逻辑

- (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;
            }
        }
    }
}

相互排斥锁小结

  1. 保证锁内的代码。同一时间,仅仅有一条线程能够运行!

  2. 相互排斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差。
  3. 速记技巧 [[NSUserDefaults standardUserDefaults] synchronize];

相互排斥锁參数

  1. 能够加锁的随意 NSObject 对象
  2. 注意:锁对象一定要保证全部的线程都能够訪问
  3. 假设代码中仅仅有一个地方须要加锁,大多都使用 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 开发建议

  1. 全部属性都声明为 nonatomic
  2. 尽量避免多线程抢夺同一块资源
  3. 尽量将加锁、资源抢夺的业务逻辑交给server端处理,减小移动client的压力

线程间通讯

主线程实现

定义属性

/// 根视图是滚动视图
@property (nonatomic, strong) UIScrollView *scrollView;
/// 图像视图
@property (nonatomic, weak) UIImageView *imageView;
/// 网络下载的图像
@property (nonatomic, weak) UIImage *image;

loadView

loadView 方法的作用:

  1. 载入视图层次结构
  2. 用纯代码开发应用程序时使用
  3. 功能和 Storyboard & XIB 是等价的

假设重写了 loadViewStoryboard & 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

  1. 视图载入完毕后运行
  2. 能够做一些数据初始化的工作
  3. 假设用纯代码开发,不要在此方法中设置界面 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];
}
时间: 2024-10-09 22:44:17

iOS开发中多线程基础的相关文章

活到老学到老:iOS开发中的基础知识(一)

本文参考 标哥的博客:宝库iOS开发笔试题 进行学习整理.与其说是看面试题,不如说是对自己知识的巩固.工欲善其事必先利其器,基础知识不牢固可能会导致编程中的一些注意不到的问题.总之一句话:活到老,学到老. 1.数组中的元素去重问题. //重复元素 NSArray *array = [NSArray arrayWithObjects:@"1",@"2",@"3",@"4",@"5",@"1"

iOS开发中多线程间关于锁的使用

为什么需要使用锁,当然熟悉多线程的你,自然不会感到陌生. 那你在代码中是否很好的使用了锁的机制呢?你又知道几种实现锁的方法呢? main.m 1 int main(int argc, const char * argv[]) { 2 @autoreleasepool { 3 //普通用法:会看到线程1锁住之后,线程2会一直等待,直到线程1执行完,线程2才执行 4 NSLog(@"使用NSLock(普通锁:已实现NSLocking协议)实现锁"); 5 [LockByNSLock exe

IOS开发中多线程的使用

一.创建多线程的五种方式 1.开启线程的方法一 NSThread * thread=[[NSThread alloc] initWithTarget:self selector:@selector(_update) object:nil]; 2.开启线程的方法二 [NSThread detachNewThreadSelector:@selector(_update) toTarget:self withObject:nil]; 3.开启线程的方法三 [self performSelectorIn

iOS开发中GCD在多线程方面的理解

GCD为Grand Central Dispatch的缩写. Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法.在Mac OS X 10.6雪豹中首次推出,并在最近引入到了iOS4.0. GCD是一个替代诸如NSThread等技术的很高效和强大的技术.GCD完全可以处理诸如数据锁定和资源泄漏等复杂的异步编程问题. GCD可以完成很多事情,但是这里仅关注在iOS应用中实现多线程所需的一些基础知识. 在开始之前,需要理解是要提供给GCD队列的是代

iOS开发中的gcd多线程tips

iOS开发中的gcd多线程tips 我们经常用到的: dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 处理耗时操作的代码块 //通知主线程刷新 dispatch_async(dispatch_get_main_queue(), ^{ //回调或者说是通知主线程刷新 }); }); 其中main_queue是系统默认的串行队列,global_queue是系统默认的并行队列. 什么是串行队列(Serial)? 创建任意个数的串行队列,每个队

iOS开发UI基础—IOS开发中Xcode的一些使用技巧

iOS开发UI基础-IOS开发中Xcode的一些使用技巧 一.快捷键的使用 经常用到的快捷键如下: 新建 shift + cmd + n     新建项目 cmd + n             新建文件 视图 option + cmd + 回车 打开助理编辑器 cmd + 回车           显示主窗口 cmd + 0             导航窗口 option + cmd + 0    工具窗口 在.m & .h之间切换           control + cmd + 上/下

ios开发中 线程、进程即多线程简单介绍

本文转自:原文http://www.cnblogs.com/wendingding/p/3805088.html 一.进程和线程 1.什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如同时打开QQ.Xcode,系统就会分别启动2个进程 通过“活动监视器”可以查看Mac系统中所开启的进程 2.什么是线程 1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程) 线程是进程的基本执行单元,一个进程(程序)的所有任务都在线

iOS开发中UIPopoverController的使用详解

这篇文章主要介绍了iOS开发中UIPopoverController的使用,代码基于传统的Objective-C,需要的朋友可以参考下 一.简单介绍 1.什么是UIPopoverController 是iPad开发中常见的一种控制器(在iPhone上不允许使用) 跟其他控制器不一样的是,它直接继承自NSObject,并非继承自UIViewController 它只占用部分屏幕空间来呈现信息,而且显示在屏幕的最前面 2.使用步骤 要想显示一个UIPopoverController,需要经过下列步骤

[转载]对iOS开发中内存管理的一点总结与理解

对iOS开发中内存管理的一点总结与理解 做iOS开发也已经有两年的时间,觉得有必要沉下心去整理一些东西了,特别是一些基础的东西,虽然现在有ARC这种东西,但是我一直也没有去用过,个人觉得对内存操作的理解是衡量一个程序员成熟与否的一个标准.好了,闲话不说,下面进入正题. 众所周知,ObjectiveC的内存管理引用的一种叫做“引用计数“ (Reference Count)的操作方式,简单的理解就是系统为每一个创建出来的对象,(这里要注意,只是对象,NSObject的子类,基本类型没有‘引用计数’)