Swift&SwiftUI调研

一、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 相比的优缺点。
image.png

一、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
image.png

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,之后这套方式被 ReactFlutter 采用,这一点上 SwiftUI 也几乎与它们一致。总结起来,这些 UI 框架都遵循以下步骤和原则:

  1. 使用各自的 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")
 }
  1. 如果 View 需要根据某个状态 (state) 进行改变,那我们将这个状态存储在变量中,并在声明 view 时使用它:
 var body: some View {
     Text("Hello \(name)")
 }

对比同一个场景界面的实现

作为常用的列表视图,在UIKit中被称为 TableView,而在 SwiftUI 中被叫做 List 或 Form。同样是实现一个列表,在 SwiftUI 仅需要声明这里有一个 List 以及每个单元的内容即可。而在UIKit 中,需要使用委托代理的方式定制每个单元的内容,还需要事无巨细的设置行和组的数量、对触摸的反应、点击过程等各方面。

苹果的全力支持

Swift 做为苹果的战略语言已经发展的越来越壮大,自 2019 年 Swift ABI 稳定后,苹果在 Swift 的投入越来越大。

  1. 苹果与 WWDC 20 推出的 WidgetKit 支持的 API 是 SwiftUI Only,同理 Clips 也一样。
  2. App Clips 10M的包大小, SwiftUI 是最合适的框架
  3. 从 WWDC17 后 苹果已经不再使用 Objective-C 做 Sample Code 演示
  4. https://developer.apple.com/****不再更新 Objective-C 相关的文档
  5. 开源社区逐步放弃 Objecive-C 如 Lottie

不足

虽然前文提到了 SwiftUI 的众多优点,包括研发效率,体验的提高,但是在国内的环境中 SwiftUI 也有它致命的弊端

  1. iOS 14 才可放心的使用。
  2. 只支持 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速查表

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容