在上一篇文章中我们为数组引入了map()和flatMap(),详细讲解了他们的用法和优点,其实map和flatMap也适用于Optionals类型 和 其他很多的类型,今天我们来探索下他们的用法。
回忆一下,我们之前学习的在Array上使用map和flatMap的用法是这样的:
// Method on Array<T> map( transform: T -> U ) -> Array<U> flatMap( transform: T -> Array<U> ) -> Array<U>
这意味着如果提供一个转换方式:T->U的话,你可以将一个包含T的Array转换成一个包含U的Array。也就是说让 Array<T>
通过简单的调用map( transform: T->U ),就会返回一个 Array<U>
。
类似的,map和flatMap在 Optional<T>
上的用法是这样的:
// Method on Optional<T> map( transform: T -> U ) -> Optional<U> flatMap( transform: T -> Optional<U> ) -> Optional<U>
ok,我们来看看map()在 Optional<T>
类型上做了什么。同理于 Array<T>
,首先是拿到了 Optional<T>(Array<T>)
里的所有内容 ( Array<T>
的内容为数组里面所有T, Optional<T>
里所有内容为该Optional的真实值和nil) ,然后根据我们所提供的转换方式(transform: T->U)进行转换后,再将结果包装成新的 Optional<U>(Array<U>)
。他们所做的事情其实是一样的。
我们来看看怎样在我们之前的代码里用上我们所说的方法。
记得在上一篇文章我们的示例代码中,我们的 itemDesc["icon"]
会返回一个 String?
,而我们的目的是将其作为图片名字转换成一个 UIImage
,只不过, UIImage(name:)
需要接收一个 String
类型的参数而不是 String?
,所以我们保证在这个Optional String值非nil的情况下,并拿到Optional里真实的String去调用这个UIImage的构造函数。
当然在以前,我们可以使用Optional Binding来做:
let icon: UIImage? if let iconName = itemDesc["icon"] as? String { icon = UIImage(named: iconName) } else { icon = nil }
为了更加简便,节省代码行数,我们还可以用 nil联结操作符 ??
let iconName = itemDesc["icon"] as? String let icon = UIImage(named: iconName ?? "")
当iconName为nil时,将""赋值给iconName作为UIImage构造函数的参数: UIImage(named: "")
,返回一个nil的image(UIImage?),保证了程序不crash,但是这样似乎有点扭曲了构造函数 UIImage(named:)
。
事实上我们需要的是当Optional UIImage(named: )
使其返回一个UIImage,所以这是这非常贴切的用法。 上代码:
let iconName = itemDesc["icon"] as? String item.icon = iconName.map { imageName in UIImage(named: imageName) }
想法是对的,可是上面的代码必须是编译不过的!问题出在哪里呢?先解释下上面这段代码。我们让iconName这个Optional值调用map()方法,映射其非nil的值(真实值){换句话说:当iconName非nil时执行map闭包里的转换规则}成为参数imageName传入闭包,构造出一个UIImage对象。问题就出在如果imageName不是一个有效图片名字,或者因为某种原因取不到的话,UIImage就为nil,所以 UIImage(named: )
返回值本身就是一个 UIImage?
,再看看map()的定义:使转换T->U并返回 U?
。那么再看看我们的例子,UIImage?就相当于 U
,那么整个map完了后会返回的 U?
是什么?------ UIImage??
。哈哈这就是所谓的Double-Optional!
flatMap做了什么转换? T->U?
,因其使结果扁平化(flattens)而得名。。。。介绍这些就不再说了,回顾可以看回前一篇文章。在这里flatMap可以为我们去掉一层optional:
let iconName = itemDesc["icon"] as? String item.icon = iconName.flatMap { imageName in UIImage(named: imageName) }
因此,在这个应用中,flatMap做了这些事情:
-如果iconName为nil的话,直接返回nil而不是作为一个UIImage?来返回
-如果iconName有值,flatMap将尝试使用iconName来创建UIImage,这时只有UIImage的构造函数失败了,才会返回nil,因此flatMap返回值为UIImage?
总而言之,只有在 itemDesc["icon"] as? String
结果非nil,并且 UIImage(named: )
成功的情况下, item.icon
才有一个非空值。
这样做比使用 ??
来欺骗构造方法更中规中矩。
在Xcode7 后构造器可以通过 .init
这个property暴露出来,因此我们可以进一步地让代码更简化更紧凑。就是说 UIImage.init
是一个参数为String返回值为UIImage?的方法,我们可以直接地让他成为flatMap的参数,而简化掉闭包:
let iconName = itemDesc["icon"] as? String item.icon = iconName.flatMap(UIImage.init)
。。。。这个用法我还没有理解得非常通,不过好像仅仅在IOS8以上生效,最后谢谢捧场!