? !
?
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,会跳转过后面的操作不执行。如果有值,也就是Optional.Some,可能就会拆包(unwrap),然后对拆包后的值执行后面的操作,来保证执行这个操作的安全性。
用if let 拆包
var errorCodeString: String?
errorCodeString = "404"
if let theError = errorCodeString, let errorCodeInterger = Int(theError, errorCodeInterger == 404 ) {
print("\(theError):\(errorCodeInterger)")
}
!
! 表示 隐式可选类型(implicitly upwrapped optionals),与可选类型 ? 类似,只是由一个区别,它们不需要解包,因为用 ! 代表你确认是有值的,不过如果没值为nil,后面的操作就会报错crash.
var myLabel: UILable!
//相当于下面写法的语法糖
var myLabel:ImplicityUnwrappedOptional<UILabel>
大多数时候好最好用 ? ,只有在你知道可选类型实例不会为nil或者一旦可选类型实例是nil就崩溃时才使用 ! 操作符
as? as!
Objective-C有多态,某些情况下,我们会将子类实例赋值给父类指针,到用的时候,再强转回子类
Swfit这种情况是用 as? as!来做,如果要判断某个实例的类型就用 is
A as? B 的意思是 如果实例A是B类型或者是B类型的子类,就将A的类型转化成B类型,并返回转换后的实例(是可选类型B?)如果不是,表达式返回nil,程序继续运行。如果用 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 与 协议
另外一种使用场景是某个类型同时实现多个协议的组合时,我们可以用 & 符号链接几个协议,然后给他们一个新的复合上下文的名字,来增强代码的可读性。
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,如果这个闭包是在函数函数执行完后才被调用,调用的地方超过了这函数的范围,所以叫逃逸闭包。
举个例子,我们常用的masonry或者snapkit的添加约束的方法就是非逃逸的。因为这闭包马上就执行了。
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)
}
网络请求结束后的回调的闭包则是逃逸的,因为发起请求后过了一段时间后这个闭包才执行。比如这个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语句会根据某个表达式返回的布尔值结果来执行代码;但不同之处是,如果某些条件没有满足,可以用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修饰的语句,并不会马上执行,而是被推入栈中,直到该作用域结束时才会被调用,如果一个作用域中有多个defer,其调用顺序是自下而上的。
声明方式如下:
defer {
// do something
}
func doSomethingWithDefer(){
//1
openDirectory()
//2
defer{ closeDirectory() }
//3
openFile()
//4
defer{ closeFile() }
}
执行顺序 1 3 4 2
fallthrough
在Swift的switch中,case后面加了fallthrough的用法,就和Objective-C的case后面没有break的用法是一样的
使用fallthrough需要注意的有:
1.使用 fallthrough 后,会直接运行 【紧跟的后一个】 case 和 default 语句,不论条件是否满足都会执行
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.执行完fallthrough后直接跳到下一个条件语句,本条件执行语句后面的语句不执行
var age = 10
switch age {
case 0...10:
print("小朋友")
fallthrough
print("我跳转了哦") //这一句没有执行
case 11...20:
print("大朋友")
case let x:
print("\(x)岁的朋友")
}
//输出结果:
小朋友
大朋友