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反向传值
主界面
- 准备等待执行的代码块
- 如果需要接收外界传入的值,需要定义参数
- 向目标控制器传递代码块
#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界面
- 准备属性接收代码块
- 回调代码块
#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的队列可以分为两大类型
串行队列:
- 让任务一个接着一个有序的执行:不管队列里面放的是什么任务,一个任务执行完毕后,再执行下一个任务
- 同时只能调度一个任务
并行队列:
- 可以让多个任务 并发/同时 执行,自动开启多个线程同时执行多个任务
- 同时可以调度多个任务执行
- 并发队列的并发功能只有内部的任务是异步任务时,才有效
任务
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);
}