作者:CoderHG
連結:https://www.jianshu.com/p/dcf208268caf
只會介紹與 OC 有明顯區別的地方,不會介紹 OC 中沒有的,比如元組。當前總結也只是蜻蜓點水而已,但是有 OC 的基礎,看這些已經足夠。
一、資料
Swift 是型別安全的語言:
-
Swift 必須明確資料型別
-
如果取值錯誤會直接報錯
-
Swift 會進行上限溢位檢查 (OC 也會檢查)
-
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 中的全部型別轉換標識,如下:
-
is : 用於判斷一個實體是否是某一種型別
-
as : 將實體轉成某一種型別 (比如子類->父類轉換)
-
as?:將某個型別轉成可選型別,透過判斷可選型別是否有值,來決定是否轉化成功
-
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:String, from: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 來說有以下兩個明顯的不同:
-
函式中可以定義函式,這個功能在 OC 也有類似的,就是方法中定義 Block。
-
函式多載,這個在 OC 中是實現不了的。
三、類
在 OC 中有三種 Class:Block,NSProxy 與 NSObject。據我現在所知,在 Swift 中沒有了 Block,但是有了一種閉包的東西。除此之外,在 Swift 中的 Class 可以不用繼承任何的基類。
在 OC 中即使是一個簡單的資料模型都需要繼承於 NSObeject,顯得有些重量級。但是在實際上還是有很多區別的。
3.1 簡單的定義
有兩個致命的規律:
-
定義的 Class 一定要有屬性,否則直接報錯
-
建立一個類的實體, 必須在建立之後, 裡面所有的非可選屬性必須有值,否則報錯
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 中有以下三種情況:
-
沒有繼承,這種情況只能使用在沒有繼承 NSObject 的 Class 中,不能使用 weak 修飾,畢竟 weak 只能修飾 Class。
-
繼承於 class,這種情況可用於所有的 Class。
-
繼承於 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 HomeController: UIViewController {
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 中實現,應該如何處理呢?
-
OC 中如何使用 Swift 中的協議?
-
如何在 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、資料庫、運維等。