Swift&SwiftUI調研 一、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 相比的優缺點。

一、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,之後這套方式被 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速查表

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章