Swift語法(更新部分swift5)
簡介
-
優於OC,快速,安全
-
預編譯指令包括宏定義(OC用的太多了)
-
取消了OC指針和不安全訪問的使用(看不到星星了)
-
全部
點語法,取消[ ] -
對Foundation框架做了很多改變,去除了NS,將絕大部分class轉換成struct結構體(為了考慮性能和安全性,絕大部分使用結構體來代替以前的類,但是在實際使用感覺不到)
-
以前是OC調UIKit,現在就是Swift調UIKit,調用的SDK沒有變化
-
swift因為語法的嚴謹性可以讓很多錯誤提前遇到,這樣很少出現bug讓程序停在main導致無法找到
-
@UIApplicationMain是程序的入口 -
只有.h沒有.m
-
所有的代碼都包括在{}里,默認方法func都有縮進
-
OC語法的allocInit替換成()
-
OS12.2的各種版本,包括macos,tvos等等,已經包含了swift5庫,這意味着以前可能swift寫的代碼體積普遍大於OC,現在則整體做了體積壓縮
基礎
-
基本寫法中不使
self.,OC中的self.view之類寫法可以省略self.,self在閉包或者編譯器提示的時候再使用 -
分號是用來分割語句的,如果一行寫很多,就可以加分號,一般時候可以不加
let a = 10; let b = 15 let aa = 10,bb = 15
- #function打印執行的函數
debugPrint(#function)
- 添加標記用到
// MARK: -選擇,如果是接下來要做的可以用// TODO:和// FIXME:
變量和常量
-
let:常量,如果從沒被修改過,那就用let,不可變的更安全,所以盡量使用let,需要變化時候改var.
像UI層的對象,采用let,但是還可以修改對應內的屬性,例如let UIView,隨后修改顏色,是因為本身地址沒變 -
var:變量
-
可以自動推導類型(Option + Click)整數默認Int,小數默認Double,但是不會做
隱試轉換,也就是說只要類型不同,無法直接計算 -
Swift對類型要求異常嚴格,任何不同類型的數據不能直接運算(哪怕是Int和Double),不會做一些自動的轉換來轉換成Double。Swift不存在基本數據類型,Int和Double都是結構體其實,強轉用Double(x)完成,或者在定義的時候直接指定變量的類型let x : Double = 10;(很少使用)
可選項(Optional)
-
定義變量時,如果是可選的,表示可以有值,也可以是nil,用
? -
強行解包
!,要少用,可能會崩 -
最常見的錯誤:解包的時候發現nil。
fatal error: unexpectedly found nil while unwrapping an Optional value -
let可選的話,沒有默認值,需要賦值。var可選的話,默認值為nil
-
可選項在參與計算時候必須解包,所以為避免很多
!,一般會采用if let/guard let的方式進行判斷計算
邏輯分支(if)
- 三目運算符:Int(x!) > 5 ? print("dayu5") : print("xiaoyu5") 或者 Int(x!) > 5 ? print("dayu5") : () 這樣就對后面的不作處理。()表示空執行。
- swift中不存在
非真既假,只有true和false
不強行解包的方法
- 強行解包的寫法:
"??"。 一個簡單的三目,這樣無需做很多判斷,是不是xy都不得0了。注意?? 必須加個(),因為??操作符號的優先級低,是最后判斷的,所以不加()可能后面的都當做判斷不執行了。??寫法不需要解包了
func demo(x: Int? , y : Int?) { print( (x ?? 0) + (y ?? 0) ) }
- if let的寫法:iflet/ifvar連用就可以進行判斷,這個let的x1和y1作用於只在循環里,iflet/var也不需要解包了
if let x1 = x , let y1 = y { print(x1 + y1) }
- guard let else的寫法:和iflet相反,else里面一般是沒值return,之后才執行需要的代碼。guard可以降低分支層次,如果要執行的代碼很多,本來在if里執行,{}就多了一層,如果用guard守衛一定有值才執行,這樣層次就少了一層。guardLet很重要,也可以省略很多解包
guard let x1 = x , let y1 = y else { print("x&y沒值") return } print(x1 + y1)
- guardlet和iflet可以用同名變量接收。因為總會取名字,if let name = name這樣就可以,注意后面使用的時候用非空的那個!並且iflet和guardlet可以依次判斷,先判斷是一個字典,再拿字典的數組,在判斷數組的值,可以一條線判斷出來。
總結
func demo() { let name: String? = "張三" let age: String? = "18" // 目的: 讓可選項在不強行解包!的情況下做計算 // 方式1:采用?? print("方式1" + (name ?? "") + (age ?? "")) // 方式2:采用if let if let name = name, let age = age { print("方式2" + name + age) } // 方式3:采用guard let guard let nameA = name, let ageA = age else{ return; } print("方式3" + nameA + ageA) }
循環
switch循環
-
OC中分支必須是整數,每一個語句都需要break,如果要定義局部變量需要{}
-
Swift可以任意判斷,一般不需要break,多值判斷用逗號,所有分支都至少需要一條指令,如果什么都不干,才要寫break
func demo(name : String) { switch name { case "guoguo","aixin": print("guoguo") default: print("shit") } }
for循環
- swift取消了
i++和++i
for i in 0...5 { } // ...是閉區間,..<是開區間 for i in 0..<5 { }
- 反序遍歷:
for i in (0..<10).reversed() { }
字符串
-
用String,是一個結構體,具有絕大多數NSString功能,支持直接遍歷
-
遍歷:
func demo3() { let str = "wowosnshi是" for s in str { print(s) } }
- 長度:
// 返回指定編碼對應的字節數,每個漢字三個字節 print(str.lengthOfBytes(using: .utf8)) // 返回真正字符串長度 print(str.count)
-
拼接:要注意可選項拼接不解決會帶上Optional,剩下的都可以拼接,再也不用看StringWithFormat了
let name = "AA"
let age = 19
let title : String? = "sss"
print("(name)(age)(title ?? "")")
-
格式化:
let h = 8 , m = 10, s = 44 // OC中用stringWithFormat格式化日期,Swift中可以 let strDate = String(format: "%02d-%02d-%02d", h,m,s) print(strDate)
- 4.2截取字符串
func demo3() {
let str = "hello world" // 截取前三個字符 print(str.prefix(3)) // 截取后三個 print(str.suffix(3)) // 截取3-6范圍 let start = str.index(str.startIndex, offsetBy: 3) let end = str.index(str.startIndex, offsetBy: 6) print(String(str[start..<end])) }
數組:
- 就是中括號,注意數組的類型,並且基本數據類型不需要包裝,可以直接方數組里,如果類型不一樣(混合數組,但是基本不用),自動推導[NSObject]。在Swift中還有一個[AnyObject類型],標示任意對象,因為在Swift中一個類可以沒有任何父類。
遍歷: let array = ["張三","李四","王五"] // 遍歷1(按照下標遍歷) for i in 0..<array.count { } // 遍歷2(遍歷元素) for s in array { } // 遍歷3(同時遍歷下標和元素) for e in array.enumerated() { // let e: (offset: Int, element: String) e是一個元組 print("\(e.offset), \(e.element)") } // 遍歷4(同時遍歷下標和元素) for (n,s) in array.enumerated() { print("\(n),\(s)") } // 反序遍歷 for s in array.reversed() { } // 反序索引下標(這樣寫才對,先枚舉再反序) for (n,s) in array.enumerated().reversed() { }
-
增刪改:
array.append("AA") array[1] = "BBB" array.remove(at: 2) -
合並:用“+”號。但是要合並的數組的兩個類型必須一致。
字典
-
一般是[String:Any]
-
增刪改:和數組都類似,就是兩個字典合並不像數組直接相加,而是需要遍歷
函數
-
Swift的類,結構體,枚舉三種都有構造函數,都可以有方法,就像OC的類。枚舉再swift變化很大,一般開發不會用到太高級語法。
-
外部參數,當外部參數用_替代的時候,會在外部調用的時候忽略形參名
func demo5(num1 a: Int,num2 b: Int) -> Int { return a + b }
- 函數的默認值(OC不具備),這個使Swift比OC靈活很多很多,一個方法可以做很多事,因為OC會有各種參數和組合,Swift只需寫一個最多的參數,然后不需要的設定默認值就是了
-
無返回值 :直接省略 () Void都可以
-
閉包:類似Block,比Block還廣泛。OC中Block是匿名函數,Swift中函數是特殊的閉包。閉包在整個開發中和Block的應用場景一樣。用於控制器/自定義視圖/異步執行完成的回調。這些回調的特點就是都是以參數回調處理結果,返回值為Void。
let biBao = { (x: Int) -> Int in return x + 100 } print(biBao(10))
-
GCD:將任務添加到隊列,指定執行任務的函數。任務就是Block/閉包,隊列以同步/異步的方式執行。
func loadData(compeletion:@escaping ( _ result: [String])->()) -> Void { DispatchQueue.global().async { print("耗時操作會獲得一些結果 (Thread.current)") Thread.sleep(forTimeInterval: 1.0) let json = ["天氣","不錯","刮大風"] // 主線程回調 DispatchQueue.main.async(execute: { print("主線程更新UI (Thread.current)") // 回調 -> 通過參數傳遞 執行閉 compeletion(json) }) } } 調用:
// 執行的適合我就拿到了值 loadData { (result) in print("獲取的新聞數據 \(result)") }
*尾隨閉包:如果函數的最后一個參數是閉包,那么參數就省略了,最后一個參數直接{}大括號包裝
面向對象
基本構造函數
-
()就是allocInit,在Swift中對應init()。在swift中一個項目所有類都是共享的,可以直接訪問,每一個類都默認有一個命名空間。A.name B.name God.name Dog.name。同一個類可以從屬於不同的命名空間(假如有一個框架有Person類,做用戶,還有一個框架做后台,也用Person。在OC中就只能靠前綴解決,HouTaiPerson,KuangJiaPerson。而Swift中的命名空間就是項目名。AAA項目有一個Person,那么AAA.Person就是AAA的Person類,此時再導入框架,那也是框架的.Person)
-
只要是構造函數,就需要給屬性設置
初始值 -
所謂構造函數,在oc中其實就是
allocInit,在swift中目前只用init就行,一般的對象init時候會在前面默認加上override,意為重寫,重寫最大的特點是方法里面可以調用super.init()來實現父類的這個方法,而且super.init()是隱式的,意味着如果不寫也會調用,但是程序員其實最害怕的是我什么都沒寫?為什么調用了?,所以為了完整的自我實現面向對象思想,還是要自己添加super.init()
重載構造函數
-
重載構造函數:(重寫是父類有這個方法,override。重載是函數名相同,參數和個數不同。init就重寫,init+參數就重載。OC是沒有重載的!都是initWithXXXXX)。重載其實是最基本的方式,OC沒有其實很low,但是Swift有。
-
重載是所有方法都可以實現的,不局限於構造函數,例
CGRect(,可以看到很多重載的實現 -
Nsobject類的isa屬性就是為了記錄當前狀態的對象是什么的,就是一個student對象,雲雲。在swift中打p可以打印類內容
KVC構造函數
-
KVC是OC特有的,是運行時特性,在運行時動態的發消息賦值,所以在KVC構造時候,
super.init()應該先調用,隨后再setValueForKey保證都創建完畢再調用。調用setValueForKey的時候,會判斷賦值的對象是不是對象。要是對象的話就去判斷是否已創建,沒有創建則實例化。要是基本數據類型則不作處理,所以基本數據類型必須指定初始值來分配空間才行 -
運行時可以獲取到一個對象的所有屬性方法等等,獲取到了屬性可以KVC進行字典轉模型,這是所有第三方轉模型插件的基礎,獲取到了方法,可以動態的發送各種方法。在swift中,如果一個基本變量Int什么的用?修飾了,可選了。此時運行時是找不到的,所以KVC會崩潰,同時,如果修飾了private屬性,運行時也是找不到,KVC也會崩潰。其實在OC中,private是基本不用的,而且存不住任何私有,都可以被運行時找出來。但是swift就是真的藏起來了,找不到這個屬性和方法了外界
-
一般在模型中加個?,然后用KVC實現(先調用init因為是運行時機制)
-
模型中屬性定義:基本數據類型 = 0,對象設置?
運行時中,基本類型設置? 屬性設置私有都會讓運行時拿不到,此時kvc就會出錯。
- 標准寫法
@objc var name: String? @objc var age: Int = 0 init(dict: [String : Any]) { super.init() setValuesForKeys(dict) } override func setValue(_ value: Any?, forUndefinedKey key: String) { }
- 如果子類沒有重寫父類方法,調用的時候就會直接調用父類的方法。當繼承一個類,就繼承所有屬性和方法,包括KVC。當PERSON寫好了KVC后,STudent即便什么也不寫,也有父類的方法
便利構造函數
-
關鍵字Convenience
-
目的:條件判斷,只有滿足條件才實例化對象,防止不必要的內存開銷,簡化對象創建。本身是不負責屬性的創建和初始化的。
-
說白了就是有些函數 帶個? 返回值可以返回空,這就是便利構造函數的功勞。由於函數里多為判斷,是否要構造,不符合構造條件就不創建,而創建的過程需要調用別的構造函數才能實現,所以最后都要調用
self.init(xxxxxxx) -
主要用於條件檢查和控件創建。
-
在控件創建的時候,就不用?了,因為我就是為了創建控件的。
-
不能被繼承,不能被重寫
-
deinit:類似OC的Dealloc
-
便利構造函數 + 分類可以省略抽取很多代碼。例如給UITextField/UIButton寫分類,然后寫便利構造函數,方便。
