iOS开发技术iOS洋洋洒洒又一年

iOS多线程(手把手教你进阶)

2016-11-03  本文已影响564人  IIronMan

.一.进程

二.线程

答:1.CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,2.每条线程被调度执行的频次会降低(线程的执行效率降低)

三.多线程在iOS开发中的应用

耗时操作放在主线程
耗时操作demo 密码: qugm

四.iOS中多线程的实现方案

iOS中多线程的实现方案

五.对多线程开辟的详细介绍

NSThread的code 密码: gsq4

线程的状态

注意:一旦线程停止(死亡)了,就不能再次开启任务,必须开辟新的线程

第一个例子: 银行取钱

银行取钱

第二个例子: 售票

售票 安全问题分析

抢票资源的解决问题

抢票资源的解决问题

卖票抢夺资源代码 密码: bwgb

六.线程之间的通信

线程间通信常用方法

举个例子:用UIImageView加载图片

//1.获取图片的路径
NSString *stringPath = @"http://h.hiphotos.baidu.com/image/h%3D360/sign=48771214b08f8c54fcd3c3290a282dee/c9fcc3cec3fdfc0375633742d03f8794a4c22635.jpg";
//开始时间(秒数,从1970年0点0时0分0秒开始算起)
CFTimeInterval begin = CFAbsoluteTimeGetCurrent();

//2.根据图片的路径去下载图片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
//结束时间
CFTimeInterval end = CFAbsoluteTimeGetCurrent();

NSLog(@"开始时间=%f 结束时间=%f 消耗时间==%f",begin,end,end -begin);

//3.加载下载好的图片
self.picture.image = [UIImage imageWithData:data];

获取时间间隔的方式:

1.第一种获取时间
NSDate *begin = [NSDate date];
//根据图片的路径去下载图片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
//结束时间
NSDate *end = [NSDate date];
NSLog(@"开始时间=%@ 结束时间=%@ 消耗时间==%f",begin,end,[end timeIntervalSinceDate:begin]);

2.第二种获取时间
//开始时间(秒数,从1970年0点0时0分0秒开始算起)
CFTimeInterval begin = CFAbsoluteTimeGetCurrent();
//2.根据图片的路径去下载图片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
//结束时间
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"开始时间=%f 结束时间=%f 消耗时间==%f",begin,end,end -begin);

最后解释一下方法的最后一个参数waitUntilDone:YES或者NO的问题

YES:意思是刷新UI之后再走子线程之后的其他任务
NO:意思是主线程刷新的同时,其他线程也在执行任务

YES与NO的区别

线程通信方式的demo 密码: 85x4

七.GCD

各种队列的执行效果

注意:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列

下面写一个GCD线程阻塞的代码

GCD线程阻塞的代码

例如下面的代码:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
     dispatch_queue_t queue = dispatch_queue_create("12235", DISPATCH_QUEUE_CONCURRENT);

     dispatch_async(queue, ^{
          NSLog(@"1111");
     });
     dispatch_async(queue, ^{
         NSLog(@"2222"); 
     });
    dispatch_barrier_async(queue, ^{
         NSLog(@"您在执行barrier"); 
    });
    dispatch_async(queue, ^{
        NSLog(@"3333");  
    });
    dispatch_async(queue, ^{
        NSLog(@"4444");
    });
}

打印结果如下

打印结果如下
八.iOS常见的延时执行有2种方式

九.一次性代码

使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken,^{

     只执行1次的代码(这里面默认是线程安全的)

  });

提醒:一次性代码慎用,它是程序在运行过程中只执行一次

十.剪切利用多线程分析 (剪切是耗时的,要放到子线程里面)

剪切利用多线程分析
#pragma mark 传统的剪切文件
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSString *form = @"/Users/jinqianxiang/Desktop/Form";
    NSString *to = @"/Users/jinqianxiang/Desktop/To";
    
    /**
     *  创建文件管理对象
     */
    NSFileManager *filemanger = [NSFileManager defaultManager];
    //获取要剪切的文件夹里面的每个对象名字
    NSArray *array = [filemanger subpathsAtPath:form];
    
    for (NSString *subpath in array) {
        //全路径
        NSString *formPath = [form stringByAppendingPathComponent:subpath];
        NSString *toPath = [to stringByAppendingPathComponent:subpath];
      
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          
          //剪切(全路径剪切)
          [filemanger moveItemAtPath:formPath  toPath:toPath error:nil];
       
        });
    }
}
比较快的剪切方式

文件的剪切demo 密码: 7edp

十一.GCD队列组和栅栏(barrier)一样的效果:确定线程的优先级顺序

如果想要快速高效地实现上述需求,可以考虑用队列组

dispatch_group_t group =  dispatch_group_create();
// 1.
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

        //执行1个耗时的异步操作

});
//2.
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

      //执行1个耗时的异步操作

});
//3.
dispatch_group_notify(group,dispatch_get_main_queue(),^{

     //等前面的异步操作都执行完毕后,回到主线程...

});

这样就是1和2执行完了才会执行3

下面以两张图片合成为例:

两张图片合成为例

两张图片合成demo 密码: 3w5f

关键性代码:

 /**
 *  3.将两个图片合成
 */
dispatch_group_notify(group, queue, ^{
    /**
     *  开启新的图形上下文
     */
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    
    //绘制图片
    [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
    //绘制图片
    [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
    
    /**
     *  取得上下文里面的图片
     */
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    
    /**
     *  结束上下文
     */
    UIGraphicsEndImageContext();
    
    /**
     *  4.进入主队列加载合成的图片
     */
    
    dispatch_async(dispatch_get_main_queue(), ^{
        
        self.imageView.image = image;
        
  });

十二.GCD单例模式

 #if __has_feature(objc_arc)
 //ARC
 #else
 //MRC
 #endif
#import "PersonMessages.h"

static PersonMessages *personMessages;
@implementation PersonMessages

 +(instancetype)allocWithZone:(struct _NSZone *)zone
 {

     static dispatch_once_t onceToken;

     dispatch_once(&onceToken, ^{
    
     personMessages = [super allocWithZone:zone];
    
    });

  return personMessages;
}
@end



说明:不管是alloc还是allocWithZone 都会调用allocWithZone

下面还是以PersonMessages类为例
.h里面

#import <Foundation/Foundation.h>

@interface PersonMessages : NSObject

+(instancetype)sharePersonMessages;

@end

.m里面

#import "PersonMessages.h"
@implementation PersonMessages
static PersonMessages *personMessages;
/**
 *  1.保证调用 [ PersonMessages sharePersonMessages] 只会调用一次init
 *
 *  @return personMessages
 */
+(instancetype)sharePersonMessages
{
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
    
    personMessages = [[self alloc]init];
    
    });

  return personMessages;
}

/**
 *  2.外面调用n次访问同一个对象
 *
 *  @return personMessages
 */
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
   static dispatch_once_t onceToken;

   dispatch_once(&onceToken, ^{
    
      personMessages = [super allocWithZone:zone];
    
   });

   return personMessages;
}

/**
 *  3.外面进行copy的时候还是同一个对象
 *
 *  @return personMessages;
 */
 -(id)copy
{
    return personMessages;
}
@end
完整的单例(粒)

解释一下:

1.static:意思防止外面人访问更改

static PersonMessages *personMessages;

2.once默认是安全的(已经加锁), onceToken是来记录访问过block

dispatch_once(&onceToken, ^{

        personMessages = [super allocWithZone:zone];

    });

下面我对单例类进行封装解释

对单例类进行封装

GCD单例的封装 密码: yxan
传统单例的封装 密码: xxne

使用方法:图中3步

.h

1

.m

2

十三.NSOperation

注意:

NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类

NSInvocationOperation

** 一张图**


NSBlockOperation
NSOperationQueue的作用

- 如果将`NSOperation`添加到`NSOperationQueue`(操作队列)中,系统会**自动异步执行**NSOperation中的操作:相当于`start`

另外还可以直接用队列添加Operation

      /**
        *  1.创建队列
        */
       NSOperationQueue *operationQueue = [NSOperationQueue new];

      /**
       *  特殊情况,队列直接添加operation(添加任务)
       */
          [operationQueue addOperationWithBlock:^{
    
             NSLog(@"%@",[NSThread currentThread]);
         }];
1.自定义创建NSOperation 2.自定义创建NSOperation

注意:放到队列之后,它会自动开启start

NSOperation创建的3中形式 密码: qaex

最大并发数 密码: xxwk

提示:在自定义的NSOperation的-(void)main{}方法里面,如果线程取消了,我们应该进行判断,防止性能消耗

取消队列的所有操作

举个例子:

暂停或者取消队列

NSOperationQueue的挂起和取消 密码: 58is

跨队列 可以监听一个操作的执行完毕

最后:提一下图片的缓存机制

图片缓存代码<不完善> 密码: wjhv

沙盒缓存

完善的图片缓存 密码: gr3k

无沙盒缓存

最后再总结一下队列类型

GCD 的队列类型

NSOperationQueue队列类型

上一篇 下一篇

猜你喜欢

热点阅读