Map all the things in Swift 映射所有东西

Blog UV: AmazingCounters.com


翻译自:http://alisoftware.github.io/swift/2015/10/11/thinking-in-swift-4/
翻译者:lzy
欢迎转载,请注明出处:http://zyden.vicp.cc/mapallthething/

在上一篇文章中我们为数组引入了map()和flatMap(),详细讲解了他们的用法和优点,其实map和flatMap也适用于Optionals类型 和 其他很多的类型,今天我们来探索下他们的用法。

对比下Array 和 Optional

回忆一下,我们之前学习的在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>  

map() on Optionals

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:)。

我们用map试试

事实上我们需要的是当Optional非nil的时候将其解包,并且将他的真实值作为参数传递给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!

救星faltMap

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才有一个非空值。
这样做比使用??来欺骗构造方法更中规中矩。

魔法:使用init简化闭包

在Xcode7 后构造器可以通过.init这个property暴露出来,因此我们可以进一步地让代码更简化更紧凑。就是说UIImage.init是一个参数为String返回值为UIImage?的方法,我们可以直接地让他成为flatMap的参数,而简化掉闭包:

let iconName = itemDesc["icon"] as? String  
item.icon = iconName.flatMap(UIImage.init)  

。。。。这个用法我还没有理解得非常通,不过好像仅仅在IOS8以上生效,最后谢谢捧场!

comments powered by Disqus