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

OC轉Swift,你需要換個思考方式

作者:翻炒吧蛋滾飯

連結:https://www.jianshu.com/p/9da924d5683f


前言

之前也有學過Swift,但並沒有使用到專案中,現在隨著Swift的完善以及三方開源SDK的支援越來越多,最近公司專案也決定採用OC+Swift混編的方式進行開發,一方面讓專案更簡潔、另一方面也滿足了自我學習的快感。


但實際開發中,Swift與OC的區別還是蠻大的,如果Swift不需要兼顧與OC的橋接、與OC共用CocoaTouch框架,那麼可以說Swift將會是與OC截然不同的兩種語言,前者誕生於2014年,後者誕生於上個世紀80年代,可以說代溝非常大了。


所以這裡列舉一些個人覺得跟OC差異化比較大的地方,在使用Swift程式設計的時候,希望不要總是帶著翻譯OC程式碼的思想,而是多利用Swift提供的現代化的新特性。(本篇文章基於Swift 4.0)

對型別的嚴格要求

在寫OC程式碼的時候,雖然一個類的屬性、一個方法的引數等等,我們都會定義對應的型別,但實際上,在使用中並沒有嚴格的去遵守,比如下麵的程式碼:

- (int)addA:(int)a B:(int)b {
    return a+b;
}

雖然定義了引數和傳回值都是int型別的,但我們可以傳進去任意型別進行加法運算,比如Double型別。

但是Swift就相對嚴格很多了:

func add(a: Int, b: Int) -> (Int) {
    return a+b
}

let b: UInt = 20
self.add(a: 10, b: b)    // 報錯,b是UInt,不是Int型別

就算將UInt傳給Int,也是會報錯的。所以請註意,Swift是強型別語言,強型別語言在編譯階段就為開發者排除了很大一部分的型別不匹配的錯誤。很多時候OC中可能修改了一個變數的型別,但可能忘了修改其他用到這個變數的地方,可能會導致unrecognized selector的錯誤丟擲,Swift則在編譯階段就很大程度上避免了這類問題的發生。

可選值

Swift不僅對型別是否匹配有嚴格要求,還對一個型別是否有值格外嚴格。

var a: String?
var b: String = "b"

對於上面兩個變數a和b,雖然他們都是String型別的,但實際上他們是不一樣的,a屬於可選String型別,b是String型別。如果想進行a+b的操作,則需要對a解包。所以在開發中,對a的處理就比較重要了:

// 確定不為空值的時候強制解包
print(a!)

// 不確定是不是空值的時候,進行可選系結
if let aTemp = a {
   print(aTemp)
}

// 使用空合運運算元保證在解包失敗的時候有一個備選值
print(a ?? "0")

雖然在Swift的官方教程中,對可選值的講解已經很透徹了,但實際專案中,不管是伺服器資料轉model還是一些邏輯上的對數值的處理,都比教程中複雜的多,所以在處理可選值的時候,請格外註意。

閉包更方便了,請合理利用

OC中的Block的語法可以說是非常噁心的了,我每次寫Block的時候,都會開啟這樣一個網站How Do I Declare A Block in Objective-C?,所以在實際開發中,很多人為了避免使用Block,在寫回呼的時候,都會使用代理樣式,但代理樣式寫起來,程式碼量就多了很多。在Swift中,閉包的語法得到了很好的規範,下麵的變數c就是一個閉包,引數、傳回值、實現,都一目瞭然,在使用中比Block方便了太多太多。

var c = { ()->() in }

當然Swift的閉包在使用中也需要處理retain cycle,同時還要註意逃逸閉、非逃逸閉包的區別。

get/set方法

在Swift中,引入了儲存屬性、計算屬性的概念,儲存屬性用於儲存一個值,計算屬性則提供get/set方法來對其他值進行操作,如果不好理解,可以類比OC的property關鍵字:

@property (nonatomiccopyNSString *aStr;

OC中的property關鍵字自動的幫我們生成兩個屬性,一個是_aStr:儲存屬性->用於儲存一個NSString,一個是aStr:計算屬性->get方法傳回_aStr、set方法對_aStr進行一些其他操作。所以來看一下沒有property關鍵字的Swift的儲存屬性、計算屬性(下麵是錯誤的寫法):

var name: String? {
        get {
            return name
        }
        set {
            name = newValue
        }
    }

如果寫成上面的形式,編譯器會報警告,因為name用在了自己的get/set方法,在執行中,因為name在自己的get/set方法中操作name(本質還是呼叫name的get/set方法),所以最後肯定都是以陷入死迴圈而告終。


那麼跟OC相比,想實現理想的get/set方法,Swift中的這個計算屬性name缺的是什麼呢?那就是一個儲存屬性_name:

var _name: String?
var name: String? {
        get {
            return _name
        }
        set {
            _name = newValue
        }
    }

以上,就實現了跟OC一樣的@property和get/set方法的重寫。

嚴格的初始化

Swift對類的初始化非常嚴格,相比OC加入了很多限制:必要建構式、逐一建構式、便利建構式、預設建構式、指定建構式、可失敗建構式。這些個概念確實需要好好區分一下,我也被繞的暈頭轉向的,只能沒事多看看官方檔案了。

引數的inout

OC中的方法,想要對自己的引數進行修改,直接修改就好了,但Swift不可以,你必須表明你的引數為inout才行:

// 引數需要標記為inout
func changeANum(a: inout Int) {
        a = 100
}

var a: Int = 10
// 傳入引數的時候需要取地址
self.changeANum(a: &a;)
print(a)

這種規定雖然在語法上麻煩了一點,但這無疑為一個函式的許可權做了限定,可以防止某些從外面傳來的引數被不小心修改了。

面向協議程式設計

OC中的Protocol因為支援可選方法、因為總被用於代理樣式、因為OC不是強型別語言,導致了OC中的Protocol並沒有發揮該有的作用。


但Swift對Protocol做出了很好的支援:比如支援繼承、支援結構體的遵守、支援預設實現等,所以Swift的出現,在iOS開發者中掀起了一股面向協議程式設計的熱潮(雖然別的語言早就玩膩了),希望剛接觸Swift的小夥伴可以改變下思想,去嘗試下Swift下的面向協議程式設計。

泛型

Swift是強型別語言,當然這也引入了很多麻煩,但Swift很好的支援了泛型(雖然也是別的語言玩剩下的,但相比OC進步了很多),使用泛型可以很好的對一個方法、類等做一些多型別的支援、協議的限定等,下麵的方法的引數可以是任何遵守了Equatable協議的變數:

func judgeEqual<TEquatable>(a: T, b: T) -> Bool {
        if a==b {
            return true
        } else {
            return false
        }
    }

對類C語法的拋棄

Swift拋棄了傳統C的++、–運算。拋棄了switch陳述句中的break,這讓每個case執行完,不會因為case後面忘了寫break而繼續執行到下一個case。


對我來說,我覺得這是好事,畢竟++、–很多時候會讓人很迷惑到底是先運算還是後運算的?同時OC中的switch的break陳述句也曾經讓我因為少寫了一個break而讓某個switch的兩個case同時執行導致了專案中的bug。

String、Array、Dictionary都是結構體

Swift中的struct和class非常的像,如果想找出他們的最大區別,那肯定就是struct是值型別的、class是取用型別的。所以對於Swift中同是結構體的String、Array、Dictionary來說,在開發中就要與OC中的NSString、NSArray、NSDictionary區別對待了。畢竟值型別每次賦值都會將值賦值給新的一個變數,而取用型別每次賦值都是指向同一個取用。


雖然蘋果為我們橋接了String和NSString,讓我們可以在String和NSString間隨意轉換,但還是要註意他們的區別。同時OC中透過NSString和NSMutableString來區分不可變和可變字串,Swift則透過let和var來區分不可變和可變。

多載運運算元

Swift支援多載運運算元,比如Swift支援”my “+”name “+”is ” -> “my name is “這樣的字串加法操作,這讓Swift比OC便利了很多,我們自己定義的類也支援運運算元的多載,比如下麵的類的“+”方法,則會合併兩個model中的每個屬性然後傳回一個新的model:

class numModel {
        var aNum: Int = 0
        var bNum: Int = 0
        var cNum: Int = 0
        init(a: Int, b: Int, c: Int) {
            aNum = a
            bNum = b
            cNum = c
        }

        static func +(left: numModel, right: numModel) -> numModel {
            return numModel(a: left.aNum+right.aNum, b: left.bNum+right.bNum, c: left.cNum+right.cNum)
        }
    }

函式式程式設計

如果對RAC、RxSwift有點瞭解的話,會發現其中的map、filter、zip、reduce等方法用起來很方便,但實際上Swift的Sequence協議就提供了這些方法,Array、Dictionary也都支援這些方法,在開發中可以善於利用。

後語

寫這篇文章的前一天晚上,突然想到想寫這樣一篇文章,記錄自己在學習Swift的過程中碰到的一些點(坑點?),目前只想到以上列舉的一些並寫了出來,後續在開發、學習中遇到的一些問題,可能會單獨拿出來寫一篇文章,也可能繼續補充到這一篇文章中。


如果以上寫的東西,在理解上有誤,非常感謝您能指出來讓我知道,非常感謝。


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

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

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

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

贊(0)

分享創造快樂