? !
?
Swift是一個強類型語言待锈,它希望在編譯器做更多的安全檢查,所以引入了類型判斷贫悄。而在類型判斷上如果要做到足夠安全瑞驱,避免空指針調用是一個最基本的要求。于是窄坦,Optional這種類型出現了唤反。
Optional在Swift中其實是一個枚舉類型,里面有None和Some兩種類型鸭津,其實所謂的nil就是Optional.None,非nil就是Optional.Some,然后會通過Some(T)包裝(wrap)原始值拴袭,這也是為什么在使用Optional的時候要拆包(從enum里取出原始值)的原因
public enum Optional<Wrapped>: ExpressibleNilLiteral {
case none
case some(Wrapped)
public init(_ some:Wrapped)
}
聲明Optional只需要在類型后面緊跟一個 ? 即可
let name: String?
//等同于
let name: Optional<String>
上面這個 Optional 的聲明曙博,意思不是“我聲明了一個Optional的String值”拥刻,而是“我聲明了一個Optional類型值,它可能包含一個String值,也可能什么都不包含父泳,也就是說實際上我們聲明的是Optional類型般哼,而不是一個String類型,這一點需搞清”
let number: Int? = Optional.some(42)
let noNumber: Int? = Optional.none
print(noNumber == nil)
// Prints "true"
使用Optional的值的時候需要在具體的操作惠窄,如調用方法蒸眠,屬性,下標索引等前面加上一個杆融?楞卡,如果是nil值,也就是Optional.None,會跳轉過后面的操作不執(zhí)行。如果有值蒋腮,也就是Optional.Some,可能就會拆包(unwrap),然后對拆包后的值執(zhí)行后面的操作淘捡,來保證執(zhí)行這個操作的安全性。
用if let 拆包
var errorCodeString: String?
errorCodeString = "404"
if let theError = errorCodeString, let errorCodeInterger = Int(theError, errorCodeInterger == 404 ) {
print("\(theError):\(errorCodeInterger)")
}
!
! 表示 隱式可選類型(implicitly upwrapped optionals),與可選類型 ? 類似池摧,只是由一個區(qū)別焦除,它們不需要解包,因為用 ! 代表你確認是有值的作彤,不過如果沒值為nil膘魄,后面的操作就會報錯crash.
var myLabel: UILable!
//相當于下面寫法的語法糖
var myLabel:ImplicityUnwrappedOptional<UILabel>
大多數時候好最好用 ? ,只有在你知道可選類型實例不會為nil或者一旦可選類型實例是nil就崩潰時才使用 ! 操作符
as? as!
Objective-C有多態(tài),某些情況下竭讳,我們會將子類實例賦值給父類指針创葡,到用的時候,再強轉回子類
Swfit這種情況是用 as? as!來做绢慢,如果要判斷某個實例的類型就用 is
A as? B 的意思是 如果實例A是B類型或者是B類型的子類灿渴,就將A的類型轉化成B類型,并返回轉換后的實例(是可選類型B?)如果不是呐芥,表達式返回nil,程序繼續(xù)運行奋岁。如果用 as! 思瘟,說明我們肯定A是類型B或者B的子類,那么強制轉換闻伶,如果不是滨攻,那么會crash.
??
?? 表達式就是三目運算符的簡單版。
let name = "huang"
let nickName = name ?? "haha" //如果name為nil蓝翰,那么表達式返回值為 ?? 之后的值光绕,如果name有值,那么表達式返回name本身的值畜份。
inout
出于某種原因诞帐,函數有時候需要修改實參的值。 in-out參數(in-out parameter)能讓函數影響函數體以外的變量爆雹。有兩個注意事項:首先停蕉,in-out參數不能有默認值,其次钙态,變長參數不能標記為inout.
var error = "The request failed"
func appendErrorCode(_ code: Int, toErrorString errorString: inout String){
if code == 400 {
errorString += " bad request"
}
}
appendErrorCode(400, toErrorString: &error)
print(error)
inout加在String前面表示這個函數期??一個特??的String:它需要一個inout的String慧起。 調用這個函數時,????給inout??數的變量需要在前面加上&册倒。這表示函數會修改這個變量蚓挤。 在這里,errorString被改為The request failed: bad request
mutating
在Swift中,包含三種類型:struct(結構體) enum(枚舉) class(類)
其中struct enum是值類型灿意,class是引用類型
但與Objective-C不同的是估灿,struct和enumd也可以擁有方法,其中方法可以為實例方法脾歧,也可以為類方法甲捏。
雖然結構體和枚可以定義自己的方法,但是默認情況下鞭执,實例方法中是不可以修改值類型的屬性司顿。
覺個簡單的例子,假如定義一個點結構體兄纺,該結構體有一個修改點位置的實例方法:
struct Point {
var x = 0 , y = 0
func moveXBy(x:Int, yBy y: Int){
self.x += x
//Cannot invoke '+=' with an argument list of type '(Int, Int)'
self.y += y
//Cannot invoke '+=' with an argument list of type '(Int, Int)'
}
}
編譯器拋出錯誤大溜,說明確實不能在實例方法中修改屬性值
為了能夠在實例方法中修改屬性的值,可以在方法定義前添加關鍵字 mutating
stuct Point {
var x = 0, y = 0
mutating func moveXBy(x:Int,)
}
var p = Point(x: 5, y: 5)
p.moveBy(3,yBy: 3)
另外估脆,在值類型的實例方法中钦奋,也可以直接修改self屬性值
enum TriStateSwitch
case Off,Low,High
mutating func next {
switch self {
case Off:
self = Low
case Low:
self = High
case High:
self = Off
}
}
typealias
- typealias 類型別名, 為已經存在的類型重新定義名字的疙赠,通過命名付材,可以是代碼變得更加清晰
extension Double {
var km: Double { return self * 1000.0}
var m: Double { return self }
var cm: Double { return self / 100 }
var ft : Double { return self / 3.28084}
}
let runningDistance: Double = 3.54.km
runningDistance
給Double取一個別名,讓代碼可讀性更強
typealias Length = Double
extension Double {
var km: Length { return self * 1000.0}
var m: Length { return self }
var cm: Length { return self / 100 }
var ft : Length { return self / 3.28084}
}
let runningDistance: Length = 3.54.km
runningDistance
- typealias 定義個閉包名字
typealias Success = (_ data: String) -> Void
typealias Failure = (_ error: String) -> Void
func request(_ url: String, success: Success, failue: Failue) {
// do request
}
- typealias 與 協(xié)議
另外一種使用場景是某個類型同時實現多個協(xié)議的組合時圃阳,我們可以用 & 符號鏈接幾個協(xié)議厌衔,然后給他們一個新的復合上下文的名字,來增強代碼的可讀性捍岳。
protocol Cat { }
protocol Dog { }
typealias Pat = Cat & Dog
associatedtype
associatedtype 其實與 typealias 一樣也是取別名富寿,但是它用在protocol里面, 并與在實現protocol的類型里的
typealias 配合使用
來看下面的例子
protocol WeightCalculable {
associatedtype WeightType
var weight: WeightType { get } //這里的weight的類型有可能是Double ,有可能是Int或其他,這里用associatedtype 取個 WeightType 代替
}
class iPhone7 : WeightCalculable {
typealias WeightType = Double //用typealias 為WeightType 指定具體類型
var weight: WeightType {
return 0.114
}
}
class Ship: WeightCalculable {
typealias WeightType = Int
let weight: WeightType
init(weight: Int){
self.weight = weight
}
}
extension Int {
typealias Weight = Int
var t: Weight { return 1_000*self}
}
//let titanic = Ship(weight: 46_328_000)
let titanic = Ship(weight: 46_328.t)
private fileprivate internal public open
這五種是Swift對于訪問權限的控制锣夹。按從低到高排序如下
pirvate < fileprivate < internal < public open
private:訪問級別所修飾的屬性或者方法只能在當前類里訪問页徐。(注意:Swift4中,extension里也可以訪問private屬性)
fileprivate修飾的屬性火方法可以在當前的Swift源文件里訪問
internal(默認訪問級別银萍,internal修飾符可寫可不寫)
- internal訪問級別所修飾的屬性或方法在源代碼所在的整個模塊都可以訪問
- 如果是框架或者是庫代碼变勇,則在整個框架內部都可以訪問,框架由外部代碼所引用時贴唇,則不可以訪問
- 如果是 App 代碼贰锁,也是在整個 App 代碼內部可以訪問。
- public 可以被任何人訪問滤蝠。但在其他 <mark>module</mark> 中不可以被 override 和 繼承豌熄,而在 <mark>module</mark> 內可以被 override 和 繼承。
- open 可以被任何人使用物咳,包括 <mark>override</mark>和繼承
noescape escaping
在Swift3之后,所有的閉包都默認為非逃逸閉包(@noescape)锣险,如果是逃逸閉包蹄皱,就用@escaping表示出來。
簡單介紹就是如果這個閉包是在這個函數結束之前內被調用芯肤,就是非逃逸的即noescape巷折,如果這個閉包是在函數函數執(zhí)行完后才被調用,調用的地方超過了這函數的范圍崖咨,所以叫逃逸閉包锻拘。
舉個例子,我們常用的masonry或者snapkit的添加約束的方法就是非逃逸的击蹲。因為這閉包馬上就執(zhí)行了署拟。
public func snap_makeConstraints(file: String = #file, line : UInt = #line, closure:(make: ConstraintMaker) -> Void) -> Void {
ConstraintMaker.makeConstraints(view: self, file: file, line: line, closure: closure)
}
網絡請求結束后的回調的閉包則是逃逸的,因為發(fā)起請求后過了一段時間后這個閉包才執(zhí)行歌豺。比如這個Alamofire里的處理返回json的completionHandler閉包推穷,就是逃逸的。
public func responseJSON(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: @escaping Response<AnyObject, NSError> -> Void)
-> Self
{
return response (
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: comletionHandler
)
}
guard
guard通常后面接一個表達式形成一個語句类咧,跟if/else語句一樣馒铃,guard語句會根據某個表達式返回的布爾值結果來執(zhí)行代碼;但不同之處是痕惋,如果某些條件沒有滿足区宇,可以用guard語句來提前退出函數
resource.request().onComplete { [weak self] response in
guard let strongSelf = self else {
return
}
let model = strongSelf.updateModel(response)
strongSelf.updateUI(model)
}
guard let 大多數情況下都可以代替 if let,以增加代碼閱讀性
不提倡:
func computeFFT(context: Context?,inputData: InputData?) throws -> Frequencies {
if let context = context {
if let inputData = inputData {
// use context and input to compute the frequencies
return frequencies
}else {
throw FFTError.noInputData
}
} else {
throw FFTError.noContext
}
}
提倡:
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
guard let context = context else {
throw FFTError.noContext
}
guard let inputData = inputData else {
throw FFTError.noInputData
}
// use context and input to compute the frequencies
return frequencies
}
不提倡:
if let number1 = number1 {
if let number2 = number2 {
if let number3 = number3 {
// do something with numbers
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
提倡:
guard let number1 = number1,
let number2 = number2,
let number3 = number3 else {
fatalError("impossible")
}
// do something with numbers
當然也不能亂用guard,記住原則是用guard語句來提前退出函數
defer
defer意為延緩值戳,推遲之意议谷,用defer修飾的語句,并不會馬上執(zhí)行述寡,而是被推入棧中柿隙,直到該作用域結束時才會被調用叶洞,如果一個作用域中有多個defer,其調用順序是自下而上的鲫凶。
聲明方式如下:
defer {
// do something
}
func doSomethingWithDefer(){
//1
openDirectory()
//2
defer{ closeDirectory() }
//3
openFile()
//4
defer{ closeFile() }
}
執(zhí)行順序 1 3 4 2
fallthrough
在Swift的switch中,case后面加了fallthrough的用法衩辟,就和Objective-C的case后面沒有break的用法是一樣的
使用fallthrough需要注意的有:
1.使用 fallthrough 后螟炫,會直接運行 【緊跟的后一個】 case 和 default 語句,不論條件是否滿足都會執(zhí)行
var age = 10
switch age {
case 0...10:
print("小朋友")
case 11...20:
print("大朋友")
case let x:
print("\(x)歲的朋友")
}
// 輸出 :
小朋友
大朋友
2.加了fallthrough語句后艺晴,【緊跟的后一個】case條件不能定義常量和變量
var age = 10
switch age {
case 0...10:
print("小朋友")
fallthrough //此處報錯
case let x:
print("\(x)歲的朋友")
}
//程序報錯:
'fallthrough' cannot transfer control to a case label that declares variables
3.執(zhí)行完fallthrough后直接跳到下一個條件語句昼钻,本條件執(zhí)行語句后面的語句不執(zhí)行
var age = 10
switch age {
case 0...10:
print("小朋友")
fallthrough
print("我跳轉了哦") //這一句沒有執(zhí)行
case 11...20:
print("大朋友")
case let x:
print("\(x)歲的朋友")
}
//輸出結果:
小朋友
大朋友