EOC第六章《块与中枢派发》

news2024/9/22 21:30:16

文章目录

    • 第37条:理解block这一概念
    • 第38条:为常用的块类型创建typedef
    • 第39条:用handler块降低代码分散程度
    • 第41条:多用派发队列,少用同步锁
      • 方案一:使用串行同步队列来将读写操作都安排到同一个队列里:
      • 方案二:将写操作放入栅栏快中,让他们单独执行;将读取操作并发执行。
    • 第42条:多用GCD,少用performSelector系列方法
    • 第43条:掌握GCD及操作队列的使用时机
    • 第44条:通过Dispath Group机制,根据系统资源状况来执行任务
    • 第45条:使用dispatch_once来执行只需运行一次的线程安全代码
    • 第46条:不要使用dispatch_get_current_queue

第37条:理解block这一概念

对于“块”的基础知识就不再赘述了,这里强调一下块的种类。

块(Block)分为三类:

  • 栈块
  • 堆块
  • 全局块
  1. 栈block

定义块的时候,其所占内存区域是分配在栈中的,而且只在定义它的那个范围内有效:

void (^block)();

if ( /* some condition */ ) {
    block = ^{
     NSLog(@"Block A");
    };

} else {
    block = ^{
     NSLog(@"Block B");
    };
}

block();

上面定义的两个块只在if else语句范围内有效,一旦离开了最后一个右括号,如果编译器覆写了分配给块的内存,那么就会造成程序崩溃。

  1. 堆block

为了解决这个问题,我们可以给对象发送copy消息,复制一份到堆里,并自带引用计数:

void (^block)();

if ( /* some condition */ ) {
    block = [^{
         NSLog(@"Block A");
   } copy];
} else {
    block = [^{
         NSLog(@"Block B");
    } copy];
}

block();
  1. 全局block

全局块声明在全局内存里,而不需要在每次用到的时候于栈中创建。

void (^block)() = ^{
     NSLog(@"This is a block");
};

第38条:为常用的块类型创建typedef

如果我们需要重复创建某种块(相同参数,返回值)的变量,我们就可以通过typedef来给某一种块定义属于它自己的新类型

例如:

int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value){
     // Implementation
     return someInt;
}

这个块有一个bool参数和一个int参数,并返回int类型。我们可以给它定义类型:

typedef int(^EOCSomeBlock)(BOOL flag, int value);

再次定义的时候,就可以通过简单的赋值来实现:

EOCSomeBlock block = ^(BOOL flag, int value){
     // Implementation
};

定义作为参数的块:

- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;

这里的块有一个NSData参数,一个NSError参数并没有返回值

typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;

通过typedef定义块签名的好处是:如果要某种块增加参数,那么只修改定义签名的那行代码即可。

第39条:用handler块降低代码分散程度

下载网络数据时,如果使用代理方法,会使得代码分布不紧凑,而且如果有多个下载任务的话,还要在回调的代理中判断当前请求的类型。但是如果使用block的话,就可以让网络下载的代码和回调处理的代码写在一起,这样就可以同时解决上面的两个问题:

用代理下载:

- (void)fetchFooData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
    _fooFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    _fooFetcher.delegate = self;
    [_fooFetcher start];

}

- (void)fetchBarData {

     NSURL *url = [[NSURL alloc] initWithString: @"http://www.example.com/bar.dat"];
    _barFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    _barFetcher.delegate = self;
    [_barFetcher start];

}

- (void)networkFetcher:(EOCNetworkFetcher*)networkFetcher didFinishWithData:(NSData*)data
{   //判断下载器类型
     if (networkFetcher == _fooFetcher) {
        _fetchedFooData = data;
        _fooFetcher = nil;

    } else if (networkFetcher == _barFetcher) {
        _fetchedBarData = data;
        _barFetcher = nil;
    }
}

用block下载:

- (void)fetchFooData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
     EOCNetworkFetcher *fetcher =
     [[EOCNetworkFetcher alloc] initWithURL:url];
     [fetcher startWithCompletionHandler:^(NSData *data){
            _fetchedFooData = data;
   }];

}



- (void)fetchBarData {

     NSURL *url = [[NSURL alloc] initWithString: @"http://www.example.com/bar.dat"];
     EOCNetworkFetcher *fetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
    [fetcher startWithCompletionHandler:^(NSData *data){
            _fetchedBarData = data;
    }];

}

还可以将处理成功的代码放在一个块里,处理失败的代码放在另一个块中:

“#import <Foundation/Foundation.h>

@class EOCNetworkFetcher;
typedef void(^EOCNetworkFetcherCompletionHandler)(NSData *data);
typedef void(^EOCNetworkFetcherErrorHandler)(NSError *error);


@interface EOCNetworkFetcher : NSObject

- (id)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler: (EOCNetworkFetcherCompletionHandler)completion failureHandler: (EOCNetworkFetcherErrorHandler)failure;

@end



EOCNetworkFetcher *fetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHander:^(NSData *data){
     // Handle success
}

 failureHandler:^(NSError *error){
 // Handle failure
}];

这样写的好处是,我们可以将处理成功和失败的代码分开来写,看上去更加清晰。
我们还可以将 成功和失败的代码都放在同一个块里:

“#import <Foundation/Foundation.h>


@class EOCNetworkFetcher;
typedef void(^EOCNetworkFetcherCompletionHandler)(NSData *data, NSError *error);

@interface EOCNetworkFetcher : NSObject

- (id)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler:

(EOCNetworkFetcherCompletionHandler)completion;

@end



EOCNetworkFetcher *fetcher =[[EOCNetworkFetcher alloc] initWithURL:url];

[fetcher startWithCompletionHander:

^(NSData *data, NSError *error){

if (error) {

     // Handle failure

} else {

     // Handle success

}
}];

这样做的好处是,如果及时下载失败或中断了,我们仍然可以取到当前所下载的data。而且,如果在需求上指出:下载成功后得到的数据很少,也视为失败,那么单一块的写法就很适用,因为它可以取得数据后(成功)再判断其是否是下载成功的。
第40条:用块引用其所属对象时不要出现保留环

如果块捕获的对象直接或间接地保留了块本身,那么就需要小心保留环问题:

@implementation EOCClass {

     EOCNetworkFetcher *_networkFetcher;
     NSData *_fetchedData;

}


- (void)downloadData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
    _networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];

    [_networkFetcher startWithCompletionHandler:^(NSData *data){

             NSLog(@"Request URL %@ finished", _networkFetcher.url);
            _fetchedData = data;

    }];

}

在这里出现了保留环:块要设置_fetchedData变量,就需要捕获self变量。而self(EOCClass实例)通过实例变量保留了获取器_networkFetcher,而_networkFetcher又保留了块。

解决方案是:在块中取得了data后,将_networkFetcher设为nil。


- (void)downloadData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
    _networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData *data){

             NSLog(@"Request URL %@ finished", _networkFetcher.url);
            _fetchedData = data;
            _networkFetcher = nil;

    }];

}

第41条:多用派发队列,少用同步锁

多个线程执行同一份代码时,很可能会造成数据不同步。作者建议使用GCD来为代码加锁的方式解决这个问题。

方案一:使用串行同步队列来将读写操作都安排到同一个队列里:

_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);

//读取字符串
- (NSString*)someString {

         __block NSString *localSomeString;
         dispatch_sync(_syncQueue, ^{
            localSomeString = _someString;
        });
         return localSomeString;

}

//设置字符串
- (void)setSomeString:(NSString*)someString {

     dispatch_sync(_syncQueue, ^{
        _someString = someString;
    });
}

这样一来,读写操作都在串行队列进行,就不容易出错。

但是,还有一种方法可以让性能更高:

方案二:将写操作放入栅栏快中,让他们单独执行;将读取操作并发执行。

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//读取字符串
- (NSString*)someString {

     __block NSString *localSomeString;
     dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
     return localSomeString;
}
//设置字符串
- (void)setSomeString:(NSString*)someString {

     dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });

}

显然,数据的正确性主要取决于写入操作,那么只要保证写入时,线程是安全的,那么即便读取操作是并发的,也可以保证数据是同步的。

这里的dispatch_barrier_async方法使得操作放在了同步队列里“有序进行”,保证了写入操作的任务是在串行队列里。

第42条:多用GCD,少用performSelector系列方法

在iOS开发中,有时会使用performSelector来执行某个方法,但是performSelector系列的方法能处理的选择子很局限:

它无法处理带有多个参数的选择子。
返回值只能是void或者对象类型。
但是如果将方法放在块中,通过GCD来操作就能很好地解决这些问题。尤其是我们如果想要让一个任务在另一个线程上执行,最好应该将任务放到块里,交给GCD来实现,而不是通过performSelector方法。

举几个 来比较这两种方案:

  1. 延后执行某个任务的方法:
// 使用 performSelector:withObject:afterDelay:
[self performSelector:@selector(doSomething) withObject:nil afterDelay:5.0];


// 使用 dispatch_after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){
    [self doSomething];
});
  1. 将任务放在主线程执行:
// 使用 performSelectorOnMainThread:withObject:waitUntilDone:
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];


// 使用 dispatch_async
// (or if waitUntilDone is YES, then dispatch_sync)
dispatch_async(dispatch_get_main_queue(), ^{
        [self doSomething];
});

注意:
如果waitUntilDone的参数是Yes,那么就对应GCD的dispatch_sync方法。
我们可以看到,使用GCD的方式可以将线程操作代码和方法调用代码写在同一处,一目了然;而且完全不受调用方法的选择子和方法参数个数的限制。

第43条:掌握GCD及操作队列的使用时机

除了GCD,操作队列(NSOperationQueue)也是解决多线程任务管理问题的一个方案。对于不同的环境,我们要采取不同的策略来解决问题:有时候使用GCD好些,有时则是使用操作队列更加合理。

使用NSOperation和NSOperationQueue的优点:

可以取消操作:在运行任务前,可以在NSOperation对象调用cancel方法,标明此任务不需要执行。但是GCD队列是无法取消的,因为它遵循“安排好之后就不管了(fire and forget)”的原则。
可以指定操作间的依赖关系:例如从服务器下载并处理文件的动作可以用操作来表示。而在处理其他文件之前必须先下载“清单文件”。而后续的下载工作,都要依赖于先下载的清单文件这一操作。
监控NSOperation对象的属性:可以通过KVO来监听NSOperation的属性:可以通过isCancelled属性来判断任务是否已取消;通过isFinished属性来判断任务是否已经完成。
可以指定操作的优先级:操作的优先级表示此操作与队列中其他操作之间的优先关系,我们可以指定它。

第44条:通过Dispath Group机制,根据系统资源状况来执行任务

有时需要等待多个并行任务结束的那一刻执行某个任务,这个时候就可以使用dispath group函数来实现这个需求:

通过dispath group函数,可以把并发执行的多个任务合为一组,于是调用者就可以知道这些任务何时才能全部执行完毕。

//一个优先级低的并发队列
dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

//一个优先级高的并发队列
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

//创建dispatch_group
dispatch_group_t dispatchGroup = dispatch_group_create();

//将优先级低的队列放入dispatch_group
for (id object in lowPriorityObjects) {
 dispatch_group_async(dispatchGroup,lowPriorityQueue,^{ [object performTask]; });
}

//将优先级高的队列放入dispatch_group
for (id object in highPriorityObjects) {
 dispatch_group_async(dispatchGroup,highPriorityQueue,^{ [object performTask]; });
}

//dispatch_group里的任务都结束后调用块中的代码
dispatch_queue_t notifyQueue = dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup,notifyQueue,^{
     // Continue processing after completing tasks
});

第45条:使用dispatch_once来执行只需运行一次的线程安全代码

有时我们可能只需要将某段代码执行一次,这时可以通过dispatch_once函数来解决。

dispatch_once函数比较重要的使用例子是单例模式:
我们在创建单例模式的实例时,可以使用dispatch_once函数来令初始化代码只执行一次,并且内部是线程安全的。

而且,对于执行一次的block来说,每次调用函数时传入的标记都必须完全相同,通常标记变量声明在static或global作用域里。

  • (id)sharedInstance {

    static EOCClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
     sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
    }
    我们可以这么理解:在dispatch_once块中的代码在程序启动到终止的过程里,只要运行了一次后,就给自己加上了注释符号,不再存在了。

第46条:不要使用dispatch_get_current_queue

我们无法用某个队列来描述“当前队列”这一属性,因为派发队列是按照层级来组织的。

那么什么是队列的层级呢?
在这里插入图片描述

安排在某条队列中的快,会在其上层队列中执行,而层级地位最高的那个队列总是全局并发队列。

在这里,B,C中的块会在A里执行。但是D中的块,可能与A里的块并行,因为A和D的目标队列是并发队列。

正因为有了这种层级关系,所以检查当前队列是并发的还是非并发的就不会总是很准确。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/341500.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

02 OpenCV图像通道处理

1 通道提取与合并 在数字图像处理中&#xff0c;图像通道是指一个图像中的颜色信息被分离为不同的颜色分量。常见的图像通道包括RGB通道、灰度通道、HSV通道等。 RGB通道是指将图像分离为红色、绿色和蓝色三个颜色通道&#xff0c;每个通道表示相应颜色的亮度。这种方式是最常…

【QT 5 相关实验-仪表盘-学习笔记-表盘组件练习与使用总结】

【QT 5 相关实验-仪表盘-学习笔记-表盘组件练习与使用总结】1、概述2、实验环境3、参考资料-致谢4、自我提升实验效果5、代码练习-学习后拆解&#xff08;1&#xff09;头文件部分&#xff08;2&#xff09;绘制事件绘制表盘代码&#xff08;3) 每一块部分绘制6、代码移植提升类…

Spring Security in Action 第十一章 SpringSecurity前后端分离实战

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;以实战为线索&#xff0c;逐步深入SpringSecurity相关知识相关知识&#xff0c;打造完整的SpringSecurity学习步骤&#xff0c;提升工程化编码能力和思维能力&#xff0c;写出高质量代码。希望大家都能够从中有所收获&#…

nginx正向代理的配置和使用

nginx正向代理的配置和使用 nginx正向代理的配置和使用nginx正向代理的配置和使用安装包准备下载nginx安装包下载正向代理模块的包版本与模块对照表部署nginx服务上传nginx包和正向模块包解压,改名安装nginx配置正向代理创建nginx用户检查nginx配置并启动nginx服务所在服务器验…

微服务02 Docker

Docker实用篇0.学习目标1.初识Docker1.1.什么是Docker微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同组件之间部署时往往会产生一些冲突。在数百上千台服务中重复部署&#xff0…

实战绕过WTS-WAF的SQL注入

实战绕过WTS-WAF的SQL注入1.前言2.测试流程2.1.发现漏洞2.1.1.正常页面2.1.2.WAF警告2.1.3.非正常页面2.2.判断字段数2.2.1.非正常页面2.2.2.正常页面2.3.判断回显位2.4.信息收集2.4.1.数据库版本2.4.2.数据库名2.5.判断数据库表2.5.1.WAF告警2.5.2.获取表2.5.3.burp suite测试…

龙曲良 Tensorflow —— tensorflow高级操作(自用)

目录 一、合并与分割 1.1 tf.concat (合并) 1.2 tf.stack &#xff08;增加新维度&#xff09; 1.3 tf.unstack &#xff08;一个一个拆分&#xff09; 1.4 tf.split &#xff08;均分拆分&#xff09; 二、数据统计 2.1 tf.norm&#xff08;默认二范数&#xff09; 2…

WebRTC(一):三种架构和基本原理

文章目录一、三种架构二、为什么SFU最为常用&#xff1f;一、三种架构 webrtc大致可以分为三种架构&#xff1a; MESH mesh架构需要所有参与连接的peer简历和所有其他peer的媒体的连接&#xff0c;如图一。 该架构需要n-1个上下行&#xff0c;以此带来的带宽消耗&#xff08…

家政服务小程序实战教程02-创建模型应用

我们在上一篇中介绍了数据源的设计及创建方法&#xff0c;本篇我们就根据我们创建好的数据源来设计功能。 按我们的需求分析&#xff0c;系统管理员来审核数据&#xff0c;要想审核数据需要给管理员提供一个管理后台。微搭中的管理后台是通过模型应用来解决的。 登录控制台&a…

我用python/C++调用ChatGPT自制了一个聊天机器人

目录1 ChatGPT完整版2 Python/C调用ChatGPT2.1 获取API秘钥2.2 测试API功能2.3 设计简单UI3 聊天问答1 ChatGPT完整版 2015年&#xff0c;OpenAI由马斯克、美国创业孵化器Y Combinator总裁阿尔特曼、全球在线支付平台PayPal联合创始人彼得蒂尔等硅谷科技大亨创立&#xff0c;公…

chatGPT都可以干什么呢?来一睹风采 (送账号)

文章目录1. 写代码2. 写文案3. 写剧本4. 写歌诗5. 写报告6. 查公式7. 写对联8. 写文章9. 做表格10. 做计划11. 等等1. 写代码 2. 写文案 3. 写剧本 4. 写歌诗 5. 写报告 这妥妥的翻译文&#xff0c;数据完全不对。 6. 查公式 傅里叶变换的时域性质有如下几点&#xff1a; 对…

JAVA集合专题4 —— Map

目录Map接口实现类的特点Map接口的常见方法Map六大遍历方式Map练习1code编程练习2code编程练习3思路codeMap接口实现类的特点 Map与Collection并列存在&#xff0c;是Map集合体系的顶级接口Map的有些子实现存储数据是有序的(LinkedHashMap)&#xff0c;有些子实现存储数据是无…

【量化交易】 量化因子 动量类因子

量化因子 - 风险类因子计算 5日乖离率 BIAS5 &#xff08;收盘价-收盘价的N日简单平均&#xff09;/ 收盘价的N日简单平均*100&#xff0c;在此n取5 60日变动速率&#xff08;Price Rate of Change&#xff09; ROC60 ①AX今天的收盘价—20天前的收盘价 ②BX60天前的收盘价 ③…

React-Router

版本5安装&#xff1a;npmnpm install react-router-dom5 -Syarnyarn add react-router-dom5import ReactDOM from "react-dom/client";// react router适用于web和原生项目&#xff0c;我们在web项目中使用&#xff0c;所以需要引入的包是react-router-dom import {…

Idea 安装 Sonar 插件提升代码质量

目录 0. 环境说明 1. Sonar 简介 2. IDEA 配置 Sonar 0. 环境说明 Java 1.8IDEA 2022.3.1SonarLint 7.4.0 1. Sonar 简介 在多人协通的软件开发过程中&#xff0c;代码风格和代码质量对于软件的整体交付是十分关键的。这时我们可以利用 Sonar 插件&#xff0c;对代码进行扫描…

Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上

Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上引言RequestMappingHandlerAdapter方法参数解析器方法参数名解析器类型转换体系简单的使用演示数据绑定器工厂定制化修改DataBinder获取泛型参数ControllerAdvice与InitBinder注解控制器方法执行流程Controll…

C语言(可变参数:stdarg.h)

目录 一.使用可变参数 1.提供一个使用省略号的函数原型 2.在函数定义中创建一个va_list类型的变量 3.用宏把该变量初始化为一个参数列表 4.用宏访问参数列表 5.使用宏完成清理工作 二.va_copy 三.实例 一.使用可变参数 该头文件提供类似接受可变数量参数的宏&#xf…

【招聘】永善县正向社会工作服务中心招聘2名工作人员

永善县正向社会工作服务中心面向社会公开招聘2名工作人员 永善县正向社会工作服务中心是2019年9月在永善县民政局注册登记成立的民办非企业单位。业务范围主要为&#xff1a;面向社会提供各类专业社工服务&#xff0c;开展活动策划、个案援助等社会服务项目&#xff1b;承接个人…

基于WEB的小型公司人事管理系统设计

技术&#xff1a;Java、JSP等摘要&#xff1a;随着当代各类企业公司员工数量越来越多、分工越来越细化、各行各业之间的联系越发密切&#xff0c;对人事管理的要求也不断提高。实现企业人事管理计算机化&#xff0c;毫无疑问会让企业的人事管理变得更加高效化和智能化。企业要生…

c/c++开发,无可避免的函数参数实践

一、函数参数表 函数由函数名以及一组操作数类型唯一地表示。函数的操作数&#xff0c;也即形参&#xff0c;在一对圆括号中声明&#xff0c;形参与形参之间以逗号分隔。每一个函数都有一个相关联的返回类型。 int setval(int val) {//函数体 }; 这里&#xff0c;定义了一个名…