多线程2

Block基本演练

@interface ViewController ()

//把block定义成全局属性
@property (nonatomic,copy) void(^task)();

@end

/**
 Block是一段代码块,只在被调用时执行,类似于函数和方法
 Block是一个匿名函数,只有函数体,没有函数名
 Bolck是一种数据类型,类似于int / NSString ...
 可以定义成临时变量,可以直接调用的临时变量
 可以当做参数传递,可以传递到另外的方法或者类里面去调用
 可以定义成全局属性,但是需要使用copy修饰符
 block也是一个指向函数的指针对象
 
 */
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //调用函数
   // demo();
    
    //定义block
    void(^task)() = ^{
        NSLog(@"haha");
    };
    
    //保存代码块
    self.task = task;
}
#pragma mark - 可以当做参数传递,可以传递到另外的方法
-(void)blockDemo2{
    //定义block
    void (^task)() = ^{
        NSLog(@"blockDemo2 - hello");
    };
    [self callBack:task];
}
//接收block的方法
-(void)callBack:(void (^)())task {
    //调用外界传入的task
    task();
}

#pragma mark - 定义临时变量, 可以直接调用的临时变量
//void(^)() 表示block的类型,类似于int
//void (^task) 表示给block起个变量名task
//提示:如果不知道Block怎么写,可以先写个`int num = 10; `,然后照着写
//注意点:一定要会手写无参无返回值的Block;(笔试题经常遇到)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //[self blockDemo1];
    self.task();
}
-(void)blockDemo1{
//    <#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
//        <#statements#>
//    };
    //定义一个无参无返回值的Block / 代码块
    void(^task)() = ^{
        NSLog(@"blockDemo1 - hello");
    };
    //调用Block : 跟函数的调用是一样的
    task();
}
//定义一个无参无返回值函数
void demo(){
    NSLog(@"demo");
}

block反向传值


主界面

  1. 准备等待执行的代码块
  2. 如果需要接收外界传入的值,需要定义参数
  3. 向目标控制器传递代码块
#import "ViewController.h"
#import "DetailViewController.h"
@interface ViewController ()
//展示用户名
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

//监听push按钮的点击事件
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    //NSLog(@"%s",__func__);
    
    //1.获取目标控制器(deatilVC)
    DetailViewController *vc = segue.destinationViewController;
    //判断目标控制器是否为空
    if(vc != nil){
        
        //2.准备代码块:等待详情调用时才执行
        void(^completion)(NSString *) = ^(NSString *name){
            self.nameLabel.text = name;
        };
        //3.向deatilVC传递代码块
        vc.completion = completion;
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

detail界面

  1. 准备属性接收代码块
  2. 回调代码块
#import "DetailViewController.h"

@interface DetailViewController ()
//获取name的空间
@property (weak, nonatomic) IBOutlet UITextField *nameTextField;

@end

@implementation DetailViewController
//保存按钮的点击事件
- (IBAction)save:(UIBarButtonItem *)sender {
    //NSLog(@"点击事件");
    
    //1.把name传递到主页控制器
    if(self.completion != nil){
        //回调主页传入的代码块
        self.completion(self.nameTextField.text);
    }
    //2.pop主页控制器
    [self.navigationController popViewControllerAnimated:YES];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

Block相关

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self blockDemo2];
}
#pragma mark - 以下面试题仅做参考
-(void)blockDemo3{
    
    NSMutableString *strM = [NSMutableString stringWithString:@"hello"];
    void(^block)() = ^{
        //strM 本来就在堆区
        [strM appendString:@"hehe"];
    };
    block();
}

#pragma mark - 当在Block内部"修改"外部变量时

/**
 1. 当在Block内部"修改"外部变量,不被允许
 2. 如果非要在Block内部修改外部变量,需要使用__block修饰外部变量
 3. __block修饰外部变量作用:使外部变量可以在Block内部修改
 4. 被__block标记的外部变量,一旦在block内部使用过(访问或修改),那么block对外部变量的拷贝就不是临时的了;block外部变量的真实值就会发生变化
 5. 为什么在block内部不能去访问外部的一个变量?
    因为block设计用来做数据的传递的,block一般会传递到另外的类里面做回调,如果block内部的变量在栈区,那么block在传递的过程中,它内部的变量容易丢失
 
 */
-(void)blockDemo2{
    //使用__block标记外部变量
    __block int num = 10;
    NSLog(@"===>%p - %d",&num,num);
    void(^task)() = ^{
        num = 30;//在block内部不能直接修改外部变量 如果非要修改需要使用__block修饰外部变量
        NSLog(@"%d",num);
        NSLog(@"===>%p - %d",&num,num);
    };
    num = 20;
    NSLog(@"===>%p - %d",&num,num);
    task();
}
#pragma mark - 当在block内部访问外部变量时

/**
 1. 当在block内部"访问"外部变量时,block会对外部的变量进行一次临时的拷贝
 2. 临时拷贝的结果: 把栈区的地址拷贝到堆区
 3. 其实,在block内部操作的是副本(临时拷贝出来的那一份),对block外部的变量的真实值不会有影响
 */
-(void)blockDemo1{
    int num  = 10;
    NSLog(@"===>%p - %d",&num,num);//===>0x7fff5c1d163c - 10 栈区
    //其实在定义代码块时,就已经完成了拷贝
    void(^task)() = ^{
        NSLog(@"%d",num);
        NSLog(@"===>%p - %d",&num,num);//===>0x6000000456f0 - 10 堆区
    };
    num = 20;//===>0x7fff5c1d163c - 20 栈区
    NSLog(@"===>%p - %d",&num,num);
    task();
}

Block内存管理

解决Block定义成属性为什么使用copy修饰符

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self blockDemo3];
}

#pragma mark -为什么Block定义成属性使用copy修饰符?MRC 演示

/**
 MRC :block存储在栈区,如果block想全局共享,那么需要在堆区
 MRC :定义block属性使用copy的原因,是为了在赋值时,在setter方法内部,会自动把栈区的Block拷贝到堆区
 ARC :block存储在堆区,所以,定义block属性,可以选择strong或者copy
 */
-(void)blockDemo3{
    int num = 10;
    void(^task)() = ^{
        NSLog(@"hello %d",num);
    };
    //MRC : 保存代码块: 以下代码在执行时,会调用setter方法,在setter方法内部会自动的把栈区的block拷贝到堆区
    self.task = task;
    //MRC :地址由栈区变到堆区
    NSLog(@"%@ -  %@",task , self.task);
    
}

#pragma mark - 函数体会发生变化
/**
 1.ARC:当函数体会发生变化时,Block存储在堆区
 2.MRC:当函数体会发生变化时,Block存储在栈区
 3.提示:Block是在ios4.0时引入的;那时候,是MRC开发环境
 */
-(void)blockDemo2{
    int num = 10;
    void(^task)() = ^{
        NSLog(@"hello %d",num);
    };
    //ARC : 堆区
    //MRC : 栈区
    NSLog(@"%@",task);
}


#pragma mark - 函数体不会发生变化

/**
 当block的函数体不会去发生变化时,无论是ARC还是MRC,内存都存储在全局区
 */
-(void)blockDemo1{
    
    void(^task)() = ^{
        NSLog(@"hello");
    };
    //ARC :<__NSGlobalBlock__: 0x10c4d7090> :全局
    NSLog(@"%@",task);
}

block的循环引用

@interface ViewController ()

@property (nonatomic,copy) void(^task)();
@property (nonatomic,strong) NSMutableArray *array;

@end

@implementation ViewController{
    NSMutableArray *_arr;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self blockDemo2];
}

/**
 不要在block内部使用成员变量
 */
-(void)blockDemo2{
    //解决 :使用__weak 修饰 self
    __weak typeof(self) weakSelf = self;
    
    //__weak ViewController *weakSelf = self;
    //block内部访问了`self`,会对`self`进行拷贝(强引用)
    self.task = ^{
        NSLog(@"%@ - %@",weakSelf.view,_arr);//在block内部建议不要使用成员变量,因为会有循环引用,但是不好发现 即使发现了,不好解决
    };
    //self.task = task;
}



/**
 提示:一旦在block内部发现了  `self.` ,需要注意是否有循环引用发生
 解决循环引用的思路:不让block在内部对self强引用
 使用__weak 修饰 self
 __weak :标记若引用;__strong:标记强引用;
 __weak:就是告诉Block不要对self 进行强引用
 */
-(void)blockDemo{
    //解决 :使用__weak 修饰 self
    __weak typeof (self) weakSelf = self;
    
    //__weak ViewController *weakSelf = self;
    //block内部访问了`self`,会对`self`进行拷贝(强引用)
    void(^task)() = ^{
        NSLog(@"%@",weakSelf.view);
    };
    self.task = task;
}
-(void)dealloc{
    NSLog(@"%s",__func__);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

GCD简介

全称是Grand Central Dispatch
纯C语言的,提供了非常强大的函数
GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU(双核 四核)
GCD会自动管理线程的生命周期(创建线程 调度任务 销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
GCD的核心
将任务添加到队列
任务:执行什么操作
队列:用来存放任务
GCD使用的两个步骤
创建任务:
确定要做的事情
GCD中的任务是BLOCK封装的
将任务添加到队列
GCD会自动将队列中的任务取出,放到对应的线程中
任务的取出遵循队列的FIFO原则 : 先进先出,后进后出.


@interface ViewController ()

//根视图
@property (strong,nonatomic) UIScrollView *scrollView;

//图片子视图
@property (nonatomic,weak) UIImageView *imgView;

@end
//需求:异步下载网络图片,图片可以滚动,滚动视图要是根视图
//分析需求:下载是耗时的操作,需要在子线程异步执行
//准备控件的工作UIImageView/UIScrollView(根视图)
//
@implementation ViewController
/*
 loadView:优先于viewDidLoad
 loadView:当self.view == nil 时调用
 loadView:不需要调用super
 */
-(void)loadView{
    //创建根视图
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    //把根视图替换成scrollView
    self.view = self.scrollView;
    self.scrollView.backgroundColor = [UIColor redColor];
    
    //创建图片子视图
    UIImageView *imgView = [[UIImageView alloc] init];
    [self.view addSubview:imgView];
    //给属性赋值:一定不能少
    self.imgView = imgView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self loadImageData];
    
}

//在子线程下载图片,在主线程更新UI,是线程间通信的一种
//线程间通信:一个线程把他执行的结果,传递到另外的一个线程

//下载图片的主方法
-(void)loadImageData{
    //将异步任务添加到队列
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //URL
        NSURL *url = [NSURL URLWithString:@"http://img05.tooopen.com/images/20150202/sy_80219211654.jpg"];
        //发送网络请求,获取图片二进制数据,是个耗时的操作
        NSData *data = [NSData dataWithContentsOfURL:url];
        //image就是子线程执行的结果,需要传递到主线程
        UIImage *image = [UIImage imageWithData:data];//下载的结果
        //下载完成后,通知主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{//通知主线程刷新UI
            self.imgView.image = image;
            [self.imgView sizeToFit];
            self.scrollView.contentSize = image.size;

        });
    }); 
}

队列

CGD的队列可以分为两大类型
串行队列:

  1. 让任务一个接着一个有序的执行:不管队列里面放的是什么任务,一个任务执行完毕后,再执行下一个任务
  2. 同时只能调度一个任务

并行队列:

  1. 可以让多个任务 并发/同时 执行,自动开启多个线程同时执行多个任务
  2. 同时可以调度多个任务执行
  3. 并发队列的并发功能只有内部的任务是异步任务时,才有效

任务

CGD中有2个用来执行任务的函数
同步的方式执行任务:在当前线程中依次执行任务
异步的方式执行任务:新开线程,在新线程中执行任务

GCD串行队列

明确无论串行队列里面添加什么任务都会按序执行
特点
以先进先出的方式,顺序调度队列中的任务执行
无论队列中所指定的执行函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务

@interface ViewController ()

@end
/*
串行队列:调度任务是有序的,必须前面一个任务执行完,再调度后面的任务;先进先出
同步任务:在当前线程,有序执行
异步任务:新开线程,在新线程执行
*/
@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   // Do any additional setup after loading the view, typically from a nib.
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   [self GCDDemo2];
}


#pragma mark -串行队列+异步任务

/**
开线程:只开一个,一个就够了
是否有序:有序
*/
-(void)GCDDemo2{
    dispatch_queue_t queue = dispatch_queue_create("szios07", DISPATCH_QUEUE_SERIAL);
   for (int i= 0; i< 10; i++) {
       dispatch_async(queue, ^{
           NSLog(@"%zd - %@",i,[NSThread currentThread]);
       });
   }
}
#pragma mark -串行队列+同步任务

/**
是否开线程?不开
*/
-(void)GCDDemo1{
   //1创建串行队列
   //参数1:表示队列的标识符
   //参数2:队列的属性(决定队列是串行的还是并行的) DISPATCH_QUEUE_SERIAL :串行队列
   dispatch_queue_t queue = dispatch_queue_create("szios07", DISPATCH_QUEUE_SERIAL);
   //循环向队列里面添加10个同步任务
   for (int i= 0; i< 10; i++) {
       //把同步任务添加到穿行队列
       dispatch_sync(queue, ^{
           NSLog(@"%zd - %@",i,[NSThread currentThread]);
       });
   }
}


- (void)didReceiveMemoryWarning {
   [super didReceiveMemoryWarning];
   // Dispose of any resources that can be recreated.
}


@end

GCD并行队列

明确并发队列想一次同时调度多个任务执行,那么任务必须是异步任务

#import "ViewController.h"
/*
 并行队列:可以同时调度多个任务执行,先进先出
 同步:在当前线程,有序执行
 异步任务:新开线程,在新线程执行
 
 总结:队列仅仅决定了任务的调度方式,无法决定开不开线程
 提示:任务怎么执行,是由队列和任务同时决定的
 */
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self GCDDemo2];
}
#pragma mark - 并行队列添加异步任务

/**
 开线程:
 无序:
 */
-(void)GCDDemo2{
    //创建并行队列
    //DISPATCH_QUEUE_CONCURRENT : 表示并行队列
    dispatch_queue_t queue = dispatch_queue_create("ios07", DISPATCH_QUEUE_CONCURRENT);
    //将异步任务添加到并行队列
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%zd - %@",i,[NSThread currentThread]);
        });
    }
}

#pragma mark - 并行队列添加同步任务

/**
 是否开线程:不开
 有序
 */
-(void)GCDDemo1{
    //创建并行队列
    //DISPATCH_QUEUE_CONCURRENT : 表示并行队列
    dispatch_queue_t queue = dispatch_queue_create("ios07", DISPATCH_QUEUE_CONCURRENT);
    //将同步任务添加到并行队列
     for (int i = 0; i < 10; i++) {
         dispatch_sync(queue, ^{
            NSLog(@"%zd - %@",i,[NSThread currentThread]);
        });
     }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

GCD主队列

#import "ViewController.h"

@interface ViewController ()

@end
/*
 主队列:
 程序一起动就会自动创建主队列,所以只需要get,不需要create
 主队列是特殊的串行队列,主队列里面无论是什么任务都是有序执行的
 主队列是专门在主线程上调度任务执行的,主队列里面的任务"一定"是在主线程执行的
 小结:主队列里面,无论添加什么任务,都是在主线程有序执行的
 提示:主队列是主队列,主线程是主线程
 注意:队列和线程的关系,队列是调度任务的,线程是执行任务的
 "主队列调度任务执行必须满足一个条件,就是只有主线程空闲时,主队列才会调度任务在主线程执行
 主队列里面必须添加异步任务
 
 */
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self GCDDemo2];
}
#pragma mark - 主队列+同步任务
-(void)GCDDemo2{
    //获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    //把同步任务添加到主队列:不被允许的指令Xcode 8 报错 Xcode 7 死锁
    dispatch_sync(mainQueue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

#pragma mark - 主队列+异步任务

/**
 不开线程,只在主线程有序执行
 */
-(void)GCDDemo1{
    //获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    //把异步任务添加到主队列
    //异步任务:可以不执行完,就让后面的代码先执行
    dispatch_async(mainQueue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

GCD全局队列(并行)

知道全局队列两个参数代表什么意思

-(void)GCDDemo{
    //并行队列
    //参数1:队列的标识符
    //参数2:队列的属性,决定了队列是串行还是并行的
    dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
    //全局队列(并行)
    //提示:程序启动会默认创建好,直接get,执行的效果是跟并行队列是一模一样的
    //全局队列的作用:方便程序员尽快的实现程序的异步执行的
    //参数2:苹果"目前"不知道这个参数是干什么用的,预留参数
    //参数1:表示队列的服务质量 / 队列的优先级,开发中,不要修改,就选择默认的(0)
    //服务质量和优先级是一一对应的,用户相关-->默认-->工具-->后台
    dispatch_queue_t global_queue = dispatch_get_global_queue(0, 0);
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容