1. 程式人生 > >Swift真的有那麼好嗎?是否有必要學習

Swift真的有那麼好嗎?是否有必要學習

一  從語法角度,他的優勢點

最近,除了N多的基於Swift的服務端開發框架,筆者不由深思,到底該這麼評價Swift呢?不可否認,在iOS的開發領域,Swift是比OJC擁有著優勢,那麼在通用語言這個層次上比較時,它又如何呢?Apple 在推出 Swift 時就將其冠以先進,安全和高效的新一代程式語言之名。前兩點在 Swift 的語法和語言特性中已經表現得淋漓盡致:像是尾隨閉包,列舉關聯值,可選值和強制的型別安全等都是 Swift 顯而易見的優點。

1. Comparison

oc java c# c++

swift

python
值物件 沒有 沒有
指標(缺陷) 沒有 沒有 沒有 沒有
記憶體管理 引用計數 垃圾回收 垃圾回收 智慧指標(缺陷) 引用計數 垃圾回收
多返回值 沒有 沒有 沒有 沒有
指令碼語言特性 沒有 沒有 沒有 沒有
移動端支援 ios android 遊戲 遊戲 ios 較少(缺陷)

近來Swift與Rust都挺好的,一個背靠Apple,一個是Mozilla的親兒子。不可否認這二者都是工程領域的集大成者,不過筆者認為Swift是會比D或者Rust具有更大的可用性與吸引力,當然,他們的瞄準的目標點也不一樣。D與Rust適合於那些長期使用C++並且已經適應了要去掌握N多的語法與概念的,但是想要使用些更加清晰明瞭與安全的語言。這型別的開發者往往從事著類似於遊戲引擎、編譯器、加解密庫、HTML渲染引擎等等類似的工作。

Swift呢,更多意義上是一門面向於應用程式設計的語言,它很容易上手,在某些方面它應該與Go、Java、Python以及C#相提並論。不過Swift比這些會更容易開始學習,它的helloworld只需要一行程式碼就好了,並且它是支援函式的(不像Java那樣完全的OO)。你不需要學習任何的類與物件的知識就可以開始撰寫簡易的Swift的程式碼。基於此,Swift是更合適用作一種教學語言的,它還有像指令碼語言一樣的互動環境,也就是REPL以及Xcode本身提供的PlayGround。

綜上所述,Swift擁有著被廣泛使用以及當做第一學習語言的潛質。並且,Swift並不是像PHP那樣的語法特性較少的語言,它擁有足夠的深度來滿足Rust或者D這樣的使用者的需求。與Go相比的話,Go背靠Google,也是個非常容易上手的語言,並且號稱自帶併發。Swift在語法層次上會更加高階,並且Swift並沒有使用GC機制,因此可以與C更好地相相容。也就是說,你可以用Swift編寫任何的庫來供任何語言使用,只要這些語言可以使用C的庫。這些特質保證了Swift擁有著比Java、C#、Python、Ruby以及Go更廣闊的適用範圍。後面這幾個傢伙,因為有GC的存在,不適合為其他語言提供基本庫。我也很喜歡Go,但是毫無疑問,Swift會是個更加嚴謹與安全的語言。型別檢測系統會幫助處理很多的錯誤,Go目前是很合適於Web開發但是不適合科學計算與遊戲引擎這些你必須要大量自定義操作符來處理向量啊、矩陣運算這樣的。

因此,Swift可以被定義為一個安全並且使用者友好的語言,並且可以像指令碼語言那樣方便實驗與使用,其實swift想做移動端的python

1.1  TypeAlias 類型別名:類似 C++ 的 typedef 和 Golang 的 type

Swift 版本:

typealias status = Int8

C++ 版本:

typedef status int

Golang 版本:

type status int

1.2   Optional 可選型別: 類似 Haskell 中的 Maybe 型別

Optional 可選型別,表示變數有值時返回儲存的值,沒有值返回 nil 。它可以在變數未宣告的情況下確保後面呼叫的安全性。

用法為 Optional<Type> 或者 Type? 。 例如:

var i:Optional<Int>
var str:String?

Haskell 中也有類似的東西: Maybe 型別,它是這樣定義的:

data Maybe a =  Nothing | Just a deriving (Eq, Ord, Read, Show)

用法也類似:

i :: Maybe Int

1.3  列舉支援元組:類似 Rust

Swift 不僅支援基本的列舉型別,還可以是元組:

enum Product
{
    case Car(String, Int)
    case Phone(String, String)
}

Rust 中的列舉也有類似用法:

enum Shape {
	Circle { center: Point, radius: f64 },
	Retangle { top_left: Point, bottom_right: Point }
}

1.4  用於 switch case 的 fallthrough: 類似 Golang 中的 fallthrough

當初學 C 語言的時候就覺得 switch case 設計成預設貫穿很坑爹:實際開發中多數是不需要貫穿的,這就導致程式碼中一大堆 break 

現在 Swift 終於從 Golang 吸收過來這個特性,一般不需要處理 case ,少數需要貫穿的情況才加上 fallthrough ,二者用法也類似,程式碼就不貼了。

1.5  switch case 支援 where 語句:類似 Haskell 中 Pattern Guard 的 where 語句

Swift 中的 switch case 語句支援 where 條件判斷:

let point = (-1, 2, 0)
switch point
{
case (let x, _, _):
    println("x: \(x)")
    fallthrough
case (x, y, z) where x * y * z == 0:
    println("x: \(x), y: \(y), z: \(z)")
default:
    println()
}

Haskell 中雖然沒有 switch case ,但有類似的 Pattern Guard,而且也支援 where 語句:

funcBMI :: (RealFloat a) => a -> a -> String
funcBMI weight height
	| bmi <= bmi_thin = "Thin."
	| bmi >= bmi_fat = "Fat."
	| otherwise = "Normal."
	where bmi = weight/height^2
	      (bmi_thin,bmi_fat) = (18.5,30.0)

1.6  支援函式型別:類似 C++11 中的 std::function 型別

Swift 中可定義函式型別,並作為變數型別、引數型別、返回值型別:

//定義函式型別:
typealias FuncType = (Double, Double) -> Double

//函式型別的變數:
var funcDDD : FuncType

//函式型別的引數:
func printFuncResult(fun : FuncType, x : Double, y : Double) {
    println(fun(x, y))
}

//函式型別的返回值:
func getFunc() -> FuncType {
    return funcDDD
}

甚至還可以將函式型別結合閉包使用:

typealias FuncType = (Double, Double) -> Double

func printFuncResult(x : Double, y : Double, fun : FuncType) {
    println("\(fun(x, y))")
}

//定義閉包:
let funPlus = { (x : Double, y : Double) -> Double in return x + y }

//將閉包作為函式型別的引數傳入函式:
printFuncResult(0.1234, 5.6789, funPlus)

C++11 中的 std::function 也可定義一個函式指標,也有上述類似用法:

//定義函式型別指標變數:
std::function<double(double, double)> multiply;

//將變數宣告為該型別的 Lambda:
multiply = [] (double x, double y) -> double {
	return x * y;
};

//呼叫 Lambda:
std::cout << multiply(1.234, 5.6789) << std::endl;

1.7  支援運算子過載和定義新運算子:類似 C++ 中的運算子過載

Swift 中可用 prefix 過載字首運算子:

prefix func - (p : Point) -> Point {
    return Point(x : -p.x, y : -p.y)
}

也可用 postfix 過載字尾運算子:

postfix func ++ (p : Point) -> Point {
    var px = p.x, py = p.y
    return Point(x : ++px, y : ++py)
}

甚至可以使用 operator 定義新的運算子:

prefix operator %& {}
prefix func %& (p : Point) -> Point {
    return Point(x : p.x % 8, y : p.y % 8)
}

C++ 中的運算子過載那是出了名的強大,甚至可以過載 IO 運算子,這裡僅給出基本用法:

class Point {
public:
	Point(int i1, int i2, int i3): x(i1), y(i2), z(i3){}
	Point operator-();
	Point operator++();
	bool operator==(const Point &p);
	Point operator+(const Point &p);
	int operator[](size_t n);
private:
	int x, y, z;
	int values[3] = {x, y, z};
};

Point Point::operator-() {
	return Point(-x, -y, -z);
}

Point Point::operator++() {
	return Point(++x, ++y, ++z);
}

bool Point::operator==(const Point &p) {
	return x == p.x && y == p.y && z == p.z;
}

Point Point::operator+(const Point &p) {
	return Point(x + p.x, y + p.y, z + p.z);
}

int Point::operator[](size_t n) {
	return values[n];
}

int main (int argc, char **argv) {
	Point p1(1, 3, 5);
	Point p2(2, 4, 6);
	std::cout << (p1 == p2 ? "p1 == p2" : "p1 != p2") << std::endl;
	Point p3 = -(p1 + p2);
	//p3.operator++();
	++p3;
	std::cout << "p3: " << p3[0] << ", " << p3[1] << ", " << p3[2] << std::endl;
	return 0;
}

1.8  函式引數可設定是否可變:類似 C++

C++ 中在函式前新增 const 即可宣告為不可變,程式碼就不貼了。

Swift 中函式引數預設就是不可變的,如果要在函式內部修改引數的值,需要在引數前宣告 var 關鍵字:

func printContactVar(var name: String, email : String) {
    name = name.uppercaseString
    printContact(name: name, email: email)
}

上面的 var 引數雖然能修改值,但作用域僅限於函式內部,如果想在函式呼叫結束後,在外部依然生效,還可將關鍵字改為 inout 

1.9  函式引數可設定預設值:類似 C++

Swift 中定義函式時,可設定引數預設值:

func printContact(name : String = "Rinc", email : String = "[email protected]") {
    println("\(name): \(email)")
}

C++ 也有類似特性:

void printContact(std::string name = "Rinc", std::string email = "[email protected]");
void printContact(std::string name, std::string email) {
	std::cout << name << ": " << email << std::endl;
}

1.91  可通過 count 和 repeatedValue 初始化陣列:類似 C++

Swift 版本:

var a = [Character](count: 10, repeatedValue: "X")

C++ 版本:

std::vector<char> v(10, 'x');

1.92  元組相關特性:類似 Golang

Swift 中可以通過 不取元組中的某個元素:

let people = ("Rinc", age : 25)
var (name, _) = people

還有用於集合資料的遍歷:

var dic : Dictionary<String, Int> = ["Rinc" : 25, "Emma": 24]
for (k, _) in dic {
    println("\(k)")
}

Golang 由於經常取回的資料集含有索引(連陣列都有),所以這種用法更常見:

mp := map[string]float32{"C": 5, "GO": 4.5, "JAVA": 6}
for key, _ := range mp {
	fmt.Println(key)
}

雖然 C++11、Rust 等語言也支援元組,但感覺大多簡單用於函式多值返回,像上面這種用法貌似沒見過;

1.93  其他特性

  1. 函式語句結束不加分號:Golang、Rust 等很多語言都支援;

  2.  var 動態型別宣告:JavaScript、Golang 等語言都支援;

  3. 函式多值返回:因為有了元組的支援,所以實現這個並不難,C++、Golang、Rust 等語言都支援;

  4. 閉包:這東西現在隨著函數語言程式設計的熱門,幾乎所有語言都提供了支援,也不多說了。

總結

總體來看 Swift 吸收的 C++ 特性最多,其次是 Golang 和 Hashekll,還有少量 Rust 特性。

2. Performance

Swift 具有一門高效語言所需要具備的絕大部分特點。與 Ruby 或者 Python 這樣的解釋型語言不需要再做什麼對比了,相較於其前輩的 Objective-C,Swift 在編譯期間就完成了方法的繫結,因此方法呼叫上不再是類似於 Smalltalk 的訊息傳送,而是直接獲取方法地址並進行呼叫。雖然 Objective-C 對執行時查詢方法的過程進行了快取和大量的優化,但是不可否認 Swift 的呼叫方式會更加迅速和高效。

另外,與 Objective-C 不同,Swift 是一門強型別的語言,這意味 Swift 的執行時和程式碼編譯期間的型別是一致的,這樣編譯器可以得到足夠的資訊來在生成中間碼和機器碼時進行優化。雖然都使用 LLVM 工具鏈進行編譯,但是 Swift 的編譯過程相比於 Objective-C 要多一個環節 -- 生成 Swift 中間程式碼 (Swift Intermediate Language,SIL)。SIL 中包含有很多根據型別假定的轉換,這為之後進一步在更低層級優化提供了良好的基礎,分析 SIL 也是我們探索 Swift 效能的有效方法。

最後,Swift 具有良好的記憶體使用的策略和結構。Swift 標準庫中絕大部分型別都是 struct,對值型別的 使用範圍之廣,在近期的程式語言中可謂首屈一指。原本值型別不可變性的特點,往往導致對於值的使用和修改意味著建立新的物件,但是 Swift 巧妙地規避了不必要的值型別複製,而僅只在必要時進行記憶體分配。這使得 Swift 在享受不可變性帶來的便利以及避免不必要的共享狀態的同時,還能夠保持效能上的優秀。

編譯器優化

Swift 編譯器十分智慧,它能在編譯期間幫助我們移除不需要的程式碼,或者將某些方法進行內聯 (inline) 處理。編譯器優化的強度可以在編譯時通過引數進行控制,Xcode 工程預設情況下有 Debug 和 Release 兩種編譯配置,在 Debug 模式下,LLVM Code Generation 和 Swift Code Generation 都不開啟優化,這能保證編譯速度。而在 Release 模式下,LLVM 預設使用 "Fastest, Smallest [-Os]",Swift Compiler 預設使用 "Fast [-O]",作為優化級別。我們另外還有幾個額外的優化級別可以選擇,優化級別越高,編譯器對於原始碼的改動幅度和開啟的優化力度也就越大,同時編譯期間消 耗的時間也就越多。雖然絕大部分情況下沒有問題,但是仍然需要當心的是,一些優化等級採用的是激進的優化策略,而禁用了一些檢查。這可能在原始碼很複雜的情 況下導致潛在的錯誤。如果你使用了很高的優化級別,請再三測試 Release 和 Debug 條件下程式執行的邏輯,以防止編譯器優化所帶來的問題。

值得一提的是,Swift 編譯器有一個很有用的優化等級:"Fast, Whole Module Optimization",也即 -O -whole-module-optimization。在這個優化等級下,Swift 編譯器將會同時考慮整個 module 中所有原始碼的情況,並將那些沒有被繼承和過載的型別和方法標記為 final,這將盡可能地避免動態派發的呼叫,或者甚至將方法進行內聯處理以加速執行。開啟這個額外的優化將會大幅增加編譯時間,所以應該只在應用要釋出的時候開啟這個選項。

雖然現在編譯器在進行優化的時候已經足夠智慧了,但是在面對編寫得非常複雜的情況時,很多本應實施的優化可能失效。因此保持程式碼的整潔、乾淨和簡單,可以讓編譯器優化良好工作,以得到高效的機器碼。

3. 註釋與換行

註釋

請將你的程式碼中的非執行文字註釋成提示或者筆記以方便你將來閱讀。Swift 的編譯器將會在編譯程式碼時自動忽略掉註釋部分。

Swift 中的註釋與C 語言的註釋非常相似。單行註釋以雙正斜槓作(//)為起始標記:

// 這是一個註釋

你也可以進行多行註釋,其起始標記為單個正斜槓後跟隨一個星號(/),終止標記為一個星號後跟隨單個正斜槓(/):

/* 這是一個, 多行註釋 */

與C 語言多行註釋不同,Swift 的多行註釋可以巢狀在其它的多行註釋之中。你可以先生成一個多行註釋塊,然後在這個註釋塊之中再巢狀成第二個多行註釋。終止註釋時先插入第二個註釋塊的終止標記,然後再插入第一個註釋塊的終止標記:

/* 這是第一個多行註釋的開頭 /* 這是第二個被巢狀的多行註釋 */ 這是第一個多行註釋的結尾 */

通過運用巢狀多行註釋,你可以快速方便的註釋掉一大段程式碼,即使這段程式碼之中已經含有了多行註釋塊。

4. Objective-C混合程式設計

參考資料

  • 從Objective-C到Swift

  • swift與objective-c混編

  • Swift and Objective-C in the Same Project

Swift 與 Objective-C混合呼叫示意圖

5. Swift類引用Objective-C檔案

因為Swift沒有內嵌的標頭檔案機制,因此Swift呼叫Objective-C需要一個名為“<工程名>-Bridging-Header.h”的橋接標頭檔案。橋接標頭檔案的作用是為Swift呼叫Objective-C物件搭建一個橋,它的命名必須是“<工程名>- Bridging-Header.h”,我們需要在橋接標頭檔案中引入Objective-C標頭檔案,所有的Swift類會自動引用這個標頭檔案。

橋接檔案

橋接檔案設定

  • OJC類如下:

//
//  ObjcFunc.h
//

# import <Foundation/Foundation.h>

@interface ObjcFunc : NSObject

-(NSString*)sayHello:(NSString*)greeting withName: (NSString*)name;

@end

//
//  ObjcFunc.m
//

# import "ObjcFunc.h"

# import "CombinedProgram-Swift.h"

@implementation ObjcFunc

- (NSString*)sayHello:(NSString*)greeting withName: (NSString*)name{

    NSString *string = [NSString stringWithFormat:@"Hi,%@ %@.",name,greeting];

    return string;

  }

@end
  • Swift類中呼叫

import Foundation
@objc class SwiftFunc: NSObject {
  func sayHello() -> Void {
  var obj : ObjcFunc = ObjcFunc()
  println(obj.sayHello("Hello", withName: "Swift"));
  return
  }
}

6. Objective-C類引用Swift檔案

(1)在Building Settings -> Packaging -> Defining中選定Module Name;

(2)在OJC的標頭檔案中引入:#import "{ModuleName}-swift.h"

SwiftFunc* obj = [[SwiftFunc alloc] init];
[obj sayHello];

有時候會發現Xcode無法自動生成*-Swift.h檔案,可以參考StackOverflow上的這篇文章。該文章總結下來,我們需要進行以下兩大步檢測:

(1)檢測你的Xcode的配置

Product Module Name : myproject

Defines Module : YES

Embedded Content Contains Swift : YES

Install Objective-C Compatibility Header : YES

sObjective-C Bridging Header : $(SRCROOT)/Sources/SwiftBridging.h

(2)檢查你的Swift類是否正規

要保證你的Swfit類中已經使用@objc關鍵字聲明瞭一個繼承自NSObject的類。Xcode不會為存在任何編譯錯誤的類進行編譯操作。

(3)忽略Xcode的報錯,先編譯一下

7. 語法要點

! VS ?

在Swift中經常看到!與?兩個操作符,譬如在型別轉換、可選型別構造中都用到,用Apple官方的話說:

It may be easiest to remember the pattern for these operators in Swift as: ! implies “this might trap,” while ?indicates “this might be nil.”

就是!操作符表示我不管你編譯器,我肯定要這麼做,那麼有可能導致執行時崩潰。而?操作符表示這個可能是nil,你幫我查查有沒有進行完備的空檢查。

二. 以上是從語言技術角度,而如果從工程角度

1, 生態環境比語言成熟慢:Swift非常適合思考,但是目前還不是工程化ready的語言,主要是上下游生態環境還不成熟,需要時間;

2,路徑依賴效應:人的路徑依賴無解;未來使用swift並獲得巨大市場成功的公司,不是今天這些市場上的巨無霸;就像諾基亞比蘋果更早理解觸屏的價值,但仍然只能眼睜睜看著蘋果後來居上;普朗克曾經說過一句關於科學真理的真理,敘述為“一個新的科學真理取得勝利並不是通過讓它的反對者們信服並看到真理的光明,而是通過這些反對者們最終死去,熟悉它的新一代成長起來。”對於技術來說也是如此。
 

Reference

Tutorial & Docs

  • 中文版 Apple 官方 Swift 教程《The Swift Programming Language》

Forum & Lessons

Blog & News

Book & Resources