Swift 抛砖引玉:从数组访问越界想到的“可抛出错误”属性

在这里插入图片描述

0. 概览

了解 Swift 语言的小伙伴们都知道,我们可以很方便的写一个可能抛出错误的方法。不过大家可能不知道的是在 Swift 中对于结构或类的实例属性我们也可以让它抛出错误。

这称之为实效只读属性(Effectful Read-only Properties)。

那么,这种属性怎么创建?并且到底有什么用处呢?

在本篇博文中,您将学到如下内容

  • 0. 概览
  • 1. 什么是“实效只读属性”
  • 2. 怎么创建“实效只读属性”?
  • 3. 数组访问越界是个头疼的问题
  • 4. 拯救者:抛出错误的“实效只读属性”
  • 5. 更进一步
  • 6. 八卦一下:ruby 中更优雅的实现
  • 总结

相信看完文本后,小伙伴们的武器库中又会多了一件“杀手锏”!

那还等什么呢?Let‘s go!!!😉


1. 什么是“实效只读属性”

“实效只读属性” 英文名称为 Effectful Read-only Properties,它是 Swift 5.5+ 中对计算属性和下标操作(computed properties and subscripts)的增强功能。

在 Swift 5.5 之前,我们只能创建异步或可抛出错误的方法(或函数),而无法构建与此类似的实例属性。

对于有些情况,一个“异步”属性可以帮上大忙!

actor AccountManager {
  // 注意: `getLastTransaction` 方法若在 AccountManager 外部调用将会“升级”为一个异步方法
  func getLastTransaction() -> Transaction { /* ... */ }
  func getTransactions(onDay: Date) async -> [Transaction] { /* ... */ }
}

class BankAccount {
  
  private let manager: AccountManager?
  var lastTransaction: Transaction {
    get {
      guard let manager = manager else {
         throw BankError.NoManager
      // ^~~~~ 错误: 普通计算属性中不能抛出错误!
      }
      return await manager.getLastTransaction()
      //     ^~~~~ 错误: 普通计算属性中不能调用异步方法
    }
  }
}

如上代码所示:在 BankAccount 类的 lastTransaction 实例属性访问过程中可能会抛出错误,并且需要等待返回一个异步方法的结果。这对于以往的实例属性来说是“不可能的任务”!

诚然,我们可以将 lastTransaction 实例属性变为一个方法:

class BankAccount {
  private let manager: AccountManager?
  //var lastTransaction: Transaction {}

  func getLastTransaction() async throws -> Transaction {
    guard let manager = manager else {
         throw BankError.NoManager
      }
      return await manager.getLastTransaction()
  }
}

但这显然有点“画蛇添足”的意味。

幸运的是, 倾听到了秃头码农们的殷切呼唤,从 Swift 5.5 开始我们便有了上面的“实效只读属性”。


想进一步了解“实效只读属性”的小伙伴们可以到 Swift 语言进化提案(swift-evolution proposals)中观赏更详细的内容:

  • SE-0310:Effectful Read-only Properties

2. 怎么创建“实效只读属性”?

从 Swift 5.5+ 开始,我们可以在实例属性的只读访问器(get)上应用 async 或 throws 关键字(效果说明符):

class BankAccount {
  // ...
  var lastTransaction: Transaction {
    get async throws {   // <-- Swift 5.5+: 效果说明符(effects specifiers)!
      guard manager != nil else {
        throw BankError.notInYourFavor
      }
      return await manager!.getLastTransaction()
    }
  }

  subscript(_ day: Date) -> [Transaction] {
    get async { // <-- Swift 5.5+: 与上面类似,我们也可以在下标的读操作上应用效果说明符。
      return await manager?.getTransactions(onDay: day) ?? []
    }
  }
}

如上代码所示,我们不但可以在实例属性上应用 async 和 throws 效果说明符(effects specifiers),同样也可以在类或结构下标操作的读访问器上使用它们。

现在,我们可以这样访问 BackAccount#lastTransaction 实例属性和下标操作:

extension BankAccount {
  func meetsTransactionLimit(_ limit: Amount) async -> Bool {
    return try! await self.lastTransaction.amount < limit
    //                    ^~~~~~~~~~~~~~~~
    //                    对该实例属性的访问是异步且可能抛出错误的!
  }                
}

func hadWithdrawlOn(_ day: Date, from acct: BankAccount) async -> Bool {
  return await !acct[day].allSatisfy { $0.amount >= Amount.zero }
  //            ^~~~~~~~~
  //            同样的,下标读操作也是异步的
}

3. 数组访问越界是个头疼的问题

秃头码农们都知道,在 Swift 中对于数组访问常常出现下标越界的情况。它会引起程序立即崩溃!

我们时常会想:如果在数组访问越界时抛出一个可捕获的错误就好了!

在过去,我们可以写一个新的“下标访问”方法来模拟这一“良好愿望”:

enum Error: Swift.Error {
    case outOfRange
}

extension Array where Element: Equatable {
    func getElemenet(at: Array.Index) throws -> Element {
        guard at < endIndex else {
            throw Error.outOfRange
        }
        
        return self[at]
    }
}

do {
    let ary = Array(1...100)
    _ = try ary.getElemenet(at: 10000)
} catch let error as Error {
    print("ERR: \(error.localizedDescription)")
}

但这种 .getElemenet(at:) 的“丑陋”写法真是让人“是可忍孰不可忍”!

不过,从 Swift 5.5 一切开始变得不同了。

4. 拯救者:抛出错误的“实效只读属性”

看到这,聪明的小伙伴们应该早就知道如何应对了。

我们可以使用 Swift 5.5 中的“实效只读属性”来“完美的”完成任务:

enum Error: Swift.Error {
    case outOfRange
}

extension Array where Element: Equatable {
    subscript(index: Array.Index) -> Element {
        get throws {
            guard index < endIndex else {
                throw Error.outOfRange
            }
            
            var temp = self
            temp.swapAt(0, index)
            return temp.first!
        }
    }
}

do {
    let ary = Array(1...100)
    _ = try ary[10000]
} catch let error as Error {
    print("ERR: \(error.localizedDescription)")
}

如上代码所示:我们使用可抛出错误的下标读访问器为 Array 下标操作“添妆加彩”。略微遗憾的是,我们需要在数组新下标操作中调用原来的下标操作,这对于结构(struct)类型的 Array 来说好似“难于上青天”,所以我们采用的是迂回战术。


对于类支持的类型来说,我们可以使用 Objc 存在的 Swizz 技术来得偿所愿。


在文章最后,我们将会看到同样问题在 ruby 语言中实现的是何其优雅。

5. 更进一步

在数组的下标访问中抛出错误还不算完,我们还可以利用 Swift 枚举的关联类型为错误添加进一步的信息。


想要了解更多 Swift 枚举的小秘密,请小伙伴们移步如下文章观赏:

  • Swift 和 Python 两种语言中带关联信息错误(异常)类型的比较

更多 Swift 语言知识的系统介绍,请移步我的专栏文章进一步观看:

  • Swift 语言开发精讲(文章平均质量分 97)

下面,我们为之前的越界错误增加关联类型,分别表示当前越界的索引和数组总长度:

enum Error: Swift.Error {
    case outOfRange(accessing: Int, end: Int)
}

接着,修改抛出错误处的代码:

subscript(index: Array.Index) -> Element {
    get throws {
        guard index < endIndex else {
            throw Error.outOfRange(accessing: index, end: count)
        }
        
        var temp = self
        temp.swapAt(0, index)
        return temp.first!
    }
}

最后,是错误捕获时的代码:

do {
    let ary = Array(1...100)
    _ = try ary[10000]
} catch let error as Error {
    if case Error.outOfRange(let accessing, let end) = error {
        print("ERR: 数组访问越界[试图访问:\(accessing),数组末尾:\(end)]")
    }
}

现在,当发生越界错误时我们可以清楚的知道事情的来龙去脉了,是不是了很赞呢:

在这里插入图片描述

6. 八卦一下:ruby 中更优雅的实现

上面我们提到过 Swift 结构类型的方法“重载”(结构没有重载之说,这里只是比喻)无法再使用“重载”前的方法了。

但是在某些动态语言中,我们可以非常方便的使用类似于“钩子”机制来访问旧方法,比如 ruby 里:

#!/usr/bin/ruby

class Array
    alias :subscript :[]

    def [](index)
        puts "试图访问索引:#{index}"
        subscript(index)
    end
end

a = [1,2,3]
puts a[1]

如上所示,我们使用别名(alias)机制将原下标操作方法 :[] 用 :subscript 名称进行“缓存”,然后在新的 :[] 方法中我们可以直接调用旧方法。

运行结果如下所示:

试图访问索引:1
2

Swift 什么时候有这种“神奇”的能力呢?让我们翘首以盼!

总结

在本篇博文中,我们讨论了 Swift 5.5 中新增的“实效只读属性”(Effectful Read-only Properties),它有哪些用途?怎么用它来解决 Swift 数组访问越界的“老问题”?最后,我们用 ruby 代码举了一个更优雅的实现。

感谢观赏,再会!😎

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

速锐得柴油发动机车辆数据的实时获取定位和运行状态监测设计思路

随着港口、油田、车队运输、物流及冷链等多种交通运输领域的兴起&#xff0c;保障性集团运输业务在这些领域凸显出重要的作用&#xff0c;数字化转型及平台系统性管理要求越来越高&#xff0c;针对柴油发动机车辆数据的实时获取定位和运行状态的检测方案配套平台系统&#xff0…

DSP开发例程(4): logbuf_print_to_uart

目录 DSP开发例程: logbuf_print_to_uart新建工程源码编辑app.cfgos.cmain.c 调试说明 DSP开发例程: logbuf_print_to_uart SYS/BIOS 提供了 xdc.runtime.Log, xdc.runtime.LoggerBuf 和 xdc.runtime.LoggerSys 这几个模块用于日志记录. 日志信息在 应用程序调试和状态监控中非…

用友NC Cloud accept.jsp接口任意文件上传漏洞复现 [附POC]

文章目录 用友NC Cloud accept.jsp接口任意文件上传漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 用友NC Cloud accept.jsp接口任意文件上传漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a…

万宾科技内涝积水监测仪使用效果一览

当一个城市突降暴雨&#xff0c;对城市管理部门来讲首当其中的是防止积水成患。随着城市人口快速增长&#xff0c;基础设施建设也日益受到更多的关注&#xff0c;城市内涝问题频繁增加&#xff0c;会给城市带来严重的经济损失和人员的安全问题。城市生命线工程建设过程中&#…

【EI会议征稿】第八届先进能源科学与自动化国际研讨会(AESA 2024)

第八届先进能源科学与自动化国际研讨会&#xff08;AESA 2024) 2024 8th International Workshop on Advances in Energy Science and Automation 继AESA 2017-2023相继成功举办之后&#xff0c;来自国内外多所高校、科研院所及企业代表在先进能源科学与自动化的科研合作和交流…

(附源码)基于spring boot 房屋租赁系统小程序-计算机毕设 88306

spring boot 房屋租赁系统小程序 目 录 摘要 1 绪论 1.1选题意义 1.2开发现状 1.3springboot框架介绍 1.4论文结构与章节安排 2 房屋租赁系统小程序系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.…

VINS-Mono-后端优化 (三:视觉雅可比推导)

用逆深度是因为这样可以在优化中从优化3个变量降低到1个&#xff0c;降低优化的维度加快求解速度 用逆深度是因为当距离很远的时候&#xff0c; 1 x \frac{1}{x} x1​ x x x 就会无穷大&#xff0c;而3D点很近的情况也一般不会有&#xff0c;这也是为了数值稳定性 用逆深度的…

SAP 20策略测试简介

20策略相信也有很多小伙伴使用过,与50最大的不同之处就在于20策略是不能做计划独立需求的。 我看一下系统中20 策略的配置图,可以看到独立需求这里的配置都是空的。 1、我们开始测试准备物料 成品物料AB4 原材料:100197 2、创建BOM—CS01 3、创建主配方—c201 ,离散制造…

创建第一个Go的程序Hello Kitty

上一篇&#xff0c;我们已经搭建好了开发要用的基础环境:Go开发基础环境搭建, 今天我们要开始用GoLand实操关于Go的代码开发。 创建工程 File > New > Project 其中 game为项目名称 在项目目录下会自动生成一个文件:go.mod ,模块是相关Go包的集合。modules是源代码交换…

R | R包安装报错-github连接速度慢或无法访问 | metaboanalystR | Retip | rJava安装

R | R包安装报错-github连接速度慢或无法访问 | metaboanalystR | Retip | rJava安装 一、metaboanalystR 安装1.1 Bioconductor报错&#xff0c;无网络连接1.2 github520-修改hosts文件 二、retip安装2.1 rJava包加载报错及安装2.2 安装Retip包 三、从Bioconductor安装Rdisop报…

web3 前端dapp从redux过滤出 (我创建与别人创建)正在执行的订单 并展示在Table上

上文 web3 从redux中拿出所有已完成订单 并渲染到对应的Table列表中 我们从redux中 取出并渲染了 已完成的订单 那么 我们继续 万里长征 就快看到尽头了呀 我们先起一下环境 ganache 终端输入 ganache -d然后 登一下 MetaMask 然后 打开我们的项目 发布一下合约 truffle mig…

GZ038 物联网应用开发赛题第4套

2023年全国职业院校技能大赛 高职组 物联网应用开发 任 务 书 &#xff08;第4套卷&#xff09; 工位号&#xff1a;______________ 第一部分 竞赛须知 一、竞赛要求 1、正确使用工具&#xff0c;操作安全规范&#xff1b; 2、竞赛过程中如有异议&#xff0c;可向现场考评…

SAP 50策略测试简介

上篇博文写了40策略的测试,40策略就是典型的按库存生产,考虑库存,考虑销售订单。 本文将测试50策略,按单生产用的最多的策略。相信很多公司按单生产应该都会用到50的策略 1、首先还是先创建物料AB3 同时将BOM中的原材料的独立集中的字段设置为1 2、创建BOM—CS01 3、同杨…

动态通讯录及程序保存在文件中

目录 一、结构体改造及增容函数 1.结构体部分 2.初始化函数及增容函数 二、信息添加及销毁和排序 1.信息添加函数&#xff08;Add&#xff09; 2.销毁函数&#xff08;Destroy&#xff09; 3.排序部分&#xff08;qsort&#xff09; 三、通讯录信息保存 1.保存在文件中…

【华为数通HCIP | 网络工程师】821-BGP 组播高频题与解析(1)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

在线直线度测量仪为什么在轧钢行业越来越受欢迎!

在线直线度测量仪是利用光电检测原理及直线法进行直线度尺寸精密检测的。其测量方法是前后两台测量仪测量的数据拟合一条直线&#xff0c;中间的测量仪所测数值与直径做对比&#xff0c;即可得到被测物的直线度尺寸。 在线直线度测量仪的优点 在线直线度测量仪是一种三台小测…

Vue生命周期全解析:从工厂岗位到任务执行,一览无遗!

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、生…

Bean的循环依赖问题

2023.11.10 通俗来讲&#xff0c;循环依赖指的是一个实例或多个实例存在相互依赖的关系&#xff08;类之间循环嵌套引用&#xff09;。比如&#xff1a;丈夫类Husband&#xff0c;妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。 正常调用这两对象不会出现问题&am…

python实现全向轮EKF_SLAM

python实现全向轮EKF_SLAM 代码地址及效果运动预测观测修正参考算法 代码地址及效果 代码地址 运动预测 简化控制量 u t u_t ut​ 分别定义为 v x Δ t v_x \Delta t vx​Δt&#xff0c; v y Δ t v_y \Delta t vy​Δt&#xff0c;和 ω z Δ t \omega_z \Delta t ωz…

解压游戏资源,导出游戏模型

游戏中有很多好看的角色&#xff0c;地图等等资源。 你有没有想过&#xff0c;把他们导出到自己的游戏中进行魔改又或则玩换肤等操作呢&#xff1f; 相信很多同学都喜欢拳皇中的角色&#xff0c; 那么我们今天就拿拳皇15举例子&#xff0c;导出他的资源。 首先要先安装好这个…