Swift - Map all the things 映射所有东
我博客原文: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<String>非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以上生效,最后谢谢捧场!