Swift关键字解析

? !

?

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

  1. 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
  1. typealias 定义个闭包名字
typealias Success = (_ data: String) -> Void
typealias Failure = (_ error: String) -> Void

func request(_ url: String, success: Success, failue: Failue) {
    // do request
}
  1. 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

  1. private:访问级别所修饰的属性或者方法只能在当前类里访问。(注意:Swift4中,extension里也可以访问private属性)

  2. fileprivate修饰的属性火方法可以在当前的Swift源文件里访问

  3. internal(默认访问级别,internal修饰符可写可不写)

  • internal访问级别所修饰的属性或方法在源代码所在的整个模块都可以访问
  • 如果是框架或者是库代码,则在整个框架内部都可以访问,框架由外部代码所引用时,则不可以访问
  • 如果是 App 代码,也是在整个 App 代码内部可以访问。
  1. public 可以被任何人访问。但在其他 <mark>module</mark> 中不可以被 override 和 继承,而在 <mark>module</mark> 内可以被 override 和 继承。
  2. 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)岁的朋友")
}

//输出结果:
小朋友
大朋友
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容