Swift 编程思想

原文链接:http://alisoftware.github.io/swift/2015/09/06/thinking-in-swift-1/

我经常看到一些刚接触Swift的小伙伴们尝试着把他们的ObjC代码翻译成Swift。但开始在Swift代码最困难的事情不是语法,而是改变你的思维方式,使用新的Swift概念是在ObjC中是并没有的。

假设你想创建一个条目列表(比如过会儿要显示在一个tableView里),每个条目都有一个图标,标题和网址。这些条目都通过一个json初始化。我们来看一下下面的ObjC代码:

@interface ListItem : NSObject
@property(strong) UIImage* icon;
@property(strong) NSString* title;
@property(strong) NSURL* url;
@end

@implementation ListItem
+(NSArray*)listItemsFromJSONData:(NSData*)jsonData { 
    NSArray* itemsDescriptors = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];

   NSMutableArray* items = [NSMutableArray new]; 
   for (NSDictionary* itemDesc in itemsDescriptors) { 
      ListItem* item = [ListItem new];    
      item.icon = [UIImage imageNamed:itemDesc[@"icon"]]; 
      item.title = itemDesc[@"title"]; 
      item.url = [NSURL URLWithString:itemDesc[@"title"]]; 
      [items addObject:item]; 
  } 
  return [items copy];
}
@end
我们来看一下直译成Swift是什么样的:
class ListItem {
  var icon: UIImage?
  var title: String = ""
  var url: NSURL!

 static func listItemsFromJSONData(jsonData: NSData?) -> NSArray {
    let jsonItems: NSArray = try! NSJSONSerialization.JSONObjectWithData(jsonData!, options: []) as! NSArray
    let items: NSMutableArray = NSMutableArray()
    for itemDesc in jsonItems {
        let item: ListItem = ListItem()
        item.icon = UIImage(named: itemDesc["icon"] as! String)
        item.title = itemDesc["title"] as! String
        item.url = NSURL(string: itemDesc["url"] as! String)!
        items.addObject(item)
    }
    return items.copy() as! NSArray
 }
}

对 Swift 稍有经验的人应该会看出来这里面有很多代码异味。Swift 的专家读到这段代码之后就很可能心脏病突发而全部挂掉了。

哪里做错了?

上面例子中第一个看起来像代码异味的地方就是一个 Swift 新手经常犯的坏毛病:到处使用隐式解析可选类型(value!),强制转型(value as! String)和强制使用try(try!)。

可选类型是你的朋友:它们很棒,因为它们能迫使你去思考你的值什么时候是nil,以及在这种情形下你该做什么。比如”如果没有图标的话我该显示什么呢?在我的 TableViewCell 里我该用一个占位符(placeholder)么?或者用另外一个完全不同的 cell 模板?”。

这些就是我们在 ObjC 中经常忘了考虑进去的用例,但是 Swift 帮助我们去记住它们,所以当值是nil的时候把它们强制拆包导致程序崩溃,把可选类型这个高级特性扔在一边不用,是很可惜的。

! ! ! 你绝不应该对一个值进行强制拆包,除非你真的知道你在干什么。记住,每次你加一个!去取悦编译器的时候,你就屠杀了一匹小马🐴。

可悲的是,Xcode是鼓励犯这种错误的,因为error提示到:”value of optional type ‘NSArray?’ not unwrapped. Did you mean to use!or??” ,修改提示建议…你在最后面加一个!✖。

Let’s save those ponies

那么我们该怎样去避开使用这些无处不在的糟糕的!呢?这儿有一些技巧:

  • 使用可选绑定(optional binding)if let x = optional { /* 使用 x */ }
  • 用as?替换掉as!,前者在转型失败的时候返回nil;你当然可以把它和if let结合使用
  • 你也可以用try?替换掉try!,前者在表达式失败时返回nil1。
好了,来看看用了这些规则之后我们的代码:
  class ListItem {
    var icon: UIImage?
    var title: String = ""
    var url: NSURL!

    static func listItemsFromJSONData(jsonData: NSData?) -> NSArray {
    if let nonNilJsonData = jsonData {
        if let jsonItems: NSArray = (try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: [])) as? NSArray {
            let items: NSMutableArray = NSMutableArray()
            for itemDesc in jsonItems {
                let item: ListItem = ListItem()
                if let icon = itemDesc["icon"] as? String {
                    item.icon = UIImage(named: icon)
                }
                if let title = itemDesc["title"] as? String {
                    item.title = title
                }
                if let urlString = itemDesc["url"] as? String {
                    if let url = NSURL(string: urlString) {
                       item.url = url
                    }
                }
                items.addObject(item)
            }
            return items.copy() as! NSArray
        }
    }
    return [] // In case something failed above
  }
  }

判决的金字塔

可悲的是,满世界的添加这些if let让我们的代码往右挪了好多,形成了臭名昭著的判决金字塔)(此处插段悲情音乐)。

Swift中有些机制能帮我们做简化:
  • 将多个if let语句合并为一个:if let x = opt1, y = opt2

  • 使用guard语句,在某个条件不满足的情况下能让我们尽早的从一个函数中跳出来,避免了再去运行函数体剩下的部分。
    当类型能被推断出来的时候,我们再用此代码把这些变量类型去掉来消除冗余 - 比如简单的用let items = NSMutableArray() - 并利用guard语句再确保我们的json确实是一个NSDictionary对象的数组。最后,我们用一个更”Swift化”的返回类型[ListItem]替换掉ObjC的NSArray:

    class ListItem {
       var icon: UIImage?
       var title: String = ""
       var url: NSURL!
    
       static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
          guard let nonNilJsonData = jsonData,
          let json = try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: []),
          let jsonItems = json as? Array<NSDictionary>
          else {
              // If we failed to unserialize the JSON
              // or that JSON wasn't an Array of NSDictionaries,
              // then bail early with an empty array
              return []
          }
    
          var items = [ListItem]()
          for itemDesc in jsonItems {
             let item = ListItem()
             if let icon = itemDesc["icon"] as? String {
                  item.icon = UIImage(named: icon)
             }
             if let title = itemDesc["title"] as? String {
                  item.title = title
             }
             if let urlString = itemDesc["url"] as? String, let url = NSURL(string: urlString) {
                  item.url = url
             }
             items.append(item)
        }
        return items
      }
    }
    

guard语句真心很赞,因为它在函数的开始部分就把代码专注于检查输入是否有效,然后在代码剩下的部分中你就不用再为这些检查操心了。如果输入并非所想,我们就尽早跳出,帮助我们专注在那些我们期望的事情上。

结论

Swift 是为了更高的安全性而设计。不要把所有东西都强制拆包而忽视了可选类型:当你在你的 Swift 代码中看见了一个!,你就应该总是要把它看做是一处代码异味,某些事情是要出错的。

注意这个try?默默的将error丢弃了:用它的时候你不会知道为什么代码出错的原因。所以通常来说如果可能的话用do { try ... } catch { }替换掉try?会更好。但是在我们的例子中,因为我们希望在 JSON 因某种原因序列化失败时返回一个空数组,这里用try?是 OK 的。↩

如你所见,我在代码的最后保留了一个as!(items.copy() as! NSArray)。有时杀死小马强制转型是 OK 的,如果你真的,真的知道返回的类型不是其他任何东西,就像这里的mutableArray.copy()。可是这种例外十分罕见,只有在你一开始的时候就认真思考过这个用例的情况下才可以接受(当心,如果那匹小马🐴死了,你将会受到良心的谴责)。

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权

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

推荐阅读更多精彩内容