Swift 裏能夠將方法進行柯里化 (Currying),也就是把接受多個參數的方法變換成接受第一個參數的方法,而且返回接受餘下的參數而且返回結果的新方法git
func add(_ v1:Int,_ v2:Int) -> Int {
return v1 + v2
}
print(add(1, 2))
//柯里化(Currying)
func add(_ v:Int) -> (Int) -> Int {
return {$0 + v}
}
print(add(1)(2))
複製代碼
Swift 的 protocol
不只能夠被 class
類型實現,也適用於 struct 和 enum
。程序員
Swift 的 mutating
關鍵字修飾方法是爲了能在該方法中修改 struct 或是 enum 的變量,因此若是你沒在協議方法裏寫 ``mutating 的話,別人若是用 struct 或者 enum 來實現這個協議的話,就不能在方法裏改變本身的變量了github
在使用 class 來實現帶有 mutating
的方法的協議時,具體實現的前面是不須要加 mutating
修飾的,由於 class 能夠隨意更改本身的成員變量。因此說在協議裏用 mutating
修飾方法,對於 class 的實現是徹底透明,能夠看成不存在的編程
protocol Vehicle {
var numberOfWheels:Int{get}
mutating func changeNumberOfWheels()
}
struct MyCar:Vehicle {
var numberOfWheels: Int = 4
mutating func changeNumberOfWheels() {
numberOfWheels = 4
}
}
class Cars: Vehicle {
var numberOfWheels: Int = 0
func changeNumberOfWheels() {
numberOfWheels = 2
}
}
複製代碼
Sequence
是一系列相同類型的值的集合,而且提供對這些值的迭代能力。swift
迭代一個Sequence
最多見的方式就是 for-in 循環api
let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals {
print(animal)
}
複製代碼
Sequence 協議的定義數組
protocol Sequence {
associatedtype Iterator: IteratorProtocol
func makeIterator() -> Iterator
}
複製代碼
Sequence
協議只有一個必須實現的方法 makeIterator()
性能優化
makeIterator()
須要返回一個 Iterator
,它是一個 IteratorProtocol
類型。bash
也就是說只要提供一個Iterator
就能實現一個 Sequence
,那麼 Iterator
又是什麼呢?網絡
Iterator Iterator 在 Swift 3.1 標準庫中即爲 IteratorProtocol,它用來爲 Sequence 提供迭代能力。對於 Sequence,咱們能夠用 for-in 來迭代其中的元素,其實 for-in 的背後是 IteratorProtocol 在起做用
IteratorProtocol 的定義以下:
public protocol IteratorProtocol {
associatedtype Element
public mutating func next() -> Self.Element?
}
複製代碼
對於這個for...in
循環
let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals {
print(animal)
}
複製代碼
實際上編譯器會把以上代碼轉換成下面的代碼
var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
print(animal)
}
複製代碼
實現一個逆序
//咱們先實現一個IteratorProtocol協議類型
class ReverseIterator<T>: IteratorProtocol {
typealias Element = T
var array: [Element]
var currentIndex = 0
init(array: [Element]) {
self.array = array
currentIndex = array.count - 1
}
func next() -> Element? {
if currentIndex < 0{
return nil
}
else {
let element = array[currentIndex]
currentIndex -= 1
return element
}
}
}
// 而後咱們來定義 Sequence
struct ReverseSequence<T>:Sequence {
var array:[T]
init (array: [T]) {
self.array = array
}
typealias Iterator = ReverseIterator<T>
func makeIterator() -> ReverseIterator<T> {
return ReverseIterator(array: self.array)
}
}
for item in ReverseSequence(array: animals){
print(item)
}
複製代碼
元組是swift編程語言中惟一的一種複合類型,他能夠將指定有限個數的任何類型一次整理爲一個對象,元組中的每一種類型均可以是任何的結構體、枚舉或類類型,甚至也能夠是一個元組以及空元組。
好比交換輸入,普通程序員亙古以來可能都是這麼寫的
func swapMel1<T>(a:inout T, b:inout T) {
let temp = a
a = b
b = temp
}
複製代碼
可是要是使用多元組的話,咱們能夠不使用額外空間就完成交換,一會兒就達到了文藝程序員的寫法
func swapMel2<T>(a:inout T, b:inout T) {
(a,b) = (b,a)
}
複製代碼
@autoclosure
)自動閉包是一種自動建立的用來把做爲實際參數傳遞給函數的表達式打包的閉包。它不接受任何實際參數
,而且當它被調用時,它會返回內部打包的表達式的值
這個語法的好處在於經過寫普通表達式代替顯式閉包而使你省略包圍函數形式參數的括號
。
func getFirstPositive1(_ v1:Int, _ v2:Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirstPositive1(1, 2)
func getFirstPositive2(_ v1:Int, _ v2:() -> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
getFirstPositive2(1, 2) //這個報錯
getFirstPositive2(1, {2})
func getFirstPositive3(_ v1:Int, _ v2:@autoclosure () -> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
getFirstPositive3(1, 2)
複製代碼
@autoclosure
會自動的將2封裝爲{2}
@autoclosure
只支持() -> T
的格式參數
??
在 Swift 中,有一個很是有用的操做符,能夠用來快速地對 nil 進行條件判斷,那就是 ??
。這個操做符能夠判斷輸入並在當左側的值是非 nil 的 Optional 值時返回其 value,當左側是 nil 時返回右側的值
??
就是一個@autoclosure
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
複製代碼
咱們來猜想一下??
的實現
func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
switch optional {
case .Some(let value):
return value
case .None:
return defaultValue()
}
}
複製代碼
可能你會有疑問,爲何這裏要使用autoclosure
,直接接受T
做爲參數並返回不行麼,爲什麼要用 () -> T
這樣的形式包裝一遍,豈不是多此一舉?其實這正是 autoclosure
的一個最值得稱讚的地方。若是咱們直接使用T
,那麼就意味着在 ??
操做符真正取值以前,咱們就必須準備好一個默認值傳入到這個方法中,通常來講這不會有很大問題,可是若是這個默認值是經過一系列複雜計算獲得的話,可能會成爲浪費 -- 由於其實若是optional
不是 nil
的話,咱們其實是徹底沒有用到這個默認值,而會直接返回optional
解包後的值的。這樣的開銷是徹底能夠避免的,方法就是將默認值的計算推遲到 optional
斷定爲 nil
以後
在 Swift 中,其實 &&
和 ||
這兩個操做符裏也用到了 @autoclosure
public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool
public static func || (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool
複製代碼
逃逸閉包(@escaping )
當閉包做爲一個實際參數傳遞給一個函數的時候,咱們就說這個閉包逃逸了,由於它是在函數返回以後調用的。當你聲明一個接受閉包做爲形式參數的函數時,你能夠在形式參數前寫 @escaping
來明確閉包是容許逃逸的。
閉包能夠逃逸的一種方法是被儲存在定義於函數外的變量裏
。好比說,不少函數接收閉包實際參數來做爲啓動異步任務的回調。函數在啓動任務後返回,可是閉包要直到任務完成——閉包須要逃逸,以便於稍後調用
例如:當網絡請求結束後調用的閉包。發起請求後過了一段時間後這個閉包才執行,並不必定是在函數做用域內執行的
override func viewDidLoad() {
super.viewDidLoad()
getData { (data) in
print("閉包返回結果:\(data)")
}
}
func getData(closure:@escaping (Any) -> Void) {
print("函數開始執行--\(Thread.current)")
DispatchQueue.global().async {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
print("執行了閉包---\(Thread.current)")
closure("345")
})
}
print("函數執行結束---\(Thread.current)")
}
複製代碼
從結果能夠看出,逃逸閉包的生命週期是長於函數的。
逃逸閉包的生命週期:
即逃逸閉包的生命週期長於函數,函數退出的時候,逃逸閉包的引用仍被其餘對象持有,不會在函數結束時釋放。
非逃逸閉包(@noescaping)
一個接受閉包做爲參數的函數, 閉包是在這個函數結束前內被調用。
override func viewDidLoad() {
super.viewDidLoad()
handleData { (data) in
print("閉包返回結果:\(data)")
}
}
func handleData(closure:(Any) -> Void) {
print("函數開始執行--\(Thread.current)")
print("執行了閉包---\(Thread.current)")
closure("123")
print("函數執行結束---\(Thread.current)")
}
複製代碼
爲何要分逃逸閉包和非逃逸閉包
爲了管理內存,閉包會強引用它捕獲的全部對象,好比你在閉包中訪問了當前控制器的屬性、函數,編譯器會要求你在閉包中顯示 self 的引用,這樣閉包會持有當前對象,容易致使循環引用。
非逃逸閉包不會產生循環引用,它會在函數做用域內釋放,編譯器能夠保證在函數結束時閉包會釋放它捕獲的全部對象;使用非逃逸閉包的另外一個好處是編譯器能夠應用更多強有力的性能優化,例如,當明確了一個閉包的生命週期的話,就能夠省去一些保留(retain)和釋放(release)的調用;此外非逃逸閉包它的上下文的內存能夠保存在棧上而不是堆上。
與 Objective-C 不一樣,Swift 支持重載操做符這樣的特性,最多見的使用方式可能就是定義一些簡便的計算了
好比咱們須要一個表示二維向量的數據結構
struct Vector2D {
var x:CGFloat = 0
var y:CGFloat = 0
}
複製代碼
一個很簡單的需求是兩個 Vector2D 相加
let v1 = Vector2D(x: 2.0, y: 3.0)
let v2 = Vector2D(x: 1.0, y: 4.0)
let v3 = Vector2D(x: v1.x + v2.x, y: v1.y + v2.y)
複製代碼
若是隻作一次的話彷佛還好,可是通常狀況咱們會進行不少這種操做。這樣的話,咱們可能更願意定義一個 Vector2D 相加的操做,來讓代碼簡化清晰
func +(left:Vector2D,right:Vector2D) -> Vector2D {
Vector2D(x: left.x + right.x, y: left.y + right.y)
}
let v3 = v1 + v2
複製代碼
在Swift語言中,常見的操做符有+、-、*、/、>、<、==、&&、||等等,若是不喜歡,你也能夠定義本身喜歡的操做符。
precedencegroup
:定義操做符的優先級associativity
:操做符的結合律higherThan
、lowerThan
:運算符的優先級prefix、infix、postfix
:前綴、中綴、後綴運算符中綴
/// 定義優先級組
precedencegroup MyPrecedence {
// higherThan: AdditionPrecedence // 優先級,比加法運算高
lowerThan: AdditionPrecedence // 優先級, 比加法運算低
associativity: none // 結合方向:left, right or none
assignment: false // true=賦值運算符,false=非賦值運算符
}
infix operator +++: MyPrecedence // 繼承 MyPrecedence 優先級組
// infix operator +++: AdditionPrecedence // 也能夠直接繼承加法優先級組(AdditionPrecedence)或其餘優先級組
func +++(left: Int, right: Int) -> Int {
return left+right*2
}
print(2+++3) // 8
複製代碼
前綴
prefix operator ==+
prefix func ==+(left: Int) -> Int {
return left*2
}
print(==+2) // 4
複製代碼
後綴
postfix operator +==
postfix func +==(right: Int) -> Int {
return right*3
}
print(2+==) // 6
複製代碼
inout
:輸入輸出參數可變形式參數只能在函數的內部作改變。若是你想函數可以修改一個形式參數的值,並且你想這些改變在函數結束以後依然生效,那麼就須要將形式參數定義爲輸入輸出形式參數。
在形式參數定義開始的時候在前邊添加一個 inout
關鍵字能夠定義一個輸入輸出形式參數。輸入輸出形式參數有一個能輸入給函數的值,函數能對其進行修改,還能輸出到函數外邊替換原來的值。
你只能把變量做爲輸入輸出形式參數的實際參數。你不能用常量或者字面量做爲實際參數,由於常量和字面量不能修改。在將變量做爲實際參數傳遞給輸入輸出形式參數的時候,直接在它前邊添加一個和符號 (&
) 來明確能夠被函數修改。
var b = 10
func test(a:inout Int) {
a = 20
}
test(a: &b)
print(b) //20
複製代碼
能夠用inout定義一個輸入輸出參數:能夠在函數內部修改外部實參的值
inout
inout
參數不能有默認值inout
參數只能傳入能夠被屢次賦值的inout
參數的本質就是地址傳遞下標相信你們都很熟悉了,在絕大多數語言中使用下標來讀寫相似數組或者是字典這樣的數據結構的作法,彷佛已是業界標準。在 Swift 中,Array 和 Dictionary 固然也實現了下標讀寫
var arr = [1,2,3]
arr[2] // 3
arr[2] = 4 // arr = [1,2,4]
var dic = ["cat":"meow", "goat":"mie"]
dic["cat"] // {Some "meow"}
dic["cat"] = "miao" // dic = ["cat":"miao", "goat":"mie"]
複製代碼
做爲一門表明了先進生產力的語言,Swift 是容許咱們自定義下標的,咱們找到Array
已經支持的下標類型
subscript (index: Int) -> T
subscript (subRange: Range<Int>) -> Slice<T>
複製代碼
咱們發現若是咱們想要取出0、二、4
下標值,咱們須要循環枚舉。
其實這裏有一個更好的作法,好比能夠實現一個接受數組做爲下標輸入的讀取方法
extension Array {
subscript(input: [Int]) -> ArraySlice<Element> {
get {
var result = ArraySlice<Element>()
for i in input {
assert(i < self.count, "Index out of range")
result.append(self[i])
}
return result
}
set {
for (index,i) in input.enumerated() {
assert(i < self.count, "Index out of range")
self[i] = newValue[index]
}
}
}
}
var arr = ["a","b","c","d","z"]
print(arr[[0,3]]) //["a", "d"]
複製代碼
咱們能夠把函數當成參數或者變量來使用,函數內部嵌套函數
func forward(_ forward:Bool) -> (Int) -> Int {
func next(_ input:Int) -> Int {
input + 1
}
func previous(_ input:Int) -> Int {
input - 1
}
return forward ? next : previous
}
複製代碼
Objective-C 一個一直以來使人詬病的地方就是沒有命名空間,在應用開發時,全部的代碼和引用的靜態庫最終都會被編譯到同一個域和二進制中。這樣的後果是一旦咱們有重複的類名的話,就會致使編譯時的衝突和失敗。爲了不這種事情的發生,Objective-C 的類型通常都會加上兩到三個字母的前綴,好比 Apple 保留的 NS 和 UI 前綴,各個系統框架的前綴 SK (StoreKit),CG (CoreGraphic) 等。Objective-C 社區的大部分開發者也遵照了這個約定,通常都會將本身名字縮寫做爲前綴,把類庫命名爲 AFNetworking 或者 MBProgressHUD 這樣。這種作法能夠解決部分問題,至少咱們在直接引用不一樣人的庫時衝突的機率大大下降了,可是前綴並不意味着不會衝突,有時候咱們確實仍是會遇到即便使用前綴也仍然相同的狀況。另一種狀況是可能你想使用的兩個不一樣的庫,分別在它們裏面引用了另外一個相同的很流行的第三方庫,而又沒有更更名字。在你分別使用「這兩個庫中的一個時是沒有問題的,可是一旦你將這兩個庫同時加到你的項目中的話,這個你們共用的第三方庫就會和本身發生衝突了。
在 Swift 中,因爲可使用命名空間了,即便是名字相同的類型,只要是來自不一樣的命名空間的話,都是能夠和平共處的。和 C# 這樣的顯式在文件中指定命名空間的作法不一樣,Swift 的命名空間是基於 module 而不是在代碼中顯式地指明,每一個 module 表明了 Swift 中的一個命名空間。也就是說,同一個 target 裏的類型名稱仍是不能相同的。在咱們進行 app 開發時,默認添加到 app 的主 target 的內容都是處於同一個命名空間中的,咱們能夠經過建立 Cocoa (Touch) Framework 的 target 的方法來新建一個 module,這樣咱們就能夠在兩個不一樣的 target 中添加一樣名字的類型了
咱們能夠給一個複雜的難以理解的類型起一個別名,方便咱們使用和理解
按照swift標準庫的定義Void
就是一個空元組
public typealias Void = ()
複製代碼
咱們知道swift中沒有byte、short、Long
類型,若是咱們想要這樣的類型,就能夠用typealias
實現
typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
複製代碼
咱們還能夠給函數起一個別名
typealias IntFn = (Int,Int) -> Int
func difference(v1:Int,v2:Int) -> Int {
v1 - v2
}
let fn:IntFn = difference
print(fn(2,1)) //1
複製代碼
咱們還能夠給元組起別名
typealias Date = (year:Int,month:Int,day:Int)
func test(_ date:Date) {
print(date.year)
}
test((2019,10,30))
複製代碼
咱們在 Swift 協議中能夠定義屬性和方法,並要求知足這個協議的類型實現它們:
protocol Food { }
protocol Animal {
func eat(_ food: Food)
}
struct Meat: Food { }
struct Grass: Food { }
複製代碼
struct Tiger: Animal {
func eat(_ food: Food) {
}
}
複製代碼
由於老虎並不吃素,因此在 Tiger 的 eat 中,咱們極可能須要進行一些轉換工做才能使用 meat
associatedtype
聲明中可使用冒號來指定類型知足某個協議
protocol Animal {
associatedtype F: Food
func eat(_ food: F)
}
struct Tiger: Animal {
func eat(_ food: Meat) {
print("eat \(meat)")
}
}
struct Sheep: Animal {
func eat(_ food: Grass) {
print("eat \(food)")
}
}
複製代碼
不過在添加associatedtype
後,Animal
協議就不能被看成獨立的類型使用了。試想咱們有一個函數來判斷某個動物是否危險:
func isDangerous(animal: Animal) -> Bool {
if animal is Tiger {
return true
} else {
return false
}
}
複製代碼
會報錯
Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements
這是由於 Swift 須要在編譯時肯定全部類型,這裏由於 Animal
包含了一個不肯定的類型,因此隨着 Animal
自己類型的變化,其中的F
將沒法肯定 (試想一下若是在這個函數內部調用 eat
的情形,你將沒法指定 eat 參數的類型)。在一個協議加入了像是 associatedtype
或者 Self
的約束後,它將只能被用爲泛型約束,而不能做爲獨立類型的佔位使用,也失去了動態派發的特性。也就是說,這種狀況下,咱們須要將函數改寫爲泛型
func isDangerous<T: Animal>(animal: T) -> Bool {
if animal is Tiger {
return true
} else {
return false
}
}
isDangerous(animal: Tiger()) // true
複製代碼
一個可變形式參數能夠接受零或者多個特定類型的值
,可變參數必須是同一類型的。當調用函數的時候你能夠利用可變形式參數來聲明形式參數能夠被傳入值的數量是可變的。能夠經過在形式參數的類型名稱後邊插入三個點符號(...
)來書寫可變形式參數。
func sum(_ numbers:Int...) -> Int{
var total = 0
for item in numbers {
total += item
}
return total
}
複製代碼
Swift自帶的print函數
/// - Parameters:
/// - items: Zero or more items to print.
/// - separator: A string to print between each item. The default is a single
/// space (`" "`).
/// - terminator: The string to print after all items have been printed. The
/// default is a newline (`"\n"`).
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")
複製代碼
初始化是爲類、結構體或者枚舉準備實例的過程。這個過須要給實例裏的每個存儲屬性設置一個初始值而且在新實例可使用以前執行任何其餘所必須的配置或初始化
你經過定義初始化器來實現這個初始化過程,它更像是一個用來建立特定類型新實例的特殊的方法。不一樣於 Objective-C 的初始化器,Swift 初始化器不返回值。這些初始化器主要的角色就是確保在第一次使用以前某類型的新實例可以正確初始化。
類有兩種初始化器
class Person {
var age: Int
var name: String
//指定初始化器
init(age:Int, name:String) {
self.age = age
self.name = name
}
//便捷初始化器
convenience init(age:Int){
self.init(age:age,name:"")
}
}
複製代碼
初始化器的相互調用規則
Swift 中表示 「類型範圍做用域」 這一律念有兩個不一樣的關鍵字,它們分別是 static
和 class
。這兩個關鍵字確實都表達了這個意思
在非class
的類型上下文中,咱們統一使用 static
來描述類型做用域
Swift 的方法是支持默認參數的,也就是說在聲明方法時,能夠給某個參數指定一個默認使用的值
。在調用該方法時要是傳入了這個參數,則使用傳入的值,若是缺乏這個輸入參數,那麼直接使用設定的默認值進行調用
func test(a:String = "1",b:String,c:String = "3"){}
複製代碼
什麼是模式 模式是用於匹配的規則,好比switch
的case
、捕獲錯誤的catch
、if\guard\while\for
語句
Swift中模式有
_
匹配任何值_?
匹配非nil值enum Life {
case human(name:String,age:Int?)
case animal(name:String,age:Int?)
}
func check(_ life:Life){
switch life {
case .human(let name,_):
print("human:",name)
case .animal(let name,_?):
print("animal",name)
default:
print("other")
break
}
}
check(.human(name: "小明", age: 20)) //human: 小明
check(.human(name: "小紅", age: nil))//human: 小紅
check(.animal(name: "dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
複製代碼
就是給對應的變量常亮賦值
let a = 10
let b = "text"
複製代碼
let point = (2,3)
switch point {
case (let x,let y):
print("x:\(x) y:\(y)")
}
//x:2 y:3
複製代碼
匹配任何元祖
let point = [(0,0),(1,0),(2,0)]
for (x,_) in point{
print(x)
}
//0
//1
//2
複製代碼
if case
語句等價於一個case
的switch
語句,簡化了一些判斷語句
let age = 2
//if
if age >= 0 && age <= 9 {
print("[0,9]")
}
//枚舉模式
if case 0...9 = age{
print("[0,9]")
}
複製代碼
let ages:[Int?] = [2,3,nil,5]
for case nil in ages{
print("有nil值")
}
複製代碼
let ages:[Int?] = [nil,2,3,nil]
for case let age? in ages{
print(age)
}
// 2
//3
複製代碼
等價於
let ages:[Int?] = [nil,2,3,nil]
for item in ages{
if let age = item {
print(age)
}
}
複製代碼
class Animal {
func eat() {
print(type(of: self),"eat")
}
}
class Dog: Animal {
func run() {
print(type(of: self),"run")
}
}
class Cat: Animal {
func jump() {
print(type(of: self),"jump")
}
}
func check(_ animal:Animal) {
switch animal {
case let dog as Dog:
dog.run()
dog.eat()
case is Cat:
animal.eat()
default:
break
}
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
複製代碼
能夠經過重載運算符,自定義表達式模式的匹配規則
struct Student {
var score = 0, name = ""
static func ~=(pattern:Int,value:Student) -> Bool{
value.score >= pattern
}
static func ~=(pattern:ClosedRange<Int>,value:Student) -> Bool{
pattern.contains(value.score)
}
static func ~=(pattern:Range<Int>,value:Student) -> Bool{
pattern.contains(value.score)
}
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print(">=100")
case 90:print(">=90")
case 80..<90:print("[80,90]")
case 60...79:print("[60,79]")
default:break
}
複製代碼
extension String{
static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
pattern(value)
}
}
func hasPrefix(_ prefix:String) ->((String) -> Bool){{$0.hasPrefix(prefix)}}
func hasSuffix(_ prefix:String) ->((String) -> Bool){{$0.hasSuffix(prefix)}}
var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k"):
print("以j開頭,或者以k結尾")
default:
break
}
複製代碼
Range 操做符,用來簡單地指定一個從 X 開始連續計數到 Y 的範圍
咱們能夠仔細看看 Swift 中對着兩個操做符的定義
/// Forms a closed range that contains both `minimum` and `maximum`.
func ...<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos)
-> Range<Pos>
/// Forms a closed range that contains both `start` and `end`.
/// Requres: `start <= end`
func ...<Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos)
-> Range<Pos>
/// Forms a half-open range that contains `minimum`, but not
/// `maximum`.
func ..<<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos)
-> Range<Pos>
/// Forms a half-open range that contains `start`, but not
/// `end`. Requires: `start <= end`
func ..<<Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos)
-> Range<Pos>
/// Returns a closed interval from `start` through `end`
func ...<T : Comparable>(start: T, end: T) -> ClosedInterval<T>
/// Returns a half-open interval from `start` to `end`
func ..<<T : Comparable>(start: T, end: T) -> HalfOpenInterval<T>
複製代碼
不難發現,其實這幾個方法都是支持泛型的。除了咱們經常使用的輸入 Int 或者 Double,返回一個 Range 之外,這個操做符還有一個接受 Comparable 的輸入,並返回 ClosedInterval 或 HalfOpenInterval 的重載
好比想確認一個單詞裏的所有字符都是小寫英文字母的話,能夠這麼作
let test = "helLo"
let interval = "a"..."z"
for c in test {
if !interval.contains(String(c)) {
print("\(c) 不是小寫字母")
}
}
// 輸出
// L 不是小寫字母
複製代碼
在 Swift 中可以表示 「任意」 這個概念的除了 Any 和 AnyObject 之外,還有一個 AnyClass
。AnyClass
在 Swift 中被一個 typealias
所定義
public typealias AnyClass = AnyObject.Type
複製代碼
經過 AnyObject.Type
這種方式所獲得是一個元類型 (Meta)
。在聲明時咱們老是在類型的名稱後面加上 .Type
,好比 A.Type
表明的是 A 這個類型的類型
。也就是說,咱們能夠聲明一個元類型來存儲 A 這個類型自己,而在從A中取出其類型時,咱們須要使用到 .self
其實在 Swift 中,.self 能夠用在類型後面取得類型自己,也能夠用在某個實例後面取得這個實例自己。前一種方法能夠用來得到一個表示該類型的值,這在某些時候會頗有用;然後者由於拿到的實例自己
class A {
class func method() {
print("Hello")
}
}
let typeA: A.Type = A.self
typeA.method()
// 或者
let anyClass: AnyClass = A.self
(anyClass as! A.Type).method()
複製代碼
也許你會問,這樣作有什麼意義呢,咱們難道不是能夠直接使用 A.method() 來調用麼?沒錯,對於單個獨立的類型來講咱們徹底沒有必要關心它的元類型,可是元類型或者元編程的概念能夠變得很是靈活和強大,這在咱們編寫某些框架性的代碼時會很是方便。好比咱們想要傳遞一些類型的時候,就不須要不斷地去改動代碼了。在下面的這個例子中雖然咱們是用代碼聲明的方式獲取了 MusicViewController 和 AlbumViewController 的元類型,可是其實這一步驟徹底能夠經過讀入配置文件之類的方式來完成的。而在將這些元類型存入數組而且傳遞給別的方法來進行配置這一點上,元類型編程就很難被替代了
class MusicViewController: UIViewController {
}
class AlbumViewController: UIViewController {
}
let usingVCTypes: [AnyClass] = [MusicViewController.self,
AlbumViewController.self]
func setupViewControllers(_ vcTypes: [AnyClass]) {
for vcType in vcTypes {
if vcType is UIViewController.Type {
let vc = (vcType as! UIViewController.Type).init()
print(vc)
}
}
}
setupViewControllers(usingVCTypes)
複製代碼
這麼一來,咱們徹底能夠搭好框架,而後用 DSL 的方式進行配置,就能夠在不觸及 Swift 編碼的狀況下,很簡單地完成一系列複雜操做了
Swift 中咱們雖然能夠經過 dynamicType 來獲取一個對象的動態類型 (也就是運行時的實際類型,而非代碼指定或編譯器看到的類型)。可是在使用中,Swift 如今倒是不支持多方法的,也就是說,不能根據對象在動態時的類型進行合適的重載方法調用
class Pet {}
class Dog:Pet {}
class Cat:Pet {}
func eat(_ pet:Pet) {
print("pet eat")
}
func eat(_ dog:Dog) {
print("dog eat")
}
func eat(_ cat:Cat) {
print("cat eat")
}
func eats(_ pet:Pet,_ cat:Cat) {
eat(pet)
eat(cat)
}
eats(Dog(), Cat())
//pet eat
//cat eat
複製代碼
咱們在打印Dog
類型信息的時候,並無被用來在運行時選擇合適的func eat(_ dog:Dog) {}
方法,而是被忽略了,並採用了編譯期間決定的Pet
方法。
由於 Swift 默認狀況下是不採用動態派發的,所以方法的調用只能在編譯時決定
要想繞過這個限制,咱們可能須要進行經過對輸入類型作判斷和轉換
func eats(_ pet:Pet,_ cat:Cat) {
if let aCat = pet as? Cat {
eat(aCat)
}else if let aDog = pet as? Cat{
eat(aDog)
}
eat(cat)
}
複製代碼
在Swift中所聲明的屬性包括
set
和get
方法咱們能夠在存儲屬性中提供了willSet
和didSet
兩種屬性觀察方法
class Person {
var age:Int = 0{
willSet{
print("即將將年齡從\(age)設置爲\(newValue)")
}
didSet{
print("已經將年齡從\(oldValue)設置爲\(age)")
}
}
}
let p = Person()
p.age = 10
//即將將年齡從0設置爲10
//已經將年齡從0設置爲10
複製代碼
在willSet
和didSet
中咱們分別可使用newValue
和oldValue
來獲取將要設定的和已經設定的值。
初始化方法對屬性的設定,以及在 willSet 和 didSet 中對屬性的再次設定都不會再次觸發屬性觀察的調用
在 Swift 中所聲明的屬性包括存儲屬性
和計算屬性
兩種。其中存儲屬性將會在內存中實際分配地址對屬性進行存儲,而計算屬性則不包括背後的存儲,只是提供set
和 get
兩種方法。在同一個類型中,屬性觀察和計算屬性是不能同時共存的
。也就是說,想在一個屬性定義中同時出現 set
和 willSet
或didSet
是一件辦不到的事情。
計算屬性中咱們能夠經過改寫 set
中的內容來達到和willSet
及 didSet
一樣的屬性觀察的目的。若是咱們沒法改動這個類,又想要經過屬性觀察作一些事情的話,可能就須要子類化這個類,而且重寫它的屬性
重寫的屬性並不知道父類屬性的具體實現狀況,而只從父類屬性中繼承名字和類型,所以在子類的重載屬性中咱們是能夠對父類的屬性任意地添加屬性觀察的,而不用在乎父類中究竟是存儲屬性仍是計算屬性
class A {
var number:Int {
get{
print("get")
return 1
}
set{
print("set")
}
}
}
class B:A {
override var number: Int{
willSet{
print("willSet")
}
didSet{
print("didSet")
}
}
}
let b = B()
b.number = 10
get
willSet
set
didSet
複製代碼
set
和對應的屬性觀察的調用都在咱們的預想之中。這裏要注意的是 get
首先被調用了一次。這是由於咱們實現了 didSet
,didSet
中會用到 oldValue
,而這個值須要在整個 set
動做以前進行獲取並存儲待用,不然將沒法確保正確性。若是咱們不實現 didSet
的話,此次 get
操做也將不存在。
延時加載或者說延時初始化是很經常使用的優化方法,在構建和生成新的對象的時候,內存分配會在運行時耗費很多時間,若是有一些對象的屬性和內容很是複雜的話,這個時間更是不可忽略。另外,有些狀況下咱們並不會當即用到一個對象的全部屬性,而默認狀況下初始化時,那些在特定環境下不被使用的存儲屬性,也同樣要被初始化和賦值,也是一種浪費
swift提供了一個關鍵字lazy
class A {
lazy var str:String = {
let str = "Hello"
print("首次訪問的時候輸出")
return str
}()
}
let a = A()
print(a.str)
複製代碼
爲了簡化,咱們若是不須要作什麼額外工做的話,也能夠對這個 lazy 的屬性直接寫賦值語句
lazy var str1 = "word"
複製代碼
Objective-C 中咱們不太會常常說起到 「反射」 這樣的詞語,由於 Objective-C 的運行時比通常的反射還要靈活和強大。可能不少讀者已經習覺得常的像是經過字符串生成類或者 selector,而且進而生成對象或者調用方法等,其實都是反射的具體的表現。而在 Swift 中其實就算拋開 Objective-C 的運行時的部分,在純 Swift 範疇內也存在有反射相關的一些內容,只不過相對來講功能要弱得多。
Swift提供了Mirror
類型來作映射的事情
struct Person {
let name:String
let age:Int
}
let xiaoming = Person(name: "小明", age: 10)
let r = Mirror(reflecting: xiaoming)
print("xiaoming是\(r.displayStyle)")
print("屬性個數:\(r.children.count)")
for child in r.children{
print("屬性名:\(child.label) 值:\(child.value)")
}
xiaoming是Optional(Swift.Mirror.DisplayStyle.struct)
屬性個數:2
屬性名:Optional("name") 值:小明
屬性名:Optional("age") 值:10
複製代碼
經過 Mirror
初始化獲得的結果中包含的元素的描述都被集合在children
屬性下
public typealias Child = (label: String?, value: Any)
public typealias Children = AnyCollection<Mirror.Type.Child>
複製代碼
若是以爲一個個打印太過於麻煩,咱們也能夠簡單地使用 dump 方法來經過獲取一個對象的鏡像並進行標準輸出的方式將其輸出出來
print(dump(xiaoming))
▿ 反射.Person
- name: "小明"
- age: 10
Person(name: "小明", age: 10)
複製代碼
常見的應用場景是相似對 Swift 類型的對象作像 Objective-C 中 KVC 那樣的 valueForKey: 的取值。經過比較取到的屬性的名字和咱們想要取得的 key 值就好了,很是簡單
func valueFrom(_ object: Any, key: String) -> Any? {
let mirror = Mirror(reflecting: object)
for child in mirror.children {
let (targetKey, targetMirror) = (child.label, child.value)
if key == targetKey {
return targetMirror
}
}
return nil
}
if let name = valueFrom(xiaoming, key: "name") as? String {
print("經過 key 獲得值: \(name)")
}
經過 key 獲得值: 小明
複製代碼
在如今的版本中,Swift 的反射特性並非很是強大,咱們只能對屬性進行讀取,還不能對其設定。 另外須要特別注意的是,雖然理論上將反射特性應用在實際的 app 製做中是可行的,可是這一套機制設計的最初目的是用於 REPL 環境和 Playground 中進行輸出的。因此咱們最好遵照 Apple 的這一設定,只在 REPL 和 Playground 中用它來對一個對象進行深層次的探索,而避免將它用在 app 製做中 -- 由於你永遠不知道何時它們就會失效或者被大幅改動
咱們點擊進入官方文檔,能夠看到optional
是一個枚舉
enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
public init(_ some: Wrapped)
}
複製代碼
咱們來看看下面這兩個值是否相同anotherString literalString
var string:String? = "string"
var anotherString:String?? = string
var literalString:String?? = "string"
print(anotherString)
print(literalString)
print(anotherString == literalString)
//Optional(Optional("string"))
//Optional(Optional("string"))
//true
複製代碼
var anotherString:String?? = string
的寫法其實就是Optional.some(string)
var literalString:String?? = "string"
的寫法是Optional.some(Optional.some("string"))
二者的返回值是同樣的,因此二者相等
下面兩個對象是否相等呢?
var aNil:String? = nil
var anoterNil:String?? = aNil
var literalNil:String?? = nil
print(anoterNil)
print(literalNil)
print(anoterNil == literalNil)
//Optional(nil)
//nil
//false
複製代碼
anoterNil
是可選項裏面包含一個可選項值爲nil
的可選項literalNil
是可選擇值爲nil
的可選項咱們可使用fr v -R
來打印具體信息
什麼是模式 模式是用於匹配的規則,好比switch
的case
、捕獲錯誤的catch
、if\guard\while\for
語句
Swift中模式有
_
匹配任何值_?
匹配非nil值enum Life {
case human(name:String,age:Int?)
case animal(name:String,age:Int?)
}
func check(_ life:Life){
switch life {
case .human(let name,_):
print("human:",name)
case .animal(let name,_?):
print("animal",name)
default:
print("other")
break
}
}
check(.human(name: "小明", age: 20)) //human: 小明
check(.human(name: "小紅", age: nil))//human: 小紅
check(.animal(name: "dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
複製代碼
就是給對應的變量常亮賦值
let a = 10
let b = "text"
複製代碼
let point = (2,3)
switch point {
case (let x,let y):
print("x:\(x) y:\(y)")
}
//x:2 y:3
複製代碼
匹配任何元祖
let point = [(0,0),(1,0),(2,0)]
for (x,_) in point{
print(x)
}
//0
//1
//2
複製代碼
if case
語句等價於一個case
的switch
語句,簡化了一些判斷語句
let age = 2
//if
if age >= 0 && age <= 9 {
print("[0,9]")
}
//枚舉模式
if case 0...9 = age{
print("[0,9]")
}
複製代碼
let ages:[Int?] = [2,3,nil,5]
for case nil in ages{
print("有nil值")
}
複製代碼
let ages:[Int?] = [nil,2,3,nil]
for case let age? in ages{
print(age)
}
// 2
//3
複製代碼
等價於
let ages:[Int?] = [nil,2,3,nil]
for item in ages{
if let age = item {
print(age)
}
}
複製代碼
class Animal {
func eat() {
print(type(of: self),"eat")
}
}
class Dog: Animal {
func run() {
print(type(of: self),"run")
}
}
class Cat: Animal {
func jump() {
print(type(of: self),"jump")
}
}
func check(_ animal:Animal) {
switch animal {
case let dog as Dog:
dog.run()
dog.eat()
case is Cat:
animal.eat()
default:
break
}
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
複製代碼
能夠經過重載運算符,自定義表達式模式的匹配規則
struct Student {
var score = 0, name = ""
static func ~=(pattern:Int,value:Student) -> Bool{
value.score >= pattern
}
static func ~=(pattern:ClosedRange<Int>,value:Student) -> Bool{
pattern.contains(value.score)
}
static func ~=(pattern:Range<Int>,value:Student) -> Bool{
pattern.contains(value.score)
}
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print(">=100")
case 90:print(">=90")
case 80..<90:print("[80,90]")
case 60...79:print("[60,79]")
default:break
}
複製代碼
extension String{
static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
pattern(value)
}
}
func hasPrefix(_ prefix:String) ->((String) -> Bool){{$0.hasPrefix(prefix)}}
func hasSuffix(_ prefix:String) ->((String) -> Bool){{$0.hasSuffix(prefix)}}
var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k"):
print("以j開頭,或者以k結尾")
default:
break
}
複製代碼