在使用闭包时防止时序问题

在使用闭包时防止时序问题

了解对闭包的不同 API 调用可能对您的 app 造成怎样的影响。

 

概览

您在 Swift 中使用的许多 API 都将闭包 (即以实例形式传递的函数) 视为参数。由于闭包可能包含与 app 中多个部分交互的代码,因此您务必要了解闭包所传递到的 API 调用闭包的不同方式。您传递给 API 的闭包可被同步 (立即) 或异步 (一段时间之后) 调用,并可能被调用一次或多次,也可能永不被调用。

重要信息

如果对何时调用闭包做出错误的假设,可能会造成数据不一致和 app 崩溃。

 

了解同步和异步调用的结果

在将闭包传递给 API 时,您需要考虑相对于代码中其他代码调用闭包的时间。在同步 API 中,闭包的调用结果会在您传递闭包后立即可用。在异步 API 中,该结果需要等待一段时间后才会可用;这一差别影响闭包“内”代码和闭包“后”代码的编写方式。

以下示例定义了两个函数:now(_:)later(_:)。您可以用相同的方式调用这两个函数:利用后置闭包而不使用其他参数。now(_:)later(_:) 都接受闭包并调用它,但 later(_:) 则会等几秒才调用其闭包。

import Dispatch
let queue = DispatchQueue(label: "com.example.queue")

func now(_ closure: () -> Void) {
    closure()
}

func later(_ closure: @escaping () -> Void) {
    queue.asyncAfter(deadline: .now() + 2) {
        closure()
    }
}

now(_:)later(_:) 函数代表着您在 app 框架内接受闭包的方法中最常遇到的两种 API 类别:类似于 now(_:) 的同步 API,和类似于 later(_:) 的异步 API。

由于调用闭包可能会改变 app 的局部和全局状态,因此在编写传递闭包后的代码时需要仔细考量闭包被调用的时间。即便是打印一组字母这样简单的任务,也可能会受到闭包调用时机的影响:

later {
    print("A") // Eventually prints "A"
}
print("B") // Immediately prints "B"

now {
    print("C") // Immediately prints "C"
}
print("D") // Immedately prints "D"

// Prevent the program from exiting immediately if you're running this code in Terminal.
let semaphore = DispatchSemaphore(value: 0).wait(timeout: .now() + 10)

运行上例中的代码通常会按照以下顺序打印字母:BCDA。尽管打印 A 的那一行位于代码的最前面,但在输出中排在后面。出现顺序差异的原因在于 now(_:)later(_:) 函数的定义方式。如果您编写的代码依赖于特定的执行顺序,您需要知道每个函数调用其闭包的方式。

注释

A 相对于其他字母的打印顺序并不是一成不变的。在典型的系统条件下,它通常会在最后打印,但是当编写的代码依赖于异步调用相对于同步代码的顺序时,务必要更加小心地实现线程间同步。

在使用接受闭包的 API 时,您经常需要考虑这种基于时间的执行问题。在许多情形中,只有一种调用顺序对您的 app 来说是正确的,因此务必要根据您所使用的 API,仔细思考您的 app 将处于的状态。您可以结合使用 API 名称、参数名称以及相关的文档来判断 API 是同步还是异步的。

一个常见的时序错误是预计异步调用的结果可在同步调用代码中使用。例如,上文中的 later(_:) 方法与 URLSession (英文) 类的 dataTask(with:completionHandler:) (英文) 方法相当,后者也是异步的。您应当避免下面这样的时序场景:在 app 的 viewDidLoad() (英文) 方法中调用 dataTask(with:completionHandler:) 方法,并试图在作为完成处理程序传递的闭包外面使用其结果。

 

在会被多次调用的闭包中,不要编写代码来进行一次性更改

如果您要将一个闭包传递给可能要多次调用它的 API,请去掉旨在对外部状态进行一次性更改的代码。

以下示例会创建一个 FileHandle (英文) 以及一组要写入该句柄所引用的文件的数据行:

import Foundation

let file = FileHandle(forWritingAtPath: "/dev/null")!
let lines = ["x,y", "1,1", "2,4", "3,9", "4,16"]

要将每一行写入文件,应将闭包传递给 forEach(_:) (英文) 方法:

lines.forEach { line in
    file.write("\(line)\n".data(using: .utf8)!)
}

FileHandle 使用完毕后,应使用 closeFile() (英文) 来结束它。调用 closeFile() 的正确位置是在闭包外面:

lines.forEach { line in
    file.write("\(line)\n".data(using: .utf8)!)
}

file.closeFile()

如果您误解了 closeFile() (英文) 的要求,可能会将调用放在闭包内。这样会导致您的 app 崩溃:

lines.forEach { line in
    file.write("\(line)\n".data(using: .utf8)!)
    file.closeFile() // Error
}

 

不要将重要代码放在可能不会被调用的闭包内

如果传递给 API 的闭包有可能不会被调用,请不要将对 app 继续运行至关重要的代码放在该闭包内。

以下示例定义了一个 Lottery 枚举,该枚举会随机挑选一个中奖号码并在正确数字被猜中时调用完成处理程序:

enum Lottery {
    static var lotteryWinHandler: (() -> Void)?
    
    @discardableResult static func pickWinner(guess: Int) {
        print("Running the lottery.")
        if guess == Int.random(in: 0 ..< 100_000_000), let winHandler = lotteryWinHandler {
            winHandler()
            return true
        }
        
        return false
    }
}

编写的代码不应依赖于要调用的完成处理程序,否则会很不可靠。因为随机猜测无法保证一定正确,如果将诸如支付账单等重要行为安排在中奖之后才发生,这些行为可能永远不会发生。

func payBills() {
    amountOwed = 0
}

var amountOwed = 25
let myLuckyNumber = 42

Lottery.lotteryWinHandler = {
    print("Congratulations!")
    payBills()
}

// You get 10 chances at winning.
for _ in 1..10 {
    Lottery.pickWinner(guess: myLuckyNumber)
}

if amountOwed > 0 {
    fatalError("You need to pay your bills before proceeding.")
}

// Code placed below this line runs only if the lottery was won.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作执行额外的逻辑。
GeoPandas是一个开源的Python库,旨在简化地理空间数据的处理和分析。它结合了Pandas和Shapely的能力,为Python用户提供了一个强大而灵活的工具来处理地理空间数据。以下是关于GeoPandas的详细介绍: 一、GeoPandas的基本概念 1. 定义 GeoPandas是建立在Pandas和Shapely之上的一个Python库,用于处理和分析地理空间数据。 它扩展了Pandas的DataFrame和Series数据结构,允许在其中存储和操作地理空间几何图形。 2. 核心数据结构 GeoDataFrame:GeoPandas的核心数据结构,是Pandas DataFrame的扩展。它包含一个或多个列,其中至少一列是几何列(geometry column),用于存储地理空间几何图形(如点、线、多边形等)。 GeoSeries:GeoPandas中的另一个重要数据结构,类似于Pandas的Series,但用于存储几何图形序列。 二、GeoPandas的功能特性 1. 读取和写入多种地理空间数据格式 GeoPandas支持读取和写入多种常见的地理空间数据格式,包括Shapefile、GeoJSON、PostGIS、KML等。这使得用户可以轻松地从各种数据源中加载地理空间数据,并将处理后的数据保存为所需的格式。 2. 地理空间几何图形的创建、编辑和分析 GeoPandas允许用户创建、编辑和分析地理空间几何图形,包括点、线、多边形等。它提供了丰富的空间操作函数,如缓冲区分析、交集、并集、差集等,使得用户可以方便地进行地理空间数据分析。 3. 数据可视化 GeoPandas内置了数据可视化功能,可以绘制地理空间数据的地图。用户可以使用matplotlib等库来进一步定制地图的样式和布局。 4. 空间连接和空间索引 GeoPandas支持空间连接操作,可以将两个GeoDataFrame按照空间关系(如相交、包含等)进行连接。此外,它还支持空间索引,可以提高地理空间数据查询的效率。
### 回答1: 在 Python 中使用闭包有以下几点好处: 1. 保存状态:闭包可以保存外部函数的状态,以便在内部函数中使用。 2. 简化代码:闭包可以简化代码结构,使得复杂的逻辑变得简单易懂。 3. 模块化编程:闭包可以更好地封装代码,提高代码的可重用性。 4. 保证函数线程安全:闭包可以保证函数的线程安全性,避免全局变量被多线程修改。 ### 回答2: 在Python中使用闭包有以下几个好处: 1. 保护数据:闭包可以将函数内部的变量和函数绑定在一起,形成一个封闭的作用域,从而更好地保护数据的隐私性和安全性。外部环境无法直接访问闭包内的变量,只能通过闭包提供的接口来操作数据,从而减少数据被外部误操作的风险。 2. 延长函数的作用域:闭包可以延长函数的作用域,即使函数执行完毕后,其产生的闭包仍然可以访问并使用函数内部的变量。这种特性可以用于保存函数的内部状态,供以后调用使用,从而实现状态的承载和保持。 3. 节约系统资源:闭包可以避免全局变量的使用,减少对全局命名空间的占用,从而节约系统资源的开销。闭包使得函数成为了一个独立的生命周期,不再需要为每个函数都分配独立的命名空间,从而减少了系统内存的使用。 4. 实现高阶函数:闭包使得函数可以作为另一个函数的参数或返回值,从而实现高阶函数的概念。通过传递闭包,可以在不修改原函数的情况下对其进行扩展和增强,使得代码更加灵活和可复用。 总之,闭包是Python语言中一种重要的高级特性,可以提供更好的数据封装和保护,延长函数的作用域,节约系统资源,以及实现高阶函数等功能。通过合理利用闭包,可以提高代码的可读性、可维护性和可扩展性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值