swift4.0語法雜記(精簡版)

小視頻

001--swift簡史小視頻

002--Playground體驗

003--常量&變量

1、swift簡史

一、介紹 swift是蘋果公司於2014年推出用於撰寫OS和iOS應用程序的語言。它由蘋果開發者工具部門總監「克里斯.拉特納」在2010年開始着手設計,歷時一年完成基本的架構。到後來蘋果公司大力投入swift語言的研發,於2014年發佈這一語言的初版本。swift2.0以後的語法則趨於穩定,2017年發佈的swift4.0雖有改動,但也只是增添了一些新特性。這些新特性須要在Xcode9上運行才能顯示出效果。值得一提的是它支持unicode9,也就是說,能夠用某些圖片圖標來充當變量。 例如:程序員

"👧🏽".count // 人 + 膚色
"👨‍👩‍👧‍👦".count // 有4個成員的家庭
"👱🏾\u{200D}👩🏽\u{200D}👧🏿\u{200D}👦🏻".count // 家庭 + 膚色
"👩🏻‍🚒".count // 人 + 膚色 + 職業
複製代碼

二、特色objective-c

  • swift取消了預編譯指令,宏也被包括在內。 某些開發者爲了讓Objective-C和swift代碼兼容,會盡少在Objective-C中定義宏。
  • 取消了Objective-C中的指針等其餘不安全訪問的使用
  • 使用點語法來調用屬性或者函數
  • 去除了NS前綴

三、爲何要學習swiftjson

  • swift做爲面向協議語言,不只能寫移動端,也能夠作到搭建服務器端。
  • 縱觀國內外iOS開發界,已經有許多公司直接或間接採用swift開發,使用swift語言開發已成爲將來iOS開發的趨勢。
  • swift以簡潔、優雅等優勢迅速俘獲廣大開發者的青睞。

2、用playground體驗swift開發

  • 打開Xcode,選擇建立一個playground項目

  • 建立一個普通的UIView對象

正如上圖所示,playgound文件的左邊是代碼區,右邊則是顯示結果的區域。當點擊用於眼睛時會實時顯示出界面效果。swift

  • swift與objective-C的重大區別api

    • 在swift中是沒有.h和.m文件之分的。全部的代碼所有都存儲在一個文件裏面。
    • 在swift中全部的代碼都被封裝在{}裏面
    • OC使用alloc init進行初始化,而swift使用()
    • OC中使用[]來調用方法,而swift中採用點語法。好比UIColor.red
    • swift中不須要用分號分割語句

3、常量和變量

一、數據類型 在swift中也有各類數據類型來存儲不一樣的信息。下表列舉的是常見的數據類型變量。 數組

但其實,在swift中,是不存在基本的數據類型的,所謂的數據類型,其實都只是結構體。這也是swift中的一個特色。安全

二、變量和常量bash

  • 聲明 swift中用let聲明常量,用var聲明變量。
var x = 10;
let y = 20;

let z   //錯誤示範,let z 在聲明的時候並無賦值常量是不可改變的,只能在聲明時賦值
複製代碼

在開發中,一般會優先選擇使用let,由於不可變會更安全一點。因此建議在寫代碼之時,先選擇let,等到須要變化的時候再改爲var。服務器

  • 自動推導

建立一個UIView,不指定類型。能夠看到控制檯上會打印出UIView的信息。這個現象被稱爲swift的自動推導。事實上,在代碼左側定義的類型只是程序員但願的類型,而右側纔是程序真實的類型。網絡

let z = UIView()
print(z)
複製代碼

也就是說,變量或常量的類型會根據右側代碼執行的結果,推導出對應的類型。 可使用熱鍵option點擊查看類型。

  • swift對類型的嚴格要求

在swift中,任何不一樣類型的數據之間是不容許直接運算的。好比下面這段代碼就會報錯。

//錯誤示範
let a = 10
let b = 12.5
print(x + y)
複製代碼

若是非要讓不一樣類型數據之間可以運算,能夠將其中一個類型進行轉換。

let a = 10
let b = 12.5
print(a + Int(b))
複製代碼

此時獲得的結果就是22。在swift中,作類型轉換時是將數據括起來,至關於swift結構體中的構造函數。

固然也能夠將前面的整數轉換成Double型。此時就能打印出小數來。

print(Double(a)+b)
複製代碼

4、String類型和Bool類型

一、String類型

  • 聲明

直接用雙引號將數據引發來

let str = "小仙女"
let str1:String = "hahh"
複製代碼
  • 拼接

字符串的鏈接有兩種方法,一種是經過加號來鏈接,另外一種則是經過反斜杆進行插入。

let str = "小仙女"
let mesg1 = "one"+str //用加號的方式
let mesg2 = "two,\(str)" //反斜槓的方式
print(mesg1,mesg2)
複製代碼

在作字符串拼接時要注意加號和反斜槓後面都不能出現空格,否則會報錯。

  • 拼接字符串時格式的變化

假設在某些特定的地方須要輸出特定位數的字符,好比或時間的輸出,就須要使用佔位符來調整字符串的格式。使用String的構造函數,調用format方法,%0後面加上數字就表示須要佔多少位數。

let min = 2
let second = 10

String(format: "%02d:%02d", min,second)
複製代碼
  • 遍歷

調用字符串的characters屬性,採用for...in...的方式來遍歷字符串。

for c in str{
    print(c)      //swift4中的遍歷
}
print(str.count)  //打印字符串長度

for char in myString.characters {
    print(char)   // swift3的遍歷
 } 
print(str..characters.count)  //swift3打印字符串長度
複製代碼
  • 字符串的截取

最方便的方式就是將String類型轉換成OC的NSString類型,再來截取。

let urlStr = "www.baidu.com"
let header = (urlStr as NSString).substring(to: 3)  //截取前三位

let middle = (urlStr as NSString).substring(with: NSMakeRange(4, 5))//去除前四個字符截取,範圍以後五位字符

let footer = (urlStr as NSString).substring(from: 10)   //從第十個字符開始截取
複製代碼

二、Bool類型

與其餘語言同樣,Bool類型表示的就是真假,可是不一樣於Objective-C,swift中用true和false來表示真假。

5、可選類型

在Objective-C開發中,若是一個變量暫時不會使用到,能夠將它賦值爲0或者賦值爲空,而在swift中,nil是一個特殊的類型,若是它和真實類型不匹配是不能進行賦值的。可是開發中將變量賦值爲空是在所不免的事情,所以就推出了可選類型。 可選類型是swift的一大特點,在定義變量時,若是指定這個變量是可選的話,就是說這個變量能夠有一個指定類型的值或者爲nil。

一、定義一個optional的變量

let x:Optional = 10
print(x)
複製代碼

點擊進去查看,能夠發現Option實際上是一個枚舉類型。這個枚舉有兩個值,一個是none,表示沒有值,而另外一個是some,表示某一類值。 在輸出的時候,能夠看見控制檯上的內容Optional(10),它的做用就是提示這是一個可選值。

而在實際開發中,通常不用上述方式建立可選值,而是指定一個類型,再在其後添一個問號。

let x:Optional = 10  //第一種寫法

let x:Int? = 20     //第二種寫法
print(x)
複製代碼

上述代碼問號的意思就是定義一個可選的Int類型,可能沒有值,也可能有一個整數。

二、 解包

試試將上面案例x和y相加,這個時候還能輸出結果麼?

此時能夠看到編譯器已經報錯。在前面的教程中提到過,不一樣類型的值是不能直接運算的。而可選項有兩種值的產生,若它的值爲nil則不能參加計算。

所以引入解包的概念,「!」表明強制解包。它的意思是從可選值中強行獲取對應的非空值。

print(x!+y!)
複製代碼

三、解包常見錯誤

//錯誤示範1
let y : Int?
print(y)
複製代碼

使用let定義的是常量,在初始化時必需要給出值。

//錯誤示範2:
let y : Int? = nil
print(y)
複製代碼

強制解包是危險操做,若是可選值爲nil,強制解包系統會奔潰。

四、let和var的可選項默認值

//默認值測試
let x: Int?
print(x)
var y :Int?
print(y)
複製代碼

用let作測試時會直接報錯,說明let的可選值是沒有默認值的,而用var作測試時,報錯信息就變成了警告,運行的結果爲nil。能夠由此推測出var的可選項默認值爲nil。

swift中有規定,對象中的任何屬性在建立對象時,都必須有明確的初始化值。

五、可選綁定

if let/var表示。它將變量賦值給一個臨時變量,在這個操做中會作兩步操做:首先判斷變量是否有值,若是沒有值,則直接不執行大括號裏面的內容;若是有值,系統會自動將變量進行解包,而且將解包後的結果,賦值給臨時變量。

好比下面這個例子:

經過一個字符串建立NSURL對象

let url: URL? = URL(string: "https://www.baidu.com")
複製代碼

接着建立NSURLRequest對象。強制解包很是危險,當url有中文的時候可能會變成nil。因此要判斷url是否爲空再對其進行解包。

if let url = url {
    let request = URLRequest(url: url)
}
複製代碼

6、swift中的分支

一、if語句 在swift中,if語句是不用帶小括號的,可是後面跟的語句必須有花括號,哪怕只有一行代碼。許多公司的代碼規範也是規定必須使用這一格式。 注意:在swift中沒有非0即真的說法,因此不能寫成if(num)這樣的格式。

let x = 9
if x > 5 {
    print("小仙女")
}else{
    print("妖精哪裏跑")
}
複製代碼

二、三目運算符

三目運算符的寫法是表達式後跟一個問號,用冒號來隔開條件是否成立的值。

let x = 10
x > 5 ? print("小仙女"):print("妖精")
複製代碼

很是有意思的是,若是開發者只想處理條件成立的部分,此時能夠在冒號後面用一個小括號來代替條件不成立的部分。

x > 5 ? print("你都寫了我兩次啦"):()
複製代碼

三、 三目運算符的簡單模式

三目運算符的簡單模式一般是用於處理可選項的。「??」的意思是說,若是表達式有值,就使用那個值,若是沒有,就使用「??」後面的值來代替。

let x:Int? = nil
let y:Int? = 9
print((x ?? 0) + (y ?? 0))
複製代碼

運行以後的結果爲9。

以後再來講說運算符的優先級。舉個簡單的栗子🌰!

let name:String? = "安琪拉"
print((name ?? "") + "火燒屁屁咯")
print(name ?? "" + "火燒屁屁咯")
複製代碼

從運行的結果能夠看到,「??」的優先級是最低的。若是沒有小括號的約束,它會將後面的語句都當成是一個表達式。

四、 guard的用法

分支如果寫得過多,就會致使代碼可讀性較差的問題。爲了下降代碼的層次,swift推出了guard。guard後面跟判斷表達式,else後面寫表達式不成立的代碼。 須要注意的是guard必須寫在函數內部,在最末尾出必需要跟關鍵字return/continue/break/throw中的一種。

import UIKit
let age = 20
func online(age : Int){
    guard age >= 18 else {
        print("還未成年呢")
        return
    }
    print("一塊兒來開黑吖")
}
複製代碼

這樣或許看不到guard的特別之處,但如果像下面這樣的代碼出現呢?

let age = 20
let money = true
let idcard  = true
func online2(age : Int,money:Bool,idcard:Bool){
    if age >= 18 {
        if money {
            if idcard {
                print("一塊兒來開黑吖")
            }else{
                print("回去帶身份證吧")
            }
        }else{
             print("回去拿錢")
        }
    }else {
        print("還未成年呢")
    }
}
//調用
online2(age: age, money: money, idcard: idcard)
複製代碼

若是用普通的分支方法,就會顯得可讀性太差。咱們能夠試着將它改爲guard的寫法。

func online1(age : Int){
    //判斷年齡
    guard age >= 18 else {
        print("還未成年呢")
        return
    }
    //判斷是否有錢
    guard money else {
        print("回去拿錢")
        return
    }
    //判斷是否帶了身份證
    guard idcard else {
         print("回去帶身份證吧")
        return
    }
    print("一塊兒來開黑吖")
}
複製代碼

執行完全部的判斷語句以後才執行代碼庫,閱讀性也比if……else分支強。

五、 switch

  • 最基本的用法 switch後面的小括號能夠省略。用case關鍵字來表示不一樣的情形,case語句結束後,break也能夠省略。
let sex = 0
switch sex {
case 0:
    print("男")
case 1:
    print("女")
default:
    print("其餘")
}

複製代碼
  • 基礎語法的補充

若是系統某一個case中產生case穿透,能夠在case結束後跟上fallthrough

case  0:
    print("男")
    fallthrough
複製代碼

case後面能夠判斷多個條件,這些條件以逗號分開

let sex = 0
switch sex {
case  0,1:
    print("正常人")

default:
    print("其餘")
}
複製代碼

switch能夠判斷浮點型、字符串類型和Bool類型

switch 3.14 {
case  0:
    print("正常人")

default:
    print("其餘")
}
複製代碼
let opration = "+"
switch opration {
case  "+":
    print("加法")
case "-":
    print("減法")
default:
    print("其餘")
}
複製代碼

7、swift的for循環和表示區間

一、變化

在swift3開始,就已經棄用了var i = 0; i < 10; i++的這種寫法。而且++這種寫法也被取消掉了,改成+=代替。

二、表示區間

swift常見區間有兩種,開區間用..<表示,閉區間用...表示。要注意的是數字和省略號之間是不能加空格的。

func demo1() {
    for i in 0..<5 {
        print(i)
    }
    print("^^^^^^^")
    
    for i in 0...5 {
        print(i)
    }
}
demo1()
複製代碼

三、逆序操做

若是想要作逆序操做,只要在in後面的表達式後添加reversed()便可。

func demo1() {
    for i in (0..<5).reversed() {
        print(i)
    }
    
}
demo1()
複製代碼

8、swift中的數組

Swift語言提供了Arrays、Sets和Dictionaries三種基本的集合類型用來存儲集合數據。數組是有序數據的集,集合是無序無重複數據的集,而字典則是無序的鍵值對的集。

數組使用有序列表存儲同一類型的多個值。相同的值能夠屢次出如今一個數組的不一樣位置中。

一、定義數組

用let定義出來的數組就是不可變的

//定義不可變數組
let array = ["愛麗絲","小紅帽","白雪公主"]
複製代碼

使用var來定義可變數組。正確的寫法是Array<Element>這樣的形式。其中Element是這個數組中惟一容許存在的數據類型。可是爲了簡便,推薦使用[Element]()的寫法。

//定義可變數組
var arrayM = [String]()
var arrayM1:[String]
var arrayM2 = Array<String>()
複製代碼

二、建立帶有默認值的數組

swift中的array類型還提供一個能夠建立特定大小而且全部數據都被默認的構造方法。開發者能夠在裏面指定它的數量和類型。

var threeDouble = Array(repeating: 0.0, count: 3)
print(threeDouble[1])
複製代碼

三、對可變數組的基本操做

使用append給數組添加元素

arrayM.append("1")
arrayM.append("2")
arrayM.append("3")
arrayM.append("4")
arrayM.append("5")
複製代碼

使用insert方法將值添加到具體索引值以前

arrayM.insert("10", at: 2)
複製代碼

使用remove系列方法能夠對數組作刪除操做

arrayM.remove(at: 0)
arrayM.removeSubrange(1..<3)
arrayM.removeAll()
arrayM.removeLast() //能夠去除最後一項,避免捕獲數組count屬性
複製代碼

經過取下標的方式對數組進行修改和查找

arrayM[0] = "小紅帽"
print(arrayM[2])
複製代碼

利用區間對具體範圍內的值替換

//替換第2項和第3項的值
arrayM[2...4] = ["22","33"]
print(arrayM[3])
複製代碼

四、數組的遍歷

//根據下標值進行遍歷
for i in 0..<arrayM.count {
    print(arrayM[i])
}
//直接遍歷數組中的元素
for i in arrayM {
    print(i)
}
複製代碼

若同時須要每一個數據項的值和索引,可使用數組的emumerated()方法來進行數組遍歷。

for(index,value) in arrayM.enumerated(){
    print(String(index+1)+":"+value)
}
複製代碼

五、數組的合併

只有相同類型的數組才能進行合併。

let resultArray = arrayM + array
複製代碼

9、swift中的集合

集合(Set)用來存儲相同類型而且沒有肯定順序的值。當集合元素順序不重要時或者但願確保每一個元素只出現一次時可使用集合而不是數組。

集合中的元素必須有肯定的hashvalue,或者是實現了hashable協議。而swift提供的Int,String等類型其實都是實現了hashable協議的。hashable是equable的子協議,若是要判斷兩個元素是否相等,就要看他們的hashvalue是否相等。

一、定義集合

使用set<Element>定義。

Element表示集合中容許存儲的類型,和數組不一樣的是,集合沒有等價的簡化形式。

//建立空集合
var letters = Set<Character>()
//使用字面量建立集合
var favorite:Set<String> = ["綺羅生","意琦行"]
複製代碼

要注意的是一個Set類型是不能直接後面跟的字面量被單獨推斷出來的,所以這個Set是必需要顯示聲明的。可是因爲swift的自動推斷功能,能夠不用寫出Set的具體類型。好比說上面那個例子,省去String,也能推斷出Set的正確類型。

var favorite:Set = ["綺羅生","意琦行"]
複製代碼

二、訪問和修改集合

經過.count屬性知道集合的長度,經過isEmpty判斷集合是否爲空。

三、添加元素

favorite.insert("寒煙翠")
print(favorite.count)
複製代碼

四、刪除元素

經過remove的方法刪除元素,若這個值真的存在就會刪除改值,而且返回被刪除的元素。若集合中不包含這個值,就會返回nil。

if let removeBack = favorite.remove("意琦行"){
    print(removeBack)
}else{
    print("沒有找到值")
}
複製代碼

五、集合操做

swift提供了許多數學方法來操做集合。

print(oddD.union(evenD).sorted()) //並集

print(oddD.intersection(evenD).sorted())//交集

print(oddD.subtracting(siggleDPrime).sorted())//取差值

print(oddD.symmetricDifference(siggleDPrime).sorted())//去掉相同值
複製代碼

六、遍歷集合

for item in favorite {
    print(item)
}
//按照首字母的順序輸出
for item1 in favorite.sorted() {
    print(item1)
}
複製代碼

七、集合的成員關係

==來判斷兩個集合是否包含所有相同的值 用 isSubset(of:)來判斷一個集合中的值是否也被包含在另一個集合中 用 isSuperset(of:)來判斷一個集合中包含另外一個集合全部的值 用isStrictSubset(of:)或者isStrictSuperset(of:)方法來判斷一個集合是不是另一個集合的子集合或父集合而且兩個集合不相等

10、字典

字典是一種存儲多個相同類型的值的容器。每一個值value都關聯這惟一的鍵key。鍵就是這個字典的標識符。並且字典中的數據項並無具體順序。鍵集合不能有重複元素,而值集合是能夠重複的。

一、定義字典

使用let定義不可變的字典,使用var定義可變字典。用字面量賦值時,系統會自動判斷[]中存放的是鍵值對仍是要一個個的元素。

let dict = [1:"one",2:"two",3:"three"]  //定義不可變字典

var dictM = Dictionary<String,NSObject>()  //定義可變字典
var dictM1 = [String:NSObject]()

//AnyObject通常用於指定類型,NSObject通常用於建立對象
複製代碼

二、對可變字典作基本操做

添加、刪除和獲取元素

dictM1["name"] = "小仙女" as NSObject
dictM["age"] = 17 as NSObject
dictM.removeValue(forKey:"name")
//獲取:swift中只保留了最簡單的寫法,OC中有objectforkey的方法在swift中也被刪除掉了。
dictM["name"]  
複製代碼

三、修改元素

若字典中已經有對應的key,操做的結果是直接修改原來的key中保存的value。若字典中沒有對應的key,則會添加新的鍵值對。

dictM["name"] = "llx"
複製代碼

四、遍歷字典

能夠經過範圍for遍歷全部的key和value。也能夠遍歷全部的鍵值對。

for (key,value) in dictM {
    print(key)
    print(value)
}
複製代碼

五、合併字典

合併字典時經過遍歷的方式將第二個字典的內容添加到第一個字典中。絕對不能用相加的方式對字典進行合併。

var dict1 = ["name":"llx","age":"17"]
var dict2 = ["num":"007"]

for (key,value) in dict2 {
    dict1[key] = value
}
dict
複製代碼

11、元組

元組是swift中特有的一種數據結構,用於定義一組數據,元組在數學中的應用十分普遍。

一、定義元組

使用()包含信息,組成元組類型的數據能夠被稱爲「元素」。

//使用元組來描述我的信息
let info1 = ("1001","張三",30)

複製代碼

二、起別名

能夠給元素加上名稱,以後能夠經過元素名稱訪問元素

//給元素加上名稱,以後能夠經過元素名稱訪問元素
let info2 = (id:"1001",name:"張三",age:30)
info2.name
複製代碼

元組通常用於做爲方法的返回值。元組中元素的別名,就是元組的名稱

let (name,age) = ("張三",18)
name
複製代碼

12、函數

函數至關於Objective-C中的方法,是一段完成特定任務的獨立代碼片斷。能夠經過給函數命名來標誌某個函數的功能。而這個名字能夠用來在須要的時候「調用」該函數完成其任務。格式以下:

func 函數名(參數列表)-> 返回值類型 {
    代碼塊
    return 返回值
}
複製代碼

func表示關鍵字,多個參數列表之間用逗號隔開,也能夠沒有參數。使用->指向返回值類型。若是沒有返回值,能夠用Void代替,也能夠省略。

一、定義無參無返回的函數

func phone()->Void {
    print("小米")
}
phone()
複製代碼

二、定義有參無返回的函數

func phoneNum() -> String {
    return "123456"
}
 print(phoneNum())
複製代碼

三、定義有參無返回的函數

func callPhone(phoneNum:String){
    print("打電話給\(phoneNum)")
}
callPhone(phoneNum: "123456")
複製代碼

四、定義有參有返回的函數

func sum(num1 : Int,num2 : Int) -> Int{
    return num1 + num2
}
sum(num1: 30, num2: 30)

複製代碼

在swift4以後,調用函數的時候,能直觀的看到參數。而在以前調用之時,只能看見第二個參數以後的名稱,表達起來並不直觀。如何解決這個問題呢?

能夠採用給參數起別名的方式,在參數前面添加一個別名。

func sum(number1 num1: Int,number2 num2 : Int) -> Int{
    return num1 + num2
}
sum(number1: 2, number2: 4)
複製代碼

五、默認參數

在swift中能夠給方法的參數設置默認值。好比說買甜筒的時候,商店默認會給顧客準備原味冰淇淋。可是用戶也能夠選擇指定口味。

func makeIceCream(flavor:String = "原味") -> String {
    return "製做一個\(flavor)冰淇淋"
}
makeIceCream()
makeIceCream(flavor: "抹茶")
複製代碼

六、可變參數

有些時候,在建立方法的時候,並不肯定參數的個數,因而swift推出了可變參數。參數的類型以後使用...表示多個參數。

func sum(num:Int...) -> Int {
    var result = 0
    for i in num {
        result += i
    }
    return result
}
sum(num: 18,29,3)

複製代碼

七、引用傳遞

若是如今有這樣一個需求:要交換兩個數的值,不能使用系統提供的方法。要如何來完成呢?

若是按照上面的寫法就會報錯,能夠按住option鍵查看,參數默認是不可變的。 並且就算可行,作到的也是值傳遞。爲了解決這一問題,swift提供了關鍵字inout來聲明數據地址傳遞,也被稱之爲引用傳值。在swift3.0的時候,inout的位置發生了改變,被放置在標籤位置。可是做用與以前相同。

func swapNum1( m : inout Int, n : inout Int) {
    let tempNum = m
    m = n
    n = tempNum
}
swapNum1(m: &m, n: &n)
print("m:\(m),n:\(n)")
複製代碼

十3、類

swift用關鍵字class來定義類。一般狀況下,定義類時,讓它繼承自NSObject,若沒有指定父類,那麼該類就是rootClass。類的格式以下:

class 類名:SuperClass {
    //定義屬性和方法
}
複製代碼

一、定義存儲屬性和建立類對象

對象的屬性必需要賦值,用解包的方式賦值爲nil。

class Person : NSObject {
    //定義存儲屬性
    var age : Int = 0
    var name : String? //對象的屬性必須賦值,不賦值會報錯的哦
}
let p = Person()
複製代碼

二、給類的屬性賦值

能夠直接賦值,也能夠經過KVC進行賦值

p.age = 10
p.name = "llx"
if let name = p.name {
    print(name)
}
複製代碼

三、定義方法

在swift中,若是使用當前某一對象的屬性或者方法,能夠直接使用,不須要加self

// 定義方法,返回平均成績
func getAverage() -> Double {
        return (mathScore + EnglishScore)*0.5
    }
複製代碼
let average = p.getAverage()
複製代碼

四、定義計算屬性

經過別的方式計算到結果的屬性,稱之爲計算屬性。

var averageS : Double {
        return (mathScore + EnglishScore) * 0.5
    }
複製代碼

五、定義類屬性

類屬性是和整個類相關的屬性,用static修飾,做用域是整個類。經過類名進行訪問。

static var courseCount : Int = 0
複製代碼

在類外經過類名訪問類屬性

Person.courseCount = 2
複製代碼

六、類的構造函數

構造函數相似於OC中的init方法。默認狀況下建立一個類時,一定會調用一個構造函數。若是一個類繼承自NSObjct,能夠對父類的構造函數進行重寫。

在構造函數中,若是沒有明確super.init()。那麼系統會默認調用super.init()

class Person : NSObject {
    var name : String?
    var age : Int = 0
    
    override init() {
        print("hello world")
    }
}
let p = Person()

複製代碼

七、自定義構造函數

自定義構造函數能夠傳入參數,作賦值操做時採用self調用屬性以示區分。

class Person : NSObject {
    var name : String?
    var age : Int = 0
    
    // 自定義構造函數
    init(name:String,age:Int){
        self.name = name
        self.age = age
    }
}
// 調用自定義的構造函數
let p1 = Person(name: "kaka", age: 12)
print(p1.age)
複製代碼

能夠定義字典類型的構造函數。用KVC的方式將字典的值取出來,要調用系統的setValue方法就必須先調用系統的構造函數建立出對象。爲了防止取出的對象沒有屬性而致使程序奔潰,須要重寫系統的setValue方法。

若是用KVC的方式必定要先調用父類的構造函數。由於系統默認調用是放在方法最後面調用的。

class Person : NSObject {
   @objc var  name : String?
   @objc var age : Int = 0
    
    
    init(dict:[String : Any]) {
        super.init()
   // 要調用系統的`setValue`方法就必須先調用系統的構造函數建立出對象
        setValuesForKeys(dict)
    }
    // 防止奔潰
    override func setValue(_ value: Any?, forUndefinedKey key: String) {
    }

}

let p2 = Person(dict:["name":"lala","age":18,"score":33])
p2.name
p2.age

複製代碼

因爲swift與objective-c的編譯方式不一樣,用KVC字典轉模型構造函數時,須要在屬性前面加上@objc

八、類的屬性監聽器

在object-c中,咱們能夠重寫set方法來監聽屬性的改變,而在swift中也能夠經過屬性觀察者來監聽和響應屬性值的變化。一般用於監聽存儲屬性和類屬性的改變。對於計算屬性則不須要定義屬性觀察者,由於咱們能夠在計算屬性的setter中直接觀察並響應這種值的變化。

能夠經過設置如下觀察方法並響應這種值的變化。 willSet:在屬性值被存儲以前設置,此時新屬性值做爲一個常量參數被傳入。該參數名默認爲newValue,開發者能夠本身定義該參數名。 didSet:在新屬性值被存儲後當即調用,與willSet不一樣的是,此時傳入的是屬性的舊值,默認參數名爲oldValue。 上面兩個方法都只有在屬性第一次被設置時纔會調用,在初始化時,不會去調用這些監聽方法。

class Person : NSObject {
    //屬性監聽器
    var name:String? {
        willSet {
            print(name as Any)
            //若是想要查看接下來的新值,可使用newValue
            print(newValue as Any)
        }
        didSet {
           print(name as Any)
        }
    }
}

let p = Person()
p.name = "llx"
複製代碼

十4、閉包

閉包是swift中很是重要的一個知識點。相似於objective-c中的block,其實函數就至關於一個特殊的閉包。閉包須要提早寫好,在適當的時候再執行。

一、定義閉包

閉包的格式是(參數列表)->(返回值類型) in 實現代碼

舉一個最簡單的栗子🌰 用常量記錄一個代碼塊,按住option鍵就能看到,b1是一個閉包。再到適合的地方去調用它。

let b1 = {
  print("幹掉他們")
}
b1()
複製代碼

再來看一個帶參數的閉包。在閉包中,參數、返回值和實現代碼都是寫在花括號裏面的。in是用來定義分割和實現的。

let b2 = {
    (x:String)->() in print(x)
}

b2("string")
複製代碼

二、閉包案例

這個案例要模擬封裝一個網絡請求的類。利用閉包將jsonData類型的數據傳遞給展現頁面。

  • 建立一個新的項目,選擇swift語言

  • 封裝一個網絡請求的類HttpTool.swift繼承自NSObject

用異步線程模擬網絡數據請求,再回到主線程中回調閉包

class HttpTool: NSObject {
    //閉包類型:(參數列表)->(返回值類型)
   
    func loadData(callback:@escaping(_ jsonData : String)->()) {
        DispatchQueue.global().async {
            print("發生網絡請求:\(Thread.current)")
        }

        
        DispatchQueue.main.async {
            ()->Void in
            print("獲取到數據,而且回調:\(Thread.current)")
            
            callback("jsonData數據")
        }
    }
}

複製代碼
  • 到須要接收數據的界面定義Httptool類的屬性,設置一個初始化值,將初始值賦值給變量

在swift中是不須要引入頭文件的,文件之間可共享

import UIKit

class ViewController: UIViewController {

    var tools : HttpTool = HttpTool()
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        //用閉包將json數據拿到
        tools.loadData { (jsonData) ->() in
            print("在viewcontroller中拿到數據\(jsonData)" )
        }
    }

}
複製代碼

三、尾隨閉包

尾隨閉包用於須要將一個很長的閉包表達式做爲最後一個參數傳遞給函數。也就是說若是按時的最後一個參數是閉包,那麼在調用它的時候就能夠把這個閉包寫在括號外面,並緊跟括號,函數的其餘參數則仍然寫在括號之中。

//這個函數接受一個String和一個閉包
//函數體內調用閉包,而且將String做爲參數傳遞給閉包
func myFunc(strP:String,closeP:(String)->Void) {
    closeP(strP)
}

//普通調用
myFunc(strP: "hello", closeP: {(string) in print(string)})
//尾隨閉包
myFunc(strP: "hello") {
    (string) in print(string)
}

複製代碼

四、逃逸閉包

當一個閉包做爲參數傳到一個函數中,可是該閉包要在函數返回以後才被執行,因而就稱這樣的閉包爲逃逸閉包。也就是說閉包逃離了函數的做用域。寫法是在這個閉包參數前加一個@escaping用來指明這個閉包是容許逃逸出該函數的。

  • 聲明一個方法,這個方法是一個逃逸閉包 該方法要作的事情,就是將閉包添加到數組中去
//定義數組,裏面的元素都是閉包類型的
var callBackArray : [()->Void] = []

//定義一個接收閉包的函數
func testEscapingClosure(callBack:@escaping ()-> Void) {
    callBackArray.append(callBack)
}
複製代碼
  • 當改變數組的時候,取第0個元素調用。此時就改變了變量x的值
class SomeClass {
    var x = 10
    
    func doSomething(){
        testEscapingClosure {
            self.x = 100
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
callBackArray.first?()
print(instance.x)
複製代碼

由於逃逸閉包是函數執行以後纔會執行,因此能夠這樣理解:建立一個類的對象instance;在對象中初始化一個x=10;利用對象執行了函數doSomething;函數內部調用全局函數testEscapingClosure,指望修改instance對象的x值爲100,可是此時並無執行這個包含了賦值語句的閉包。

查找全局數組callBackArray,找到裏面第一個元素,顯然找到的是在testEscapingClosure函數中添加的閉包{self.x = 100},此時才經過全局數組的查詢找出閉包並執行,因而x此時才被賦值爲100。這就是在函數執行完畢後才執行閉包。恰好符合逃逸閉包的定義。

結論: 逃逸閉包將在函數執行以後執行,因而這段代碼最後輸出爲100是由於閉包最後才被執行……

  • 解決循環引用的三種方式 一、可使用weak關鍵字將對象之間的聯繫變爲弱引用
weak var weakself = self
複製代碼

二、第一種方式的簡化

[weak self]
複製代碼

三、使用unowned解決

[unowned self]
複製代碼

可是該方法十分危險,要確保數據必定有值。不然會發生奔潰。

__weak 與__unretained有何區別? __weak修飾的弱引用,若是指向的對象被銷燬,那麼指針會立馬指向nil __unretained修飾的弱引用,若是指向的對象被銷燬,它的指針依然會指向以前的內存地址,很容易產生野指針(殭屍對象)

十5、tableView的用法

一、 懶加載

swift中也有懶加載的方式,而且在swift中有專門的關鍵字lazy來實現某一個屬性實現懶加載。 格式:lazy var 變量:類型 = {建立變量代碼}() 懶加載的本質在第一次使用的時候執行閉包,將閉包的返回值賦值給屬性,而且只會賦值一次。

//懶加載只能用於結構體或者類的成員變量中
class Person:NSObject {
    lazy var array : [String] = {
        ()->[String] in
        return ["llx","lll"]
    }()
}

複製代碼

二、tableView的使用

使用步驟以下:

  • 建立tableView對象

使用懶加載的方式,到須要用到的時候再建立tableView。將tableView添加到控制器上的View。

class ViewController: UIViewController {
    
    lazy var tableView:UITableView = UITableView()
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(tableView)
       
        }
}
複製代碼
  • 設置tableView的frame
tableView.frame = view.bounds
複製代碼
  • 設置數據源和代理

實現UITableView的協議,併爲tableView設置數據源

class ViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate{
    
    lazy var tableView:UITableView = UITableView()
    override func viewDidLoad() {
        super.viewDidLoad()
       
        view.addSubview(tableView)
        tableView.frame = view.bounds
        //設置數據源
        tableView.dataSource = self
        tableView.delegate = self
    }
  }
複製代碼
  • 實現代理方法
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 20
    }
複製代碼

建立cell。由於cell是個可選類型,有可能有值,也可能爲nil。因此要進行判斷。給cell設置數據的時候,選擇textLabel點擊option會發現textLabel也是可選類型。 在最後返回cell的時候,對cell進行強制解包。由於以前已經作過判斷,因此不會出現程序奔潰的問題。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let CellID = "CellID"
        var cell = tableView.dequeueReusableCell(withIdentifier: CellID)
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: CellID)
        }
        cell?.textLabel?.text = "測試數據:\(indexPath.row)"
        return cell!
    }
複製代碼

實現點擊的代理方法

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("點擊了:\(indexPath.row)")
    }
複製代碼

##十6、swift中的註釋 在swift中,相似於paramg --mark的寫法是不可行的。

它是以下兩種形式 //MARK:- 要寫的內容 用於分組

class ViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate{
    // MARK:- 懶加載
    lazy var tableView:UITableView = UITableView()
    // MARK:- 系統回調函數
    override func viewDidLoad() {
        super.viewDidLoad()
        }
    }
複製代碼

這樣寫的話,就能夠在菜單欄看到分組的信息

/// 提示信息 用於提示

若在tableView系列的某個方法上面寫上///提示,到其餘地方調用該方法時,會出現前面寫的註釋信息。

十7、枚舉

一、定義

在swift中,枚舉使用的是由enum關鍵字來建立的枚舉,枚舉的全部成員都放在一對大括號裏面。它爲一組相關的值定義一個共同的類型。使用case關鍵字來定義一個新的枚舉成員值。

enum SomeEnum {
    // 在這裏定義枚舉
    case north
    case south
    case east
    case west
}
複製代碼

上面這個枚舉定義的東南西北四個值就是這個枚舉的成員值。與C語言和objective-c不一樣的是,swift的枚舉成員值在建立的時候並不會被賦予一個默認的整形值。這些值的類型就是剛剛定義好的枚舉的名字SomeEnum

若是但願多個成員值要寫在同一行中,可使用逗號將他們分割開。

enum Plant {
    case mercury,earth,mars
}
複製代碼

每一個枚舉都定義了一個新的類型,就像swift中的其餘類型同樣。此時能夠把它賦值給一個變量,並且能夠用點語法這種形式調用。

var directionT = SomeEnumeration.west

directionT = .east
複製代碼

注意:在switch中使用枚舉值的時候,必定要窮舉出全部的狀況,若是忽略其中的一個,代碼都沒法編譯經過。由於它沒有考慮到枚舉類的所有成員。若是說不須要匹配全部的枚舉成員,能夠提供一個default分支來涵蓋其餘未明確處理的枚舉成員。

class Person:NSObject{
    var directionT = SomeEnum.west
   
    func direc()  {
        switch directionT {
        case .north:
            print("north")
        case .east:
            print("east")
        default:
            print("沒有方向")
        }
    }
}
複製代碼

二、關聯值

能夠定義swift的枚舉類存儲任意類型的關聯值,並且每一個枚舉成員的關聯值類型均可以不相同。好比說,來建立一個條形碼類型。相似於庫存,能夠有不一樣類型的條形碼去識別商品,好比說經過數字,或者根據產品代碼來識別。

enum BarCode {
    case upc(Int,Int,Int,Int)
    case qrCode(String)
}
複製代碼

上面代碼能夠理解爲定義一個名爲BarCode的枚舉類型。它的一個成員值是一個具備(Int,Int,Int,Int)類型關聯值的upc,另外一個成員值是具備String類型的qrCode

以後可使用任意的條形碼類型去建立新的條形碼

class Person:NSObject {
    // 建立一個名爲pBar變量,並將Barcode.upc賦值給它。
    func function() {
        var pBar = BarCode.upc(9, 0, 3, 3)
        pBar = .qrCode("ABCD")
    }
    
}
複製代碼

這個時候原來的barcode.upc和其整數關聯值被新的Barcode.qrCode和其字符串關聯值所替代了。

三、枚舉的原始值

枚舉的原始值就是枚舉的默認值,這些原始值的類型必須相同。在定義枚舉的時候必須給出類型。

enum ASCIICHAR : Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}
複製代碼

在使用原始值爲整數或者字符串類型的枚舉時,不須要顯式的爲每個枚舉成員設置原始值,swift將會自動未它們賦值。

enum Planet : Int {
    case mercury = 1, venus,earth,mars
}
複製代碼

上面這個例子,Planet.mercury原始值是1,那麼後面的venus就是2,以後以此類推。

能夠經過rawValue屬性來訪問枚舉變量的原始值.

let earthsOrder = Planet.earth.rawValue
複製代碼

四、枚舉遞歸

枚舉成員的關聯值爲當前枚舉類型時稱爲遞歸枚舉。那咱們能夠經過使用indirect修飾枚舉變量。indirect修飾整個枚舉時,全部成員都可遞歸(也可不遞歸😝)。

indirect enum Ari {
    case number(Int)
    case addition(Ari,Ari)
    case multi(Ari,Ari)
}
複製代碼

上面定義的枚舉類型能夠存儲三種算術表達式:純數字、兩個表達式相加、兩個表達式相乘。

let five = Ari.number(5)
let four  = Ari.number(4)
let sum = Ari.addition(five, four)
let product = Ari.multi(sum, Ari.number(2))

複製代碼

經過枚舉遞歸,就成功的建立了一個(5+4)*2的式子。

十8、結構體

結構體經過struct去聲明。在swift中,用到了大量的結構體,好比說基本的數據類型都是結構體而不是類。這意味着它們被賦值給新的常量或者變量,或者被傳入函數或方法中時,值會被拷貝。

struct teacher {
    var name : String = ""
    var age : Int  = 30
}
複製代碼

十9、擴展

擴展 (Extension)能夠作到無需修改本來的代碼就直接把想要的功能實現。

extension 某個現有的class {
    //添加新功能
  }
複製代碼

限制:

  • 不能添加任何已存在的 法或是屬性
  • 添加的屬性不能是存儲屬性,只能是計算屬性

一、擴展在方法中的應用

extension String {
    func sayHello() {
        print("Hello from extension")
    }
}
複製代碼

上面這段代碼是對String作了一個擴展。以後聲明一個變量調用擴展方法。

var hello = "hi"
hello.sayHello()
複製代碼

此後,任何String類型均可以調用該擴展方法。

二、用擴展進行計算

extension Int {
    var squared : Int {
        return (self * self)
    }
}
複製代碼

上面這段代碼對Int擴展了一個屬性,讓它計算一個數字的平方值。

var newInt = 30
newInt.squared
999.squared
複製代碼

三、擴展類或結構體

  • 建立一個普通類
class Lisa {
    var lisa = "半邊天使"
}
複製代碼
  • 對類擴展,新增一個方法,使其能作自我介紹
extension Lisa {
    func describe() -> String {
        return "我但是會傲嬌的"
    }
    
}
複製代碼
  • 建立對象調用方法

二10、泛型

泛型可讓開發者寫出靈活可重複使用的方法跟結構。 先看一個栗子🌰!!

var stringArray = ["Hi", "Hello", "Bye"]
 var intArray = [1,2,3]
 var doubleArray = [1.1,2.2,3.3]
複製代碼

上面建立了三個不一樣類型的數組,如果要求打印全部數組中的元素,一般會怎麼作呢?

func printStringFromArray(a: [String]) {
      for s in a {
print(s) }
}
  func printIntFromArray(a: [Int]){
      for i in a {
print(i) }
}
  func printdoubleFromArray(a:[Double]) {
      for d in a {
print(d) }
}
  printStringFromArray(a: stringArray)
  printIntFromArray(a: intArray)
  printdoubleFromArray(a: doubleArray)
複製代碼

上面這段冗長的代碼實在讓人不忍直視。而泛型的出現正好能夠解決這一問題。

func printEelementFormArray<T>(a:[T]){
    for element in a {
              print(element)
          }
      }
複製代碼

這段代碼中的T表明了任意的元素。不管上面類型的數據都能放入其中。以後只要調用者一個方法,傳入不一樣的數組就能將不一樣類型的元素打印出來。

二11、協議

一、對面向對象語言的吐槽

  • 使用子類時,協議繼承父類的屬性和方法。其中某些方法或屬性並非開發者所須要的。這會讓代碼變得異常的臃腫。
  • 若一個類擁有不少父類,會讓開發者很難找到每一個類中的問題並進行修改。
  • 對象引用到內存的同一地方,如果發生改變,可能會形成代碼混亂的現象。

而swift是一種面向協議的語言。協議其實就像籃球教練,會告訴選手如何去訓練,可是教練自己並不會出如今球場。Swift中的protocol不只能定義方法還能定義屬性,配合extension擴展的使用還能提供一些方法的默認實現,並且不只類能夠遵循協議,如今的枚舉和結構體也能遵循協議了。

二、一個簡單的協議案例

  • 建立一個簡單的協議,並讓一個結構體去遵循

遵循協議的方法與繼承相似。

protocol People {
    
}

struct Lisa: People {
    
}

複製代碼
  • 完善協議

給協議添加一些屬性和方法,用get set 設定協議的狀態。遵循協議時要了解變量是否能讀取或賦值。

protocol People {
    var name: String {get set}
    var race: String {get set}
    func sayHi()
}
複製代碼
  • 在結構體中實現協議的方法和變量
struct Lisa: People {
    var name: String = "Lisa"
    var race: String = "Asian"
    func sayHi() {
        print("Hi~, I'm \(name)")
    }
}
複製代碼

三、協議的繼承

  • 建立一個協議,讓該協議繼承自以前建立的People協議
protocol superman {
      var canFly: Bool {get set}
      func punch()
}

protocol superman: People {
      var canFly: Bool {get set}
      func punch()
}
複製代碼
  • 調用
struct AngleLisa: superman {
var name: String = "Lisa"
var race: String = "Asian"
func sayHi() {
    print("Hi, I'm \(name)")
}
var canFly: Bool = true
func punch() {
    print("punch Vergil")
}
}
複製代碼

由此可知,一旦協議進行了繼承,不但要實現本協議中所聲明的方法和屬性,連協議父類的方法和屬性也不能落下。

二12、swift4新特性

如下內容來自 最全的 Swift 4 新特性解析

感謝大佬提供學習資源!!!

一、語法改進

  • 在擴展extension中能夠訪問private的屬性

舉一個簡單的栗子🌰!

struct Date: Equatable, Comparable {
    private let secondsSinceReferenceDate: Double
    static func ==(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
    }
    static func <(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
    }
}
複製代碼

上面代碼定義了一個 Date 結構體,並實現 Equatable 和 Comparable 協議。爲了讓代碼更清晰,可讀性更好,通常會把對協議的實現放在單獨的 extension 中,這也是一種很是符合 Swift 風格的寫法,以下:

struct Date {
    private let secondsSinceReferenceDate: Double
}
extension Date: Equatable {
    static func ==(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
    }
}
extension Date: Comparable {
    static func <(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
    }
}
複製代碼

可是在 Swift 3 中,這樣寫會致使編譯報錯,extension 中沒法獲取到 secondsSinceReferenceDate 屬性,由於它是 private 的。因而在 Swift 3 中,必須把 private 改成 fileprivate

struct Date {
    fileprivate let secondsSinceReferenceDate: Double
}
...
複製代碼

可是若是用 fileprivate,屬性的做用域就會比咱們須要的更大,可能會不當心形成屬性的濫用。

在 Swift 4 中,private 的屬性的做用域擴大到了 extension 中,而且被限定在了 struct 和 extension 內部,這樣struct的屬性就不須要再用 fileprivate修飾了,這是最好的結果。

  • 類型和協議的組合類型
protocol Shakeable {
    func shake()
}

extension UIButton: Shakeable { func shake() {/* */ } }
extension UISlider: Shakeable { func shake() {/* */ } }

func shakeEm(controls: [???]) {
    for control in controls where control.state.isEnabled {
    }
    control.shake()
}

複製代碼

仔細思考上面的代碼,若是是swift3中,func shakeEm(controls: [???])中的???應該寫上面類型呢?若是寫UIControl,那麼調用control.shake()時就會報錯。若是寫Shakeable類型,那麼control.state.isEnabled這條語句就會報錯。

swift4爲了解決相似問題,實現了把類型和協議用&組合在一塊兒做爲一個類型使用的寫法。把它聲明爲UIControl & Shakeable類型。

func shakeEm(controls: [UIControl & Shakeable]) {
    for control in controls where control.isEnabled {
        control.shake()
    }
}
複製代碼
  • Associated Type 能夠追加 Where 約束語句

在 Swift 4 中能夠在 associatedtype後面聲明的類型後追加 where 語句。

protocol Sequence {
    associatedtype Element where Self.Element == Self.Iterator.Element
    // ...
}
複製代碼

它限定了 Sequence 中 Element 這個類型必須和 Iterator.Element 的類型一致。

經過 where 語句能夠對類型添加更多的約束,使其更嚴謹,避免在使用這個類型時作多餘的類型判斷。

  • 新的 Key Paths 語法

先來看看 Swift 3 中 Key Paths 的寫法:

@objcMembers class Kid: NSObject {
    dynamic var nickname: String = ""
    dynamic var age: Double = 0.0
    dynamic var friends: [Kid] = []
}

var ben = Kid(nickname: "Benji", age: 5.5)

let kidsNameKeyPath = #keyPath(Kid.nickname)

let name = ben.valueForKeyPath(kidsNameKeyPath)
ben.setValue("Ben", forKeyPath: kidsNameKeyPath)
複製代碼

Swift 4 中建立一個 KeyPath\做爲開頭:

\Kid.nickname
複製代碼

當編譯器能夠推導出類型時,能夠省略基礎類型部分:

\.nickname
複製代碼

上面的代碼在 Swift 4 中就能夠這樣寫:

struct Kid {
    var nickname: String = ""
    var age: Double = 0.0
    var friends: [Kid] = []
}

var ben = Kid(nickname: "Benji", age: 8, friends: [])

let name = ben[keyPath: \Kid.nickname]
ben[keyPath: \Kid.nickname] = "BigBen"
複製代碼

相比 Swift 3,Swift 4 的 Key Paths 具備如下優點:

類型能夠定義爲 class、struct 定義類型時無需加上 @objcMembers、dynamic 等關鍵字 性能更好 類型安全和類型推斷,例如 ben.valueForKeyPath(kidsNameKeyPath) 返回的類型是 Any,ben[keyPath: \Kid.nickname] 直接返回 String 類型 能夠在全部值類型上使用

  • 下標支持泛型

Swift 支持經過下標來讀寫容器中的數據,可是若是容器類中的數據類型定義爲泛型,之前的下標語法就只能返回 Any,在取出值後須要用 as? 來轉換類型。Swift 4 定義下標也可使用泛型了。可是並不須要作轉型操做。

struct GenericDictionary<Key: Hashable, Value> {
    private var data: [Key: Value]
    
    init(data: [Key: Value]) {
        self.data = data
    }
    
    subscript<T>(key: Key) -> T? {
        return data[key] as? T
    }
}

let dictionary = GenericDictionary(data: ["Name": "Xiaoming"])

let name: String? = dictionary["Name"] // 不須要再寫 as? String

複製代碼

二、字符串

  • Unicode 字符串在計算 count 時的正確性改善

在 Unicode 中,有些字符是由幾個其它字符組成的,好比 é 這個字符,它能夠用 \u{E9} 來表示,也能夠用 e 字符和上面一撇字符組合在一塊兒表示 \u{65}\u{301}。

var family = "👩"
family += "\u{200D}👩"
family += "\u{200D}👧" 
family += "\u{200D}👦"

print(family)
print(family.characters.count)
複製代碼

這個 family 是一個由多個字符組合成的字符,打印出來的結果爲 👩‍👩‍👧‍👦。上面的代碼在 Swift 3 中打印的 count 數是 4,在 Swift 4 中打印出的 count 是 1。

  • 更快的處理速度

Swift 4 的字符串優化了底層實現,對於英語、法語、德語、西班牙語的處理速度提升了 3.5 倍。對於簡體中文、日語的處理速度提升了 2.5 倍。

  • 去掉了characters

  • One-sided Slicing

Swift 4 新增了一個語法糖 ... 能夠對字符串進行單側邊界取子串。

Swift 3:

let values = "abcdefg"
let startSlicingIndex = values.index(values.startIndex, offsetBy: 3)
let subvalues = values[startSlicingIndex..<values.endIndex]
// defg
Swift 4:

let values = "abcdefg"
let startSlicingIndex = values.index(values.startIndex, offsetBy: 3)
let subvalues = values[startSlicingIndex...] // One-sided Slicing
// defg
複製代碼
  • String 當作 Collection 來用

Swift 4 中 String 能夠當作 Collection 來用,並非由於 String 實現了 Collection 協議,而是 String 自己增長了不少 Collection 協議中的方法,使得 String 在使用時看上去就是個 Collection。例如:

翻轉字符串:

let abc: String = "abc"
print(String(abc.reversed()))
// cba
複製代碼

遍歷字符:

let abc: String = "abc"
for c in abc {
    print(c)
}
/*
a
b
c
*/
複製代碼

Map、Filter、Reduce:

// map
let abc: String = "abc"
_ = abc.map {
    print($0.description)
}

// filter
let filtered = abc.filter { $0 == "b" }

// reduce
let result = abc.reduce("1") { (result, c) -> String in
    print(result)
    print(c)
    return result + String(c)
}
print(result)
複製代碼
  • Substring

在 Swift 中,String 的背後有個 Owner Object 來跟蹤和管理這個 String,String 對象在內存中的存儲由內存其實地址、字符數、指向 Owner Object 指針組成。Owner Object 指針指向 Owner Object 對象,Owner Object 對象持有 String Buffer。當對 String 作取子字符串操做時,子字符串的 Owner Object 指針會和原字符串指向同一個對象,所以子字符串的 Owner Object 會持有原 String 的 Buffer。當原字符串銷燬時,因爲原字符串的 Buffer 被子字符串的 Owner Object 持有了,原字符串 Buffer 並不會釋放,形成極大的內存浪費。

在 Swift 4 中,作取子串操做的結果是一個 Substring 類型,它沒法直接賦值給須要 String 類型的地方。必須用 String() 包一層,系統會經過複製建立出一個新的字符串對象,這樣原字符串在銷燬時,原字符串的 Buffer 就能夠徹底釋放了。

let big = downloadHugeString()
let small = extractTinyString(from: big)

mainView.titleLabel.text = small // Swift 4 編譯報錯

mainView.titleLabel.text = String(small) // 編譯經過

複製代碼
  • 多行字符串字面量

Swift 3 中寫很長的字符串只能寫在一行。

func tellJoke(name: String, character: Character) {
    let punchline = name.filter { $0 != character }
    let n = name.count - punchline.count
    let joke = "Q: Why does \(name) have \(n) \(character)'s in their name?\nA: I don't know, why does \(name) have \(n) \(character)'s in their name?\nQ: Because otherwise they'd be called \(punchline)."
    print(joke)
}
tellJoke(name: "Edward Woodward", character: "d")
複製代碼

字符串中間有換行只能經過添加 \n 字符來表明換行。

Swift 4 能夠把字符串寫在一對 """ 中,這樣字符串就能夠寫成多行。

func tellJoke(name: String, character: Character) {
    let punchline = name.filter { $0 != character }
    let n = name.count - punchline.count
    let joke = """ Q: Why does \(name) have \(n) \(character)'s in their name? A: I don't know, why does \(name) have \(n) \(character)'s in their name? Q: Because otherwise they'd be called \(punchline). """
    print(joke)
}
tellJoke(name: "Edward Woodward", character: "d")

複製代碼

三、Swift 標準庫

  • Encoding and Decoding

當須要將一個對象持久化時,須要把這個對象序列化,往常的作法是實現 NSCoding 協議,寫過的人應該都知道實現 NSCoding 協議的代碼寫起來很痛苦,尤爲是當屬性很是多的時候。幾年前有一個工具能自動生成 Objective-C 的實現 NSCoding 協議代碼,當時用着還不錯,但後來這個工具已經沒有人維護好久了,並且不支持 Swift。

Swift 4 中引入了 Codable 幫咱們解決了這個問題。

struct Language: Codable {
    var name: String
    var version: Int
}
複製代碼

咱們想將這個 Language 對象的實例持久化,只須要讓 Language 符合 Codable 協議便可,Language 中不用寫別的代碼。符合了 Codable 協議之後,能夠選擇把對象 encode 成 JSON 或者 PropertyList。

Encode 操做以下:

let swift = Language(name: "Swift", version: 4)
if let encoded = try? JSONEncoder().encode(swift) {
    // 把 encoded 保存起來
}
複製代碼

Decode 操做以下:

if let decoded = try? JSONDecoder().decode(Language.self, from: encoded) {
    print(decoded.name)
}
複製代碼
  • Sequence 改進
Swift 3:

protocol Sequence {
    associatedtype Iterator: IteratorProtocol
    func makeIterator() -> Iterator
}
複製代碼
Swift 4:

protocol Sequence {
    associatedtype Element
    associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
    func makeIterator() -> Iterator
}
複製代碼

因爲 Swift 4 中的 associatedtype 支持追加 where 語句,因此 Sequence 作了這樣的改進。 Swift 4 中獲取 Sequence的元素類型能夠不用 Iterator.Element,而是直接取 Element

SubSequence 也作了修改:

protocol Sequence {
    associatedtype SubSequence: Sequence 
        where SubSequence.SubSequence == SubSequence,
              SubSequence.Element == Element
}
複製代碼

經過 where 語句的限定,保證了類型正確,避免在使用 Sequence 時作一些沒必要要的類型判斷。

Collection 也有一些相似的修改。

  • Protocol-oriented integers

整數類型符合的協議有修改,新增了 FixedWidthInteger 等協議,具體的協議繼承關係以下:

+-------------+   +-------------+
        +------>+   Numeric   |   | Comparable  |
        |       |   (+,-,*)   |   | (==,<,>,...)|
        |       +------------++   +---+---------+
        |                     ^       ^
+-------+------------+        |       |
|    SignedNumeric   |      +-+-------+-----------+
|     (unary -)      |      |    BinaryInteger    |
+------+-------------+      |(words,%,bitwise,...)|
       ^                    ++---+-----+----------+
       |         +-----------^   ^     ^---------------+
       |         |               |                     |
+------+---------++    +---------+---------------+  +--+----------------+
|  SignedInteger  |    |  FixedWidthInteger      |  |  UnsignedInteger  |
|                 |    |(endianness,overflow,...)|  |                   |
+---------------+-+    +-+--------------------+--+  +-+-----------------+
                ^        ^                    ^       ^
                |        |                    |       |
                |        |                    |       |
               ++--------+-+                +-+-------+-+
               |Int family |-+              |UInt family|-+
               +-----------+ |              +-----------+ |
                 +-----------+                +-----------+
複製代碼
  • Dictionary and Set enhancements

這裏簡單列一下 Dictionary 和 Set 加強了哪些功能:

經過 Sequence 來初始化 能夠包含重複的 Key Filter 的結果的類型和原類型一致 Dictionary 的 mapValues 方法 Dictionary 的默認值 Dictionary 能夠分組 Dictionary 能夠翻轉

  • NSNumber bridging and Numeric types
let n = NSNumber(value: 999)
let v = n as? UInt8 // Swift 4: nil, Swift 3: 231
複製代碼

在 Swift 4 中,把一個值爲 999 的 NSNumber 轉換爲 UInt8 後,能正確的返回 nil,而在 Swift 3 中會不可預料的返回 231。

  • MutableCollection.swapAt(::)

MutableCollection 如今有了一個新方法 swapAt(::) 用來交換兩個位置的值,例如:

var mutableArray = [1, 2, 3, 4]
mutableArray.swapAt(1, 2)
print(mutableArray)
// 打印結果:[1, 3, 2, 4]
複製代碼

四、構建過程改進

  • New Build System

Xcode 9 引入了 New Build System,可在 Xcode 9 的 File -> Project Settings... 中選擇開啓。

  • 預編譯 Bridging Headers 文件

對於 Swift 和 Objective-C 混合的項目,Swift 調用 Objective-C 時,須要創建一個 Bridging Headers 文件,而後把 Swift 要調用的 Objective-C 類的頭文件都寫在裏面,編譯器會讀取 Bridging Headers 中的頭文件,而後生成一個龐大的 Swift 文件,文件內容是這些頭文件內的 API 的 Swift 版本。而後編譯器會在編譯每個 Swift 文件時,都要編譯一遍這個龐大的 Swift 文件的內容。

有了預編譯 Bridging Headers 之後,編譯器會在預編譯階段把 Bridging Headers 編譯一次,而後插入到每一個 Swift 文件中,這樣就大大提升了編譯速度。

蘋果宣稱 Xcode 9 和 Swift 4 對於 Swift 和 Objective-C 混合編譯的速度提升了 40%

  • Indexing 能夠在編譯的同時進行

用 Swift 開發項目時,近幾個版本的 Xcode 進行 Indexing 的速度慢的使人髮指。Xcode 9 和 Swift 4 在這方面作了優化,能夠在編譯的同時進行 Indexing,通常編譯結束後 Indexing 也會同時完成。

  • COW Existential Containers

Swift 中有個東西叫 Existential Containers,它用來保存未知類型的值,它的內部是一個 Inline value buffer,若是 Inline value buffer 中的值佔用空間很大時,這個值會被分配在堆上,然而在堆上分配內存是一個性能比較慢的操做。

Swift 4 中爲了優化性能引入了 COW Existential Containers,這裏的 COW 就表明 "Copy-On-Write",當存在多個相同的值時,他們會共用 buffer 上的空間,直到某個值被修改時,這個被修改的值纔會被拷貝一份並分配內存空間

  • 移除未調用的協議實現
struct Date {
    private let secondsSinceReferenceDate: Double
}

extension Date: Equatable {
    static func ==(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
    }
}

extension Date: Comparable {
    static func <(lhs: Date, rhs: Date) -> Bool {
        return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
    }
}

複製代碼

看上面例子,Date 實現了 Equatable 和 Comparable 協議。編譯時若是編譯器發現沒有任何地方調用了對 Date 進行大小比較的方法,編譯器會移除 Comparable 協議的實現,來達到減少包大小的目的。

  • 減小隱式 @objc 自動推斷

在項目中想把 Swift 寫的 API 暴露給 Objective-C 調用,須要增長 @objc。在 Swift 3 中,編譯器會在不少地方爲咱們隱式的加上 @objc,例如當一個類繼承於 NSObject,那麼這個類的全部方法都會被隱式的加上 @objc。

class MyClass: NSObject {
    func print() { ... } // 包含隱式的 @objc
    func show() { ... } // 包含隱式的 @objc
}
複製代碼

這樣不少並不須要暴露給 Objective-C 也被加上了 @objc。大量 @objc 會致使二進制文件大小的增長。

在 Swift 4 中,隱式 @objc 自動推斷只會發生在不多的當必需要使用 @objc 的狀況,好比:

複寫父類的 Objective-C 方法 符合一個 Objective-C 的協議 其它大多數地方必須手工顯示的加上 @objc。

減小了隱式 @objc 自動推斷後,Apple Music app 的包大小減小了 5.7%。

五、 Exclusive Access to Memory

在遍歷一個 Collection 的時候能夠去修改每個元素的值,可是在遍歷時若是去添加或刪除一個元素就可能會引發 Crash。

例如爲 MutableCollection 擴展一個 modifyEach 方法來修改每一個元素的值,代碼以下:

extension MutableCollection {
    mutating func modifyEach(_ body: (inout Element) -> ()) {
        for index in self.indices {
            body(&self[index])
        }
    }
}

複製代碼

假如在調用 modifyEach 時去刪除元素:

var numbers = [1, 2, 3]
numbers.modifyEach { element in
    element *= 2
    numbers.removeAll()
}
複製代碼

就會在運行時 Crash。Swift 4 中引入了 Exclusive Access to Memory,使得這個錯誤能夠在編譯時被檢查出來。

相關文章
相關標籤/搜索