岗哨值
int ch;
while ((ch = getchar()) != EOF) {
printf("Read character %c\n", ch);
}
printf("Reached end-of-ĩle\n");
这段c代码是读取文件,getchar()是获取字符,EOF是一个表示文件末尾的定义,实质是一个-1的宏定义,如果返回-1,则表示文件已经到末尾。
函数返回一个 “魔法” 数来表示函数并没有返回真实的值。这样的值被称为 “哨岗值。比如上面的-1。
但是这种策略是有问题的,返回-1如果被当作字符操作就会出现异常。
OC允许向nil发送消息,具体做法是runtime时返回等价0的值,如果是对象,则返回nil,如果是数值类型,则返回0
···
NSString *someString = ...;
if ([someString rangeOfString:@"Swift"].location != NSNotFound) {
NSLog(@"Someone mentioned Swift!");
}
···
如果是NSRange这种结构体,会返回一个值都是0的结构体,也就是说上面这个location是0,即使someString是nil,也不会返回NSNotFound,if里面依旧执行。
使用枚举来处理魔法数
swift的枚举可以有一个关联值
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
现在rangeof不再返回一个具体的值,而是一个枚举值
extension Collection where Element: Equatable {
func index(of element: Element) -> Optional<Index> {
var idx = startIndex while idx != endIndex {
if self[idx] == element {
return .some(idx)
}
formIndex(after: &idx)
}
// 没有找到,返回 .none
return .none
}
}
swift的rangeof使用上面的indexof,是通过给Collection协议增加扩展实现的,它返回一个Optional<Index>,或者说Index?类型。
没错,实际上可选值的实质就是一个枚举,即便没有这个特性,也是可以自己实现的。
可选值遵守 ExpressibleByNilLiteral 协议,因此你可以用 nil 来替代 .none;像上面 idx 这样的非可选值将在需要的时候自动 “升级”为可选值,这样你就可以直接写 return idx,而不用 return .some(idx)。这个语法糖实际上掩盖了 Optional 类型的真正本质。
var array = ["one", "two", "three"]
let idx = array.index(of: "four")
// 编译错误:remove(at:) 接受 Int,⽽不是 Optional<Int>
array.remove(at: idx)
现在就没有魔法数了。
由于是枚举,所以需要用switch来进行模式匹配,不过swift提供了更多方便的语法。
if let
if let idx = array.index(of: "four"), idx != array.startIndex {
array.remove(at: idx)
}
var str : String?
guard let s = str, !s.isEmpty else{return}
if let拆包可以绑定一个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)
}
甚至可以绑定更多,而且后面的操作可以用到前面创建的参数,这种特性在同时判断多个可选值的时候非常好用,可以不必写很多次控制语句。
if segue.identifer == "showUserDetailsSegue",
let userDetailVC = segue.destination as? UserDetailViewController {
userDetailVC.screenName = "Hello"
}
还可以把bool条件作为前置。
**while let **
while let 语句和 if let 非常相似,它代表一个当条件返回 nil 时终止的循环。
集合遵循 Sequence 协议,有一个迭代器
var iterator2 = (0..<10).makeIterator()
while let i = iterator.next() {
if i % 2 == 0 {
print(i)
}
}
看起来就像是for循环一样,使用for循环大概是这样子
for i in 0..<10 where i % 2 == 0 {
print(i, terminator: " ")
} // 0 2 4 6 8
for let
for case let i? in maybeInts {
// i 将是 Int 值,⽽不是 Int?
print(i, terminator: " ")
}
for case nil in maybeInts {
// 将对每个 nil 执⾏⼀次
print("No value")
}
guard
在 guard 的 else 中你可以做任何事,包括执行多句代码。唯一的限制是你必须在 else 中离开当前的作用域,也就是说,在代码块的最后你必须写 return 或者调用 fatalError (或者其他被声明为返回 Never 的方法)。如果你是在循环中使用 guard 的话,那么最后也可以是 break 或者 continue。
一个函数的返回值如果是 Never 的话,就意味着告诉了编译器这个函数不会返回。有两类常⻅的函数会这么做:一种是像 fatalError 那样表示程序失败的函数,另一种是像 dispatchMain 那样运行在整个程序生命周期的函数。编译器会使用这个信息来进行控制流诊断。举例来说,guard 语句的 else 路径必须退出当前域或者调用一个不会返回的函数。
Never 又被叫做无人类型 (uninhabited type)。这种类型没有有效值,因此也不能够被构建。它的唯一作用就是给编译器提供信号。一个返回值被声明为无人类型的函数将永远不能正常返回。在 Swift 中,无人类型是由一个不包含任意成员的 enum 来实现的
public enum Never { }
一般来说你不会需要自己定义一个返回 Never 的方法,除非你在为 fatalError 或者preconditionFailure 写封装。一个很有意思的应用场景是,当你在写新代码时,可能会遇到一个很复杂的 switch 语句,你正在慢慢填上所有的 case,这时候编译器会用空的 case 语句或者是没有返回值这样的错误一直轰炸你,而你又想先集中精力先处理一个 case 语句的逻辑。这时候,你可以放几个 fatalError() 就能让编译器闭嘴。你还可以写一个 unimplemented() 方法,这样能够更好地表达这些调用是暂时没有实现的意思:
func unimplemented() -> Never {
fatalError("This code path is not implemented yet.")
}
Swift 在区分各种” 无 “类型上非常严密,对 “东西不存在”(nil),“存在且为空”(Void) 以及“不可能发生” (Never) 这几个概念进行了仔细的区分。出了 nil 和 Never,还有 Void,Void 是空元组 (tuple) 的一种写法
public typealias Void = ()
Void 或者 () 最常⻅的用法是作为那些不返回任何东西的函数的类型,不过它也还有其他使用场景。举例来说,在一个响应式编程的框架中,模型事件流被定义为Observable<T>,这里 T 表示发送的事件中载荷内容的类型。比如文本框会提供一个Observable<String>,在每次用户编辑文本时发送事件。类似地,按钮对象也会在用户每次击按钮是发送事件,不过这个事件没有附加的内容,所以它的事件流类型将会是 Observable<()>。
可选链
在OC中,向nil发送消息是安全的,而在swift种,正常情况下你无法向nil发送消息,除非是一个可选值被强制拆包,但是只要不强制包,本质上就实现了类似OC的效果。
delegate?.callback()
只不过从肯能会向nil发送消息变成了,可能不会发送消息。
可选链的返回值也是一个可选值,借此,可选链具有一个传递效果
let str: String? = "Never say never"
let result = str?.uppercased() // Optional("NEVER SAY NEVER")
let lower = str?.uppercased().lowercased() // Optional("never say never")
第三行并没有写成?.lowercased()
extension Int {
var half: Int? {
guard self < -1 || self > 1 else { return nil }
return self / 2
}
}
20.half?.half?.half // Optional(2)
上面这个扩展,half返回的本身就是可选值,庆幸swift特性给的结果是Int?而不是Int???。
nil合并运算符
swift??合并操作也能够进行链接 — 如果你有多个可能的可选值,并且想要选择第一个非 nil 值,你可以将它们按顺序合并
let i: Int? = nil
let j: Int? = nil
let k: Int? = 42
let m = i ?? j ?? k // 42
type(of: m) // Optional<Int>
这种方式经常和 if let 配合起来使用,你可以将它想像成 “or” 版本的 if let:
if let n = i ?? j {
// 和 if i != nil || j != nil 类似
print(n)
}
如果你将 if let 的 ?? 操作符看作是和 “or” 语句类似的话,多个 if let 语句并列则等价于 “and”.
可选值map与flatmap
let characters: [Character] = ["a", "b", "c"]
let char = characters.first.map { String($0) }
常规使用
let stringNumbers = ["1", "2", "3", "foo"]
let x = stringNumbers.frst.map { Int($0) } // Optional(Optional(1))
let y = stringNumbers.frst.flatMap { Int($0) } // Optional(1)
这种情况下 返回了??类型。
使用flatMap可以展平为单个 可选值。
let numbers = ["1", "2", "3", "foo"]
var sum = 0
for case let i? in numbers.map({ Int($0) }) {
sum += i
}
这段代码计算sum
sum = numbers.map{ Int($0) }.reduce(0) { partialResult, i in
partialResult + (i ?? 0)
}
//或者写作
sum = numbers.map { Int($0) }.reduce(0) { $0 + ($1 ?? 0) }
因为map返回的集合中元素是可选的,所以reduce还得拆包,实际上,你想要的版本应该是一个可以将那些 nil 过滤出去并将非 nil 值进行解包的 map。Collection中的compactMap正是你想要的,在swift3中,flatmap可以完成,但现在废弃了,需要使用compactMap。
numbers.compactMap { Int($0) }.reduce(0, +) // 6
可选值判等
let regex = "^Hello$" // ...
if regex.frst == "^" {
// 只匹配字符串开头
}
这里的==方法的实现类似于这样
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
}
}
它接受2个可选值,需要注意如果两个都是nil,会判为相等
上面的例子中 ,"^" 并不是可选值,这是因为编译器自动转换为了可选值。这一点在swift中很常见,比如字典
字典存储的是可选纸,但是赋值的时候我们从来不会特意转化成可选值。
另外字典还有一个特点,给key赋值为nil,其实是移除了这个key,
var dictWithNils: [String: Int?] = [ "one": 1,"two": 2,"none": nil ]
这个字典有三个键,其中一个的值是 nil。如果我们想要把 "two" 的键也设置为 nil 的话,下面
的代码是做不到的,它会把two移除掉。
dictWithNils["two"] = nil
dictWithNils // ["none": nil, "one": Optional(1)]
应该使用下面的方式
dictWithNils["two"] = Optional(nil)
dictWithNils["two"] = .some(nil)
dictWithNils["two"]? = nil dictWithNils // ["none": nil, "one": Optional(1), "two": nil]
而且第三种还不太一样,它是找到值然后修改,如果对不存在的key这么操作是无效的,不会插入新的键
隐式拆包可选值
声明一个隐式的可选值UIView!
第一种情况是接受一个可选值,但是你知道它不可能是nil,比如OC桥接swift
第二种情况是它可能在极短的时间内是nil,比如初始化需要一些条件,但是声明的时候缺乏这个条件,而你可以保证在访问之前会正确的初始化它,再或者就是使用Interface Builder的时候,view controller 负责延时创建他们的 view,所以在 viewcontroller 自身已经被初始化,但是它的 view 还没有被加载的这段时间里,view 的对象的outlet 引用还没有被创建。
隐式可选值书写时会隐藏可选性质,但是当作可选值来写也没有问题
var s: String! = "Hello"
s.isEmpty // Optional(false)
s?.isEmpty // Optional(false)
不过也有特殊之处,比如不能将一个隐式解包的值通过 inout 的方法传递给一个函数
func increment(inout x: Int) {
x += 1
}
var i = 1 // 普通的
Int increment(&i) // 将 i 增加为 2 var j:
Int! = 1 // 隐式解包的
Int increment(&j) // 错误:Cannot pass immutable value of type 'Int' as inout argument
网友评论