Swift关键字解析

作者: HuangJn | 来源:发表于2018-07-24 13:58 被阅读5次

    ? !

    ?

    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)岁的朋友")
    }
    
    //输出结果:
    小朋友
    大朋友
    

    相关文章

      网友评论

        本文标题:Swift关键字解析

        本文链接:https://www.haomeiwen.com/subject/nmwjmftx.html