Swift-27个关键字,助力开发(篇幅有点长)

一、柯里化(Currying)

Swift 里能够将方法进行柯里化 (Currying),也就是把接受多个参数的方法变换成接受第一个参数的方法,而且返回接受余下的参数而且返回结果的新方法git

func add(_ v1:Int,_ v2:Int) -> Int {
    return v1 + v2
}
print(add(1, 2))


//柯里化(Currying)
func add(_ v:Int) -> (Int) -> Int {
    return {$0 + v}
}
print(add(1)(2))

复制代码

二、mutating

Swift 的 protocol 不只能够被 class类型实现,也适用于 struct 和 enum程序员

Swift 的 mutating 关键字修饰方法是为了能在该方法中修改 struct 或是 enum 的变量,因此若是你没在协议方法里写 ``mutating 的话,别人若是用 struct 或者 enum 来实现这个协议的话,就不能在方法里改变本身的变量了github

在使用 class 来实现带有 mutating的方法的协议时,具体实现的前面是不须要加 mutating修饰的,由于 class 能够随意更改本身的成员变量。因此说在协议里用 mutating修饰方法,对于 class 的实现是彻底透明,能够看成不存在的编程

protocol Vehicle {
   var numberOfWheels:Int{get}
   mutating func changeNumberOfWheels()
}

struct MyCar:Vehicle {
   var numberOfWheels: Int = 4
   
   mutating func changeNumberOfWheels() {
       numberOfWheels = 4
   }
}

class Cars: Vehicle {
   var numberOfWheels: Int = 0
   func changeNumberOfWheels() {
       numberOfWheels = 2
   }
}
复制代码

三、Sequence

Sequence 是一系列相同类型的值的集合,而且提供对这些值的迭代能力。swift

迭代一个Sequence最多见的方式就是 for-in 循环api

let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals {
   print(animal)
}
复制代码

Sequence 协议的定义数组

protocol Sequence {
   associatedtype Iterator: IteratorProtocol
   func makeIterator() -> Iterator
}
复制代码

Sequence 协议只有一个必须实现的方法 makeIterator()性能优化

makeIterator() 须要返回一个 Iterator,它是一个 IteratorProtocol 类型。bash

也就是说只要提供一个Iterator 就能实现一个 Sequence,那么 Iterator 又是什么呢?网络

Iterator Iterator 在 Swift 3.1 标准库中即为 IteratorProtocol,它用来为 Sequence 提供迭代能力。对于 Sequence,咱们能够用 for-in 来迭代其中的元素,其实 for-in 的背后是 IteratorProtocol 在起做用

IteratorProtocol 的定义以下:

public protocol IteratorProtocol {
   associatedtype Element
   public mutating func next() -> Self.Element?
}
复制代码

对于这个for...in循环

let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals {
   print(animal)
}
复制代码

实际上编译器会把以上代码转换成下面的代码

var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
    print(animal)
}
复制代码
  • 一、获取到 animals 数组的 Iterator
  • 二、在一个 while 循环中,经过 Iterator 不断获取下一个元素,并对元素进行操做
  • 三、当 next() 返回 nil 时,退出循环

实现一个逆序

//咱们先实现一个IteratorProtocol协议类型
class ReverseIterator<T>: IteratorProtocol {
   typealias Element = T
   var array: [Element]
   var currentIndex = 0

   init(array: [Element]) {
       self.array = array
       currentIndex = array.count - 1
   }
   func next() -> Element? {
       if currentIndex < 0{
           return nil
       }
       else {
           let element = array[currentIndex]
           currentIndex -= 1
           return element
       }
   }
}

// 而后咱们来定义 Sequence
struct ReverseSequence<T>:Sequence {
   var array:[T]
   init (array: [T]) {
       self.array = array
   }
   typealias Iterator = ReverseIterator<T>
   func makeIterator() -> ReverseIterator<T> {
       return ReverseIterator(array: self.array)
   }
}

for item in ReverseSequence(array: animals){
   print(item)
}

复制代码

参考:Swift 中的 Sequence(一)

四、元组(Tuple)

元组是swift编程语言中惟一的一种复合类型,他能够将指定有限个数的任何类型一次整理为一个对象,元组中的每一种类型均可以是任何的结构体、枚举或类类型,甚至也能够是一个元组以及空元组。

好比交换输入,普通程序员亘古以来可能都是这么写的

func swapMel1<T>(a:inout T, b:inout T) {
    let temp = a
    a = b
    b = temp
}
复制代码

可是要是使用多元组的话,咱们能够不使用额外空间就完成交换,一会儿就达到了文艺程序员的写法

func swapMel2<T>(a:inout T, b:inout T) {
   (a,b) = (b,a)
}
复制代码

五、自动闭包(@autoclosure)

自动闭包是一种自动建立的用来把做为实际参数传递给函数的表达式打包的闭包。它不接受任何实际参数,而且当它被调用时,它会返回内部打包的表达式的值

这个语法的好处在于经过写普通表达式代替显式闭包而使你省略包围函数形式参数的括号

func getFirstPositive1(_ v1:Int, _ v2:Int) -> Int {
    return v1 > 0 ? v1 : v2
}
getFirstPositive1(1, 2)


func getFirstPositive2(_ v1:Int, _ v2:() -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive2(1, 2) //这个报错
getFirstPositive2(1, {2})

func getFirstPositive3(_ v1:Int, _ v2:@autoclosure () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive3(1, 2)
复制代码
  • @autoclosure会自动的将2封装为{2}

  • @autoclosure只支持() -> T的格式参数

??

在 Swift 中,有一个很是有用的操做符,能够用来快速地对 nil 进行条件判断,那就是 ??。这个操做符能够判断输入并在当左侧的值是非 nil 的 Optional 值时返回其 value,当左侧是 nil 时返回右侧的值

??就是一个@autoclosure

public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
复制代码

咱们来猜想一下??的实现

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
   switch optional {
       case .Some(let value):
           return value
       case .None:
           return defaultValue()
       }
}
复制代码

可能你会有疑问,为何这里要使用autoclosure,直接接受T 做为参数并返回不行么,为什么要用 () -> T 这样的形式包装一遍,岂不是多此一举?其实这正是 autoclosure 的一个最值得称赞的地方。若是咱们直接使用T,那么就意味着在 ?? 操做符真正取值以前,咱们就必须准备好一个默认值传入到这个方法中,通常来讲这不会有很大问题,可是若是这个默认值是经过一系列复杂计算获得的话,可能会成为浪费 -- 由于其实若是optional 不是 nil 的话,咱们其实是彻底没有用到这个默认值,而会直接返回optional 解包后的值的。这样的开销是彻底能够避免的,方法就是将默认值的计算推迟到 optional 断定为 nil 以后

在 Swift 中,其实 &&|| 这两个操做符里也用到了 @autoclosure

public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool

public static func || (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool
复制代码

六、逃逸闭包(@escaping )与非逃逸闭包(@noescaping)

逃逸闭包(@escaping )

当闭包做为一个实际参数传递给一个函数的时候,咱们就说这个闭包逃逸了,由于它是在函数返回以后调用的。当你声明一个接受闭包做为形式参数的函数时,你能够在形式参数前写 @escaping 来明确闭包是容许逃逸的。

闭包能够逃逸的一种方法是被储存在定义于函数外的变量里。好比说,不少函数接收闭包实际参数来做为启动异步任务的回调。函数在启动任务后返回,可是闭包要直到任务完成——闭包须要逃逸,以便于稍后调用

例如:当网络请求结束后调用的闭包。发起请求后过了一段时间后这个闭包才执行,并不必定是在函数做用域内执行的

override func viewDidLoad() {
        super.viewDidLoad()
         
        getData { (data) in
            print("闭包返回结果:\(data)")
        }
    }

    func getData(closure:@escaping (Any) -> Void) {
        print("函数开始执行--\(Thread.current)")
        DispatchQueue.global().async {
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
                print("执行了闭包---\(Thread.current)")
                closure("345")
            })
        }
        print("函数执行结束---\(Thread.current)")
    }
复制代码

从结果能够看出,逃逸闭包的生命周期是长于函数的。

逃逸闭包的生命周期:

  • 一、闭包做为参数传递给函数;
  • 二、退出函数;
  • 三、闭包被调用,闭包生命周期结束

即逃逸闭包的生命周期长于函数,函数退出的时候,逃逸闭包的引用仍被其余对象持有,不会在函数结束时释放。

非逃逸闭包(@noescaping)

一个接受闭包做为参数的函数, 闭包是在这个函数结束前内被调用。

override func viewDidLoad() {
        super.viewDidLoad()
         
        handleData { (data) in
            print("闭包返回结果:\(data)")
        }
    }

    func handleData(closure:(Any) -> Void) {
        print("函数开始执行--\(Thread.current)")
        print("执行了闭包---\(Thread.current)")
        closure("123")
        print("函数执行结束---\(Thread.current)")
    }
复制代码

为何要分逃逸闭包和非逃逸闭包

为了管理内存,闭包会强引用它捕获的全部对象,好比你在闭包中访问了当前控制器的属性、函数,编译器会要求你在闭包中显示 self 的引用,这样闭包会持有当前对象,容易致使循环引用。

非逃逸闭包不会产生循环引用,它会在函数做用域内释放,编译器能够保证在函数结束时闭包会释放它捕获的全部对象;使用非逃逸闭包的另外一个好处是编译器能够应用更多强有力的性能优化,例如,当明确了一个闭包的生命周期的话,就能够省去一些保留(retain)和释放(release)的调用;此外非逃逸闭包它的上下文的内存能够保存在栈上而不是堆上。

七、操做符

与 Objective-C 不一样,Swift 支持重载操做符这样的特性,最多见的使用方式可能就是定义一些简便的计算了

系统操做符

好比咱们须要一个表示二维向量的数据结构

struct Vector2D {
   var x:CGFloat = 0
   var y:CGFloat = 0
}
复制代码

一个很简单的需求是两个 Vector2D 相加

let v1 = Vector2D(x: 2.0, y: 3.0)
let v2 = Vector2D(x: 1.0, y: 4.0)
let v3 = Vector2D(x: v1.x + v2.x, y: v1.y + v2.y)  
复制代码

若是只作一次的话彷佛还好,可是通常状况咱们会进行不少这种操做。这样的话,咱们可能更愿意定义一个 Vector2D 相加的操做,来让代码简化清晰

func +(left:Vector2D,right:Vector2D) -> Vector2D {
   Vector2D(x: left.x + right.x, y: left.y + right.y)
}
let v3 = v1 + v2
复制代码

八、自定义操做符

在Swift语言中,常见的操做符有+、-、*、/、>、<、==、&&、||等等,若是不喜欢,你也能够定义本身喜欢的操做符。

  • precedencegroup:定义操做符的优先级
  • associativity:操做符的结合律
  • higherThanlowerThan:运算符的优先级
  • prefix、infix、postfix:前缀、中缀、后缀运算符

中缀

/// 定义优先级组
precedencegroup MyPrecedence {
    // higherThan: AdditionPrecedence   // 优先级,比加法运算高
    lowerThan: AdditionPrecedence       // 优先级, 比加法运算低
    associativity: none                 // 结合方向:left, right or none
    assignment: false                   // true=赋值运算符,false=非赋值运算符
}

infix operator +++: MyPrecedence        // 继承 MyPrecedence 优先级组
// infix operator +++: AdditionPrecedence // 也能够直接继承加法优先级组(AdditionPrecedence)或其余优先级组
func +++(left: Int, right: Int) -> Int {
    return left+right*2
}
 
print(2+++3) // 8
复制代码

前缀

prefix operator ==+
prefix func ==+(left: Int) -> Int {
   
   return left*2
}
print(==+2) // 4
复制代码

后缀

postfix operator +==
postfix func +==(right: Int) -> Int {
   
   return right*3
}
print(2+==) // 6
复制代码

九、inout:输入输出参数

可变形式参数只能在函数的内部作改变。若是你想函数可以修改一个形式参数的值,并且你想这些改变在函数结束以后依然生效,那么就须要将形式参数定义为输入输出形式参数。

在形式参数定义开始的时候在前边添加一个 inout关键字能够定义一个输入输出形式参数。输入输出形式参数有一个能输入给函数的值,函数能对其进行修改,还能输出到函数外边替换原来的值。

你只能把变量做为输入输出形式参数的实际参数。你不能用常量或者字面量做为实际参数,由于常量和字面量不能修改。在将变量做为实际参数传递给输入输出形式参数的时候,直接在它前边添加一个和符号 (&) 来明确能够被函数修改。

var b = 10
func test(a:inout Int) {
   a = 20
}
test(a: &b)
print(b) //20
复制代码

能够用inout定义一个输入输出参数:能够在函数内部修改外部实参的值

  • 一、不可变参数不能标记为inout
  • 二、inout参数不能有默认值
  • 三、inout参数只能传入能够被屡次赋值的
  • 四、inout参数的本质就是地址传递

十、下标

下标相信你们都很熟悉了,在绝大多数语言中使用下标来读写相似数组或者是字典这样的数据结构的作法,彷佛已是业界标准。在 Swift 中,Array 和 Dictionary 固然也实现了下标读写

var arr = [1,2,3]
arr[2]            // 3
arr[2] = 4        // arr = [1,2,4]

var dic = ["cat":"meow", "goat":"mie"]
dic["cat"]          // {Some "meow"}
dic["cat"] = "miao" // dic = ["cat":"miao", "goat":"mie"]  
复制代码

做为一门表明了先进生产力的语言,Swift 是容许咱们自定义下标的,咱们找到Array已经支持的下标类型

subscript (index: Int) -> T
subscript (subRange: Range<Int>) -> Slice<T>
复制代码

咱们发现若是咱们想要取出0、二、4下标值,咱们须要循环枚举。

其实这里有一个更好的作法,好比能够实现一个接受数组做为下标输入的读取方法

extension Array {
   subscript(input: [Int]) -> ArraySlice<Element> {
       get {
           var result = ArraySlice<Element>()
           for i in input {
               assert(i < self.count, "Index out of range")
               result.append(self[i])
           }
           return result
       }
       set {
           for (index,i) in input.enumerated() {
               assert(i < self.count, "Index out of range")
               self[i] = newValue[index]
           }
       }
   }
}

var arr = ["a","b","c","d","z"]
print(arr[[0,3]]) //["a", "d"]
复制代码

十一、嵌套函数

咱们能够把函数当成参数或者变量来使用,函数内部嵌套函数

func forward(_ forward:Bool) -> (Int) -> Int {
    
    func next(_ input:Int) -> Int {
        input + 1
    }

    func previous(_ input:Int) -> Int {
        input - 1
    }
    
    return forward ? next : previous
}
复制代码

十二、命名空间

Objective-C 一个一直以来使人诟病的地方就是没有命名空间,在应用开发时,全部的代码和引用的静态库最终都会被编译到同一个域和二进制中。这样的后果是一旦咱们有重复的类名的话,就会致使编译时的冲突和失败。为了不这种事情的发生,Objective-C 的类型通常都会加上两到三个字母的前缀,好比 Apple 保留的 NS 和 UI 前缀,各个系统框架的前缀 SK (StoreKit),CG (CoreGraphic) 等。Objective-C 社区的大部分开发者也遵照了这个约定,通常都会将本身名字缩写做为前缀,把类库命名为 AFNetworking 或者 MBProgressHUD 这样。这种作法能够解决部分问题,至少咱们在直接引用不一样人的库时冲突的几率大大下降了,可是前缀并不意味着不会冲突,有时候咱们确实仍是会遇到即便使用前缀也仍然相同的状况。另一种状况是可能你想使用的两个不一样的库,分别在它们里面引用了另外一个相同的很流行的第三方库,而又没有更更名字。在你分别使用“这两个库中的一个时是没有问题的,可是一旦你将这两个库同时加到你的项目中的话,这个你们共用的第三方库就会和本身发生冲突了。

在 Swift 中,因为可使用命名空间了,即便是名字相同的类型,只要是来自不一样的命名空间的话,都是能够和平共处的。和 C# 这样的显式在文件中指定命名空间的作法不一样,Swift 的命名空间是基于 module 而不是在代码中显式地指明,每一个 module 表明了 Swift 中的一个命名空间。也就是说,同一个 target 里的类型名称仍是不能相同的。在咱们进行 app 开发时,默认添加到 app 的主 target 的内容都是处于同一个命名空间中的,咱们能够经过建立 Cocoa (Touch) Framework 的 target 的方法来新建一个 module,这样咱们就能够在两个不一样的 target 中添加一样名字的类型了

1三、typealias别名

咱们能够给一个复杂的难以理解的类型起一个别名,方便咱们使用和理解

按照swift标准库的定义Void就是一个空元组

public typealias Void = ()
复制代码

咱们知道swift中没有byte、short、Long类型,若是咱们想要这样的类型,就能够用typealias实现

typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
复制代码

咱们还能够给函数起一个别名

typealias IntFn = (Int,Int) -> Int
func difference(v1:Int,v2:Int) -> Int {
   v1 - v2
}
let fn:IntFn = difference
print(fn(2,1))  //1
复制代码

咱们还能够给元组起别名

typealias Date = (year:Int,month:Int,day:Int)
func test(_ date:Date) {
   print(date.year)
}
test((2019,10,30))
复制代码

1四、associatedtype

咱们在 Swift 协议中能够定义属性和方法,并要求知足这个协议的类型实现它们:

protocol Food { }

protocol Animal {
   func eat(_ food: Food)
}

struct Meat: Food { }
struct Grass: Food { }
复制代码
struct Tiger: Animal {
   func eat(_ food: Food) {

   }
}
复制代码

由于老虎并不吃素,因此在 Tiger 的 eat 中,咱们极可能须要进行一些转换工做才能使用 meat

associatedtype 声明中可使用冒号来指定类型知足某个协议

protocol Animal {
   associatedtype F: Food
   func eat(_ food: F)
}

struct Tiger: Animal {
   func eat(_ food: Meat) {
       print("eat \(meat)")
   }
}

struct Sheep: Animal {
   func eat(_ food: Grass) {
       print("eat \(food)")
   }
} 
复制代码

不过在添加associatedtype 后,Animal 协议就不能被看成独立的类型使用了。试想咱们有一个函数来判断某个动物是否危险:

func isDangerous(animal: Animal) -> Bool {
   if animal is Tiger {
       return true
   } else {
       return false
   }
}
复制代码

会报错

Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements

这是由于 Swift 须要在编译时肯定全部类型,这里由于 Animal 包含了一个不肯定的类型,因此随着 Animal 自己类型的变化,其中的F 将没法肯定 (试想一下若是在这个函数内部调用 eat的情形,你将没法指定 eat 参数的类型)。在一个协议加入了像是 associatedtype 或者 Self 的约束后,它将只能被用为泛型约束,而不能做为独立类型的占位使用,也失去了动态派发的特性。也就是说,这种状况下,咱们须要将函数改写为泛型

func isDangerous<T: Animal>(animal: T) -> Bool {
   if animal is Tiger {
       return true
   } else {
       return false
   }
}

isDangerous(animal: Tiger()) // true
复制代码

1五、可变参数

一个可变形式参数能够接受零或者多个特定类型的值,可变参数必须是同一类型的。当调用函数的时候你能够利用可变形式参数来声明形式参数能够被传入值的数量是可变的。能够经过在形式参数的类型名称后边插入三个点符号(...)来书写可变形式参数。

func sum(_ numbers:Int...) -> Int{
   var total = 0
   for item in numbers {
       total += item
   }
   return total
}
复制代码

Swift自带的print函数

/// - Parameters:
///   - items: Zero or more items to print.
///   - separator: A string to print between each item. The default is a single
///     space (`" "`).
///   - terminator: The string to print after all items have been printed. The
///     default is a newline (`"\n"`).
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")
复制代码
  • 一、第一个参数:就是须要打印的值,是一个可变参数
  • 二、第二个参数:两个打印值链接的地方,默认是空格
  • 三、第三个参数:结尾默认是\n换行

1六、初始化

初始化是为类、结构体或者枚举准备实例的过程。这个过须要给实例里的每个存储属性设置一个初始值而且在新实例可使用以前执行任何其余所必须的配置或初始化

你经过定义初始化器来实现这个初始化过程,它更像是一个用来建立特定类型新实例的特殊的方法。不一样于 Objective-C 的初始化器,Swift 初始化器不返回值。这些初始化器主要的角色就是确保在第一次使用以前某类型的新实例可以正确初始化。

类有两种初始化器

  • 一、指定初始化器(designated initializer)
  • 二、便捷初始化器(convenience initializer)
class Person {
   var age: Int
   var name: String
   
   //指定初始化器
   init(age:Int, name:String) {
       self.age = age
       self.name = name
   }
   //便捷初始化器
   convenience init(age:Int){
       self.init(age:age,name:"")
   }
}
复制代码
  • 一、每个类至少有一个指定初始化器,指定初始化器是类的最主要初始化器
  • 二、默认初始化器老是类的指定初始化器
  • 三、类偏向于少许指定初始化器,一个类一般就只有一个指定初始化器

初始化器的相互调用规则

  • 一、指定初始化器必须从他的直系父类调用指定初始化器
  • 二、便捷初始化器必须从相同的类里调用另外一个初始化器
  • 三、便捷初始化器最终必须调用一个指定初始化器

1七、Static & Class

Swift 中表示 “类型范围做用域” 这一律念有两个不一样的关键字,它们分别是 staticclass。这两个关键字确实都表达了这个意思

非class的类型上下文中,咱们统一使用 static 来描述类型做用域

1八、default 参数

Swift 的方法是支持默认参数的,也就是说在声明方法时,能够给某个参数指定一个默认使用的值。在调用该方法时要是传入了这个参数,则使用传入的值,若是缺乏这个输入参数,那么直接使用设定的默认值进行调用

func test(a:String = "1",b:String,c:String = "3"){}
复制代码

1九、匹配模式

什么是模式 模式是用于匹配的规则,好比switchcase、捕获错误的catchif\guard\while\for语句

Swift中模式有

  • 一、通配符模式(Wildcard Pattern)
  • 二、标识符模式(Identifier Pattern)
  • 三、值绑定模式(Value-Binding Pattern)
  • 四、元祖模式(Tuple Pattern)
  • 五、枚举Case模式(Enumeration Case Pattern)
  • 六、可选模式(Optional Pattern)
  • 七、类型转换模式(Type-Casting Pattern)
  • 八、表达式模式(Expression Pattern)

通配符模式(Wildcard Pattern)

  • _ 匹配任何值
  • _?匹配非nil值
enum Life {
    case human(name:String,age:Int?)
    case animal(name:String,age:Int?)
}

func check(_ life:Life){
    switch life {
    case .human(let name,_):
        print("human:",name)
    case .animal(let name,_?):
        print("animal",name)
    default:
        print("other")
        break
    }
}

check(.human(name: "小明", age: 20)) //human: 小明
check(.human(name: "小红", age: nil))//human: 小红
check(.animal(name: "dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
复制代码

标识符模式(Identifier Pattern)

就是给对应的变量常亮赋值

let a = 10
let b = "text"
复制代码

值绑定模式(Value-Binding Pattern)

let point = (2,3)
switch point {
case (let x,let y):
    print("x:\(x) y:\(y)")
}
//x:2  y:3

复制代码

元祖模式(Tuple Pattern)

匹配任何元祖

let point = [(0,0),(1,0),(2,0)]
for (x,_) in point{
    print(x)
}
//0
//1
//2
复制代码

枚举Case模式(Enumeration Case Pattern)

if case语句等价于一个caseswitch语句,简化了一些判断语句

let age = 2
//if
if age >= 0 && age <= 9 {
    print("[0,9]")
}
//枚举模式
if case 0...9 = age{
    print("[0,9]")
}
复制代码
let ages:[Int?] = [2,3,nil,5]
for case nil in ages{
    print("有nil值")
}
复制代码

可选模式(Optional Pattern)

let ages:[Int?] = [nil,2,3,nil]
for case let age? in ages{
    print(age)
}
// 2
//3
复制代码

等价于

let ages:[Int?] = [nil,2,3,nil]
for item in ages{
    if let age = item {
        print(age)
    }
}

复制代码

类型转换模式(Type-Casting Pattern)

class Animal {
    func eat() {
        print(type(of: self),"eat")
    }
}

class Dog: Animal {
    func run() {
        print(type(of: self),"run")
    }
}

class Cat: Animal {
    func jump() {
        print(type(of: self),"jump")
    }
}

func check(_ animal:Animal) {
    switch animal {
    case let dog as Dog:
        dog.run()
        dog.eat()
    case is Cat:
        animal.eat()
    default:
        break
    }
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
复制代码

表达式模式(Expression Pattern)

能够经过重载运算符,自定义表达式模式的匹配规则

struct Student {
    var score = 0, name = ""
    static func ~=(pattern:Int,value:Student) -> Bool{
        value.score >= pattern
    }
    
    static func ~=(pattern:ClosedRange<Int>,value:Student) -> Bool{
        pattern.contains(value.score)
    }
    static func ~=(pattern:Range<Int>,value:Student) -> Bool{
        pattern.contains(value.score)
    }
    
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print(">=100")
case 90:print(">=90")
case 80..<90:print("[80,90]")
case 60...79:print("[60,79]")
default:break
}
复制代码
extension String{
    static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
        pattern(value)
    }
}

func hasPrefix(_ prefix:String) ->((String) -> Bool){{$0.hasPrefix(prefix)}}
func hasSuffix(_ prefix:String) ->((String) -> Bool){{$0.hasSuffix(prefix)}}



var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k"):
    print("以j开头,或者以k结尾")
default:
    break
}
复制代码

20、... 和 ..<

Range 操做符,用来简单地指定一个从 X 开始连续计数到 Y 的范围

咱们能够仔细看看 Swift 中对着两个操做符的定义

/// Forms a closed range that contains both `minimum` and `maximum`.
func ...<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos)
        -> Range<Pos>

/// Forms a closed range that contains both `start` and `end`.
/// Requres: `start <= end`
func ...<Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos)
        -> Range<Pos>


/// Forms a half-open range that contains `minimum`, but not
/// `maximum`.
func ..<<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos)
        -> Range<Pos>

/// Forms a half-open range that contains `start`, but not
/// `end`.  Requires: `start <= end`
func ..<<Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos)
        -> Range<Pos>


/// Returns a closed interval from `start` through `end`
func ...<T : Comparable>(start: T, end: T) -> ClosedInterval<T>
  
  
 /// Returns a half-open interval from `start` to `end`
func ..<<T : Comparable>(start: T, end: T) -> HalfOpenInterval<T>  
复制代码

不难发现,其实这几个方法都是支持泛型的。除了咱们经常使用的输入 Int 或者 Double,返回一个 Range 之外,这个操做符还有一个接受 Comparable 的输入,并返回 ClosedInterval 或 HalfOpenInterval 的重载

好比想确认一个单词里的所有字符都是小写英文字母的话,能够这么作

let test = "helLo"
let interval = "a"..."z"
for c in test {
    if !interval.contains(String(c)) {
        print("\(c) 不是小写字母")
    }
}

// 输出
// L 不是小写字母
复制代码

2一、AnyClass,元类型和 .self

在 Swift 中可以表示 “任意” 这个概念的除了 Any 和 AnyObject 之外,还有一个 AnyClassAnyClass 在 Swift 中被一个 typealias 所定义

public typealias AnyClass = AnyObject.Type
复制代码

经过 AnyObject.Type 这种方式所获得是一个元类型 (Meta)。在声明时咱们老是在类型的名称后面加上 .Type,好比 A.Type 表明的是 A 这个类型的类型。也就是说,咱们能够声明一个元类型来存储 A 这个类型自己,而在从A中取出其类型时,咱们须要使用到 .self

其实在 Swift 中,.self 能够用在类型后面取得类型自己,也能够用在某个实例后面取得这个实例自己。前一种方法能够用来得到一个表示该类型的值,这在某些时候会颇有用;然后者由于拿到的实例自己

class A {
    class func method() {
        print("Hello")
    }
}

let typeA: A.Type = A.self
typeA.method()

// 或者
let anyClass: AnyClass = A.self
(anyClass as! A.Type).method() 
复制代码

也许你会问,这样作有什么意义呢,咱们难道不是能够直接使用 A.method() 来调用么?没错,对于单个独立的类型来讲咱们彻底没有必要关心它的元类型,可是元类型或者元编程的概念能够变得很是灵活和强大,这在咱们编写某些框架性的代码时会很是方便。好比咱们想要传递一些类型的时候,就不须要不断地去改动代码了。在下面的这个例子中虽然咱们是用代码声明的方式获取了 MusicViewController 和 AlbumViewController 的元类型,可是其实这一步骤彻底能够经过读入配置文件之类的方式来完成的。而在将这些元类型存入数组而且传递给别的方法来进行配置这一点上,元类型编程就很难被替代了

class MusicViewController: UIViewController {

}

class AlbumViewController: UIViewController {

}

let usingVCTypes: [AnyClass] = [MusicViewController.self,
    AlbumViewController.self]

func setupViewControllers(_ vcTypes: [AnyClass]) {
    for vcType in vcTypes { 
            if vcType is UIViewController.Type {
            let vc = (vcType as! UIViewController.Type).init()
            print(vc)
        }

    }
}

setupViewControllers(usingVCTypes) 
复制代码

这么一来,咱们彻底能够搭好框架,而后用 DSL 的方式进行配置,就能够在不触及 Swift 编码的状况下,很简单地完成一系列复杂操做了

2二、动态类型

Swift 中咱们虽然能够经过 dynamicType 来获取一个对象的动态类型 (也就是运行时的实际类型,而非代码指定或编译器看到的类型)。可是在使用中,Swift 如今倒是不支持多方法的,也就是说,不能根据对象在动态时的类型进行合适的重载方法调用

class Pet {}
class Dog:Pet {}
class Cat:Pet {}
 
func eat(_ pet:Pet) {
    print("pet eat")
}

func eat(_ dog:Dog) {
    print("dog eat")
}

func eat(_ cat:Cat) {
    print("cat eat")
}
 
func eats(_ pet:Pet,_ cat:Cat) {
    eat(pet)
    eat(cat)
}
eats(Dog(), Cat())
//pet eat
//cat eat
复制代码

咱们在打印Dog类型信息的时候,并无被用来在运行时选择合适的func eat(_ dog:Dog) {}方法,而是被忽略了,并采用了编译期间决定的Pet方法。

由于 Swift 默认状况下是不采用动态派发的,所以方法的调用只能在编译时决定

要想绕过这个限制,咱们可能须要进行经过对输入类型作判断和转换

func eats(_ pet:Pet,_ cat:Cat) {
    if let aCat = pet as? Cat {
        eat(aCat)
    }else if let aDog = pet as? Cat{
        eat(aDog)
    }
    eat(cat)
}
复制代码

2三、属性

在Swift中所声明的属性包括

  • 一、存储属性:存储属性将会在内存中实际分配地址进行属性的存储
  • 二、计算属性:计算属性则不包括存储,只是提供setget方法

存储属性

咱们能够在存储属性中提供了willSetdidSet两种属性观察方法

class Person {
    var age:Int = 0{
        willSet{
            print("即将将年龄从\(age)设置为\(newValue)")
        }
        didSet{
            print("已经将年龄从\(oldValue)设置为\(age)")
        }
    }
}

let p = Person()
p.age = 10
//即将将年龄从0设置为10
//已经将年龄从0设置为10

复制代码

willSetdidSet中咱们分别可使用newValueoldValue 来获取将要设定的和已经设定的值。

初始化方法对属性的设定,以及在 willSet 和 didSet 中对属性的再次设定都不会再次触发属性观察的调用

计算属性

在 Swift 中所声明的属性包括存储属性计算属性两种。其中存储属性将会在内存中实际分配地址对属性进行存储,而计算属性则不包括背后的存储,只是提供setget 两种方法。在同一个类型中,属性观察和计算属性是不能同时共存的。也就是说,想在一个属性定义中同时出现 setwillSetdidSet 是一件办不到的事情。

计算属性中咱们能够经过改写 set 中的内容来达到和willSetdidSet 一样的属性观察的目的。若是咱们没法改动这个类,又想要经过属性观察作一些事情的话,可能就须要子类化这个类,而且重写它的属性

重写的属性并不知道父类属性的具体实现状况,而只从父类属性中继承名字和类型,所以在子类的重载属性中咱们是能够对父类的属性任意地添加属性观察的,而不用在乎父类中究竟是存储属性仍是计算属性

class A {
    var number:Int {
        get{
            print("get")
            return 1
        }
        set{
            print("set")
        }
    }
}

class B:A {
    override var number: Int{
        willSet{
            print("willSet")
        }
        didSet{
            print("didSet")
        }
    }
}

let b = B()
b.number = 10

get
willSet
set
didSet
复制代码

set 和对应的属性观察的调用都在咱们的预想之中。这里要注意的是 get 首先被调用了一次。这是由于咱们实现了 didSetdidSet 中会用到 oldValue,而这个值须要在整个 set 动做以前进行获取并存储待用,不然将没法确保正确性。若是咱们不实现 didSet 的话,此次 get操做也将不存在。

2四、lazy修饰符

延时加载或者说延时初始化是很经常使用的优化方法,在构建和生成新的对象的时候,内存分配会在运行时耗费很多时间,若是有一些对象的属性和内容很是复杂的话,这个时间更是不可忽略。另外,有些状况下咱们并不会当即用到一个对象的全部属性,而默认状况下初始化时,那些在特定环境下不被使用的存储属性,也同样要被初始化和赋值,也是一种浪费

swift提供了一个关键字lazy

class A {
    lazy var str:String = {
        let str = "Hello"
        print("首次访问的时候输出")
        return str
    }()
}

let a = A()
print(a.str)
复制代码

为了简化,咱们若是不须要作什么额外工做的话,也能够对这个 lazy 的属性直接写赋值语句

lazy var str1 = "word"
复制代码

2五、Reflection 和 Mirror

Objective-C 中咱们不太会常常说起到 “反射” 这样的词语,由于 Objective-C 的运行时比通常的反射还要灵活和强大。可能不少读者已经习觉得常的像是经过字符串生成类或者 selector,而且进而生成对象或者调用方法等,其实都是反射的具体的表现。而在 Swift 中其实就算抛开 Objective-C 的运行时的部分,在纯 Swift 范畴内也存在有反射相关的一些内容,只不过相对来讲功能要弱得多。

Swift提供了Mirror类型来作映射的事情

struct Person {
    let name:String
    let age:Int
}

let xiaoming = Person(name: "小明", age: 10)
let r = Mirror(reflecting: xiaoming)

print("xiaoming是\(r.displayStyle)")
print("属性个数:\(r.children.count)")

for child in r.children{
    print("属性名:\(child.label) 值:\(child.value)")
}


xiaoming是Optional(Swift.Mirror.DisplayStyle.struct)
属性个数:2
属性名:Optional("name") 值:小明
属性名:Optional("age") 值:10
复制代码

经过 Mirror 初始化获得的结果中包含的元素的描述都被集合在children 属性下

public typealias Child = (label: String?, value: Any)
public typealias Children = AnyCollection<Mirror.Type.Child> 
复制代码

若是以为一个个打印太过于麻烦,咱们也能够简单地使用 dump 方法来经过获取一个对象的镜像并进行标准输出的方式将其输出出来

print(dump(xiaoming))

▿ 反射.Person
  - name: "小明"
  - age: 10
Person(name: "小明", age: 10)
复制代码

常见的应用场景是相似对 Swift 类型的对象作像 Objective-C 中 KVC 那样的 valueForKey: 的取值。经过比较取到的属性的名字和咱们想要取得的 key 值就好了,很是简单

func valueFrom(_ object: Any, key: String) -> Any? {
    let mirror = Mirror(reflecting: object)
    for child in mirror.children {
        let (targetKey, targetMirror) = (child.label, child.value)
        if key == targetKey {
            return targetMirror
        }
    }
    return nil
}


if let name = valueFrom(xiaoming, key: "name") as? String {
    print("经过 key 获得值: \(name)")
}

经过 key 获得值: 小明
复制代码

在如今的版本中,Swift 的反射特性并非很是强大,咱们只能对属性进行读取,还不能对其设定。 另外须要特别注意的是,虽然理论上将反射特性应用在实际的 app 制做中是可行的,可是这一套机制设计的最初目的是用于 REPL 环境和 Playground 中进行输出的。因此咱们最好遵照 Apple 的这一设定,只在 REPL 和 Playground 中用它来对一个对象进行深层次的探索,而避免将它用在 app 制做中 -- 由于你永远不知道何时它们就会失效或者被大幅改动

2六、可选值(optional)

咱们点击进入官方文档,能够看到optional是一个枚举

enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
    public init(_ some: Wrapped)
}
复制代码

咱们来看看下面这两个值是否相同anotherString literalString

var string:String? = "string"
var anotherString:String?? = string
var literalString:String?? = "string"

print(anotherString)
print(literalString)
print(anotherString == literalString)

//Optional(Optional("string"))
//Optional(Optional("string"))
//true
复制代码
  • 一、var anotherString:String?? = string的写法其实就是Optional.some(string)
  • 二、var literalString:String?? = "string"的写法是Optional.some(Optional.some("string"))

二者的返回值是同样的,因此二者相等

下面两个对象是否相等呢?

var aNil:String? = nil
var anoterNil:String?? = aNil
var literalNil:String?? = nil
print(anoterNil)
print(literalNil)
print(anoterNil == literalNil)
//Optional(nil)
//nil
//false
复制代码
  • 一、anoterNil是可选项里面包含一个可选项值为nil的可选项
  • 二、literalNil是可选择值为nil的可选项

咱们可使用fr v -R来打印具体信息

1

2七、匹配模式

什么是模式 模式是用于匹配的规则,好比switchcase、捕获错误的catchif\guard\while\for语句

Swift中模式有

  • 一、通配符模式(Wildcard Pattern)
  • 二、标识符模式(Identifier Pattern)
  • 三、值绑定模式(Value-Binding Pattern)
  • 四、元祖模式(Tuple Pattern)
  • 五、枚举Case模式(Enumeration Case Pattern)
  • 六、可选模式(Optional Pattern)
  • 七、类型转换模式(Type-Casting Pattern)
  • 八、表达式模式(Expression Pattern)

通配符模式(Wildcard Pattern)

  • _ 匹配任何值
  • _?匹配非nil值
enum Life {
    case human(name:String,age:Int?)
    case animal(name:String,age:Int?)
}

func check(_ life:Life){
    switch life {
    case .human(let name,_):
        print("human:",name)
    case .animal(let name,_?):
        print("animal",name)
    default:
        print("other")
        break
    }
}

check(.human(name: "小明", age: 20)) //human: 小明
check(.human(name: "小红", age: nil))//human: 小红
check(.animal(name: "dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
复制代码

标识符模式(Identifier Pattern)

就是给对应的变量常亮赋值

let a = 10
let b = "text"
复制代码

值绑定模式(Value-Binding Pattern)

let point = (2,3)
switch point {
case (let x,let y):
    print("x:\(x) y:\(y)")
}
//x:2  y:3

复制代码

元祖模式(Tuple Pattern)

匹配任何元祖

let point = [(0,0),(1,0),(2,0)]
for (x,_) in point{
    print(x)
}
//0
//1
//2
复制代码

枚举Case模式(Enumeration Case Pattern)

if case语句等价于一个caseswitch语句,简化了一些判断语句

let age = 2
//if
if age >= 0 && age <= 9 {
    print("[0,9]")
}
//枚举模式
if case 0...9 = age{
    print("[0,9]")
}
复制代码
let ages:[Int?] = [2,3,nil,5]
for case nil in ages{
    print("有nil值")
}
复制代码

可选模式(Optional Pattern)

let ages:[Int?] = [nil,2,3,nil]
for case let age? in ages{
    print(age)
}
// 2
//3
复制代码

等价于

let ages:[Int?] = [nil,2,3,nil]
for item in ages{
    if let age = item {
        print(age)
    }
}

复制代码

类型转换模式(Type-Casting Pattern)

class Animal {
    func eat() {
        print(type(of: self),"eat")
    }
}

class Dog: Animal {
    func run() {
        print(type(of: self),"run")
    }
}

class Cat: Animal {
    func jump() {
        print(type(of: self),"jump")
    }
}

func check(_ animal:Animal) {
    switch animal {
    case let dog as Dog:
        dog.run()
        dog.eat()
    case is Cat:
        animal.eat()
    default:
        break
    }
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
复制代码

表达式模式(Expression Pattern)

能够经过重载运算符,自定义表达式模式的匹配规则

struct Student {
    var score = 0, name = ""
    static func ~=(pattern:Int,value:Student) -> Bool{
        value.score >= pattern
    }
    
    static func ~=(pattern:ClosedRange<Int>,value:Student) -> Bool{
        pattern.contains(value.score)
    }
    static func ~=(pattern:Range<Int>,value:Student) -> Bool{
        pattern.contains(value.score)
    }
    
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print(">=100")
case 90:print(">=90")
case 80..<90:print("[80,90]")
case 60...79:print("[60,79]")
default:break
}
复制代码
extension String{
    static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
        pattern(value)
    }
}

func hasPrefix(_ prefix:String) ->((String) -> Bool){{$0.hasPrefix(prefix)}}
func hasSuffix(_ prefix:String) ->((String) -> Bool){{$0.hasSuffix(prefix)}}



var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k"):
    print("以j开头,或者以k结尾")
default:
    break
}
复制代码

总结

  • 一、文章中代码的demo地址
  • 二、文章是读王巍 (onevcat). “Swifter - Swift 必备 Tips (第四版)总结所得
相关文章
相关标签/搜索