歡迎光臨
每天分享高質量文章

從 OC 到 Swift 的快速入門與專業實踐

作者:CoderHG

連結:https://www.jianshu.com/p/dcf208268caf


只會介紹與 OC 有明顯區別的地方,不會介紹 OC 中沒有的,比如元組。當前總結也只是蜻蜓點水而已,但是有 OC 的基礎,看這些已經足夠。

一、資料

Swift 是型別安全的語言:

  1. Swift 必須明確資料型別

  2. 如果取值錯誤會直接報錯

  3. Swift 會進行上限溢位檢查 (OC 也會檢查)

  4. Swift 沒有隱式型別轉換, 不允許不同型別的資料型別進行運算

1.1 簡單使用

Swift 很接近指令碼語言,尤其是在資料型別方面。定義資料型別只允許使用 let 與 var,let 標識的是常量, var 標識的是變數。那麼問題來了:那應該如何使用?在 Swift 中有可變型別麼?

let name = "CoderHG"
print(name)

上面簡單的定義了一個 name,如果沒有看到後面具體的值,根本就不知道 name 是一個字串型別。但是打斷點檢視,name 就是一個 String 型別的資料。將上面的程式碼中的 let 換成 var,會發現在上面的使用上沒有任何的區別, 能正常使用。


var name = "CoderHG"
print(name)

但是還是有區別的,上面已經介紹 let 標識的是常量, var 標識的是變數。盡然是變數,可否將一個數字型別的 2 賦值給 name 呢?答案肯定是不可以的。在 Swift 中的定義,必須在定義的那一刻就要決定其資料型別。所以下麵的這種定義是錯誤的:


var name
print(name)

這樣的話,Xcode 是會直接報錯的,因為在定義的時候沒有指明 name 是什麼型別。那麼問題又來了,如何定義一個字串,而又不希望有初始值呢?


var name: String
name = "CoderHG"
print(name)


這樣,name 就是一個字串型別的了。那麼又出先了一個新問題,我可否這樣定義:


let name: String
name = "CoderHG"
print(name)


使用 OC 的套路來思考上面的程式碼,肯定是不行的,因為一個常量只可能在定義的那一刻賦值,以後都是能讀取其值,即為 只讀。但是在 Swift 中有點不一樣,Swift 在意的是第一次賦值,而不是定義時。所以上面的程式碼是沒有問題的,但是如果再次給 name 賦值,那麼肯定就出錯了。

接下來主要介紹一下:在 Swift 中的可變型別。,在 OC 中一般使用 NSMutable¥ 來表示一個可變型別,那麼在 Swift 中如何表示呢?其實 var 不僅代表一個變數,也代表著 OC 中的可變性。比如,可以這麼使用:


var name = "CoderHG"
name.append(", Very GOOD!")
print(name)

如果換成 let 肯定是不行的。

上面簡單的介紹了一下 let 與 var 的簡單用法與註意事項。


看到這裡,是否會不由自主的想到 OC 中這樣的程式碼:


id obj = [[NSObject alloc] init];
   obj = [[HGPerson alloc] init];

然後 Swift 中也來了這麼一段:


var obj = NSObject()
// var obj:HGPerson = NSObject() as! HGPerson
print(obj)
obj = HGPerson()
print(obj)

以上兩段程式碼說明在 Swift 中的 var 也有 OC 中 id 的影子,所以在 Swift 中做型別檢測也是很有必要的。所以在 Swift 中會經常看類似這樣的程式碼:


var obj = NSObject()
print(obj)
obj = HGPerson()
print(obj)

let person = obj as! HGPerson
print(person)

在上面用到了一個型別轉換的標識 as!,在 Swift 中的全部型別轉換標識,如下:

  1. is : 用於判斷一個實體是否是某一種型別

  2. as : 將實體轉成某一種型別 (比如子類->父類轉換)

  3. as?:將某個型別轉成可選型別,透過判斷可選型別是否有值,來決定是否轉化成功

  4. as!: 將某個型別轉成具體的型別,但是註意:如果不是該型別,那麼程式會崩潰


1.2 資料型別

在 OC 中的資料型別主要分成兩種:基本資料型別與物件型別,在 Swift 中也一樣。但是在 Swift 中最為常見的是 結構體(基本資料型別),比如 String 與 Int8:


public struct String
public struct Int8 : FixedWidthInteger, SignedInteger

在 OC 中字串是物件型別,數字是基本資料型別(NSNumber 除外)。當然這些結構體型別的資料,都是可以無縫銜接到物件型別,比如 NSString,一般使用 String 就能滿足很多的場景。

1.3 可選與非可選資料型別

在 Swift 中,一個變數沒有 預設值 這種說法。一個變數要麼是有值、要麼沒有值,這就叫做 可選型別。Swift 中的可選型別,是一種單獨的資料型別。有可選型別,那麼就有非可選型別。

關於這部分,前不久在簡書上簡單的總結了一下,可以參考 對 Swift 中可選型別的理解。

有值與沒值、是兩種狀態,而不是兩種具體的值。


1.4 結構體

先看一個簡單的結構體:

// 定義一個結構體
struct HGStruct {
    var name:String?
    var des:String?

    func desFunc() -> Void {
        print(name! + "_" + des!)
    }
}

// 可以這樣使用:
// 無參建構式
var st = HGStruct()
// 逐一建構式
st = HGStruct(name: "HG", des: "Good")
// 呼叫結構體函式
st.desFunc()


對於一個結構體來說,只要是有屬性,系統預設生成兩個建構式,一個是無參建構式,一個是 逐一建構式。

逐一建構式: 將所有的屬性作為引數的建構式。

建構式:不用 func 作為修飾,函式名統一為 init。

以上的兩種建構式是自動生成的,也可以自定義建構式。比如:

// 自定義建構式
init(name:String) {
    self.name = name
    des = "Good!"
}


自定義的建構式有一個明顯的特點,不需要加 func 關鍵字。還有一個特點是:一旦自定義了建構式,那麼自動生成的建構式都將失效。

這裡有一個方法可以做到建構式的隨意組合,就是重寫 逐一建構式,將所有的引數都弄一個預設值。如下:

// 重寫 逐一建構式
init(name:String = ""des:String = "") {
    self.name = name
    self.des = des
}


關於結構體,也是屬於基本資料型別,是 值 型別,是不能直接在結構體內部直接修改其 屬性 的值的。比如:

// 更新名字
func update(name:String) -> Void {
    self.name = name
}


這樣是會直接報錯的,必須在 func 的前面加一個關鍵字 mutating。如:

// 更新名字
mutating func update(name:String) -> Void {
    self.name = name
}


到這裡,關於 Swift 中結構體的使用介紹,基本差不多了。在 Swift 的實際開發中,結構體的使用也是比較頻繁的。由上面的介紹可以知道,功能也比 OC 中的多,主要的原因是有 函式。在上面的程式碼中也能看在,也有 self 關鍵字,使用方式與 Class 幾乎一致。所以在一些輕量級的場合,可以直接選擇使用結構體。

1.5 列舉

簡單的定義:

// 列舉定義
enum HGEnum {
    case go
    case back
}


可以這樣的使用:

{
    direction(d: HGEnum.go)
}

func direction(d:HGEnum) -> Void {
    switch d {
    case .go:
        print("go")
    case .back:
        print("back")

    }
}


從上面也能看出獲取列舉的方式,HGEnum.go 與 .go 是同一個,但是要保證只有這個列舉有這個 go,否則出錯。

關於列舉的值

直接這樣列印:


print(HGEnum.go)


發現列印結果是:go,列舉值僅僅是一個符號, 不代表任何型別。如果想要系結原始值, 必須指明列舉的型別,比如:

// 列舉定義
enum HGEnum:String {
    case go   = "go_value"
    case back = "back_value"
}


一旦指明瞭列舉的型別,在使用上沒有區別,可以使用 rawValue 獲取具體的值:

print(HGEnum.go.rawValue)  // go_value
print(HGEnum.go)           // go


列舉有能定義函式


func enumFunc() {
    print(self.rawValue, "_哈哈哈哈哈")
}


代用方式:

HGEnum.go.enumFunc()
HGEnum.back.enumFunc()


二、方法到函式

在 Swift 中就沒有方法這種叫法了,統一稱函式。函式定義的模板如下:

func 函式名(引數串列) -> 傳回值型別 { 
    程式碼塊 
    return 傳回值 


對於函式這一塊,沒有什麼特別的。這裡有一個規律,就是 Swift 函式轉成 OC 方法的時候,是這樣的:

// Swift 函式
func hello(name:Stringfrom:String) -> String {
    return "你好!我是 " + name + ", 來自於 " + from
}

// 轉成 OC 是這樣的 
// - (NSString*)helloWithName:(NSString*)name from:(NSString*)from;

上面程式碼的亮點是函式名與方法名,是有規律的。這也給我們一個在 OC 中方法命名規範的提醒:第一個引數以 With 做拼接,並首字母大寫,其它引數前的方法名部分直接使用引數的名稱。當然,規範僅僅是一個規範而已,蘋果的 API 也並非全部按照這樣的規範,比如:

// tap
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // TODO: 待處理
}


關於 函式 這一塊,相對 OC 來說有以下兩個明顯的不同:

  1. 函式中可以定義函式,這個功能在 OC 也有類似的,就是方法中定義 Block。

  2. 函式多載,這個在 OC 中是實現不了的。


三、類

在 OC 中有三種 Class:Block,NSProxy 與 NSObject。據我現在所知,在 Swift 中沒有了 Block,但是有了一種閉包的東西。除此之外,在 Swift 中的 Class 可以不用繼承任何的基類。

在 OC 中即使是一個簡單的資料模型都需要繼承於 NSObeject,顯得有些重量級。但是在實際上還是有很多區別的。

3.1 簡單的定義

有兩個致命的規律:

  1. 定義的 Class 一定要有屬性,否則直接報錯

  2. 建立一個類的實體, 必須在建立之後, 裡面所有的非可選屬性必須有值,否則報錯


class HGPerson {

}


沒有任何屬性,直接報錯。


class HGPerson {
    var name:String
}

name 為非可選,建立實體之後 name 沒有值,直接報錯。

class HGPerson {
    var name:String
    // var name:String = ""
    // 建構式
    init() {
        name = ""
    }
}


重寫建構式,非可選屬性 name 預設有值。每個 Class 都會有一個預設的無參建構式,一旦有重寫,預設建構式將失效。

在使用上,與 OC 中的幾乎完全一樣。

3.2 特殊方法

在一個 Class 中,我們往往比較在乎的是一個實體的生命週期。總之一句話:生於建構式,毀於虛構函式。

建構式:一個特殊的函式,與結構體中的一樣。不用 func 作為修飾,函式名統一為 init。

虛構函式:實體銷毀時系統呼叫的函式 deinit,功能與 OC 中的 dealloc 一樣。

3.3 setter 與 getter 方法

這裡的 setter 與 getter 方法,和 OC 中的還有點不一樣。比如:

var doSomething:String {
    set {
        // setter

    }

    get {
        // getter
        return ""
    }
}


這裡需要註意一點,在 Swift 中的只讀屬性,將上面的 set 去掉,就是隻讀屬性的。

3.4 屬性監聽

var sex:String = "" {
    willSet(newValue) {
        print("當前的值 = " + self.sex + ",新值 = " + newValue)
    }
    didSet(oldValue) {
        print("當前的值 = " + self.sex + ",之前的值 = " + oldValue)
    }
}


這裡要註意一個問題:在建構式中的 setter 方法是不會被監聽到的。

3.3 註意事項

Swift 中的 Class 是可以沒有基類的。

四、協議(代理)

4.1 簡單使用

定義

/// MARK 定義一個代理
protocol DetailDelegate: NSObjectProtocol {
    // 從控制器傳回 content 內容
    func detail(vc:DelegateDetailController?, content:String?)
}


關鍵字:protocol 與 NSObjectProtocol。這裡需要註意一點的是,在 Swift 中的協議也是可以沒有基類的,在 OC 中也一樣,但是一般都是繼承於 NSObject 協議。在 Swift 中有以下三種情況:

  1. 沒有繼承,這種情況只能使用在沒有繼承 NSObject 的 Class 中,不能使用 weak 修飾,畢竟 weak 只能修飾 Class。

  2. 繼承於 class,這種情況可用於所有的 Class。

  3. 繼承於 NSObjectProtocol,這種情況可用於所有的 Class。與第2中的區別是,這個協議自帶了很多的系統協議。所以繼承於這種協議的不推薦使用在沒有繼承於 NSObject 的 Class 中,因為在 Swift 中的所有協議函式都是強制必須要實現的。


綜上,繼承於 class一般使用在沒有繼承於 NSObject 的 Class 中,而繼承於 NSObjectProtocol一般使用於繼承於 NSObject 的 Class 中。沒有繼承的使用在結構體與列舉中,這個就很厲害了,在上面的結構體與列舉中就知道,這兩種資料結構也是可以定義函式的,所以有這樣的的協議場景也是很相當合理的。

delegate變數

// 定義個代理變數
weak var delegate:DetailDelegate?


與 OC 一致,需要弱化。

執行


let cell = tableView.cellForRow(at: indexPath)
delegate?.detail(vc: self, content: cell?.textLabel?.text)


五、控制器中的程式碼佈局

這裡說的控制器程式碼佈局,僅僅是一個例子,只要是 Class 都是同樣的套路

在 OC 中,預設的程式碼結構是這樣的:

#import "HomeController.h"

@interface HomeController ()

@end

@implementation HomeController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}


@end


核心的程式碼都是寫在 @implementation 與 @end 之間,如果要將其中的功能分開,只能是透過 分類 或者直接另建檔案。在 Swift 中,預設的結構是這樣的:

class HomeControllerUIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}


全部的程式碼,都是寫在第一個大括號中。但是可以藉助 extension 來做分割:

/// 系統相關函式實現
class HomeController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
}

/// 登入 相關的函式實現
extension HomeController {

}

/// 叫車 相關的函式實現
extension HomeController {

}

/// UITableViewDelegate 的協議函式
extension HomeController: UITableViewDelegate {

}

/// UITableViewDataSource 的協議函式
extension HomeController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 45;
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "id")

        return cell!
    }
}

/// HGObjectDelegate 協議函式
extension HomeController: HGObjectDelegate {


}


以上是一個簡單的分割,在實際的開發中,可能沒有這麼簡單,畢竟實際的專案的程式碼更加複雜。

上面簡單的程式碼中可以看到可以透過 extension 做針對性的分離。

六、@objc

@objc 這個關鍵組合,作用是在 Swift 中實現,在 OC 中使用。

6.1 協議中使用

在協議中,會看到這個關鍵詞。在上面的介紹中,Swift 中的協議一旦被遵循,那所有的函式都必須是先實現的,沒有可選函式這一說。換成 OC 的說法就是必選的。在 OC 中沒有實現必選方法是報警告,在 Swift 中是直接報錯。那麼問題來了:在 OC 中是有可選協議方法的,如果這個協議是在 Swift 中實現,應該如何處理呢?

  1. OC 中如何使用 Swift 中的協議?

  2. 如何在 Swift 中給 OC 提供可選協議函式?


一個簡單的例子如下:

@objc
protocol HGObjectDelegate {
    // 可選的協議方法
    @objc optional func optionalFunc()
}


@objc 代表可以在 OC 中使用,optional 在 OC 中是可選的協議方法。

現在看在 HGObjectDelegate 沒有任何的整合,相當於在 OC 中沒有繼承 NSObject 一樣。但是可以直接使用與 OC 中的所有 Class 中。在 Swift 中,這個協議是不能使用在沒有繼承的 Class 中的。

6.2 函式中使用

在 Swift 實現的函式,是可以很好的轉換成 OC 方法的,一般不使用轉換,其實在上面也已經有介紹。但是 Swift 中的函式與 OC 中的方法還是有所差異的,比如在 Swift 中有多載,然而。。。。這種情況就需要 @objc 做一下轉換。比如以下的程式碼:

@objc(sumIntWithA:b:)
func sum(a:Int, b:Int) -> Int {
    print("Int")
    return a+b;
}

@objc(sumDoubleWithA:b:)
func sum(a:Double, b:Double) -> Double {
    print("Double")
    return a+b
}


一看就懂,無需介紹。

在 OC 中這麼使用:


HGObject* obj = [HGObject new];
NSInteger int_Result = [obj sumIntWithA:9 b:4];
float doble_Result = [obj sumDoubleWithA:3.2 b:2.3];
NSLog(@"%zd %f", int_Result, doble_Result);


在 Swift 中這麼使用:


let obj = HGObject();
let sum1 = obj.sum(a: 1, b: 2)
let sum2 = obj.sum(a: 2.3, b: 2.5)
print(sum1, sum2)


編號275,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂