一、Swift介绍
- Swift是苹果于2014年WWDC(苹果开发者大会)发布的新开发语言,可与Objective-C共同运行于iOS、MAC OS平台,用于搭建基于苹果平台的应用程序。
- 截止至2022.5月,Swift更新到了5.6版本,从swift5开始ABI基本稳定。
- Swift 使用自动引用计数(Automatic Reference Counting, ARC)来简化内存管理
- Swift Foundation框架可无缝对接现有的 Cocoa 框架,并且兼容 Objective-C 代码,支持面向过程编程和面向对象编程,Swift也是一门类型安全的语言。
- Swift中泛型是用来使代码能安全工作,可以在函数数据和普通数据类型中使用,例如类、结构体或枚举。泛型可以解决代码复用的问题
//举个简单例子:这两个方法很类似,主要就一个参数类型的区别。
func isIntEqual(x:Int,y:Int) -> Bool {
return x == y
}
func isStringEqual(x:String,y:String) -> Bool {
return x == y
}
//我们可以利用范型合并一下:
func isObjEqual<T:Equatable>(x:T,y:T) -> Bool {
return x == y
}
苹果对于swift的描述:
对比OC语言
我们来总结下它与 Objective-C 相比的优缺点。一、Swift优点
1、简洁的语法,性能较好
swift语言比OC精简,整个项目中丢掉了.h头文件,以及头文件的引入。性能是Objective-C的1.3倍
2、定义变量简单
定义变量不用区分整型,浮点型等等,变量使用var,常量使用let。
3、可视化互动效果
开发工具带来了Xcode Playgrounds功能,该功能提供强大的互动效果,能让Swift源代码在撰写过程中实时显示出其运行结果。
4、函数式编程的支持(Map、FlatMap、Filter、Reduce等函数方法)
Swift 可以面向协议编程、函数式编程、面向对象编程 Objective-C 以面向对象编程为主,通过引入 ReactiveCocoa 这个库才可支持函数式编程。
二、Swift缺点
1、语言覆盖率还比较低
可能是Swift的ABI稳定得太晚,不止各大APP里已经积累了大量的OC库和业务代码,苹果系统里的OC占比也依然很高,_博客《Evolution of the programming languages from iPhone OS 1.0 to iOS 14》 _统计了 iOS 历史版本 OC 占比,从文章中可以看到最近的iOS 14版本里OC占比高达88%,C和C++主要用于音视频、电话、网络等比较基础的模块,其占比相对稳定,特别是C并没有明显增加。不过在最近几个版本中,Swift占比持续增高,iOS 14达到了8%,可以看出苹果正在使用Swift重构以前的库。
Out of all the binaries in iOS 14:
- 88% are using Objective-C
- 17% are using C++
- 8% are using Swift
- 8% are entirely written in C
- 1% are using SwiftUI
2、第三方库的支持不够多
老版本的swift还不稳定,每次更新API变动极大,到了5.0后ABI基本稳定,技术社区的开源项目相对于沉淀多年的OC偏少。当遇到一些问题的时候,解决问题的方案很少,网上的资源也很稀缺。对于不支持Swift的一些第三方类库,如果非得使用,只能混合编程,利用桥接文件实现。
3、App体积变大
使用 Swift 后, App 体积大概增加 5-8 M左右,对体积大小敏感的慎用。(体积变大的原因是因为 Swift 还在变化,所以 Apple 没有在 iOS 系统里放入 Swift 的运行库,反而是每个 App 里都要包含其对应的 Swift 运行库。)
4、适配版本较高
大部分三方库支持的版本较高,测试的demo中最低支持iOS13,同时SwiftUI最低支持也是iOS13
Swift代码举例
(1) 更好用的switch...case
不需要break,一个case可以写多个条件,使用fallthrough继续执行
switch num {
case 1, 2:
print("1, 2")
case 3..<5:
print("3, 4")
case ..<7:
print("6")
case ...8:
print("8")
default:
print("defalut")
}
基本数据类型都能判断,并且能使用where语句
switch testArray {
case let array1 where array1.first == 1:
print(array1.first!)
default:
print(testArray)
}
(2) for循环
}
for i in 0..<8 where i % 2 == 0 {
}
for i in (0..<8).reversed() {
}
for i in stride(from: 0, to: 8, by: 2) {
}
for i in stride(from: 0, through: 8, by: 2) {
}
(3) 枚举
enum Language: String {
case Swift
case ObjectiveC = "Objective-C"
case C
case Java
}
print(Language.Swift.rawValue)
Language.init(rawValue: "Swift")
// 可以绑定值
enum RequestResult {
case Success(Int)
case Error(String)
}
let requestResult = RequestResult.Success(1)
switch requestResult {
case .Success(let code):
print(code)
case .Error(let errorTint):
print(errorTint)
}
// 可以定义方法和计算型属性
enum Device {
case iPad, iPhone, AppleTV, AppleWatch
func introduced() -> String {
switch self {
case .iPad: return "iPad"
case .iPhone: return "iPhone"
case .AppleWatch: return "AppleWatch"
case .AppleTV: return "AppleTV"
}
}
var year: Int {
switch self {
case .iPhone: return 2007
case .iPad: return 2010
case .AppleTV: return 2011
case .AppleWatch: return 2012
}
}
}
let introduce = Device.iPhone.introduced()
(4) 结构体
结构体在Swift中的地位很重要,Array Dictionary Set Int Float Double Bool String都是结构体
什么时候用结构体,什么时候用类
把结构体看做值
位置(经纬度)坐标(二维坐标,三维坐标)温度
把类看做是物体 人 车 动物
*/
/*
结构较小,适用于复制操作,相比一个class的实例被多次引用,struct更加安全
无须担心内存泄漏或者多线程冲突问题
*/
struct Location {
var longitude: Double
var latitude: Double
// 使用Failable-Initializer
init?(coordinateString: String) {
// 非常简洁的非空判断,安全可靠
guard
let commaIndex = coordinateString.index(of: ","),
let firstElement = Double(coordinateString[coordinateString.startIndex..<commaIndex]),
let secondElement = Double(coordinateString[coordinateString.index(commaIndex, offsetBy: 1)..<coordinateString.endIndex])
else {
// 可能会失败的init return nil
return nil
}
self.longitude = firstElement
self.latitude = secondElement
}
}
(5) 函数
let testString = "林哲生"
//(4)函数
func sayHello(name: String = testString, greeting: String = "你们好啊", extra: Dictionary<String, String>? = nil) {
print(name + greeting)
}
/*
1.支持重载
2.函数参数可设置默认值
*/
sayHello()
sayHello(name: "1")
sayHello(greeting: "2")
sayHello(extra: ["one": "1"])
// 改变外部传入的参数
var array = [1,2,3,4]
func sayHello(array: inout [Int]) {
array.append(5)
}</pre>
#### (6) 可选性,安全的语言
缩减代码量,安全处理数据逻辑。
<pre data-language="swift" id="Ai0N3" class="ne-codeblock language-swift" style="border: 1px solid rgb(232, 232, 232); border-radius: 2px; background-color: rgb(249, 249, 249); padding: 16px; font-size: 13px; color: rgb(89, 89, 89);">var string1: String? = "Hello"
var string2: String? = "Hi"
// 解包判断1
if let string1 = string1 {
print(string1)
}
func sayHello(name: String = "gy", greeting: String = "你们好", extra: Dictionary<String, String>? = nil) {
// 解包判断2
guard let uExtra = extra else {
return
}
print(uExtra)
}
func sayHello(greeting: String = "你们好", extra: Dictionary<String, String>? = nil) {
// 解包判断2
guard let uExtra = extra else {
return
}
print(uExtra)
}
二、SwiftUI介绍
什么是 SwiftUI
SwiftUI 于 2019 年度 WWDC 全球开发者大会上发布,它是基于 Swift 建立的声明式框架。该框架可以用于 watchOS、tvOS、macOS、iOS 等平台的应用开发,等于说统一了苹果生态圈的开发工具。
官方的定义写得非常明确:
SwiftUI is a user interface toolkit that lets us design apps in a declarative way.
可以理解为 SwiftUI 就是⼀种描述式的构建 UI 的⽅式。
为什么苹果要推出 SwiftUI
SwiftUI 的两个组成部分,Swift + UI,即是这个问题的答案。
Swift:编程语言和体验的一致性
苹果希望直接优化语言本身,并统一所有设备的开发体验,让开发者更容易上手,也更容易将心里的想法转化为运行的程序。先有了Swift,紧接着又推出了 SwiftUI。SwiftUI 使用了大量 Swift 的语言特性,特别是 5.0 之后新增的特性。Swift 5.1 的很多特性几乎可以说都是为了 SwiftUI 量身定制的,比如 Opaque return types、Property Delegate 和 Function builder 等。
UI:开发的困局
在 SwiftUI 出现之前,苹果不同的设备之前的开发框架并不互通,移动端的⼯程师和桌⾯端的⼯程师需要掌握的知识,有很⼤⼀部分是差异化的。
从 iOS SDK2.0 开始,移动端的开发者⼀直使⽤ UIKit 进⾏⻚⾯部分的开发。UIKit 的思想继承了成熟的 AppKit 和MVC(Model-View-Controller)模式,作出了⼀些改进,但本质上改动不⼤。UI 包括了⽤⼾能看到的⼀切,包括静⽌的显⽰和动态的动画。
再到后来苹果推出了Apple Watch,在这块狭小屏幕上,又引入了一种新的布局方式。这种类似堆叠的逻辑,在某种程度上可以看做 SwiftUI 的未完全体。
截止此时,macOS 的开发需要使用 AppKit,iOS 的开发需要使用 UIKit,WatchOS 的开发需要使用堆叠,这种碎片化的开发体验无疑会大大增加开发者所需消耗的时间精力,也不利于构建跨平台的软件体验。
即使单看 iOS 平台,UIKit 也不是完美的开发⽅案。
UIKit 的基本思想要求 ViewController 承担绝⼤部分职责,它需要协调 model,view 以及⽤⼾交互。这带来了巨⼤的 sideeffect 以及⼤量的状态,如果没有妥善安置,它们将在 ViewController 中混杂在⼀起,同时作⽤于 view 或者逻辑,从⽽使状态管理愈发复杂,最后甚⾄不可维护⽽导致项⽬崩溃。换句话说,在不断增加新的功能和⻚⾯后,同⼀个ViewController会越来越庞杂,很容易在意想不到的地⽅产⽣ bug。⽽且代码纠缠在⼀起后也会⼤⼤降低可读性,伤害到维护和协作的效率。
SwiftUI特点
声明式的界面开发方式
近年来,随着编程技术和思想的进步,使用声明式或者函数式的方式来进行界面开发,已经越来越被接受并逐渐成为主流。最早的思想大概是来源于 Elm,之后这套方式被 React 和 Flutter 采用,这一点上 SwiftUI 也几乎与它们一致。总结起来,这些 UI 框架都遵循以下步骤和原则:
- 使用各自的 DSL 来描述「UI 应该是什么样子」,而不是用一句句的代码来指导「要怎样构建 UI」。
比如传统的 UIKit,我们会使用这样的代码来添加一个 “Hello World” 的标签,它负责“创建 label”,“设置文字”,“将其添加到 view 上”:
func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Hello World"
view.addSubview(label)
// 省略了布局的代码
}
而相对起来,使用 SwiftUI 我们只需要告诉 SDK 我们需要一个文字标签:
var body: some View {
Text("Hello World")
}
- 如果 View 需要根据某个状态 (state) 进行改变,那我们将这个状态存储在变量中,并在声明 view 时使用它:
var body: some View {
Text("Hello \(name)")
}
对比同一个场景界面的实现
作为常用的列表视图,在UIKit中被称为 TableView,而在 SwiftUI 中被叫做 List 或 Form。同样是实现一个列表,在 SwiftUI 仅需要声明这里有一个 List 以及每个单元的内容即可。而在UIKit 中,需要使用委托代理的方式定制每个单元的内容,还需要事无巨细的设置行和组的数量、对触摸的反应、点击过程等各方面。
苹果的全力支持
Swift 做为苹果的战略语言已经发展的越来越壮大,自 2019 年 Swift ABI 稳定后,苹果在 Swift 的投入越来越大。
- 苹果与 WWDC 20 推出的 WidgetKit 支持的 API 是 SwiftUI Only,同理 Clips 也一样。
- App Clips 10M的包大小, SwiftUI 是最合适的框架
- 从 WWDC17 后 苹果已经不再使用 Objective-C 做 Sample Code 演示
- https://developer.apple.com/****不再更新 Objective-C 相关的文档
- 开源社区逐步放弃 Objecive-C 如 Lottie
不足
虽然前文提到了 SwiftUI 的众多优点,包括研发效率,体验的提高,但是在国内的环境中 SwiftUI 也有它致命的弊端
- iOS 14 才可放心的使用。
- 只支持 Apple Platform,这和国内的要支持 Mobile Platform 从理念上冲突。
大型 APP 要解决的是如何部署到低版本操作系统上和安卓平台上,毕竟很多公司还在支持 iOS 9 对于升级到最低支持 iOS 14 好像还需要一个世纪那么漫长,而且国内的设备占比大头还是以 Android 巨多 。
SwiftUI VS Flutter
下面我们看一段简单的声明式语法
SwiftUI
struct ContentView : View {
var body: some View {
VStack {
MapView()
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack(alignment: .top) {Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
Spacer()
}
}
}
flutter
Widget _listItemBuilder(BuildContext context, int index) {
return Container(
color: Colors.white,
margin: EdgeInsets.all(8.0),
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
AspectRatio(
aspectRatio: 16/9,
child: Image.network(posts[index].imageUrl, fit: BoxFit.cover),
),
SizedBox(height: 16.0),
Text(
posts[index].title,
style: Theme.of(context).textTheme.title
),
Text(
posts[index].author,
style: Theme.of(context).textTheme.subhead
),
SizedBox(height: 16.0),
],
),
Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
splashColor: Colors.white.withOpacity(0.3),
highlightColor: Colors.white.withOpacity(0.1),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => PostShow(post: posts[index]))
);
}
),
),
),
],
),
);
}
观察语法的细节,我们可以注意到一些特征,Flutter 使用典型的声明式语法,开发者声明 UI 的布局方式,一切的布局都交给引擎来解决。在写代码层面 Dart 使用 , 来分割不同的 Widget,这会造成在复杂的布局中,()语法嵌套极其复杂,配上 VSCode 的插件也看的眼花缭乱。
而 Swift 虽然也是声明式语法,但是仔细注意到,Swift 的 View 组合并不是由, 分割,而是由换行分割,在 Swift 中 函数调用是可以换行分割的。这样的 DSL 对开发者的体验更为友好,推测 SwiftUI 使用了类似标记的特征,在统一的时机去做布局。
总结
更简洁、更稳定、更安全且效率更高的Swift,以及声明式、跨平台且体积更小的SwiftUI,相比于"老旧不堪"的OC是如此的优秀,加上苹果的全力支持、推广,RealityKit、CareKit、Create ML、System、WidgetKit、CryptoKit、Combine、SwiftUI等框架在与OC混编时都非常困难,从这些方面可以看出,苹果所有新开发的框架都在避免和OC产生关系,甚至自WWDC2020起新增加的App Widget只能用SwiftUI开发。
一些耳熟能详的App,比如微信、淘宝、百度、支付宝、拼多多、京东、哔哩哔哩、优酷、小红书等都已经开始尝试使用Swift,这些App无一例外都采用了Swift和OC混编开发。由于国内业务竞争压力大,很难像国外公司Uber那样花大半年时间全部用Swift重构,因此如果要在现有工程基础上引入Swift开发,不可避开采用混编开发。很多App使用Swift混编,也是因为苹果对Widget功能开发语言设置了限制,即只能使用Swift,看来苹果公司这个策略是相当有效的。我们团队恰巧也有开发Widget小组件的意愿,第一个Swift+UI的落地场景即将到来。
作为一个客户端开发者,跟上时代的步伐!
参考资料
如何评价SwiftUI-阿里巴巴大淘宝技术
A站 的 Swift 实践
SwiftUI与Swift的区别
SwiftUI 的一些初步探索 (一)
OC开发转Swift - OC映射Swift速查表