设为首页 加入收藏

TOP

万字长文详解如何使用Swift提高代码质量(一)
2023-07-23 13:24:53 】 浏览:446
Tags:文详解 何使用 Swift 高代码

前言

京喜APP最早在2019年引入了Swift,使用Swift完成了第一个订单模块的开发。之后一年多我们持续在团队/公司内部推广和普及Swift,目前Swift已经支撑了70%+以上的业务。通过使用Swift提高了团队内同学的开发效率,同时也带来了质量的提升,目前来自Swift的Crash的占比不到1%。在这过程中不断的学习/实践,团队内的Code Review,也对如何使用Swift来提高代码质量有更深的理解。

Swift特性

在讨论如何使用Swift提高代码质量之前,我们先来看看Swift本身相比ObjC或其他编程语言有什么优势。Swift有三个重要的特性分别是富有表现力/安全性/快速,接下来我们分别从这三个特性简单介绍一下:

富有表现力

Swift提供更多的编程范式特性支持,可以编写更少的代码,而且易于阅读和维护。

  • 基础类型 - 元组、Enum关联类型
  • 方法 - 方法重载
  • protocol - 不限制只支持class、协议默认实现、专属协议
  • 泛型 - protocol关联类型、where实现类型约束、泛型扩展
  • 可选值 - 可选值申明、可选链、隐式可选值
  • 属性 - let、lazy、计算属性`、willset/didset、Property Wrappers
  • 函数式编程 - 集合filter/map/reduce方法,提供更多标准库方法
  • 并发 - async/await、actor
  • 标准库框架 - Combine响应式框架、SwiftUI申明式UI框架、CodableJSON模型转换
  • Result builder - 描述实现DSL的能力
  • 动态性 - dynamicCallable、dynamicMemberLookup
  • 其他 - 扩展、subscript、操作符重写、嵌套类型、区间
  • Swift Package Manager - 基于Swift的包管理工具,可以直接用Xcode进行管理更方便
  • struct - 初始化方法自动补齐
  • 类型推断 - 通过编译器强大的类型推断编写代码时可以减少很多类型申明

提示:类型推断同时也会增加一定的编译耗时,不过Swift团队也在不断的改善编译速度。

安全性

代码安全

  • let属性 - 使用let申明常量避免被修改。
  • 值类型 - 值类型可以避免在方法调用等参数传递过程中状态被修改。
  • 访问控制 - 通过publicfinal限制模块外使用class不能被继承重写
  • 强制异常处理 - 方法需要抛出异常时,需要申明为throw方法。当调用可能会throw异常的方法,需要强制捕获异常避免将异常暴露到上层。
  • 模式匹配 - 通过模式匹配检测switch中未处理的case。

类型安全

  • 强制类型转换 - 禁止隐式类型转换避免转换中带来的异常问题。同时类型转换不会带来额外的运行时消耗。。

提示:编写ObjC代码时,我们通常会在编码时添加类型检查避免运行时崩溃导致Crash

  • KeyPath - KeyPath相比使用字符串可以提供属性名和类型信息,可以利用编译器检查。
  • 泛型 - 提供泛型和协议关联类型,可以编写出类型安全的代码。相比Any可以更多利用编译时检查发现类型问题。
  • Enum关联类型 - 通过给特定枚举指定类型避免使用Any

内存安全

  • 空安全 - 通过标识可选值避免空指针带来的异常问题
  • ARC - 使用自动内存管理避免手动管理内存带来的各种内存问题
  • 强制初始化 - 变量使用前必须初始化
  • 内存独占访问 - 通过编译器检查发现潜在的内存冲突问题

线程安全

  • 值类型 - 更多使用值类型减少在多线程中遇到的数据竞争问题
  • async/await - 提供async函数使我们可以用结构化的方式编写并发操作。避免基于闭包的异步方式带来的内存循环引用和无法抛出异常的问题
  • Actor - 提供Actor模型避免多线程开发中进行数据共享时发生的数据竞争问题,同时避免在使用锁时带来的死锁等问题

快速

  • 值类型 - 相比class不需要额外的堆内存分配/释放和更少的内存消耗
  • 方法静态派发 - 方法调用支持静态调用相比原有ObjC消息转发调用性能更好
  • 编译器优化 - Swift的静态性可以使编译器做更多优化。例如Tree Shaking相关优化移除未使用的类型/方法等减少二进制文件大小。使用静态派发/方法内联优化/泛型特化/写时复制等优化提高运行时性能

提示:ObjC消息派发会导致编译器无法进行移除无用方法/类的优化,编译器并不知道是否可能被用到。

  • ARC优化 - 虽然和ObjC一样都是使用ARCSwift通过编译器优化,可以进行更快的内存回收和更少的内存引用计数管理

提示: 相比ObjC,Swift内部不需要使用autorelease进行管理。

代码质量指标

以上是一些常见的代码质量指标。我们的目标是如何更好的使用Swift编写出符合代码质量指标要求的代码。

提示:本文不涉及设计模式/架构,更多关注如何通过合理使用Swift特性做局部代码段的重构。

一些不错的实践

利用编译检查

减少使用Any/AnyObject

因为Any/AnyObject缺少明确的类型信息,编译器无法进行类型检查,会带来一些问题:

  • 编译器无法检查类型是否正确保证类型安全
  • 代码中大量的as?转换
  • 类型的缺失导致编译器无法做一些潜在的编译优化

使用as?带来的问题

当使用Any/AnyObject时会频繁使用as?进行类型转换。这好像没什么问题因为使用as?并不会导致程序Crash。不过代码错误至少应该分为两类,一类是程序本身的错误通常会引发Crash,另外一种是业务逻辑错误。使用as?只是避免了程序错误Crash,但是并不能防止业务逻辑错误。

func do(data: Any?) {
    guard let string = data as? String else {
        return
    }
    // 
}

do(1)
do("")


以上面的例子为例,我们进行了as?转换,当dataString时才会进行处理。但是当do方法内String类型发生了改变函数,使用方并不知道已变更没有做相应的适配,这时候就会造成业务逻辑的错误。

提示:这类错误通常更难发现,这也是我们在一次真实bug场景遇到的。

使用自定义类型代替Dictionary

代码中大量Dictionary数据结构会降低代码可维护性,同时带来潜在的bug

  • key需要字符串硬编码,编译时无法检查
  • value没有类型限制。修改时类型无法限制,读取时需要重复类型转换和解包操作
  • 无法利用空安全特性,指定某个属性必须有值

提示:自定义类型还有个好处,例如JSON自定义类型时会进行类型/nil/属性名检查,可以避免将错误数据丢到下一层。

不推荐

let dic: [String: Any]
首页 上一页 1 2 3 4 5 6 下一页 尾页 1/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇最近几天 下一篇给我两分钟的时间:微博风格九宫..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目