Swift编程swiftSwift

Swift关键字解析

2018-07-24  本文已影响5人  HuangJn

? !

?

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修饰符可写可不写)

  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)岁的朋友")
}

//输出结果:
小朋友
大朋友
上一篇 下一篇

猜你喜欢

热点阅读