Swift学习笔记(一)

Posted by GeorgeWang on May 2, 2017

内容主要参考官方文档

1. swift关键知识点

  • !与 ?: !与 ?对应最新版oc的nonnull和nullable关键字,分别用于指明变量的非空和可选,保障变量的安全性;非空即,变量不能为空,否则编译不过;可选即,变量可以为空也可以不为空;
  • 拆包(unwrapper): 拆包对应?变量的读取,可选变量读取时,如果加上!,就表示变量非空,如果变量为空,将报错,如value!.obj;还有另一种拆包方式,如下:

      if let val = value.obj {
          ...
      }
    
  • @discardableResult: 默认情况下编译器就是会去检查返回参数是否有被使用,没有的话就会给出警告。如果你不想要这个警告,可以自己手动加上 @discardableResult

  • as、as!、as? :分别表示类向上转换、向下转换(拆包失败报错)、向下转换(拆包失败返回nil),这里的向上向下指的是,将变量转换为其定义的类的超类或子类,向上转换不会出现失败的情况,向下转换时,如果变量本身不是子类类型或子子类类型,那么就会失败,因此才有as!as?之分,提高代码安全性
  • typealiasassociatedtypetypealias相当于typedef,为类型起别名,用法:typealias Float = CGFloatassociatedtype用于指定协议中的泛型,在实现时最好用typealias指定其类型
  • 版本检测

      if #available(iOS 10, macOS 10.12, *) {
      		// Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
      } else {
      		// Fall back to earlier iOS and macOS APIs
      }
    

2. Collection Types(收藏类型)

  • swift提供了三种收藏类型,数组(array)、集合(set)和字典(dictionary),在swift中他们都是通过generic实现的
  • 把某个收藏类型赋给常量(let)时,该常量是immutable的,赋给变量(var)时,该变量是mutable的;换句话说,swift中,数组、集合、字典是否可变是通过常量变量来区分的
  • 数组可以通过**Array**或**[T]**表示,初始化用`Array(repeating: x, count: n)`或`array1 + array2`或`[e1, e2, e3]`;基于swift的类型参照,如果初始化的数组合法,可以省去类型声明
  • 数组常用:

      array.count
      array.isEmpty
      array.append()
      array += array2
      array[n]
      array[n..m] = [x个元素] // 不管是否x=m-n,n~m这个范围都会被替换成x个元素
      array.insert(t, at:n)
      array.remove(at:n)
      array.removeLast()
      for item in array {//...}
      for (index, value) in array.enumerate() {//...}
    
  • 集合Set为无序元素组成,元素类型必须遵循Hashable协议,声明时用**Set**,初始化`Set()`、`[]`
  • Set常用:

      set.isEmpty / set.count / set.insert(t) / set.remove(t) / set.removeAll() / set.contains(t)
      for item in set.sorted() {//...}
      交集并集:a.intersection(b) / a.union(b) / a.symmetricDifference(b) / a.subtracting(b)
                   交集				/ 并集	      /    并集-交集              / 并集-b
               a.isSubset(of:b) / a. isSuperset(of:b) / a. isDisjoint(with:b)
               是否b的子集       / 是否b的超集          / 是否不交
    
  • 字典用Dictionary<Key, Value>表示,或[Key : Value],初始化[Key : Value]()[k1:v1, k2:v2][:]后者要在类型参照情况下使用
  • 字典常用:

      dic.count / dic.isEmpty / dic[key] = value
      updateValue(_:forKey:) // 相比[]用法,如果替换成功会返回旧值
      dic.removeValue(forKey:)
      for (key, value) in dic {//...}
      for key in dic.keys
      for value in dic.values
    

3. 控制流 (Control Flow)

  • swift 支持while, for-in, switch-where, if, guard, continue, break
  • for-in : for value in stringArray, for (key, value) in dicArray, for idx in 1...n, for idx in 1..<n, 1...n1..<n的区别是,前者包含n,而后者不
  • while控制流可以有whilerepeat-while(和OC的do-while类似)两种形式,前者在函数块执行前判断条件是否成立,后者在函数块执行后判断是否成立
  • 条件控制流有ifswitch,if用于简单的条件判断,switch用于复杂的条件判断,switch在switch和OC中的不同是,switch不用break,执行完第一个case之后自动跳出,每个case可以包含多个匹配值,用,隔开,如:case "a", "A":;case还可以用1..<n这样的range来做范围匹配;case也可以用用tuple来匹配多种组合关系,如switch (1, 1) {case (0, 0):{} case (_, 1):{}},用_来表示任何值;case判断可以取出绑定值(在enum枚举中介绍过),如:case let (x, y), case (let x, 0), 将传入的值赋予常量xy
  • switch-where中,where可以对case的绑定值做额外的判断,如:case let (x, y) where x == y:
  • 其他的控制关键字,如:continuebreakfallthroughreturnthrow,在OC中有些用过了,这里说下fallthrough、和 throw;switch默认只会匹配第一个case之后跳出,用fallthrough之后能够接着下面的case判断,当然依然需要判断,而不是直接进入下一个case,除非是default;
  • swift中支持Labeled statement语法,用于多重控制流嵌套时候的标识,如while嵌套case,可以这样用

      labelName : while x<y {
    	
          switch x: {
          case 1:
              break labelName
          case 2:
              continue labelName
          }
      }
    

    在控制关键字breakcontinue后面加上控制流的labelName,直接跳出或执行下一个while循环,而不是switch

  • swift中的guard和if相似,不同的是它永远跟随一个else代码块,用于执行退出之类的操作

4. 函数

  • 函数声明格式:

      访问权限 func 函数名字(参数标签:参数类型, 参数标签:参数类型...) -> 返回类型
    
  • 如果函数没有返回值,可以省略 -> 返回类型,相当于-> VoidVoid是一个空的tuple类型()
  • 函数可以返回多个值组成的tuple类型,(标签:类型, 标签:类型...),使用时函数名().标签取出tuple的数据;返回的tuple也可以是optional的,如-> (标签:类型, 标签:类型...)?,这样可以返回空
  • 函数参数可以有标签名参数名,前者用于调用,后者用于函数内部使用,如func 函数名字(标签 参数:参数类型)默认情况下标签名和参数名是同一个;标签名可以用_替换,表示省略;参数可以有默认值,如func 函数名字(标签 参数:参数类型 = value),当一个参数有默认值的时候,调用时可以省略,一般情况下,没有默认值的参数比较重要,所以最好放在有默认值的参数之前
  • 函数可以有可变参数(variadic parameter),如(numbers: Double...)调用的时候,可以传入0~无限个Double类型的值,在函数内部相当于数组使用,如:numbers[3];一个函数最多只能有一个可变参数
  • 函数的参数默认是常量,所以一般情况下是不能修改的;如果需要修改参数的值,那么要在参数类型前面加上关键字in-out,如func xx(para: in-out String),并且调用的时候要在参数前加&,如:xx(&aString),就是引用传参
  • 将函数相当于值来传递,var mathFunction: (Int, Int) -> Int = addTwoInts,前提是变量与函数类型(包括参数和返回类型)一致;也可以不用声明类型,让swift类型引用let anotherMathFunction = addTwoInts
  • 既然函数可以作为变量,那他也可以作为参数以及返回值;如func xx(para: (T, T)->T)func xx() -> (T, T)->T
  • 函数也可以有嵌套函数,用于函数内部使用,如:

      func chooseStepFunction(backward: Bool) -> (Int) -> Int {
      		func stepForward(input: Int) -> Int { return input + 1 }
      		func stepBackward(input: Int) -> Int { return input - 1 }
      		return backward ? stepBackward : stepForward
      }
    
  • 以上两个特性都和闭包类似

5. 访问控制

  • 读取控制: swift相比oc,增加了访问控制的关键字,可以对类、结构体、enum,以及其中的方法,属性的控制。如果只是单一target的工程,那么没必要自己去指明访问控制,因为所有个体都有默认访问控制权限。
  • 控制范围: 模块(modules)和源文件(sourcefile),模块相当于framework、target,可以用于import;源文件就是单纯的*.swift文件;
  • 控制等级: swift提供了五个控制等级,分别是OpenpublicInternalFile-privatePrivate,它们和源文件及所在模块相关联;Open是最开放的,而Private是最封闭的。Openpublic对于模块间,文件间都是可访问的,具体不同下面介绍;Internal只供同个module内部使用,File-private只供源文件内部使用;Private只供定义的上下文使用。任何属性和方法的控制等级都不能高于其所在的class及其他entity的控制等级,即内部entity控制等级不得高于外部entity。
  • Open 和 public: Open定义的class在其他module可以继承,而public只能在同个module继承;属性方面,前者可以在不同模块的继承class中覆盖属性,而后者只能在同个module覆盖。
  • 内外关系: 默认的class、enum等访问等级为internal,成员属性、方法的默认访问等级也是internal;但是,如果class、enum等的访问等级高于等于internal,那么成员的等级还是internal;如果低于internal,比如privatefile-private,那么,成员的默认等级会是privatefile-private
  • Tuple 类型: Tuple访问等级和其最低等级的成员一致。返回值为Tuple的方法,要注意返回值的控制等级也和Tuple规则一样,所以方法的声明要对等级做声明,否则会对使用者造成误解。
  • Enumeration: Enumeration的每个case访问等级必须默认和其一致,不能单独声明case的访问等级。
  • Nested: Nested的外部entity为privatefile-private时,其等级都为private;外部为publicinternal时,其等级为internal,如果要想为public,必须明确指明。
  • class 继承: subclass的访问等级不得高于superclass,但覆盖superclass的成员属性和方法时,其访问等级可以高于superclass的。赋给变量、常量、属性的值,访问等级不得高于变量、常量、属性。
  • setter 和 getter: setter 和 getter默认访问等级和属性声明时的等级一致,不过可以通过level(set)来指定setterd的等级,不过level必须比属性的声明等级低(也就是getter等级),如:internal private(set) var number = 0,number为internal(getter为internal),而setter为private,这样以来,对于外部来说,number就是reonly的了。
  • 初始化方法: 可分为自定义初始化方法和默认初始化方法,自定义初始化方法等级低于或等于其类、结构体,默认初始化方法默认等级和其类、结构体等级一致,除非类、结构体为public时,那么方法还是internal,必须明确指明才为public。结构体的初始化方法访问等级和其最低的成员属性一致。
  • 协议(protocol): 协议的require成员访问等级必须和协议一致,这就要求实现者对protocol的require成员定义时对等级的一致。继承协议时,等级和superprotocol一致。类只能遵循比起定义时的控制等级低的协议,比如public class 遵循internal protocol,而protocol require的成员,实现时访问等级也必须高于或等于protocol访问等级。
  • 扩展(extension): 类、结构体、enum默认的扩展的成员访问等级和原来一样,不过可以明确地指明extension的访问等级,低于原等级。如果扩展用来实现协议,那么不能明确指明扩展的访问等级,因为协议的访问等级就够了。
  • Generics: 访问等级取其自身或参数的等级的最小值。
  • 类型重命名(alias): 访问等级必须比原来的类型的等级低。

6. 闭包

  • 全局函数(Global function)是命名的不能捕捉上下文变量的闭包,内嵌函数(Nested function)是既可命名又可捕捉上下文变量的闭包
  • 函数传闭包参数的时候,可以先通过定义普通函数后传参,也可以通过inline方式(一般使用这种,更简洁),使用如下:

      // normal function
      func backward(_ s1: String, _ s2: String) -> Bool {
      		return s1 > s2
      }
      var reversedNames = names.sorted(by: backward)
    	
      // inline closure
      reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
      		return s1 > s2
      })
    
  • 上下文类型参照(Inferring Type From Context):内联闭包可以根据函数上下文,使用简洁写法,省略参数类型、返回类型,如下:

      reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
    
  • 省略return(Implicit Returns from Single-Expression Closures):当closure内容只有一行表达式的时候,可以省略return,比如:

      reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
    
  • 用$0、$1替代参数,在省略return的基础上,还可以继续简略写法,用$0、$1替代参数,参数列表可以直接省略,如:

      reversedNames = names.sorted(by: { $0 > $1 } )
    

    甚至可以更短:

    	reversedNames = names.sorted(by: >)
    
  • 尾闭包(Trailing Closures):当闭包作为函数参数最后一个时,可以将闭包移出括号外面如:

      someFunctionThatTakesAClosure() {} 如果只有闭包一个参数,甚至可以省略括号:
      
    	reversedNames = names.sorted { $0 > $1 }
    
  • 值捕获:在闭包中使用闭包外的常量或变量,都会被闭包所捕获,下次嗲用还会存在,因此,闭包同样存在着循环引用
  • 闭包是引用类型:let alsoIncrementByTen = incrementByTen后者是闭包,alsoIncrementByTen只是对闭包的引用,而不是真实赋值
  • @escaping: 表示可逃逸闭包。逃逸闭包指的是,当闭包当作参数传递给方法时,如果在方法执行期间执行闭包,那么称其为不可逃逸闭包,如果在这期间还传递给其他方法,或者本方法结束之后再执行,那么称为可逃逸闭包。在swift3.0之前,默认闭包是可逃逸的,不可逃逸用@noescape表示;swift3.0之后,改为用@escaping表示可逃逸,默认为不可逃逸。@escaping的作用:编译器通过其是否可逃逸,来优化内存管理,比如,不可逃逸的闭包内容不用拷贝(和oc的block拷贝类似),而且,不可逃逸闭包也无需使用weakSelf、strongSelf
  • 自动闭包(Autoclosures):自动闭包是没有任何参数的闭包,可以省略括号,但不省略大括号,如:

      let customerProvider = { customersInLine.remove(at: 0) }
      // 以传参形式
      func serve(customer customerProvider: () -> String) {
      		print("Now serving \(customerProvider())!")
      }
      serve(customer: { customersInLine.remove(at: 0) } )
    

customerProvider是一个闭包。而在函数定义时,对于参数我们也可以定义为自动闭包类型 @autoclosure,这样子传参数时表达式将自动转化为闭包,甚至可以省略大括号,如:

func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
		customerProviders.append(customerProvider)
	}
	collectCustomerProviders(customersInLine.remove(at: 0))

其中customersInLine.remove(at: 0)被转为可逃逸闭包。 自动闭包应该少用,因为可读性不高。

7. Enumeration

  • swift中的枚举和oc有很大的不同,它的case不仅可以是int,还可以是stringcharfloat等,case定义的时候也不会按顺序自动被赋值为0、1、2…
  • swift的枚举还可以像类/结构体一定定义各种类型的变量/常量,还可以扩展,遵循协议等,还可以通过initialize方法来初始化case的值
  • enum的定义如下:

      1.//
      enum CompassPoint {
      		case north
      		case south
      		case east
      		case west
      }
      2.//
      enum Planet {
      		case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
      }
      3.//
      var directionToHead = CompassPoint.west
      directionToHead = .east // 已知directionToHead类型情况下,使用点语法
    
  • switch-case 时用点语法
  • 枚举类型的case可以用元组(tuple)表示,称为绑定值(Associated values),如:

      enum Barcode {
      		case upc(Int, Int, Int, Int)
      		case qrCode(String)
      } 可以用case对常量/变量赋值:
      		
    		var productBarcode = Barcode.upc(8, 85909, 51226, 3)
    

    在switch的时候,可以分别在每个case里面对元组(tuple)的值进行提取,如:

    	switch productBarcode {
          case .upc(let numberSystem, let manufacturer, let product, let check):
      			print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
          case .qrCode(let productCode):
      			print("QR code: \(productCode).")
      } 如果case中都是常量或变量,可以把let/var提到前面:
      
    	 switch productBarcode {
          case let .upc(numberSystem, manufacturer, product, check):
      			print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
          case let .qrCode(productCode):
      			print("QR code: \(productCode).")
          }
    
  • 枚举类型case可以有默认值(RawValue),但这个默认值和上面提到的绑定值不一样,默认值对于每个case来说是永远不会变的,且只能在枚举定义的时候设置,而绑定值是为常量/变量赋case值的时候,附带绑上去的,与case本身无关,默认值可以是string、char、integer、float,但不能是tuple,tuple只能是绑定值
  • 把枚举定义为Int或String类型时,case可以指定raw-value也可以不指定,Int不指定默认第一个为0后面的按顺序+1/指定时指定case后面的默认+1,String不指定时默认为case自身
  • 枚举还可以根据raw-value逆向得到某个case(称为enum initialize),如:

      let possiblePlanet = Planet(rawValue: 7) 得不到的时候,默认返回nil,因此该初始化方法返回值为**optional**
    
  • 递归枚举,是指在绑定元组(Associated tuple)中递归绑定其他枚举,递归枚举需要在enum前或case前加上关键字indirect,加在enum前面表示所有case都可以用递归,如一下枚举可以用来表示+/*算术表达式:

      indirect enum ArithmeticExpression {
      		case number(Int)
      		case addition(ArithmeticExpression, ArithmeticExpression)
      		case multiplication(ArithmeticExpression, ArithmeticExpression)
      }
    

8. Subscripts

  • swift中的Class、struct、enum支持subscripts下标语法,可以自定义如instance[key]的读写语法,一个类或结构体可以定义多个下标,使用哪个下标取决于key的类型,下标语法也支持多维度
  • subscripts定义在Class、struct、enum内部,可以为可读写,也可以为只读,取决于定义的方式,如下:

      // 可读写
      subscript(index: Int) -> Int {
      		get {
      		// 返回下标对应的值
      		}
      		set(newValue) {
      		// 设置对应下标的值
      		}
      }
    其中newValue为新的值,index为参数,参数可以有多个
      	
    	// 只读
    	subscript(index: Int) -> Int {
      		// 返回下标对应的值
      }
    	
      let ins = SomeType()
      ins[1] = 3 // 一维下标设置
      ins[1,2] = 2 // 二维下标设置
    

9. ARC

  • Swift 语言和OC一样,使用ARC,struct和enum是值类型,不是引用类型,故和arc无关,这里的介绍是在熟悉oc的前提下的
  • Swift 中弱引用以weakunowned表示
  • 声明为weak的变量,在arc自动释放后设置其为nil是,KVO不会触发(因为不是通过setter)
  • unownedweak不同的是,不会自动置nil,因此,要确保在实例的生命周期里面,他不为空,如果调用被释放了的unowned变量,会报错
  • swift还有一个unowned(unsafe),声明不安全的非持有属性,和unowned不同的是,arc不会做检测,因此不会报错,但我们会访问到错误的数据;在程序需要提高效率的时候可以使用unowned(unsafe),但要确保操作得当
  • 有一种情况是,两个类要互相持有,且都不能为空,这个时候,初始化就有点先有鸡才有蛋的问题了;解决办法是,在类A初始化init时,就用self传给类B去初始化,顺便把B赋给A,A.b为unwrapper类型,B.a为unowned let类型
  • 闭包也会出现循环引用的问题,这个和oc也是一样的,问题在于破解循环引用的办法,闭包采用定义捕获列表(captuer list)的方法,如下:

      var someClosure: (Int, String) -> String = {
      		[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
      		// closure body goes here
      } 在闭包的参数前面,用**[] 大括号表示列表,逗号分隔开捕获参数**,如果self有可能在闭包执行期间释放,那么应该定义为weak,并在闭包内对self做空判断
    

分享到: