Swift基础之Optional

import UIKit
import PlaygroundSupport

/// 可选值: Optional
/// Mark: 可选值的由来
/// 在编程语言中有一些特定的值来代表特定的含义:比如:EOF(对-1 的#define)代表文件的结束,null代表空
///这些特定的值用来表示函数没有返回真实的值。这些值被称为“岗哨值”
///岗哨值是有缺陷的
/// 1、这个特定值未被定义,只能通过文档查看, 2、很容易引起其他问题。例如下面代码

//NSString * someString = "hello"
//if ([someString rangeOfString:@"swift"].location != NSNotFound) {
//  // 如果someString为空,那么location为0, 而NSNotFound 被定义为NSIntegerMax,这样if内语句将被执行
//  print("xxxxx")
//}

//3、如果一个函数的返回值一定不为空,那么只能通过文档才能知道,从函数本身是看不出来的


// Mark: 通过可选值解决上述问题
//使用swift枚举中含有“关联值”特性(有些语言中被称为标签联合或可辨识联合)
//将若干种不同的可能类型保存在内存的同一空间中,并使用一个标签来区分到底被持有的是什么类型
//enum Optional<Wrapped> {
//  case none
//  case some(Wrapped)
//}

///获取关联值的唯一方法: 通过switch 或者 if case 进行模式匹配。除非显式进行捷豹,否则不可能意外使用Optional中的值

///因为可选值在swift中非常基础,所以有更简单的写法:Optional<Index> 可以写作: Index?;
///可选值遵循 ExpressibleByNilLiteral协议,可以用nil代替.none;
///非可选值在需要时可自动升级为可选值: return .some(index) 可以写作:return index
///可选值的本质就是普通的枚举值
var array = ["one", "two", "three"]
switch array.firstIndex(of: "four") {
case .some(let idx):
  array.remove(at: idx)
default:
  break
}

//简化写法
switch array.firstIndex(of: "four") {
case let idx?:  //?作为匹配模式后缀
  array.remove(at: idx)
case nil:
  break         //nil 匹配.none
}


// Mark: if let 可选绑定
if let idx = array.firstIndex(of: "two") {
  array.remove(at: idx)
}

// if 可以绑定多个值,而且后面绑定的值可以基于之前解包成功的值进行操作
let urlString = "https://www.objc.io/logo.png"
if let url = URL(string: urlString),
  let data = try? Data(contentsOf: url),
  let image = UIImage(data: data) {
  let view = UIImageView(image: image)
  PlaygroundPage.current.liveView = view
}

//if 可以有用Bool值限定和可选绑定混用

if let url = URL(string: urlString),
  url.pathExtension == "png",
  let data = try? Data(contentsOf: url),
  let image = UIImage(data: data) {
    let view = UIImageView(image: image)
    PlaygroundPage.current.liveView = view
}

// Mark: while let 可选值绑定
//while  let line = readLine(), !line.isEmpty {
//  print(line)
//}


// Mark: while 配合Iterater使用: for x in seq 要求 seq遵循Sequence协议,该协议提供makeIterator方法创建迭代器,迭代器next方法负责返回序列中的值,当序列值耗尽时,返回nil
let numers = [1, 2, 3]
var iterator = numers.makeIterator()
while let i = iterator.next() {
  print(i, terminator:" ")
}

for i in 0..<10 where i % 2 == 0 {
  print(i, terminator: " ")
}

var iterator2 = numers.makeIterator()
while let i = iterator2.next() {
  if i % 2 == 0 {
    print(i, terminator: " ")
  }
}

// while 和 for是有区别的 : for 等价于 while let ,而while就是while

var functions:[() ->Int] = []
for i in 0..<3 {
  functions.append({i})  //每次迭代都是不同的变量,虽然名字一样,但是已经不是原来的变量,相当于 let i1, let i2, let i3(解释上面 for 等价于 while let)
}
print("***")
for f in functions {
  print("\(f())", terminator: "")  // 在其他语言中:因为闭包会捕获局部变量,虽然每次存储闭包时,i的值不一样,但是所有的闭包捕获的是同一个局部变量,所以当闭包执行时,打印的都是2(i的最后一个值)
  // swift当中每次append的时候都是不同的i变量
}


// Mark: 双重可选值: 可选值被另外一个可选值包装
let stringNumbers = ["1", "2", "three"]
let maybeInts = stringNumbers.map{ Int($0) } // Optional(1), Optinal(2), nil //maybeInts是一个类型为Optional<Int>的数组,因为Int.init(String)可能失败
//mabeInts中最后一个元素是nili
for maybeInt in maybeInts {
  //得到两个Int? 和 一个nil
  print(maybeInt, terminator: " ")
}
print()
//iterator3.next() 返回的是Optonal<Optonal<Int>> 或者说是 Int??
var iterator3 = maybeInts.makeIterator()
while let maybeInt = iterator3.next() {  //while let 会进行第一层解包(最外层),所以最后一个值是.some(nil)
  print(maybeInt, terminator: " ")
}
print()
//for写法:使用case来进行模式匹配
// 方式 1
for case let i? in maybeInts {  // i? 是 .some(i)的简写
  print(i, terminator: " ")
}
print()
// 方式 2
for case .some(let i) in maybeInts {
  print(i, terminator: " ")
}
print()
// 方式 3
for case let .some(i) in maybeInts {
  print(i, terminator: " ")
}

//for let x in maybeInts {   //不存在这种写法: 因为 for x in maybeInts 就已经表达了这个意思, 不过可以用 for var x in mybeInts
//  print(x)
//}

// Mark: 基于case模式匹配的规则应用到for,if,while中
let j = 5
if case 0..<10 = j {  // 0 到9 任意一个值等于j
  print("\(j)在范围内")
}

//Mark: 重载case模式匹配
//因为case匹配可以通过重载~=运算符来进行扩展,所以可以将if case 和 for case 进行有趣的扩展

struct Pattern {
  let s: String
  init(_ s: String) {
    self.s = s
  }
}

func ~=(pattern: Pattern, value: String) -> Bool {
  return value.range(of: pattern.s) != nil
}

let s = "Simon Swift"
if case Pattern("Swfit") = s {
  print("\(String(reflecting: s)) contains \"Swift\"")
}

//恶作剧代码
//func ~=<T, U>(_: T,_: U) -> Bool { return true}

// 除了 let意外, var也可以搭配 for , while, if 等使用。用法同 let一样

// Mark: 解包后的作用域
// if语句中的解包变量只能在if作用域中使用,不能再外面使用

if let firstElement = array.first {
  print(firstElement)
}
//外面无法使用firstElement

//swift延时初始化能力

extension String {
  var fileExtension: String? {
    let period: String.Index //定义局部变量且未初始化
    if let idx = lastIndex(of: ".") {
      period = idx          //初始化
    } else {
      return nil
    }
    let extStart = index(after: period)
    return String(self[extStart...])
  }
}

// Mark: guard。 等价于 if not let
//guard let x = ... else { ...} . guard的唯一要求是: 在else中必须离开作用域,既return, 或 在循环中使用break,continue

///一个函数的返回值如果是Never的话,这就意味着告诉编译器这个函数不会返回。通常有两种函数会这样做: 一类是 fatalError,另一个是dispatchMain那样在整个生命周期的函数。
/// 编译器会使用这个信息进行控制流诊断。比如guard语句的else语句路径必须退出当前域或者调用一个不会反悔的函数

///Never 又被叫做无人类型。这种类型没有有效值,因此也不能够被构建。它的唯一作用就是给编译器提供信号。一个返回值被声明为无人类型的函数将永远不能正常返回。
///在swift中,无人类型由一个不包含任意成员的enum来实现的

public enum Never{}

// Mark: Never用法
//关闭编译器提示错误
func unimplemented() -> Never {
  fatalError("编译器先闭嘴,我还没写完呢,不要给我提示错误")
}

/// Mark: Swift 各种“无”类型: nil, Never,Void。 Void标识空元组
public typealias Void = ()
///Swift对“东西不存在”(nil),“存在且为空”(Void) i以及 “不可能发生”(Never)定义的非常清楚


// Mark: 可选链: swift中可以通过“可选链” 实现“对nil发送消息什么都不会发生”
// delegate?.callBack 。 swift会强制要求你声明的消息的接受者可能为nil

let str: String? = "Never say never"
// 为什么在第一个uppercase()后加问号,而后面的不需要? 因为可选链是一个“展平”操作,str?.uppercase()返回一个可选值,如果再调用?.lowercased()将返回一个可选值的可选值。而我们要的是一个可选值,所以后面的不用加,可选特性已经在前面捕获了
let result = str?.uppercased().lowercased()

//如果返回值是可选值的话,那么后面仍然需要添加问号来表示正在链接这个可选值
extension Int {
  var half: Int? {
    guard self < -1 || self > 1 else { return nil }
    return self / 2
  }
}

20.half?.half?.half //Optional(2)   编译器会展平结果类型,结果是Int? 而不是Int???
let dictOfArray = ["nine": [0, 2, 3, 4]]
dictOfArray["nine"]?[3]

let dictOfFunctions: [String:(Int, Int) -> Int] = [
  "add":(+),
  "substract": (-)
]

dictOfFunctions["add"]?(1,2)

// Mark: 通过可选链进行赋值
struct Person {
  var name: String
  var age: Int
}

var optionalLisa: Person? = Person(name: "Lisa Simpson", age: 8)
if optionalLisa != nil {
  optionalLisa!.age += 1
}

if var lisa = optionalLisa {
  lisa.age += 1  //此时optionalLisa的值不会改变,因为Person是值类型,可选绑定会创建一个副本,即使lisa的值改变,也不会影响optionalLisa的值 。如果Person是类的话,是可以的
}

//使用可选值赋值
optionalLisa?.age += 1

// Mark: 可选值可以直接赋值
var a: Int? = 5
a? = 10

// Mark: nil合并运算符: ??
//在解包一个可选值时,如果是nil,可以用一个默认值来替换它
let stringteger = "1"
let number = Int(stringteger) ?? 0
// lhs ?? rhs 等价于 lhs!=nil? lhs:rhs

let array1 = [1, 2, 3]
!array.isEmpty ? array1[0] : 0
// 可以通过 ?? 完成合并操作
array1.first ?? 0

//使用可选值确保满足某个条件
array1.count > 5 ? array1[5] : 0
extension Array {
  subscript(guarded idx: Int) -> Element? {
    guard (startIndex..<endIndex).contains(idx) else { return nil }
    return self[idx]
  }
}

array1[guarded: 5] ?? 0

//多个可选值合并
let i: Int? = nil
let l: Int? = nil
let k: Int? = 45
i ?? l ?? k ?? 0  // 42

// Mark:字符串插值使用可选值
let bodyTemperature: Double? = 37.0
let bloodGlucose: Double? = nil
print(bodyTemperature)  //警告:表达式隐式强制从 “Double?”转换为Any
print("Blood glucose leve:\(bloodGlucose)") //警告: 字符串榨汁将使用调试时的可选值描述

// ?? 表达式要求两侧的类型必须匹配: Int?的默认值必须是Int, Double?的默认值必须是Double

// Mark: 自定义运算符

infix operator ???: NilCoalescingPrecedence
public func ???<T>(optional: T?, defaltValue: @autoclosure() -> String) -> String { //@autoclosure()标注确保只有当需要时,才对第二个表达式求值
  switch optional {
  case let value?:
    return String(describing: value)
  case nil:
    return defaltValue()
  }
}

print("Body temperature:\(bodyTemperature ??? "a/n")")

// Mark: 可选值map
//可选值拥有map功能,当有值时则调用闭包。 区别于结合中的map,可选值map只操作一个值

let characters:[Character] = ["a", "b", "c"]
let firstChar = characters.first.map{String($0)}

// Mark: 可选值map原理
extension Optional {
  func map<U>(transform:(Wrapped) -> U) -> U? {
    if let value = self {
      return transform(value)
    }
    return nil
  }
}

// 实现不接受初始值的reduce方法
extension Array {
  func reduce(_ nextPartialReult:(Element, Element) ->Element) -> Element? {
    guard let fst = first else { return nil }
    return dropFirst().reduce(fst, nextPartialReult)
  }
}

// 不使用gaurd实现reduce
extension Array {
  func reduce_alt(_ nextPartialResult: (Element, Element) -> Element) -> Element? {
    return first.map {
      dropFirst().reduce($0, nextPartialResult)
    }
  }
}

// Mark: 可选值flatMap : 将多层可选值展平
let stringNumbers2 = ["1", "2", "3", "foo"]
let x = stringNumbers2.first.map{Int($0)} //Optional(Optional(1)) // first 返回值是可选值,Int(String)返回值也是可选值,所以x 为Int??
//flatMap把结果展平为Int?
let y = stringNumbers2.first.flatMap{Int($0)} //Optional(1)

//flatMap 等价于 if let
extension Optional {
  func flatMap<U>(transform:(Wrapped) -> U?) -> U? {
    if let value = self, let transformed = transform(value) {
      return transformed
    }
    return nil
  }
}

//Mark: 使用可选值过滤 nil
let numbers1 = ["1", "2", "3", "foo"]
var sum1 = 0
for case .some(let i) in numbers1.map({Int($0)}) {
  sum1 += i
}

print(sum1)

let sum2 = numbers1.map({Int($0)}).reduce(0){$0 + ($1 ?? 0)}
print(sum2)

//因为现在还无法定义一个只作用于可选值序列的Sequence扩展,所以定义为全局函数
func flatten<S: Sequence, T>(source: S) -> [T] where S.Element == T? {
  let filtered = source.lazy.filter {$0 != nil}
  return filtered.map{$0!}
}
extension Sequence {
  func flatMap<U>(transform:(Element) -> U?) -> [U] {
    return flatten(source: self.lazy.map(transform))
  }
}

// Mark: 可选值判等
//可选值判等重载==运算符
func ==<T: Equatable>(lhs: T?, rhs: T?) -> Bool {
  switch (lhs, rhs) {
  case (nil, nil):
    return true
  case let (x?, y?):
    return x == y
  case (nil,_?),(_?,nil):
    return false
  }
}

let regex = "^Hello"
if regex.first == Optional("^") {
  //如果使用非可选值和可选值匹配时,swfit会将非可选值”升级“为可选值,所以这段代码不可取。理想状态时:regex.fist == "^",这种写法比较简单
}
//推导: 为了实现非可选值和可选值判等的简单写法,我们需要以下三个函数
//    func ==<T: Equatable>(lhs: T?, rhs: T?) -> Bool
//    func ==<T: Equatable>(lhs: T, rhs: T?) -> Bool
//    func ==<T: Equatable>(lhs: T?, rhs: T) -> Bool

//事实上只需要一种,因为Swift包含隐士转换,比如map函数返回值是可选的,但是我们不用写成这样: return Optional(1), 而是直接写 return 1

// Mark: 注意"字典"赋值nil时操作
var dictWithNils: [String: Int?] = [
  "one" : 1,
  "two" : 2,
  "three" : 3,
  "foure" : 4,
  "none" : nil
]

dictWithNils["one"] = nil  //会移除键
print(dictWithNils)
dictWithNils["two"] = Optional(nil) //会移除键
print(dictWithNils)
dictWithNils["three"] = .some(nil) //不会移除键
print(dictWithNils)
dictWithNils["foure"]? = nil //不会移除键 //因为是用的可选链,如果键不存在,后面赋值不会执行,也不会插入
print(dictWithNils)
dictWithNils["five"] = Optional(5)
print(dictWithNils)

// Mark: Equalable 和 ==
//Equalble是协议, == 是操作符

// Mark: 强制解包
//当结果不可能为nil时使用强制解包
func flatten2<S: Sequence, T>(source: S) -> [T] where S.Element == T? {
  let filtered = source.lazy.filter {$0 != nil}
  return filtered.map{$0!} //因为上一句已经保证了每个元素不可能为nil
}

// Mark: 改进强制解包错误信息: 定义!!操作符

infix operator !!
func !!<T>(Wrapped: T?, failureText: @autoclosure() -> String) -> T {
  if let x = Wrapped { return x }
  fatalError(failureText())
}

//let ss = "foo"
//let yyy = Int(ss) !! "失败鸟"

// Mark: 在调试版本中进行断言
//一般在发布版本中会把”解包失败“替换成空值或默认值,而在测试中一般通过断言,让程序崩溃

// 实现疑问感叹号(!?)操作符,对解包失败进行断言,切在发布版本总替换成默认值
infix operator !?
func !?<T: ExpressibleByIntegerLiteral>(wrapped: T?, failureText: @autoclosure() -> String) -> T {
  assert(wrapped != nil, failureText())
  return wrapped ?? 0
}

let sss = "20"
let iii = Int(sss) !? "Expecting integer,got\(s)"

// 对字面量转换协议进行重载,可以覆盖不少能够有默认值的类型

func !?<T: ExpressibleByArrayLiteral> (wrapped: T?, failureText: @autoclosure() -> String) -> T {
  assert(wrapped != nil, failureText())
  return wrapped ?? []
}

func !?<T: ExpressibleByStringLiteral>(wrapped: T?, failureText: @autoclosure() -> String) -> T {
  assert(wrapped != nil, failureText())
  return wrapped ?? ""
}

// Mark: 隐式解包
//隐式解包的可选值看上去像非可选值,但本质还是可选值。你可以像可选值一样使用它
var ssss: String! = "Hello"
ssss?.isEmpty
if let x = ssss {print(x)}


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

推荐阅读更多精彩内容