http://www.reibang.com/p/bdaa49f9d1a4
1辕坝、Swift 比 Objective-C 有什么優(yōu)勢宰僧?
Swift 速度更快,運算性能更高诈泼。
Swift 語法簡單易讀,代碼更少煤禽,更加清晰铐达,易于維護。
Swift 更加安全檬果,它是類型安全的語言瓮孙。
Swift 泛型,結構體选脊,枚舉都很強大杭抠。
Swift 是靜態(tài)語言,有類型推斷恳啥,OC 是動態(tài)語言偏灿。
Swift 面向協(xié)議編程,OC 面向對象編程钝的。
Swift 注重值類型升薯,OC注重引用類型痪寻。
Swift 支持泛型膘螟,OC 只支持輕量泛型念逞。
Swift 支持靜態(tài)派發(fā)(效率高)、動態(tài)派發(fā)(函數表派發(fā)碗脊、消息派發(fā))啼肩,OC 支持動態(tài)派發(fā)(消息派發(fā))。
Swift 支持函數式編程衙伶。
Swift 的協(xié)議不僅可以被類實現祈坠,也可以被 struct 和 enum 實現。
Swift 有元組類型矢劲、支持運算符重載颁虐。
Swift 支持命名空間。
Swift 支持默認參數卧须。
2另绩、struct 與 class 的區(qū)別
1儒陨、struct 是值類型,class 是引用類型笋籽。
值類型的變量直接包含它們的數據蹦漠,對于值類型都有它們自己的數據副本,因此對一個變量操作不會影響另一個變量车海。值類型包括結構體 (數組和字典)笛园、枚舉、基本數據類型 (boolean侍芝、integer研铆、float等)。
引用類型的變量存儲對他們的數據引用州叠,對一個變量操作可能影響另一個變量棵红。
2、property 的初始化不同
class 在初始化時不能直接把 property 放在默認的 constructor 的參數里咧栗,而是需要自己創(chuàng)建一個帶參數的 constructor逆甜。struct 可以把屬性放在默認的 constructor 的參數里。
3致板、變量賦值方式不同
struct 是值拷貝交煞,class 是引用拷貝。
4斟或、immutable 變量
swift 的可變內容和不可變內容用 var 和 let 來甄別素征,如果初始為 let 的變量再去修改會發(fā)生編譯錯誤。struct 遵循這一特性萝挤,class 不存在這樣的問題御毅。
5、mutating function
struct 和 class 的差別是 struct 的 function 要去改變 property 的值的時候要加上 mutating平斩,而 class 不用亚享。
6咽块、繼承
struct 不可以繼承绘面,class可以繼承。
7侈沪、struct 比 class 更輕量
struct 分配在棧中,class分配在堆中亭罪。
8应役、struct 會自動生成需要的構造方法(constructor)情组,哪個屬性沒有賦初始值就會生成以哪個屬性為參數的構造方法燥筷。而 class 沒有,要自己寫院崇。
//struct
struct StructTest {
var name:String
var age:Int
}
//class
class ClassTest {
var name = "XIAOMING"
var age = 18
}
//class
class ClassTest {
var name:String?
var age:Int?
}
var structTest = StructTest(age: 66)
var classTest = ClassTest()
9肆氓、struct 的屬性可以不賦初始值,而 class 的屬性必須賦初始值或者設為可選類型(下面也是可以的)底瓣,區(qū)別只是 struct 自動生成了帶參數的 init 方法谢揪。
class ScanInfo: NSObject {
var advertisementData: [String : Any]
var rssi: NSNumber
init(advertisementData: [String : Any], rssi: NSNumber) {
self.advertisementData = advertisementData
self.rssi = rssi
}
}
10、class 中可以有單例對象屬性捐凭,struct 中不能有(在 let 前加 static拨扶,struct 中也可以創(chuàng)建單例)
class ClassTest {
let sharaedInstance = ClassTest()
private init() {
print("調用單利類")
}
}
struct StructTest {
static let sharaedInstance = StructTest()
private init() {
print("調用單利類")
}
}
11、Struct 不能被序列化成 NSData 對象茁肠,原因是無法歸解檔患民。歸解檔的類必須遵守 NSCoding 協(xié)議,struct 不能遵守 NSCoding 協(xié)議官套。
12酒奶、當項目的代碼是 Swift 和 Objective-C 混合開發(fā)時,會發(fā)現在 Objective-C 的代碼里無法調用 Swift 的 Struct奶赔。因為要在 Objective-C 里調用 Swift 代碼的話惋嚎,對象需要繼承于 NSObject。
3站刑、Swift 為什么將 String另伍、Array、Dictionary 設計成值類型绞旅?
1摆尝、值類型相比引用類型最大的優(yōu)勢在于內存使用的高效。值類型在棧上操作因悲,引用類型在堆上操作堕汞。棧上的操作僅僅是單個指針的上下移動,而堆上的操作則牽涉到合并晃琳、移位讯检、重新鏈接等。Swift 這樣設計大幅減少了堆上的內存分配和回收的次數卫旱,同時寫時復制又將值傳遞和復制的開銷降到了最低人灼。
2、String顾翼、Array投放、Dictionary 設計成值類型也是為了線程安全考慮。通過 Swift 的 let 設置适贸,使得這些數據達到了真正意義上的“不變”灸芳,它也從根本上解決了多線程中內存訪問和操作順序的問題涝桅。
3、設計成值類型還可以提升 API 的靈活度烙样。
4苹支、swift 屬性
1、計算屬性和存儲屬性
存儲型屬性:即成員變量误阻,存儲在實例對象的內存中债蜜。
計算型屬性:本質就是方法(函數),不占用實例對象的內存究反,不能用來存儲值寻定。
struct Circle {
// 存儲屬性
var radius: Double
// //或者可以這么寫
// var radius: Double {
// willSet {
// print("在willSet中,radius = \(radius),newValue = \(newValue)")
// }
//
// didSet {
// print("在didSet中精耐,radius = \(radius),oldValue = \(oldValue)")
// }
// }
// 計算屬性
var diameter: Double {
set {
radius = newValue / 2
}
get {
return radius * 2
}
}
}
注:
計算型屬性特點:僅有 get 或者有 get+set 的屬性是計算型屬性狼速。這里的 get+set 僅僅是用來作為其他屬性的外部接口。
2卦停、類型屬性(Type Property)
屬性可以分為:
實例屬性(Instance Property):只能通過實例對象去訪問向胡。
存儲實例屬性(Stored Instance Property):存儲在實例對象的內存中,每個實例對象都有1份惊完。
計算實例屬性(Computed Instance Property)
類型屬性(Type Property):只能通過類型去訪問僵芹。
存儲類型屬性(Stored Type Property):整個程序運行過程中,就只有1份內存(類似于全局變量)小槐。
計算類型屬性(Computed Type Property)
與存儲實例屬性不同拇派,存儲類型屬性必須要設定初始值,因為類型沒有像實例對象那樣的 init 初始化器來初始化存儲屬性凿跳。存儲類型屬性默認就是 lazy件豌,會在第一次使用的時候才初始化,就算被多個線程同時訪問控嗜,保證只會初始化一次茧彤。
3、重寫屬性
1疆栏、子類可以將父類的屬性(存儲曾掂、計算)重寫為計算屬性;不可以將父類屬性重寫為存儲屬性承边。
2遭殉、只能重寫 var 屬性石挂,不能重寫 let 屬性博助。
3、重寫時痹愚,屬性名富岳、類型要一致蛔糯。
4、子類重寫后的屬性權限不能小于父類屬性的權限窖式。
-- 如果父類屬性是只讀的蚁飒,那么子類重寫后的屬性可以是只讀的、也可以是可讀寫的萝喘。
-- 如果父類屬性是可讀寫的淮逻,那么子類重寫后的屬性也必須是可讀寫的。
5阁簸、被 class 修飾的計算類型屬性爬早,可以被子類重寫;被 static 修飾的類型屬性(存儲启妹、計算)筛严,不可以被子類重寫。
4饶米、extension 中能增加存儲屬性嗎?
extension 中能增加計算屬性不能增加存儲屬性桨啃。extension 是用來給存在的類型添加新行為的并不能改變類型或者接口本身。如果增加存儲屬性檬输,需要額外的內存空間存儲新的值照瘾。extension 是不能管理這樣的任務的。
5丧慈、Swift 是面向對象還是函數式編程語言?
Swift 既是面向對象的网杆,又是函數式的編程語言。
說 Swift 是面向對象的語言伊滋,是因為 Swift 支持類的封裝碳却、繼承和多態(tài)。
說 Swift 是函數式編程語言笑旺,是因為 Swift 支持 map昼浦、reduce、filter筒主、flatmap 這類去除中間狀態(tài)关噪、數學函數式的方法,更加強調運算結果而不是中間過程乌妙。
6使兔、什么是函數式編程?
面向對象編程:將要解決的問題抽象成一個類藤韵,通過給類定義屬性和方法虐沥,讓類幫助我們解決需要處理的問題(即命令式編程,給對象下一個個命令)。
函數式編程:數學意義上的函數欲险,即映射關系(如:y = f(x)镐依,就是 y 和 x 的對應關系,可以理解為"像函數一樣的編程")天试。它的主要思想是把運算過程盡量寫成一系列嵌套的函數調用槐壳。
例:
數學表達式
(1 + 2) * 3 - 4
傳統(tǒng)編程
var a = 1 + 2
var b = a * 3
var c = b - 4
函數式編程
var result = subtract(multiply(add(1,2), 3), 4)
函數式編程的特點
1、函數是"第一等公民"
函數和其他數據類型一樣喜每,可以作為參數务唐、可以賦值給其他變量、可以作為返回值带兜。
2绍哎、高階函數
高階函數:接受至少一個函數作為參數,返回的結果是一個函數鞋真。
3崇堰、柯里化
把一個多參數的函數,轉換為單參數函數且這個函數的返回值也是一個函數涩咖。
例:
// 柯里化之前
function add(x, y) {
return x + y;
}
add(1, 2) // 3
// 柯里化之后
function addX(y) {
return function (x) {
return x + y;
};
}
addX(2)(1) // 3
4海诲、沒有"副作用"
不會產生運算以外的結果。
5檩互、純函數
純函數編程和函數編程的區(qū)別:是否允許在函數內部執(zhí)行一些非函數式的操作特幔,同時這些操作是否會暴露給系統(tǒng)中的其他地方,也就是是否存在副作用闸昨。如果不存在副作用或者說可以不用在意這些副作用蚯斯,那么就將其稱為純粹的函數式編程。
6饵较、引用透明性
函數無論在何處何時調用拍嵌,如果使用相同的輸入總能得到相同的結果。這種不依賴外部變量或“狀態(tài)”循诉,只依賴輸入參數的特性就被稱為引用透明性横辆。
函數式編程的好處
代碼簡潔,開發(fā)迅速茄猫;
接近自然語言狈蚤,易于理解;
更方便的代碼管理划纽;
易于"并發(fā)編程"脆侮;
代碼的熱升級。
7勇劣、Swift 的派發(fā)
1靖避、OC 中的方法是動態(tài)派發(fā)(方法調用),Swift 中的方法分為靜態(tài)派發(fā)和動態(tài)派發(fā)。
2筋蓖、動態(tài)派發(fā):運行時查找函數表,找到后再跳轉到實現退敦。動態(tài)派發(fā)僅僅多一個查表環(huán)節(jié)并不是他慢的原因粘咖,真正的原因是它阻止了編譯器可以進行的內聯等優(yōu)化手段。
3侈百、靜態(tài)派發(fā):編譯器將函數地址直接編碼在匯編中瓮下,調用的時候根據地址直接跳轉到實現。
4钝域、靜態(tài)派發(fā)的特點:靜態(tài)派發(fā)更高效讽坏,不僅因為靜態(tài)派發(fā)免去了查表操作,更因為編譯器進行內聯等優(yōu)化例证;靜態(tài)派發(fā)的條件是方法內部的代碼必須對編譯器透明且在運行時不能被更改路呜。
5、值類型對象的函數的調用方式是靜態(tài)調用织咧,即直接地址調用胀葱。調用函數指針,這個函數指針在編譯笙蒙、鏈接完成后就已經確定了抵屿,存放在代碼段,而結構體內部并不存放方法捅位。因此可以直接通過地址直接調用轧葛。
6、直接派發(fā)包含:所有值類型艇搀;協(xié)議和 class 的 extensions 尿扯;NSObject 的子類用 @nonobjc 或 final 修飾的函數;添加 static 關鍵字函數焰雕;添加 @inline 關鍵字的函數姜胖。
函數表派發(fā):初始聲明函數。
消息派發(fā):dynamic 修飾的函數淀散;dynamic 修飾的 extensions 右莱;添加 @objc 關鍵字的函數。
8档插、閉包
1慢蜓、閉包是自包含的函數代碼塊,可以在代碼中被傳遞和使用
閉包可以捕獲和存儲其所在上下文中任意常量和變量的引用郭膛,被稱為包裹常量和變量晨抡。 Swift 會為你管理在捕獲過程中涉及到的所有內存操作。
1、閉包捕獲值的本質是在堆區(qū)開辟內存然后存儲其在上下文中捕獲到的值耘柱。
2如捅、修改值也是修改的堆空間的值。
3调煎、閉包是一個引用類型镜遣。
4、閉包的底層結構是一個結構體士袄。存儲閉包的地址悲关,加上捕獲值的地址。
5娄柳、在捕獲的值中寓辱,會對定義的變量和函數中的參數分開存儲。
6赤拒、存儲的時候內部會有一個 HeapObject 結構秫筏,用于管理內存,引用計數挎挖。
7跳昼、函數是特殊的閉包,只不過函數不捕獲值肋乍,所以在閉包結構體中只存儲函數地址鹅颊,不存儲指向捕獲值的指針。
使用閉包有很多好處:
1墓造、可以利用上下文自動推斷參數和返回值的類型堪伍。
2、隱式返回單表達式閉包觅闽,也就是單表達式的時候可以省略 return 關鍵字帝雇。
3、參數名稱可以簡寫或者不寫蛉拙,使用 $0 表示第一個參數尸闸。
4、尾隨閉包表達式孕锄。
2吮廉、逃逸閉包
當閉包作為函數的參數且可能在函數結束之后才被調用,即逃離了函數的作用域(例:網絡請求)畸肆。
特點:必須添加 @escaping 關鍵字宦芦。
func methodCallback(closure: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now()+2) {
closure()
}
}
print("start at \(Date.now)")
methodCallback {
print("callback at \(Date.now)")
}
3、尾隨閉包
閉包作為方法的最后一個參數時轴脐,就是尾隨閉包调卑。
//尾隨閉包抡砂,定義一個方法,最后一個參數是閉包
func methodWithClosure(closure:(_ info: String) -> Void) {
closure("info from closure")
}
//普通的寫法
methodWithClosure { info in
print(info)
}
//這是尾隨閉包的寫法
methodWithClosure() {info in
print(info)
}
//如果閉包內實現只有一行代碼恬涧,可以這樣簡寫(裝逼)
methodWithClosure() { print($0) }
4注益、自動閉包
自動閉包是一種自動創(chuàng)建的閉包,用于包裝傳遞給函數作為參數的表達式溯捆。這種閉包不接受任何參數丑搔,當它被調用的時候,會返回被包裝在其中的表達式的值现使。這種便利語法讓你能夠省略閉包的花括號低匙,用一個普通的表達式來代替顯式的閉包旷痕。自動閉包讓你能夠延遲求值碳锈,因為直到你調用這個閉包,代碼段才會被執(zhí)行欺抗。延遲求值對于那些有副作用和高計算成本的代碼來說是很有益處的售碳,因為它使得你能控制代碼的執(zhí)行時機。
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
5绞呈、swift 中 closure 與 OC 中 block 的區(qū)別贸人?
1、closure 是匿名函數佃声,block 是一個結構體對象艺智。
2、closure 可以通過逃逸閉包來在內部修改變量圾亏,block 通過 __block 修飾符十拣。
9、泛型
泛型(generic)可以使我們在程序代碼中定義一些可變的部分志鹃,在運行的時候指定夭问。使用泛型可以最大限度地重用代碼,保護類型的安全以及提高性能曹铃。
func areTheyEqual<T: Equatable>(x: T, _ y: T) -> Bool {
return x == y
}
areTheyEqual("ray", "ray")
areTheyEqual(1, 1)
10缰趋、Optional(可選型)
Optional 是一個泛型枚舉
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Optional 類型表示:有值 / 沒有值舌厨。
在 Objective-C 中并沒有 Optional 類型涩禀,只有 nil 并且 nil 只能用于表示對象類型無值褒纲,并不能用于基礎類型(int, float)辛藻、枚舉和結構體正驻∽话拢基礎類型需要返回類似 NSNotFound 的特殊值來表示無值哎榴。所以在 Swift 中定義了 Optinal 類型來表示各種類型的無值狀態(tài)琼蚯,并規(guī)定了nil 不能用于非可選的常量和變量蜕着,只能用于Optinal 類型谋竖。
解決方式:
強行打開 - 不安全
let a: String = x!
隱式解包變量聲明 - 在許多情況下不安全
var a = x!
可選鏈接 - 安全
let a = x?.count
無合并操作員 - 安全
let a = x ?? ""
可選綁定 - 安全
if let a = x {
print("x was successfully unwrapped and is = \(a)")
}
警衛(wèi)聲明 - 安全
guard let a = x else {
return
}
可選模式 - 安全
if case let a? = x {
print(a)
}
11红柱、不通過繼承,代碼復用的方式有哪些?
全局函數,類擴展蓖乘,泛型锤悄。
12、權限修飾符
open:公開權限, 最高的權限, 可以被其他模塊訪問, 繼承及復寫嘉抒。只能用于類和類的成員零聚。
public:公有訪問權限,類或者類的公有屬性或者公有方法可以從文件或者模塊的任何地方進行訪問些侍。public 的權限在 Swift 3.0 后無法在其他模塊被復寫方法/屬性或被繼承隶症。
internal:顧名思義,internal 是內部的意思岗宣,即有著 internal 訪問權限的屬性和方法說明在模塊內部可以訪問蚂会,超出模塊內部就不可被訪問了。在 Swift 中默認就是 internal 的訪問權限耗式。
fileprivate:文件私有訪問權限胁住,被 fileprivate 修飾的類或者類的屬性或方法可以在同一個物理文件中訪問。如果超出該物理文件刊咳,那么有著 fileprivate 訪問權限的類, 屬性和方法就不能被訪問彪见。
private:私有訪問權限,被 private 修飾的類或者類的屬性或方法可以在同一個物理文件中的同一個類型(包含 extension)訪問娱挨。如果超出該物理文件或不屬于同一類型余指,那么有著 private 訪問權限的屬性和方法就不能被訪問。
從高到低排序如下:
open > public > interal > fileprivate > private
模塊與源文件:
模塊:指的是獨立的代碼分發(fā)單元跷坝, 框架或應用程序會作為一個獨立的模塊來構建和發(fā)布(一個 App 就是一個模塊酵镜,一個第三方 API, 第三方框架等都是一個完整的模塊)。在 Swift 中探孝,一個模塊可以使用 import 關鍵字導入另外一個模塊笋婿。
源文件:是 Swift 中的源代碼文件,它通常屬于一個模塊顿颅,即一個應用程序或者框架缸濒。盡管我們一般會將不同的類型分別定義在不同的源文件中,但是同一個源文件也可以包含多個類型粱腻、函數之類的定義庇配。
使用規(guī)則:
1、一個實體不能被更低訪問級別的實體定義绍些。
定義時的訪問級別要不小于使用時的訪問級別捞慌;
函數中參數類型、返回值類型的訪問級別要不小于函數的訪問級別柬批;
父類的訪問級別要不小于子類的訪問級別啸澡;
父類協(xié)議的訪問級別要不小于子類協(xié)議的訪問級別袖订;
原類型的訪問權限要不小于 typealias 定義的訪問權限;
原始值類型嗅虏、關聯值類型的訪問權限要不小于枚舉類型洛姑;
2、元祖類型的訪問級別取決于元祖中訪問權限最低的成員皮服。
3楞艾、泛型類型的訪問級別取決于類型及參數中訪問權限最低的那個。
4龄广、類型的訪問級別會影響成員(屬性硫眯、方法、初始化器择同、下標)两入、嵌套類型的默認訪問級別。若類型的訪問級別小于 internal奠衔,則成員和嵌套類型的默認訪問級別和類型的訪問級別相同谆刨;若類型的訪問級別不小于 internal塘娶,則成員和嵌套類型默認的訪問級別為 internal归斤。
setter/getter:
setter/getter 默認接收它們所屬環(huán)境的訪問級別〉蟀叮可以單獨設置 setter 的級別脏里,但 setter 的訪問級別要比 getter 的訪問級別低。
枚舉:
不能給枚舉中的每個 case 單獨設置訪問級別虹曙,故每個 case 的訪問級別和枚舉的訪問級別相同迫横。若枚舉的訪問級別為 public,則每個 case 的訪問級別也為 public酝碳。
協(xié)議:
不能給協(xié)議中的每個成員單獨設置訪問級別矾踱,故每個成員的訪問級別和協(xié)議的訪問級別相同。若協(xié)議的訪問級別為 public疏哗,則每個成員的訪問級別也為 public呛讲。
協(xié)議使用時的訪問級別取決于協(xié)議和使用者兩者中最低的訪問級別。
擴展:
若顯示的設置了擴展的訪問級別返奉,則擴展中的成員的訪問級別和擴展相同贝搁;若沒有顯示的設置擴展的訪問級別,則擴展中的成員的訪問級別和類中成員訪問級別規(guī)則相同芽偏。
擴展中的成員可以單獨設置訪問級別雷逆。
示例:
以下代碼運行在全局作用域
private struct Dog {
var age: Int = 0
func run() {}
}
fileprivate struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run()
dog.age = 1
}
}
結果:在全局作用域下可以正常使用。
說明:fileprivate 的作用域是在當前源文件污尉。private 的作用域是在其所定義地方都可以訪問膀哲,而在全局作用域下的 private 所定義的作用域為當前源文件往产,故在全局作用域下定義的 private 等價于 fileprivate。此時內部的成員默認也是為 fileprivate某宪。
將代碼修改一下:
private struct Dog {
private var age: Int = 0
private func run() {}
}
fileprivate struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run()
dog.age = 1
}
}
結果:以上代碼無論是不是在全局都不能正常運行捂齐。
說明:private 修飾的成員只能在當前大括號中使用,即 Dog 的大括號中使用缩抡。
class test {
private struct Dog {
var age: Int = 0
func run() {}
}
private struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run()
dog.age = 1
}
}
}
結果:以上代碼可以通過
說明:Dog 的作用域在整個 test 的大括號內奠宜。Dog 成員的默認訪問權限為 private,但成員的作用域和 Dog 的作用域相同即 test 的大括號內瞻想。
將代碼修改一下:
class test {
private struct Dog {
private var age: Int = 0
private func run() {}
}
private struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run()
dog.age = 1
}
}
}
結果:以上代碼不能通過压真。
說明:Dog 中 private 修飾的成員作用域只在 Dog 的大括號中,不能在 Person 中調用蘑险。
13滴肿、swift 中模式匹配
模式:單個或者復合值的結構,即模式不是一個特定的值佃迄,是一種抽象結構泼差。
模式分為兩種,一種是匹配任何類型的值呵俏;另外一種在運行時匹配某個特定的值堆缘,可能會失敗。
第一種模式用于結構簡單變量普碎,常量和可選綁定中的值吼肥。此類模式包括通配符模式,標識符模式麻车,以及包含前兩種模式的值綁定模式和元組模式缀皱。你可以為這類模式指定一個類型標注,從而限制它們只能匹配某種特定類型的值动猬。
第二種模式用于全局模式匹配啤斗。這種情況下,你試圖匹配的值在運行時可能不存在赁咙。此類模式包括枚舉用例模式钮莲、可選模式、表達式模式序目、類型轉換模式臂痕。你在 switch 語句的 case 標簽中,do 語句的 catch 子句中猿涨,或者在 if握童、while、guard叛赚、for-in 語句的 case 條件語句中使用這類模式澡绩。
模式匹配:
通配符模式:通配符模式由一個下劃線(_)構成稽揭,用于匹配并忽略任何值,當你想忽略被匹配的值時可以使用該模式肥卡。
for _ in 1...3{
}
標識符模式:標識符模式可以匹配任何值溪掀,并將匹配的一個變量或者常量綁定起來。
let someValue = 42
值綁定模式:值綁定模式把匹配到的值綁定給一個變量或者常量步鉴。把匹配到的值給常量是用關鍵字 let, 給變量時用關鍵字 var揪胃。
let point = (3, 2)
switch point {
//將point中的元素綁定到 x 和 y
case let (x, y):
print("point is at x:(\(x), \(y)")
}
元祖模式:元組模式是由逗號分隔的,具有零個或多個模式的列表氛琢,并由一對圓括號括起來喊递。元組模式匹配到響應的元組類型值。
let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
//便利 y==0 x是任意值的元組
for (x, y) in points where y==0 {
print("\(x) and \(y)")
}
枚舉用例模式:枚舉用例模式匹配現有的某個枚舉類型的某個用例阳似。枚舉用例模式出現在switch 語句中的 case 標簽中骚勘,以及 if,while,guard,for-in 語句的 case 條件中。
可選模式:可選項模式匹配 Optional 枚舉在 some(Wrapped) 中包裝的值撮奏∏味铮可選項模式為 for-in 語句提供了一種迭代數組的簡便方式,只為數組中非 nil 的元素執(zhí)行循環(huán)體畜吊。
let someOptional: Int? = 42
if case .some(let x) = someOptional {
print(x)//42
}
if case let x? = someOptional {
print(x)//42
}
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
for case let number? in arrayOfOptionalInts {
print("fond a \(number)")//2,3,5
}
類型轉換模式:case is Int: 或者 case let n as String:
表達式模式:范圍匹配 case (0..<2) case(0...2, 2...4)
條件句中使用where: case (let age) where age > 30
if case let:if case let .Student(name) = xiaoming { }
for case let: for case let x in array where x > 10 {} 或者 for x in array where x > 10
14泽疆、觀察者模式
觀察者模式(Observer Pattern):定義對象間的一對一,一對多的依賴關系定拟。當被觀察對象發(fā)生改變時于微,所有觀察對象都會收到通知逗嫡。
觀察者 Observer:通過 NSNotificationCenter 的 addObserver:selector:name:object 接口來注冊通知青自。
NSNotificationCenter:通知中心,負責廣播通知驱证。
被觀察的對象延窜,通過 postNotificationName:object:userInfo: 發(fā)送通知。
通知對象 NSNotification抹锄,當有通知來的時候逆瑞,Center 會調用觀察者注冊的接口來廣播通知,同時傳遞存儲著更改內容的 NSNotification 對象伙单。
15获高、兩段式初始化和安全檢查
Swift 在編碼安全方面為了保證初始化過程的安全,設定了兩段式初始化吻育、安全檢查念秧。
兩段式初始化
第1階段:初始化所有存儲屬性。
1布疼、外層調用指定/便捷初始化器摊趾。
2币狠、分配內存給實例,但未初始化砾层。
3漩绵、指定初始化器確保當前類定義的存儲屬性都初始化。
4肛炮、指定初始化器調用父類的初始化器止吐,不斷向上調用,形成初始化器鏈侨糟。
第2階段:設置新的存儲屬性值
1祟印、從頂部初始化器往下,鏈中的每一個指定初始化器都有機會進一步定制實例粟害。
2蕴忆、初始化器現在能夠使用 self(訪問、修改它的屬性悲幅,調用它的實例方法等等)套鹅。
3、最終汰具,鏈中任何便捷初始化器都有機會定制實例以及使用 self卓鹿。
安全檢查
1、指定初始化器必須保證在調用父類初始化器之前留荔,其所在類定義的所有存儲屬性都要初始化完成吟孙。
2、指定初始化器必須先調用父類初始化器聚蝶,然后才能為繼承的屬性設置新值杰妓。
3、便捷初始化器必須先調用同類中的其它初始化器碘勉,然后再為任意屬性設置新值巷挥。
4、初始化器在第1階段初始化完成之前验靡,不能調用任何實例方法倍宾、不能讀取任何實例屬性的值,也不能引用 self胜嗓。
5高职、直到第1階段結束,實例才算完全合法辞州。
16怔锌、mutating 關鍵字的使用
默認情況下,不能在實例方法中修改值類型的屬性。若在實例方法中使用 mutating 關鍵字产禾,不僅可以在實例方法中修改值類型的屬性排作,而且會在方法實現結束時將其寫回到原始結構。
mutating 特點:
1亚情、結構體中的函數如果想修改其中的屬性妄痪,需要在函數前加上 mutating,而類則不用楞件。
2衫生、mutating 本質是加一個 inout 修飾的 self,將 self 從 let 常量改成了 var 變量土浸。
3罪针、Inout 相當于取地址,可以理解為地址傳遞黄伊,即引用泪酱。
4、mutating 修飾方法还最,而 inout 修飾參數墓阀。
17、inout 的作用
inout 可以修改屬性的值拓轻。inout 是放在參數類型前斯撮,冒號后,mutating 放在方法的前面修改屬性值扶叉。值類型變量作為參數傳入函數勿锅,外界和函數參數的內存地址一致,函數內對參數的更改得到了保留枣氧。
1溢十、函數參數默認為常量。試圖從函數主體內部更改函數參數的值會導致編譯時錯誤作瞄。這意味著您不能錯誤地更改參數的值茶宵。如果您希望函數修改參數的值并且希望這些更改在函數調用結束后仍然存在,請將該參數定義為輸入輸出參數宗挥。
2、可以將 inout 關鍵字放在參數類型的前面來編寫輸入/輸出參數种蝶。
3契耿、只能將變量作為輸入輸出參數的參數傳遞,不能將常量或文字值作為參數傳遞螃征,因為無法修改常量和文字搪桂。將一個與號(&)作為變量傳入 inout 參數時,將它放在變量名的前面,以表明該變量可以被函數修改踢械。
4酗电、輸入輸出參數不具有默認值且可變參數不能標記為 inout。
注意事項:
1内列、使用 inout 關鍵字的函數撵术,在調用時需要在該參數前加上 & 符號。
2话瞧、inout 參數在傳入時必須為變量嫩与,不能為常量或字面量(literal)。
3交排、inout 參數不能有默認值划滋,不能為可變參數。
4埃篓、inout 參數不等同于函數返回值处坪,是一種使參數的作用域超出函數體的方式。
5架专、多個 inout 參數不能同時傳入同一個變量稻薇,因為拷入拷出的順序不定,那么最終值也不能確定胶征。
inout 參數的傳遞過程:
1塞椎、當函數被調用時,參數值被拷貝睛低。
2案狠、在函數體內,被拷貝的參數修改钱雷。
3骂铁、函數返回時,被拷貝的參數值被賦值給原有的變量罩抗。
func swap( a: inout Int, b: inout Int) {
let temp = a
a = b
b = temp
}
var a = 1
var b = 2
print(a, b)// 1 2
swap(a: &a, b: &b)
print(a, b)// 2 1
18拉庵、associatedtype 的作用
關聯類型:為協(xié)議中的某個類型提供了一個別名,其代表的真實類型在實現者中定義套蒂。
//協(xié)議钞支,使用關聯類型
protocol TableViewCell {
associatedtype T
func updateCell(_ data: T)
}
//遵守TableViewCell
class MyTableViewCell: UITableViewCell, TableViewCell {
typealias T = Model
func updateCell(_ data: Model) {
// do something ...
}
}
19、map操刀、filter烁挟、reduce 的作用
map 用于映射,可以將一個列表轉換為另一個列表骨坑。
[1, 2, 3].map{"\($0)"}// 數字數組轉換為字符串數組
["1", "2", "3"]
filter 用于過濾撼嗓,可以篩選出想要的元素。
[1, 2, 3].filter{$0 % 2 == 0} // 篩選偶數
// [2]
reduce 合并
[1, 2, 3].reduce(""){$0 + "\($1)"}// 轉換為字符串并拼接
// "123"
20、map 與 flatmap 的區(qū)別
1且警、map 可以對一個集合類型的所有元素做一個映射操作粉捻。
2、flatMap
第一個作用和 map 一樣斑芜,對一個集合類型的所有元素做一個映射操作且可以過濾為 nil 的情況肩刃。
例如:
let array = [1,2,5,6,7,nil]
let array_map = array.map { $0 }
//[Optional(1), Optional(2), Optional(5), Optional(6), Optional(7), nil]
let array_flatmap = array_map.flatMap { $0 }
//[1, 2, 5, 6, 7]
第二種情況可以進行“降維”操作。
let array = [["1", "2"],["3", "4"]]
let array_map = array.map { $0 }
//[["1", "2"], ["3", "4"]]
let array_flatmap = array_map.flatMap { $0 }
//["1", "2", "3", "4"]
21押搪、defer树酪、guard 的作用
defer 語句塊中的代碼,會在當前作用域結束前調用大州,無論函數是否會拋出錯誤续语。每當一個作用域結束就進行該作用域 defer 執(zhí)行。如果有多個 defer厦画,那么后加入的先執(zhí)行疮茄。
guard:過濾器/攔截器
guard 和 if 類似,不同的是根暑,guard 總是有一個 else 語句力试,如果表達式是假或者值綁定失敗的時候,會執(zhí)行 else 語句且在 else 語句中一定要停止函數調用排嫌。
22畸裳、throws 和 rethrows 的用法與作用
throws 用在函數上,表示這個函數會拋出錯誤淳地。
有兩種情況會拋出錯誤怖糊,一種是直接使用 throw 拋出;另一種是調用其他拋出異常的函數時颇象,直接使用 try XX 沒有處理異常伍伤。
enum DivideError: Error {
case EqualZeroError;
}
func divide(_ a: Double, _ b: Double) throws -> Double {
guard b != Double(0) else {
throw DivideError.EqualZeroError
}
return a / b
}
func split(pieces: Int) throws -> Double {
return try divide(1, Double(pieces))
}
rethrows 與 throws 類似,不過只適用于參數中有函數且函數會拋出異常的情況遣钳,rethrows 可以用 throws 替換扰魂,反過來不行。
func processNumber(a: Double, b: Double, function: (Double, Double) throws -> Double) rethrows -> Double {
return try function(a, b)
}
23蕴茴、do - catch
有 throw 的方法劝评,要進行 try 處理,在用 try 處理的時候荐开,要用上 do - catch 方法付翁。
do {
//解析二進制數據
let objcs = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! [[String : AnyObject]]
for dict in objcs {
let chilControllerName = dict["vcName"] as? String
let title = dict["title"] as? String
let imageName = dict["imageName"] as? String
addChildViewController(chilControllerName, title: title, imageName: imageName)
}
}catch {
//如果do里邊的代碼發(fā)生錯誤,比如晃听,解析不了數據,就會執(zhí)行catch里邊的代碼
}
補充:
在 do 中代碼發(fā)生錯誤的時候,才會執(zhí)行 catch 中代碼能扒。
try 正常處理異常佣渴,也就是要使用 do - catch 方法處理。
try! 告訴系統(tǒng)一定不會有異常初斑,可以不通過 do-catch 方式處理辛润,不建議這么寫。
try? 可以不通過 do-catch 方式處理见秤。如果沒錯砂竖,返回給我們一個可選類型的值,如果有錯鹃答,返回 nil乎澄。
24、@discardableResult
取消 Xcode 的警告??
25测摔、Swift 中的 KVC 和 KVO
KVC:要繼承 NSObject 并在屬性前加上 @objc
class KVCClass :NSObject{
@objc var someValue: String = "123"
}
let kvc = KVCClass()
kvc.someValue // 123
kvc.setValue("456", forKey: "someValue")
kvc.someValue // 456
KVO:要繼承自 NSObject 并將觀測的對象標記為 dynamic.
class KVOClass:NSObject {
dynamic var someValue: String = "123"
var someOtherValue: String = "abc"
}
class ObserverClass: NSObject {
func observer() {
let kvo = KVOClass()
kvo.addObserver(self, forKeyPath: "someValue", options: .new, context: nil)
kvo.addObserver(self, forKeyPath: "someOtherValue", options: .new, context: nil)
kvo.someValue = "456"
kvo.someOtherValue = "def"
kvo.removeObserver(self, forKeyPath: "someValue")
kvo.removeObserver(self, forKeyPath: "someOtherValue")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("\(keyPath!) change to \(change![.newKey] as! String)")
}
}
ObserverClass().observer()
//someValue change to 456
問&答
1置济、隱式解包可選什么時候使用?
1、當屬性在初始化不能為空的時候锋八。
2浙于、解決強引用循環(huán)的問題,當兩個實例相互引用挟纱,需要一個實例是非空引用羞酗。
2、Set 獨有的方法有哪些
// 定義一個 set
let setA: Set<Int> = [1, 2, 3, 4, 4]// {1, 2, 3, 4}, 順序可能不一致, 同一個元素只有一個值
let setB: Set<Int> = [1, 3, 5, 7, 9]// {1, 3, 5, 7, 9}
// 取并集 A | B
let setUnion = setA.union(setB)// {1, 2, 3, 4, 5, 7, 9}
// 取交集 A & B
let setIntersect = setA.intersection(setB)// {1, 3}
// 取差集 A - B
let setRevers = setA.subtracting(setB) // {2, 4}
// 取對稱差集, A XOR B = A - B | B - A
let setXor = setA.symmetricDifference(setB) //{2, 4, 5, 7, 9}
3紊服、什么時候使用 final
不允許 class/函數被繼承/重寫的時候在 class/函數前加上 final檀轨。
4、什么時候使用 @objc
1围苫、@objc 用途是為了在 Objective-C 和 Swift 混編的時候点骑,能夠正常調用 Swift 代碼『M荩可以用于修飾類慰照、協(xié)議、方法腺占、屬性淤袜。
2、常用的地方是在定義 delegate 協(xié)議中衰伯,會將協(xié)議中的部分方法聲明為可選方法铡羡,需要用到 @objc。
5意鲸、dynamic 的作用
由于 swift 是一個靜態(tài)語言烦周,沒有 Objective-C 中的消息發(fā)送這些動態(tài)機制尽爆,dynamic 的作用就是讓 swift 代碼也能有 Objective-C 中的動態(tài)機制,常用的地方就是 KVO 了读慎,如果要監(jiān)控一個屬性漱贱,則必須要標記為 dynamic。
6夭委、dynamic framework 和 static framework 的區(qū)別是什么幅狮?
靜態(tài)庫和動態(tài)庫。靜態(tài)庫是每一個程序單獨打包一份株灸,而動態(tài)庫則是多個程序之間共享崇摄。
靜態(tài)庫和動態(tài)庫是相對編譯期和運行期的:靜態(tài)庫在程序編譯時會被鏈接到目標代碼中,程序運行時將不再更改靜態(tài)庫慌烧;動態(tài)庫在程序編譯時并不會被鏈接到目標代碼中逐抑,只是在程序運行時才被載入。
不同點:
靜態(tài)庫在鏈接時杏死,會被完整的復制到可執(zhí)行文件中泵肄,如果多個 App 都使用了同一個靜態(tài)庫,那么每個 App 都會拷貝一份淑翼,缺點是浪費內存腐巢。
動態(tài)庫不會復制,只有一份玄括,程序運行時動態(tài)加載到內存中冯丙,系統(tǒng)只會加載一次,多個程序共用一份遭京,節(jié)約了內存胃惜。
共同點:
靜態(tài)庫和動態(tài)庫都是閉源庫,只能拿來滿足某個功能的使用哪雕,不會暴露內部具體的代碼信息船殉。
7、set 和 array 的區(qū)別
set 存儲的內容是無序的斯嚎,在內存中存儲的方式是不連續(xù)的利虫;array 是有序的,在內存中存儲的位置是連續(xù)的堡僻。
set 搜索元素的速率比 array 的要高糠惫,主要是 set 用了一個 hash 算法。
set 和 array 中都只能添加對象钉疫。
8硼讽、Error 如果要兼容 NSError 需要做什么操作
Error 是一個協(xié)議,swift 中的 Error 都是 enum牲阁,可以轉 NSError固阁。如果需要 Error 有 NSError 的功能壤躲,需要實現 LocalizedError 和 CustomNSError 協(xié)議。
9您炉、String 與 NSString 的關系與區(qū)別
String 是值類型柒爵,NSString 是引用類型役电。
10赚爵、定義靜態(tài)方法時關鍵字 static 和 class 有什么區(qū)別
static 定義的方法不可以被子類繼承,class 則可以法瑟。
11冀膝、Swift 中 self 和 Self
Self 是表示特定類型,并且只能用在協(xié)議中或者作為某個類的方法的返回值類型霎挟。
self 在實例方法中代指當前實例窝剖,在類方法中則代指當前類。
12酥夭、下面的功能特性都包含在 Swift 中嗎
1赐纱、泛型類
2、泛型結構體
3熬北、泛型協(xié)議
答案:Swift 包含1和2特性疙描。泛型可以在類、結構體讶隐、枚舉起胰、全局函數或者方法中使用。
3是通過 typealias 部分實現的巫延。typealias 不是一個泛型類型效五,它只是一個占位符的名字。它通常是作為關聯類型被引用炉峰,只有協(xié)議被一個類型引用的時候它才被定義畏妖。
13、如何自定義下標獲取
實現 subscript 即可疼阔,如
extension AnyList {
subscript(index: Int) -> T{
return self.list[index]
}
subscript(indexString: String) -> T?{
guard let index = Int(indexString) else {
return nil
}
return self.list[index]
}
}
索引除了數字之外戒劫,其他類型也是可以的。
14竿开、為什么數組索引越界會崩潰谱仪,而字典用下標取值時 key 沒有對應值的話返回的是 nil 不會崩潰。
struct Array<Element> {
subscript(index: Int) -> Element
}
struct Dictionary<Key: Hashable, Value> {
subscript(key: Key) -> Value?
}
1否彩、數組索引訪問的是一段連續(xù)地址疯攒,越界訪問也能訪問到內存,但這段內存不一定可用列荔,所以會引起Crash敬尺。
2枚尼、字典的 key 并沒有對應確定的內存地址,所以是安全的砂吞。
15署恍、給集合中元素是字符串的類型增加一個擴展方法,應該怎么聲明蜻直?
使用 where 子句, 限制 Element 為 String
extension Array where Element == String {
var isStringElement:Bool {
return true
}
}
["1", "2"].isStringElement
//[1, 2].isStringElement// error
16盯质、一個函數的參數類型只要是數字(Int、Float)都可以,要怎么表示?
Int概而、Float 都有一個協(xié)議
func myMethod<T>(_ value: T) where T: Numeric {
print(value + 1)
}
或者 ExpressibleByIntegerLiteral 協(xié)議也行
17呼巷、一個類型表示選項,可以同時表示有幾個選項選中(類似 UIViewAnimationOptions )赎瑰,用什么類型表示?
需要實現自 OptionSet王悍,一般使用 struct 實現。由于 OptionSet 要求有一個不可失敗的 init(rawValue:) 構造器餐曼,而枚舉無法做到這一點(枚舉的原始值構造器是可失敗的且有些組合值是沒辦法用一個枚舉值表示的)压储。
struct SomeOption: OptionSet {
let rawValue: Int
static let option1 = SomeOption(rawValue: 1 << 0)
static let option2 = SomeOption(rawValue:1 << 1)
static let option3 = SomeOption(rawValue:1 << 2)
}
let options: SomeOption = [.option1, .option2]
18、下面枚舉可以定義嗎?
enum Edges : (Double, Double) {
case TopLeft = (0.0, 0.0)
case TopRight = (1.0, 0.0)
case BottomLeft = (0.0, 1.0)
case BottomRight = (1.0, 1.0)
}
不能源譬。原始數值類型是元組類型集惋,即使元組中的數值滿足條件,也是不兼容的瓶佳。
原始值類型必須滿足:實現 Equatable 協(xié)議芋膘;是 Int、String霸饲、Character 轉換的類型为朋。
19、如何解決引用循環(huán)?
1厚脉、轉換為值類型习寸,只有類會存在引用循環(huán),所以如果能不用類傻工,是可以解引用循環(huán)的霞溪。
2、delegate 使用 weak 屬性中捆。
3鸯匹、閉包中,對有可能發(fā)生循環(huán)引用的對象泄伪,使用 weak 或者 unowned 修飾殴蓬。
20、在一個 HTTPS 連接的網站里蟋滴,輸入賬號密碼點擊登錄后染厅,到服務器返回這個請求前中間經歷了什么?
1痘绎、客戶端打包請求。包括 url肖粮,端口號孤页,賬號密碼等等。賬號密碼登陸應該用的是 Post 方式涩馆,所以相關的用戶信息會被加載到 body 里面行施。這個請求應該包含三個方面:網絡地址,協(xié)議凌净,資源路徑悲龟。注意,這里是 HTTPS冰寻,就是HTTP + SSL / TLS,在 HTTP 上又加了一層處理加密信息的模塊(相當于是個鎖)皿渗。這個過程相當于是客戶端請求鑰匙斩芭。
2、服務器接受請求乐疆。一般客戶端的請求會先發(fā)送到 DNS 服務器划乖。 DNS 服務器負責將你的網絡地址解析成IP地址,這個IP地址對應網上一臺機器挤土。這其中可能發(fā)生 Hosts Hijack 和 ISP failure 的問題琴庵。過了 DNS 這一關,信息就到了服務器端仰美,此時客戶端會和服務器的端口之間建立一個 socket 連接迷殿,socket 一般都是以 file descriptor 的方式解析請求。這個過程相當于是服務器端分析是否要向客戶端發(fā)送鑰匙模板咖杂。
3庆寺、服務器端返回數字證書。服務器端會有一套數字證書(相當于是個鑰匙模板)诉字,這個證書會先發(fā)送給客戶端懦尝。這個過程相當于是服務器端向客戶端發(fā)送鑰匙模板。
4壤圃、客戶端生成加密信息陵霉。根據收到的數字證書(鑰匙模板),客戶端會生成鑰匙伍绳,并把內容鎖上踊挠,此時信息已經加密。這個過程相當于客戶端生成鑰匙并鎖上請求墨叛。
5止毕、客戶端發(fā)送加密信息模蜡。服務器端會收到由自己發(fā)送出去的數字證書加鎖的信息。 這個時候生成的鑰匙也一并被發(fā)送到服務器端扁凛。這個過程是相當于客戶端發(fā)送請求忍疾。
6、服務器端解鎖加密信息谨朝。服務器端收到加密信息后卤妒,會根據得到的鑰匙進行解密,并把要返回的數據進行對稱加密字币。這個過程相當于服務器端解鎖請求则披、生成、加鎖回應信息洗出。
7士复、服務器端向客戶端返回信息◆婊睿客戶端會收到相應的加密信息阱洪。這個過程相當于服務器端向客戶端發(fā)送回應。
8菠镇、客戶端解鎖返回信息冗荸。客戶端會用剛剛生成的鑰匙進行解密利耍,將內容顯示在瀏覽器上蚌本。
21、以下有什么區(qū)別?
const int number = 0;
let number = 0
const 是一個變量在編譯期間被初始化值或者在編譯期間表達式的值.
let 關鍵字創(chuàng)建常量是在 Runtime 時初始化的,它能夠用靜態(tài)的或者動態(tài)表達式的結果初始化.注意它的值只能被初始化一次.
22隘梨、如何聲明一個只能被類 conform 的 protocol ?
protocol OnlyClassProtocol : class {
}
23程癌、自定義運算符
自定義的運算符可以在全局使用。需要使用 operator 關鍵字出嘹。使用 prefix席楚、infix、postfix 標記運算符使用的位置税稼。
prefix operator +++ {}
prefix func +++ (inout vector: Vector2D) -> Vector2D {
vector += vector
return vector
}
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled now has values of (2.0, 8.0)
// afterDoubling also has values of (2.0, 8.0)
24烦秩、自定義操作符
可以定義操作符的關聯性 associativity 和優(yōu)先級 precedence。associativity 有三個值:left郎仆、right只祠、none,默認是 none扰肌。precedence 默認值是:100抛寝。
infix operator +- { associativity left precedence 140 }
func +- (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y - right.y)
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector is a Vector2D instance with values of (4.0, -2.0)
25、nil 和 .None
var optional1: String? = nil
var optional2: String? = .None
nil 和 .None 有什么不同?變量 optional1 和 optional2 有什么不同盗舰?
答:nil和.None是一樣的晶府。當可選變量沒有值時,Optional.None(.None for short)是一般初始化可選變量的方式钻趋,而nil則是另一種寫法川陆。
事實上,下面條件語句輸出是true:
nil == .None
注: enumeration 結構
enum Optional<T> {
case None
case Some(T)
}
示例
1蛮位、下面代碼輸出什么较沪?
protocol Chef {
func makeFood()
}
extension Chef {
func makeFood() {
print("make food")
}
}
struct SeafoodChef: Chef {
func makeFood() {
print("make seafood")
}
}
let chefOne: Chef = SeafoodChef()
let chefTwo: SeafoodChef = SeafoodChef()
chefOne.makeFood()
chefTwo.makeFood()
結果:
make seafood
make seafood
分析:
在 Swift 中,協(xié)議中方法是動態(tài)派發(fā)失仁,擴展中方法是靜態(tài)派發(fā)尸曼。
如果協(xié)議中有方法聲明,則會根據對象的實際類型進行調用萄焦。
如果協(xié)議中沒有方法聲明控轿,則會根據對象的聲明類型進行調用。
上面 makeFood() 方法在 Chef 協(xié)議中已經聲明了楷扬,而 chefOne 雖然聲明為 Chef解幽,但實際實現為 SeafoodChef。所以烘苹,根據實際情況,makeFood() 會調用 SeafoodChef 中的實現片部。chefTwo 也是同樣的道理镣衡。
如果 protocol 中沒有聲明 makeFood() 方法,代碼又會輸出什么档悠?
make food
make seafood
因為協(xié)議中沒有聲明 makeFood() 方法廊鸥,所以,此時只會按照擴展中的聲明類型進行靜態(tài)派發(fā)辖所。也就是說惰说,會根據對象的聲明類型進行調用。chefone 被聲明為 Chef缘回,所以會調用擴展的實現吆视,chefTwo 被聲明為 SeafoodChef,則會調用 SeafoodChef 中的實現酥宴。
2啦吧、下面代碼有什么問題
public class Node {
public var value: Int
public var prev: Node?
public var post: Node?
public init(_ value: Int) {
self.value = value
}
}
答案:應該在 prev 或者 post 前面加上 weak。
原因:表面上看拙寡,以上代碼毫無問題授滓。但是我這樣一寫,問題就來了:
let head = Node(0)
let tail = Node(1)
head.post = tail
tail.prev = head
此時,head 和 tail 互相指向般堆,形成循環(huán)引用(retain cycle)在孝。
3、實現一個函數淮摔,輸入是任一整數私沮,輸出要返回輸入的整數 + 2
//利用 Swift 的 Currying 特性:
func add(_ num: Int) -> (Int) -> Int {
return { val in
return num + val
}
}
4、下面代碼有什么問題噩咪?
protocol SomeProtocol {
func doSomething()
}
class Person {
weak var delegate: SomeProtocol?
}
解析:聲明 delegate 屬性的時候錯誤顾彰,編譯器會報錯。
Swift 中協(xié)議不僅可以被 class 這樣的引用類型實現胃碾,也可以被 struct 和 enum 這樣的值類型實現(這是和 OC 的一大區(qū)別)涨享。weak 關鍵詞是 ARC 環(huán)境下,為引用類型提供引用計數這樣的內存管理仆百,它是不能被用來修飾值類型的厕隧。
修改方案如下:
1)在 protocol 前面加上 @objc。在 OC 中俄周,協(xié)議只能由 class 來實現吁讨,這樣一來,weak 修飾的對象與 OC 一樣峦朗,只不過是 class 類型建丧。如下:
@objc protocol SomeProtocol {
func doSomething()
}
2)在 SomeProtocol 之后添加 class 關鍵詞。如此一來就聲明該協(xié)議只能由類(class)來實現波势。如下:
protocol SomeProtocol: class {
func doSomething()
}
5翎朱、編程
public struct Thermometer {
public var temperature: Double
public init(temperature: Double) {
self.temperature = temperature
}
}
可以通過以下方式使用
var t: Thermometer = Thermometer(temperature:56.8)
能實現以下方法調用嗎?要怎么實現?
var thermometer: Thermometer = 56.8
解決:swift 提供以下協(xié)議,通過使用賦值操作符,用字面值直接初始化.
NilLiteralConvertible , BooleanLiteralConvertible , IntegerLiteralConvertible , FloatLiteralConvertible , UnicodeScalarLiteralConvertible , ExtendedGraphemeClusterLiteralConvertible , StringLiteralConvertible , ArrayLiteralConvertible , DictionaryLiteralConvertible.
采用相對應的協(xié)議,提供一個公有的構造器,允許字面值方式初始化它.
在這個溫度計這個例子中,需要實現FloatLiteralConvertible協(xié)議.
extension Thermometer : FloatLiteralConvertible {
public init(floatLiteral value: FloatLiteralType) {
self.init(temperature: value)
}
}
可以實現
var thermometer: Thermometer = 56.8
示例二:
func countUniques<T: Comparable>(array: Array<T>) -> Int {
let sorted = array.sort(<)
let initial: (T?, Int) = (.None, 0)
let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) }
return reduced.1
}
調用:
countUniques([1, 2, 3, 3]) // result is 3
重寫以上代碼,使其可以這樣調用
[1, 2, 3, 3].countUniques() // should print 3
泛型是可以被擴展的,但需要類型約束限制尺铣,如果泛型不滿足約束拴曲,那么擴展也是不可見或者不可訪問的。因此凛忿,全局函數 countUniques 可以被重寫為數組 Array 的擴展:
extension Array where Element: Comparable {
func countUniques() -> Int {
let sorted = sort(<)
let initial: (Element?, Int) = (.None, 0)
let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) }
return reduced.1
}
}
注意:這個被重寫的方法只有當泛型的類型 Element 實現了 Comparable 協(xié)議才是有效的澈灼。
6、給一個數組店溢,要求寫一個函數叁熔,交換數組中的兩個元素
func swap<T>(_ nums: inout [T], _ p: Int, _ q: Int) {
(nums[p], nums[q]) = (nums[q], nums[p])
}
7、以下函數會打印出什么逞怨?
var car = "Benz"
let closure = { [car] in
print("I drive \(car)")
}
car = "Tesla"
closure()
答案:"I drive Benz
當聲明閉包的時候者疤,捕獲列表會創(chuàng)建一份變量的 copy,被捕獲到的值是不會改變的叠赦,即使外界變量的值發(fā)生了改變驹马。所以會打印出"I drive Benz"革砸。
//稍作修改:
var car = "Benz"
let closure = {
print("I drive \(car)")
}
car = "Tesla"
closure()
答案:I drive Tesla
如果去掉閉包中的捕獲列表,編譯器會使用引用代替 copy糯累。在這種情況下算利,當閉包被調用時,變量的值是可以改變的泳姐。所以 clousre 用的還是全局的 car 變量效拭,此時將會打印出 "I drive Tesla"
8、以下代碼有什么問題胖秒?
public class ThermometerClass {
private(set) var temperature: Double = 0.0
public func registerTemperature(temperature: Double) {
self.temperature = temperature
}
}
let thermometerClass = ThermometerClass()
thermometerClass.registerTemperature(56.0)
public struct ThermometerStruct {
private(set) var temperature: Double = 0.0
public mutating func registerTemperature(temperature: Double) {
self.temperature = temperature
}
}
let thermometerStruct = ThermometerStruct()
thermometerStruct.registerTemperature(56.0)
在最后一行編譯器會提示錯誤缎患,結構體 ThermometerStruct 聲明一個可變的函數修改內部變量 temperature,但是 registerTemperature 卻被一個用 let 創(chuàng)建的實例所調用阎肝,用 let 定義的變量是不可變的挤渔,所以編譯通不過。
在結構體中风题,改變內部狀態(tài)的方法必須用 mutating 聲明判导,而且不允許用不可變的實例調用它。
9沛硅、簡化以下代碼
let animals = ["fish", "cat", "chicken", "dog"]
let sortedAnimals = animals.sort { (one: String, two: String) -> Bool in
return one < two
}
1 可以簡化閉包的參數眼刃,因為在閉包中,系統(tǒng)是可以通過類型推斷方式推算出參數的類型摇肌。所以你可以去掉參數的類型:
let sortedAnimals = animals.sort { (one, two) -> Bool in return one < two }
2 返回類型也可以推算出來擂红,所以可以去掉閉包的返回類型:
let sortedAnimals = animals.sort { (one, two) in return one < two }
3 可以用$i符號替換掉參數的名字,代碼然后就變成這樣:
let sortedAnimals = animals.sort { return $0 < $1 }
4 在單語句的閉包中围小,關鍵字return也可以省略篮条。最后一條語句的返回值就變成閉包的返回值:
let sortedAnimals = animals.sort { $0 < $1 }
5 終極奧義
let sortedAnimals = animals.sort(<)