From 1cff8f2e0b4c3102707e52227f5e90b37b1e757c Mon Sep 17 00:00:00 2001 From: Linus <360725966@qq.com> Date: Sun, 13 Sep 2015 16:36:39 +0800 Subject: [PATCH] update --- ...\227(Swift 2.0 - Let\342\200\231s try).md" | 164 ++++++++ ...2.0\357\274\232Protocol-Oriented MVVM).md" | 329 ++++++++++++++++ ...4\250(Understanding Enums using Swift).md" | 193 +++++++++ ...rotocols - My Current Recommendations).md" | 93 +++++ ...o Non-Optional UIImage Named in Swift).md" | 54 +++ ...5\205\270\346\240\221(A Trie in Swift).md" | 370 ++++++++++++++++++ 6 files changed, 1203 insertions(+) create mode 100644 "TranslationSet/Swift 2.0 \344\270\255\344\275\277\347\224\250 try? \345\205\263\351\224\256\345\255\227(Swift 2.0 - Let\342\200\231s try).md" create mode 100644 "TranslationSet/Swift 2.0 \344\270\255\347\232\204\351\235\242\345\220\221\345\215\217\350\256\256\347\232\204MVVM(Swift 2.0\357\274\232Protocol-Oriented MVVM).md" create mode 100644 "TranslationSet/Swift\344\270\255\346\236\232\344\270\276\347\232\204\344\275\277\347\224\250(Understanding Enums using Swift).md" create mode 100644 "TranslationSet/\345\215\217\350\256\256 - \346\210\221\345\275\223\345\211\215\347\232\204\346\216\250\350\215\220(Protocols - My Current Recommendations).md" create mode 100644 "TranslationSet/\345\246\202\344\275\225\345\234\250 Swift \344\270\255\344\274\230\351\233\205\345\234\260\344\275\277\347\224\250 UIImage(A Beautiful Solution to Non-Optional UIImage Named in Swift).md" create mode 100644 "TranslationSet/\345\246\202\344\275\225\345\234\250 Swift \344\270\255\344\275\277\347\224\250\345\255\227\345\205\270\346\240\221(A Trie in Swift).md" diff --git "a/TranslationSet/Swift 2.0 \344\270\255\344\275\277\347\224\250 try? \345\205\263\351\224\256\345\255\227(Swift 2.0 - Let\342\200\231s try).md" "b/TranslationSet/Swift 2.0 \344\270\255\344\275\277\347\224\250 try? \345\205\263\351\224\256\345\255\227(Swift 2.0 - Let\342\200\231s try).md" new file mode 100644 index 0000000..ba77c98 --- /dev/null +++ "b/TranslationSet/Swift 2.0 \344\270\255\344\275\277\347\224\250 try? \345\205\263\351\224\256\345\255\227(Swift 2.0 - Let\342\200\231s try).md" @@ -0,0 +1,164 @@ +# Swift 2.0 中使用 try? 关键字 + +--- +原文链接:[Swift 2.0: Let's try?](http://natashatherobot.com/swift-2-0-try/) + +阅读 Xcode 7 Beta 6 发布说明时,我一下子就被下面这三个特性迷住了: + +>![](http://twitter.com/NatashaTheRobot/status/635963256968998915/photo/1) + +___ + +>![](http://twitter.com/NatashaTheRobot/status/635963578923749376/photo/1) + +___ + +>![](http://twitter.com/NatashaTheRobot/status/635964260070371329/photo/1) + +还有一个想法我没有发到推特上:我对 Swift 最大的改动——`try?`关键字——很不理解。 + +![](http://natashatherobot.com/wp-content/uploads/Screen-Shot-2015-08-26-at-4.48.03-AM.png) + +>Swift 新增一个关键字`try?`。`try?`会试图执行一个可能会抛出异常的操作。如果成功抛出异常,执行的结果就会包裹在可选值(optional)里;如果抛出异常失败(比如:已经在处理 error),那么执行的结果就是`nil`,而且没有 error。`try?`配合`if let`和`guard`一齐使用效果更加。 + +>``` +>func produceGizmoUsingTechnology() throws -> Gizmo {...} +>func produceGizmoUsingMagic() throws -> Gizmo {...} +> +>if let result = try? produceGizmoUsingTechnology() {return result} +>if let result = try? produceGizmoUsingMagic() {return result} +>print("warning: failed to produce a Gizmo in any way") +>return nil +>``` +>值得注意的是,`try?`总是给已经在求值的结果类型又增添一层Optional。如果一个方法正常返回的类型是`Int?`,那么使用`try?`调用这个方法就会返回`Int??`或者`Optional>`。(21692467) + +我昨天发给[@allonsykraken](https://twitter.com/allonsykraken)一些代码,然后他用`try?`漂亮地重构了那些代码。这帮我深深的理解了`try?`的重要性,所以我把这些代码共享出来。 + +## 用 try 解析 JSON + +这里的用例就是 ... 看 ↑ ,没错就是 Json 解析。以下的代码是我为一个简单的代办 app 写的: + +```swift +struct TodoListParser { + + enum Error: ErrorType { + case InvalidJSON + } + + func parse(fromData data: NSData) throws -> TodoList { + + // Notice the need to use try here + guard let jsonDict = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String : AnyObject] else { + throw Error.InvalidJSON + } + + guard let todoListDict = jsonDict["todos"] as? [[String : AnyObject]] else { + throw Error.InvalidJSON + } + + let todoItems = todoListDict.flatMap { TodoItemParser().parse(fromData: $0) } + + return TodoList(items: todoItems) + } +} +``` + +### Issue 1: 出乎意料的异常 + +首先,可以注意到`NSJSONSerialization.JSONObjectWithData`这个类方法抛出 error。之前这个问题我一直不理解。我认为如果抛出 error 时只会从`guard`语句跳到`else`的代码块中,然后用我准备好的错误处理代码去处理。然而事情并非我想的那样。`JSONObjectWithData`会抛出自己的 error,然后在进入`else`代码块之前退出当前方法。 + +为了处理这个问题,我用`do-catch`把之前需要处理的语句包裹起来,像这样: + +``` +func parse(fromData data: NSData) throws -> TodoList { + + do { + guard let jsonDict = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String : AnyObject] else { + throw Error.InvalidJSON + } + + guard let todoListDict = jsonDict["todos"] as? [[String : AnyObject]] else { + throw Error.InvalidJSON + } + + let todoItems = todoListDict.flatMap { TodoItemParser().parse(fromData: $0) } + + return TodoList(items: todoItems) + + } catch { + throw Error.InvalidJSON + } +} +``` + +### Issue 2: 重复的抛出异常 + +现在出现了另外一个问题。`jsonDict`和`todoListDict`两者的赋值报出了相同的错,如果两者都没有成功解包,就会报错`Error.InvalidJSON`。为了解决这个问题,我把两个`guard`语句合并成一个。这虽然跟`try`没什么关系,但是我还是想提一下,是因为我还是习惯用`guard`语句。修改后的代码如下: + +```swift +func parse(fromData data: NSData) throws -> TodoList { + + do { + guard let jsonDict = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String : AnyObject], + // todoListDict is now moved up here + todoListDict = jsonDict["todos"] as? [[String : AnyObject]] else { + throw Error.InvalidJSON + } + + let todoItems = todoListDict.flatMap { TodoItemParser().parse(fromData: $0) } + + return TodoList(items: todoItems) + + } catch { + throw Error.InvalidJSON + } +``` + +现在让我们来比较一下这个版本和`try?`的版本: + +## 用 try? 解析 JSON + +`try?`才是真正我一开始想要的处理过程:当有 error 要抛出的时候,会进到`else`代码块中: + +```swift +struct TodoListParser { + + enum Error: ErrorType { + case InvalidJSON + } + + func parse(fromData data: NSData) throws -> TodoList { + + guard let jsonDict = try? NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String : AnyObject], + // Notice the extra question mark here! + todoListDict = jsonDict?["todos"] as? [[String : AnyObject]] else { + throw Error.InvalidJSON + } + + let todoItems = todoListDict.flatMap { TodoItemParser().parse(fromData: $0) } + + return TodoList(items: todoItems) + + } +} +``` + +代码中唯一的不足(呵呵,双关!)就是返回的值是一个有两层的可选值。`guard let`对其中的一层可选值进行了解包,所以对`todoListDict`进行赋值的时候,只需要再进行一层解包即可。 + +警告:我自己仍然处在学习`try?`的过程中,所以如果你有什么意见,一定要在下面留言。我会虚心接受的 :) + + + + + + + + + + + + + + + + diff --git "a/TranslationSet/Swift 2.0 \344\270\255\347\232\204\351\235\242\345\220\221\345\215\217\350\256\256\347\232\204MVVM(Swift 2.0\357\274\232Protocol-Oriented MVVM).md" "b/TranslationSet/Swift 2.0 \344\270\255\347\232\204\351\235\242\345\220\221\345\215\217\350\256\256\347\232\204MVVM(Swift 2.0\357\274\232Protocol-Oriented MVVM).md" new file mode 100644 index 0000000..4ed7b3b --- /dev/null +++ "b/TranslationSet/Swift 2.0 \344\270\255\347\232\204\351\235\242\345\220\221\345\215\217\350\256\256\347\232\204MVVM(Swift 2.0\357\274\232Protocol-Oriented MVVM).md" @@ -0,0 +1,329 @@ +# Swift 2.0 中的面向协议的MVVM + +--- +原文链接:[Swift 2.0: Protocol-Oriented MVVM](http://natashatherobot.com/swift-2-0-protocol-oriented-mvvm/) + +自从令人兴奋的[Swift 面向协议编程 WWDC 讲座](https://developer.apple.com/videos/wwdc/2015/?id=408)发布,我就在思考协议的用法。但是现实中,我并没有使用过它们。我仍在消化面向协议编程的含义,之后就可以在代码中用面向协议编程模式替换面向过程编程模式了。 + +一个庞大的案例涌上心头:`MVVM` ! 我之前用过 `MVVM`,如果你想要具体了解,可以看我之前发表的[关于 MVVM 的博客](http://natashatherobot.com/swift-mvvm-optionals/)。不过面向协议的内容都在本篇文章。 + +接下来我会用一个简单的例子来说明。现在有一个设置界面,其中只有一个设置:开启、关闭Minion Mode,当然你也可以推广到有许多设置的情况: + +![](http://natashatherobot.com/wp-content/uploads/Simulator-Screen-Shot-Aug-17-2015-8.26.21-AM.png) + +##视图单元格 + +一个拥有`label`和`switch`按钮的`cell`是很通用的。你可以把相同的 `cell` 运用在不同的地方,比如放在登录界面的 Remember Me 设置中。因此,你想让这个视图通用化。 + +###一个复杂的配置方法 + +通常,我会在 `cell` 中使用配置(`configure`)方法来追踪在应用程序不同部分的所有可能用到这个 `cell` 的设置。这个方法大概是这样的: + +``` +class SwitchWithTextTableViewCell: UITableViewCell { + + @IBOutlet private weak var label: UILabel! + @IBOutlet private weak var switchToggle: UISwitch! + + typealias onSwitchToggleHandlerType = (switchOn: Bool) -> Void + private var onSwitchToggleHandler: onSwitchToggleHandlerType? + + override func awakeFromNib() { + super.awakeFromNib() + } + + func configure(withTitle title: String, + switchOn: Bool, + onSwitchToggleHandler: onSwitchToggleHandlerType? = nil) + { + label.text = title + switchToggle.on = switchOn + + self.onSwitchToggleHandler = onSwitchToggleHandler + } + + @IBAction func onSwitchToggle(sender: UISwitch) { + onSwitchToggleHandler?(switchOn: sender.on) + } +} +``` + +使用 Swift 的默认参数,可以非常方便的,在不修改其他地方代码的情况下,添加额外的设置。举个例子,当设计师需要你将 switch 按钮的颜色改成不同颜色时,可以像下面这样添加一个默认的参数: + +``` +func configure(withTitle title: String, + switchOn: Bool, + switchColor: UIColor = .purpleColor(), + onSwitchToggleHandler: onSwitchToggleHandlerType? = nil) { + label.text = title + switchToggle.on = switchOn + // color option added! + switchToggle.onTintColor = switchColor + + self.onSwitchToggleHandler = onSwitchToggleHandler +} +``` + +在这个案例中,这样写好像没什么大不了的,但是在现实中,这个配置(configure)方法会随着时间的增长和需求的增加变得又长又复杂。这就是需要酷酷的面向协议编程方式出场的地方了。 + +###面向协议的方式 + +``` +protocol SwitchWithTextCellProtocol { + var title: String { get } + var switchOn: Bool { get } + + func onSwitchTogleOn(on: Bool) +} + +class SwitchWithTextTableViewCell: UITableViewCell { + + @IBOutlet private weak var label: UILabel! + @IBOutlet private weak var switchToggle: UISwitch! + + private var delegate: SwitchWithTextCellProtocol? + + override func awakeFromNib() { + super.awakeFromNib() + } + + func configure(withDelegate delegate: SwitchWithTextCellProtocol) { + self.delegate = delegate + + label.text = delegate.title + switchToggle.on = delegate.switchOn + } + + @IBAction func onSwitchToggle(sender: UISwitch) { + delegate?.onSwitchTogleOn(sender.on) + } +} +``` + +当设计师又过来想要添加修改默认颜色的方法时,协议扩展就可以发挥神奇的作用了。 + +``` +extension SwitchWithTextCellProtocol { + + // 这里设置默认颜色 + func switchColor() -> UIColor { + return .purpleColor() + } +} + +class SwitchWithTextTableViewCell: UITableViewCell { + + // 省略部分同上 + + func configure(withDelegate delegate: SwitchWithTextCellProtocol) { + self.delegate = delegate + + label.text = delegate.title + switchToggle.on = delegate.switchOn + // 颜色选项被添加 + switchToggle.onTintColor = delegate.switchColor() + } +} +``` + +协议扩展实现了默认 `switch` 颜色的选项,所以任何实现这个协议或不关心设置颜色的不要担心。只有新的 `cell` 可以设置一次不同的 `switch` 颜色。 + +###The ViewModel + +现在,剩下的部分就很简单了。我需要为 `Minion Mode` 设置一个 `ViewModel`: + +``` +import UIKit + +struct MinionModeViewModel: SwitchWithTextCellProtocol { + var title = "Minion Mode!!!" + var switchOn = true + + func onSwitchTogleOn(on: Bool) { + if on { + print("The Minions are here to stay!") + } else { + print("The Minions went out to play!") + } + } + + func switchColor() -> UIColor { + return .yellowColor() + } +} +``` + +###The ViewController + +最后一步就是把 `ViewModel` 传递给在 `ViewController` 中设置的 `cell`: + +``` +import UIKit + +class SettingsViewController: UITableViewController { + + enum Setting: Int { + case MinionMode + // other settings here + } + + override func viewDidLoad() { + super.viewDidLoad() + } + + // MARK: - Table view data source + + override func tableView(tableView: UITableView, + numberOfRowsInSection section: Int) -> Int + { + return 1 + } + + override func tableView(tableView: UITableView, + cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell + { + if let setting = Setting(rawValue: indexPath.row) { + switch setting { + case .MinionMode: + let cell = tableView.dequeueReusableCellWithIdentifier("SwitchWithTextTableViewCell", forIndexPath: indexPath) as! SwitchWithTextTableViewCell + + // this is where the magic happens! + cell.configure(withDelegate: MinionModeViewModel()) + return cell + } + } + + return tableView.dequeueReusableCellWithIdentifier("defaultCell", forIndexPath: indexPath) + } + +} +``` + +伴随着协议扩展的使用,面向协议编程开始变得有意义起来,而且我也希望找出可以多使用它的方式来。你可以在[这里](https://github.com/NatashaTheRobot/ProtocolOrientedMVVMExperimentSwift)下载到所有的代码例子。 + +##更新: 分割cell的数据源协议和委托协议 + +在原文的评论里,Marc Baldwin 建议把 `cell` 的数据源和 `delegate` 分割成两个协议,就像 `UITableView` 那样,我喜欢这样的主意。接下来就是修改后的版本: + +###The View Cell + +cell 有两个协议,两者都可以配置(configure): + +``` +import UIKit + +protocol SwitchWithTextCellDataSource { + var title: String { get } + var switchOn: Bool { get } +} + +protocol SwitchWithTextCellDelegate { + func onSwitchTogleOn(on: Bool) + + var switchColor: UIColor { get } + var textColor: UIColor { get } + var font: UIFont { get } +} + +extension SwitchWithTextCellDelegate { + + var switchColor: UIColor { + return .purpleColor() + } + + var textColor: UIColor { + return .blackColor() + } + + var font: UIFont { + return .systemFontOfSize(17) + } +} + +class SwitchWithTextTableViewCell: UITableViewCell { + + @IBOutlet private weak var label: UILabel! + @IBOutlet private weak var switchToggle: UISwitch! + + private var dataSource: SwitchWithTextCellDataSource? + private var delegate: SwitchWithTextCellDelegate? + + override func awakeFromNib() { + super.awakeFromNib() + } + + func configure(withDataSource dataSource: SwitchWithTextCellDataSource, delegate: SwitchWithTextCellDelegate?) { + self.dataSource = dataSource + self.delegate = delegate + + label.text = dataSource.title + switchToggle.on = dataSource.switchOn + // color option added! + switchToggle.onTintColor = delegate?.switchColor + } + + @IBAction func onSwitchToggle(sender: UISwitch) { + delegate?.onSwitchTogleOn(sender.on) + } +} +``` + +###The ViewModel + +现在可以在扩展里把数据源和 delegate 逻辑分开了: + +``` +import UIKit + +struct MinionModeViewModel: SwitchWithTextCellDataSource { + var title = "Minion Mode!!!" + var switchOn = true +} + +extension MinionModeViewModel: SwitchWithTextCellDelegate { + + func onSwitchTogleOn(on: Bool) { + if on { + print("The Minions are here to stay!") + } else { + print("The Minions went out to play!") + } + } + + var switchColor: UIColor { + return .yellowColor() + } +} +``` + +###The ViewController + +这部分我有点不确定 - ViewController 会传入两次 viewModel: + +``` +override func tableView(tableView: UITableView, + cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + if let setting = Setting(rawValue: indexPath.row) { + switch setting { + case .MinionMode: + let cell = tableView.dequeueReusableCellWithIdentifier("SwitchWithTextTableViewCell", forIndexPath: indexPath) as! SwitchWithTextTableViewCell + + // 发生魔法的地方 + let viewModel = MinionModeViewModel() + cell.configure(withDataSource: viewModel, delegate: viewModel) + return cell + } + } + + return tableView.dequeueReusableCellWithIdentifier("defaultCell", forIndexPath: indexPath) + } +``` + +我更新示例代码,在[我的Github里](https://github.com/NatashaTheRobot/ProtocolOrientedMVVMExperimentSwift)。 + + + + + + + diff --git "a/TranslationSet/Swift\344\270\255\346\236\232\344\270\276\347\232\204\344\275\277\347\224\250(Understanding Enums using Swift).md" "b/TranslationSet/Swift\344\270\255\346\236\232\344\270\276\347\232\204\344\275\277\347\224\250(Understanding Enums using Swift).md" new file mode 100644 index 0000000..e9a662d --- /dev/null +++ "b/TranslationSet/Swift\344\270\255\346\236\232\344\270\276\347\232\204\344\275\277\347\224\250(Understanding Enums using Swift).md" @@ -0,0 +1,193 @@ +# Swift中枚举的使用 + +--- +原文链接:[Understanding Enums using Swift](http://rajkandathi.com/understanding-enums-using-swift/) + +枚举是用来表示值的“类型”,其中这些值之间是类似的。假如现在有个应用叫`MyFamily`,你每次打开这个应用它都会展示你家庭中的成员以及成员之间的关系。现在,家庭中每个成员跟你就只有那么几种关系(值)。这种关系可以用枚举类型的`FamilyRelationshipType`来表示。 + +这个`FamilyRelationshipType`在本质上是一种全新的数据类型。 + +```swift +enum FamilyRelationshipType { + case GrandFather + case GrandMother + case Father + case Mother + case Brother + case Sister + case Son + case Daughter +} +``` + +虽然可能还有其他的成员关系,我们暂时只关注上面列出的这些。像上面写的那样,我们构建了一个新的叫`FamilyRelationshipType`的类型之后,我们就能在应用里使用它了。每次添加一个家庭成员到应用中去时,我们需要指定该成员与其他成员的关系。这个过程的代码大致如下: + +```swift +struct FamilyMember { + var name: String + var relationshipType: FamilyRelationshipType + + init(name: String, relationshipType: FamilyRelationshipType) { + self.name = name + self.relationshipType = relationshipType + } +} + +let sister = FamilyMember(name: "Jacky", relationshipType: FamilyRelationshipType.Sister) +let brother = FamilyMember(name: "Jack", relationshipType: FamilyRelationshipType.Brother) +``` + +我们需要将上述的成员数据通过网络传输到远程的数据库里保存起来。然而,服务器端或远程数据库并不知道`GrandFather`到底代表什么意思,它们不认识自定义的数据类型,只认识已知的数据类型,比如`Int`类型的1,2,3或者`String`类型的 "GrandFather" 和 "GrandMother" 等。因此,我们需要把上面的枚举类型用应用和服务器端(数据库)双方都能识别的数据类型表示出来。在`Swift`中是使用`rawValue`来表示枚举类型的具体值。默认情况下,`rawValue`会在枚举类型的定义中按自上向下的顺序从0,1,2这样每次递增1。当然,我们也可以自己指定每个枚举类型的`rawValue`,代码如下: + +```swift +enum FamilyRelationshipType: Int { + case GrandFather = 400 + case GrandMother = 500 + case Father = 600 + case Mother = 700 + case Brother = 800 + case Sister = 900 + case Son = 1000 + case Daugther = 1100 +} +``` + +枚举类型用的最多的地方是在`Switch`条件语句。举个例子,你要给每个家庭成员赠送礼物,并且有个`giftFor `函数会根据对方关系的不同来为你推荐不同的礼物。这个推荐礼物的函数如下: + +```swift +static func giftFor(member: FamilyMember) -> String { + switch (member.relationshipType) { + case .GrandFather: + return "Book" + case .GrandMother: + return "Sweater" + case .Father: + return "Shirt" + case .Mother: + return "Flowers" + default: + return "Choclates" + } +} +``` + +Swift中也可以把每个枚举与它的值联合起来。这些联合值(associated values)只可以在`Switch`语句里使用枚举时访问。它完全不同于之前的`rawValue`,它不能通过`.`来读取。这样讲可能有点抽象,回到`giftFor`函数,现在需要根据家庭成员的年龄大小来决定具体的礼物,使用联合值的枚举可以这样来定义: + +```swift +enum FamilyRelationshipType { + case GrandFather(age: Int) + case GrandMother(age: Int) + case Father(age: Int) + case Mother(age: Int) + case Husband(age: Int) + case Wife(age: Int) + case Brother(age: Int) + case Sister(age: Int) + case Son(age: Int) + case Daugther(age: Int) +} +``` + +`giftFor`函数代码如下。其中,我们可以在`giftFor`函数中使用`let`或`var`修饰家庭成员的年龄,并决定推荐什么礼物。为了更容易理解,我把`FamilyMember`结构体也加进来了。 + +```swift +struct FamilyMember { + var name: String + var relationshipType: FamilyRelationshipType + + init(name: String, relationshipType: FamilyRelationshipType) { + self.name = name + self.relationshipType = relationshipType + } + + static func giftFor(member: FamilyMember) -> String { + switch (member.relationshipType) { + case .Brother(let age): + if age > 10 { + return "video games" + } else { + return "toys" + } + case .GrandFather: + return "Book" + case .GrandMother: + return "Sweater" + case .Father: + return "Shirt" + case .Mother: + return "Flowers" + default: + return "Choclates" + } + } +} +``` + +最后,说一下`Swift`中枚举最酷的地方,就是在枚举中可以有方法,也可以使用构造器。`giftFor`函数其实可以从`FamilyMember`结构内移到`FamilyRelationType`枚举中去,因为该方法是基于成员关系及其联合值的。对上面的代码进行重构(我同时把函数`giftFor`也改名为`gift`)后,最终的代码如下: + +```swift +enum FamilyRelationshipType { + case GrandFather(age: Int) + case GrandMother(age: Int) + case Father(age: Int) + case Mother(age: Int) + case Husband(age: Int) + case Wife(age: Int) + case Brother(age: Int) + case Sister(age: Int) + case Son(age: Int) + case Daugther(age: Int) + + func gift() -> String { + switch(self) { + case .Brother(let age): + if age > 10 { + return "video games" + } else { + return "toys" + } + case .GrandFather: + return "Book" + case .GrandMother: + return "Sweater" + case .Father: + return "Shirt" + case .Mother: + return "Flowers" + default: + return "Choclates" + } + } +} + +struct FamilyMember { + var name: String + var relationshipType: FamilyRelationshipType + + init(name: String, relationshipType: FamilyRelationshipType) { + self.name = name + self.relationshipType = relationshipType + } +} +``` + +在我的下一篇文章中,我将简单分析枚举的其他例子,并将重点放在如何将`Objective - c`中的枚举重构为`Swift`的枚举。 + + + + + + + + + + + + + + + + + + + diff --git "a/TranslationSet/\345\215\217\350\256\256 - \346\210\221\345\275\223\345\211\215\347\232\204\346\216\250\350\215\220(Protocols - My Current Recommendations).md" "b/TranslationSet/\345\215\217\350\256\256 - \346\210\221\345\275\223\345\211\215\347\232\204\346\216\250\350\215\220(Protocols - My Current Recommendations).md" new file mode 100644 index 0000000..4284209 --- /dev/null +++ "b/TranslationSet/\345\215\217\350\256\256 - \346\210\221\345\275\223\345\211\215\347\232\204\346\216\250\350\215\220(Protocols - My Current Recommendations).md" @@ -0,0 +1,93 @@ +# 协议 - 我当前的推荐 + +原文链接:[Protocols - My Current Recommendations](http://owensd.io/2015/08/06/protocols.html?utm_campaign=Swift%2BSandbox&utm_medium=web&utm_source=Swift_Sandbox_2) + +最近 Swift 的热点都围绕在协议上。他们觉得任何东西都应该是协议。理论上这挺好,但是事实上,这种观点会产生一些不好的副作用。 + + + +我在代码中使用协议时,总是牢记下面两条规则: + +## 1.不要把协议当成类型 + +我看到的(和我一开始写的)许多方法都是在继承关系里把协议当成一个基类。我不认为这是协议的正确用法,这种设计模式仍然停留在“面向对象”的思维方式上。 + +换句话说,假如你的协议只在继承关系里有意义,那就应该扪心自问是否真的有必要使用协议。我不赞同这种说法:“我的类型是结构体,所以我需要用协议来代替。”如果真的需要这样,那就重构它,把它变得更通用。 + +进一步的验证可见:[http://swiftdoc.org/swift-2/](http://swiftdoc.org/swift-2/)。需要关注所有那些协议(不以 `_` 开头的协议)吗?所有的协议都能适用不同的类型,不管是不是类型继承。 + +## 2.不要把协议泛型化,除非你必须这么做! + +这并不是一个小问题,一旦你把协议泛型化,就无法再使用包含不同协议实例的类型集合。我认为这是严重的设计缺陷。比如说,无论在哪里使用协议,协议中所有不受 `Self` 限制的功能都应该保证调用安全。 + +这条规则同样适用于遵守泛型协议的协议,比如 `Equatable` 。泛型会传染。 + +下面这行代码: + +```swift +protocol Foo : Equatable {} +``` + +就是典型的例子。 + +来个实际点的例子吧: + +我们想要对 HTTP response 建模,并且想要支持两种不同的 response 类型:String 和 JSON。 + +你可能会这样写: + +```swift +class HTTPResponse { + var response: ResponseType + init(response: ResponseType) { self.response = response } +} +``` + +我认为这样写不好。一旦这样写,我们就人为地限制了使用这个类型的能力;比如,这不能在复杂情况下的集合里使用。现在,为什么我想要在集合里使用不同的 `ResponseType`?假设,我想要构建一个 response/request 的测试工具。返回 response 的集合里需要有我支持的类型:String 和 JSON。 + +使用 `AnyObject` 是个不错的做法。虽然可行,但是真的很操蛋。 + +另一种方法是使用协议。然而,除了构建 `ResponseType` 协议之外,让我们想想我们真正想要什么。我真正关心的是, `HTTPResponse` 接收到的 `ResponseType` 都能表示成 `String` 。 + +揣着这样的想法,我写出这样的代码: + +```swift +protocol StringRepresentable { + var stringRepresentation: String { get } +} + +class HTTPResponse { + var response: StringRepresentable + init(response: StringRepresentable) { self.response = response } +} +``` + +对我而言,这为 API 使用者提供了极大的方便,也维持了类型的明确性。 + +当然,这样做是有弊端的。假如你真的需要为 response 使用特定的类型,那么就需要进行类型转换。 + +``` +class JSONResponse : StringRepresentable { + var stringRepresentation: String = "{}" +} + +let http = HTTPResponse(response: JSONResponse()) +let json = http.response as? JSONResponse +``` + +>*这个方法明显更好。调用者知道可能的返回类型或返回值。这明显与遍历整个集合取出返回值是不同的,因为,代码的使用者可能使用的是其他的返回类型,比如 `XMLResponse` ,而我们的代码不可能知道这个。* + +最好是这样写: + +```swift +class HTTPResponse { + var response: ResponseType +} + +let responses = [json, string] // responses 变量是以 HTTPResponse 为元素的数组,元素中的 ResponseType 是未指定的 +``` + +如果要用集合,你还是需要强制转换 response 的类型,不过现在你可以直接用 `json` 实例来验证类型。 + +反正我是每次都用 `[HTTPResponse]` 类型的集合,而不是 `[AnyObject]` 。 + diff --git "a/TranslationSet/\345\246\202\344\275\225\345\234\250 Swift \344\270\255\344\274\230\351\233\205\345\234\260\344\275\277\347\224\250 UIImage(A Beautiful Solution to Non-Optional UIImage Named in Swift).md" "b/TranslationSet/\345\246\202\344\275\225\345\234\250 Swift \344\270\255\344\274\230\351\233\205\345\234\260\344\275\277\347\224\250 UIImage(A Beautiful Solution to Non-Optional UIImage Named in Swift).md" new file mode 100644 index 0000000..2f0e212 --- /dev/null +++ "b/TranslationSet/\345\246\202\344\275\225\345\234\250 Swift \344\270\255\344\274\230\351\233\205\345\234\260\344\275\277\347\224\250 UIImage(A Beautiful Solution to Non-Optional UIImage Named in Swift).md" @@ -0,0 +1,54 @@ +# 如何在 Swift 中优雅地使用 UIImage + +--- +原文链接:[A Beautiful Solution to Non-Optional UIImage Named in Swift](http://natashatherobot.com/non-optional-uiimage-named-swift/) + +昨天,我抽空看了[Swift in Practice WWDC15 Session](https://developer.apple.com/videos/wwdc/2015/?id=411)的视频,很喜欢其中对 `Image` 命名的处理建议。 + +这个视频里解决的问题是方法`UIImage:named:`总需要传入硬编码(写死)的字符串参数,然后返回一个可空(optional)的`UIImage`。这就意味着可能会有两种出错的情况:一种是字符串的拼写错误;另一种是对可选的`UIImage`不正确解包。 + +一种可以解决这个字符串拼写错误的方式就是构建一个`Image`的常量文件,但是这不能解决出错的第二种情况。在 Swift 中,有个更好的解决方案。可以扩展`UIImage`,把所有的`Image`名字作为枚举类型,然后建立便利构造器来通过枚举构造对应的`Image`。看代码: + +``` +// UIImage+Extension.swift +import UIKit + +extension UIImage { + enum AssetIdentifier: String { + // Image Names of Minions + case Bob, Dave, Jorge, Jerry, Tim, Kevin, Mark, Phil, Stuart + } + + convenience init!(assetIdentifier: AssetIdentifier) { + self.init(named: assetIdentifier.rawValue) + } +} +``` + +这样,就可以通过以下的方式在任何需要的地方构建`Image`: + +``` +let minionBobImage = UIImage(assetIdentifier: .Bob) +``` + +这样的方式是不是很清晰呀,哈哈。首先,使用了漂亮的枚举值,关键是不再需要硬编码的字符串了。并且,枚举的值是自动填充出来的,不必担心拼写错误。其次,`Image`不再是可空的了,因为你可以确保它一定是存在的。 + +我自己建了个工程测试了一下,工程在 Github 可以下载,地址在[这里](https://github.com/NatashaTheRobot/ImageNamingInSwift)。如果你想知道它如何在一个app中实现的,可以 check out 后看看。 + +## Update + +正如很多读者指出的,有很多开源的第三方库可以将`Image`名字导出成枚举类型。 +可以 check out 以下的第三方库后查看: + +* [Shark](https://github.com/kaandedeoglu/Shark) + +* [SwiftGen](https://github.com/AliSoftware/SwiftGen) + +同时,可以随时在下面添加任何的评论。 + + + + + + + diff --git "a/TranslationSet/\345\246\202\344\275\225\345\234\250 Swift \344\270\255\344\275\277\347\224\250\345\255\227\345\205\270\346\240\221(A Trie in Swift).md" "b/TranslationSet/\345\246\202\344\275\225\345\234\250 Swift \344\270\255\344\275\277\347\224\250\345\255\227\345\205\270\346\240\221(A Trie in Swift).md" new file mode 100644 index 0000000..e8e5655 --- /dev/null +++ "b/TranslationSet/\345\246\202\344\275\225\345\234\250 Swift \344\270\255\344\275\277\347\224\250\345\255\227\345\205\270\346\240\221(A Trie in Swift).md" @@ -0,0 +1,370 @@ +# 如何在 Swift 中使用字典树 + +--- +原文链接:[A Trie in Swift](https://bigonotetaking.wordpress.com/2015/08/11/a-trie-in-swift/?utm_campaign=Swift%2BSandbox&utm_medium=web&utm_source=Swift_Sandbox_2) + +(此文中的代码都可以在[这里](https://github.com/oisdk/SwiftTrie)下载) +如果上 Google 搜“酷酷的数据结构”,你首先会看到[这个](http://stackoverflow.com/questions/500607/what-are-the-lesser-known-but-useful-data-structures)结果。其中主要是 stackoverflow 上的一个问题:“哪些是我们很少知道但是很有用的数据结构?”。而点赞最多的答案,就是本期主题:字典树。我读了一下,发现了很多酷酷的东西都是关于字典树的用途(同时发现我也是那种会去 Google 搜“酷酷的数据结构”的人,哈哈)。然后我就打开 playground,开始写这篇文章。 + +字典树是一种前缀树。它是一种递归的数据结构:每个字典树包含子字典树,子字典树通过前缀来辨认。 + +字典树这种数据结构不仅广泛使用,而且也有一些有用的应用。它也有类似 Set 一样的操作,比如插入和查找都是`O(n)`复杂度,其中`n`是查找序列的长度。目前,Set是哈希化(hashable)和元素无序化的最好方式。但是,如果要查找的序列中元素是哈希化的,那么字典树就很适合。(有一点要注意:Set 本身是哈希化的,所以,假如需要保存的序列是无序的话,那么 Set 的集合会更合适) + +![A trie for keys “A”, “to”, “tea”, “ted”, “ten”, “i”, “in”, and “inn”.](https://upload.wikimedia.org/wikipedia/commons/thumb/b/be/Trie_example.svg/1092px-Trie_example.svg.png) + +在 Swift 中,我们可以让字典树包含一系列的前缀和子字典树来实现。 +代码如下: + +```swift +public struct Trie { + private var children: [Element:Trie] +} +``` + +我们定义的结构可以递归,因为我不是直接在字典树里保存字典树——我们保存的是引用子字典树的字典类型。在这个字典中,把前缀当作键。那么我们怎么初始化呢?可以像列表那样使用生成器的分解属性。 + +```swift +extension Trie { + private init(var gen: G) { + if let head = gen.next() { + children = [head:Trie(gen:gen)] + } else { + children = [:] + } + } + public init + + (_ seq: S) { + self.init(gen: seq.generate()) + } +} +``` + +这还远远不够。我们不仅需要保存一个序列,还需要`insert`操作: + +```swift +extension Trie { + private mutating func insert + + (var gen: G) { + if let head = gen.next() { + children[head]?.insert(gen) ?? {children[head] = Trie(gen: gen)}() + } + } + public mutating func insert + + (seq: S) { + insert(seq.generate()) + } +} +``` + +上面有一行代码看起来很奇怪: + +```swift +children[head]?.insert(gen) ?? {children[head] = Trie(gen: gen)}() +``` + +老实说,我自己也不太喜欢这样的写法。你可以调用可选链的可变方法(mutating methods)。在这个例子中调用时,可选值是由字典查找返回的:如果我们进行插入操作时这个值存在,我们就会修改这个值。 + +如果不存在,就需要处理这个添加动作。我们可以尝试着把子字典树抽取出来,像这样: + +```swift +if let head = gen.next() { + if var child = children[head] { + child.insert(gen) + } else { + children[head] = Trie(gen: gen) + } +} +``` + +但是,代码中的子字典树只是我们想要修改的真正的子字典树的拷贝。我们之后会把它放回字典中——虽然现在看起来可能这是在做无用功。 + +我们都知道,那些看起来没有返回值的函数,其实会返回特殊的值,叫做`Void`或`()`。本文中,`()?` (或者 `Optional`)也属于这类。我们不关心`void`本身,很明显,我们只关心是不是`nil`。因此,我们可以这样做: + +```swift +if let _ = children[head]?.insert(gen) { return } +children[head] = Trie(gen: gen) +``` + +或者使用`guard`: + +```swift +guard let _ = children[head]?.insert(gen) else { children[head] = Trie(gen: gen) } +``` +不过我认为用`nil coalescing`运算符可能更好,省去`let`或`_`对理解的干扰。 + +感觉字典树这个数据结构并不按常理出牌。一开始,可以直接用变异方法更简单的返回字典树。此外,因为基本上每个方法都包含整个字典树,所以惰性加载几乎是不可能的。(如果谁可以想出一个有效的方法实现惰性加载,请告诉我。) + +字典树中最重要的`contains`函数是这样的: + +```swift +extension Trie { + private func contains + + (var gen: G) -> Bool { + return gen.next().map{self.children[$0]?.contains(gen) ?? false} ?? true + } + public func contains + + (seq: S) -> Bool { + return contains(seq.generate()) + } +} +``` + +这里使用了很多生成器。如果生成器是空的(`gen.next()`返回`nil`),那么字典树就包含序列,因为我们只有当前的元素。`map()`从生成器中寻找下一个元素并返回。如果返回的是`nil`,字典树就不包含这个序列。最后,返回是否包含其余生成器的子字典树,有就是`true`,没有就是`false`。举个栗子: + +```swift +var jo = Trie([1, 2, 3]) +jo.insert([4, 5, 6]) +jo.insert([7, 8, 9]) + +jo.contains([4, 5, 6]) // true +jo.contains([2, 1, 3]) // false +``` + +这里有个小问题。`contains`方法不能像我们想象那样执行以下代码: + +```swift +jo.contains([1, 2]) // true +``` +因为只要生成器里没有数据,就都返回`true`,所以字典树就“包含”了每个已经被插入的序列的前缀。这并不是我们想要的。一种解决方案是:只有在最后一个字典树没有子节点的时候返回`true`。修改后如下: + +```swift +extension Trie { + private func contains + + (var gen: G) -> Bool { + return gen.next().map{self.children[$0]?.contains(gen) ?? false} ?? children.isEmpty + } +} +``` + +但是这好像并没有用。如果我们调用`jo.insert([1, 2])`会怎么样呢?我们发现返回值是`false`,字典树并没有包含`[1, 2]`。 + +针对这种情况,我们需要一个额外的布尔变量,用这个变量来标记字典树是否在序列的末端。 + +```swift +public struct Trie { + private var children: [Element:Trie] + private var endHere : Bool +} +``` + +我们也需要修改我们的`insert`和`init`方法,以便生成器返回`nil`时,`endHere`被初始化为`true`。 + +```swift +extension Trie { + private init(var gen: G) { + if let head = gen.next() { + (children, endHere) = ([head:Trie(gen:gen)], false) + } else { + (children, endHere) = ([:], true) + } + } +} + +extension Trie { + private mutating func insert + + (var gen: G) { + if let head = gen.next() { + children[head]?.insert(gen) ?? {children[head] = Trie(gen: gen)}() + } else { + endHere = true + } + } +} +``` + +同时,`contains`方法返回`endHere`变量,而不再是`true`。 + +```swift +public extension Trie { + private func contains + + (var gen: G) -> Bool { + return gen.next().map{self.children[$0]?.contains(gen) ?? false} ?? endHere + } +} +``` + +我们在优化`contains`方法时,用`guard`可以增加代码可读性: + +```swift +public extension Trie { + private func contains< + G : GeneratorType where G.Element == Element + >(var gen: G) -> Bool { + guard let head = gen.next() else { return endHere } + return children[head]?.contains(gen) ?? false + } +} +``` + +Chris Eidhof给了我如下建议: + +>*This is how Swift 2 improves our Functional Data Structures chapter in [@FunctionalSwift](https://twitter.com/FunctionalSwift) : [pic.twitter.com/LgwCBm7zA6](http://t.co/LgwCBm7zA6) +— Chris Eidhof (@chriseidhof) [August 6, 2015](https://twitter.com/chriseidhof/status/629215881843884032)* + +(显然,这是他的[Functional Programming in Swift](http://www.objc.io/books/fpinswift/)中的字典树实现。我没看过,现在准备去看。如果[Advanced Swift](http://www.objc.io/books/advanced-swift/)可以超过前者,那肯定很有意思。) + +我们希望字典树能像 Set 那样使用所有的并集、交集方法。而`insert`、`init` 和`contains`方法已经实现了,还有`remove`方法没有实现。 + +`remove`方法看上去有点难度。你可能会在序列的末端删除,然后把`endHere`从`true`改成`false`,其实这没有什么卵用。我的意思是你还是会在删除操作后保存相同的信息。而你真正需要的操作是删除不再使用的分支。 + +说起来这个事情有点复杂。你仅仅找到想要删除的,然后就把所有的子节点删除了。你不能这样做,因为这可能会删除其他的入口点( entries )。你也不能删除只有一个子节点的字典树,因为子节点可能会随后延伸开去,或许也将包含你想要删除的序列的前缀。 + +至关重要的是,所有关于能不能删除给定入口的信息都来自字典树的子节点。所以,我决定用递归调用变异方法来实现,并且这个方法会返回一个重要的值。在这种情况下,这个重要的值是一个布尔类型的,它表示这个字典树能不能被删除。因为我使用的是私有方法做生成器,公有包装方法来处理序列,所以我会在公共方法中返回布尔类型表示能不能被删除。 + +让我们继续: + +```swift +private mutating func remove< + G : GeneratorType where G.Element == Element + >(var g: G) -> Bool { +``` + +到目前为止,这跟其他方法没什么区别。接着,处理生成器的头部: + +```swift +if let head = g.next() { +``` + +这个`if`代码块里是具体的逻辑步骤,我们先跳过它来处理`g.next()`返回`nil`时的情况: + +```swift +private mutating func remove< + G : GeneratorType where G.Element == Element + >(var g: G) -> Bool { + if let head = g.next() {...} + endHere = false + return children.isEmpty +} +``` + +到这里就结束删除序列的操作了。这意味着现在不管在哪棵字典树都应该把`endHere`设为`false`。对于字典树的使用者来说,从现在开始,如果再给 +`contains`方法传入那个序列作为参数的话,就会返回`false`。 + +然而,如果可以删除数据本身,就会返回`children.isEmpty`的值。如果当前字典树没有子节点,那么就是可以删除了的。 +现在再回到`if`代码块里具体的实现: + +```swift +guard children[head]?.remove(g) == true else { return false } +children.removeValueForKey(head) +return !endHere && children.isEmpty +``` + +调用`remove`来删除子字典树对应的`head`。`guard`语句会有两种情况返回false:一种情况是`children`没有包含`head`,另一种是要被删除的sequence已经不在当前的字典树中了。因为没有成功删除或变异,这个方法将返回`false`。 + +如果`children`包含了`head`,但是返回还是`false`的话,这表示它的子节点是不可删除的,所以才会返回`false`。否则,会成功删除(`children.removeValueForKey(head)`)。 + +接着,字典树通过`return !endHere && children.isEmpty`会知道自己是不是可以删除的。如果`endHere`等于`true`的话,表示已经在 sequence 末端了,那就是不可删除的;如果没有子节点就是可删除的。完整的方法如下: + +```swift +extension Trie { + private mutating func remove< + G : GeneratorType where G.Element == Element + >(var g: G) -> Bool { // Return value signifies whether or not it can be removed + if let head = g.next() { + guard children[head]?.remove(g) == true else { return false } + children.removeValueForKey(head) + return !endHere && children.isEmpty + } + endHere = false + return children.isEmpty + } + public mutating func remove< + S : SequenceType where S.Generator.Element == Element + >(seq: S) { + remove(seq.generate()) + } +} +``` + +这代码又丑又长,用`count`属性来简化一下吧: + +``` +extension Trie { + public var count: Int { + return children.values.reduce(endHere ? 1 : 0) { $0 + $1.count } + } +} +``` + +通过`count`属性记录`endHere`有多少次`true`。如果当前的字典树到末端了,`count`就加一`(endHere ? 1 : 0)`,同时也加到所有孩子节点的总和里。 + +接下来处理`SequenceType`。[Getting tree-like structures to conform to `SequenceType` is a bit of a pain](http://airspeedvelocity.net/2015/07/22/a-persistent-tree-using-indirect-enums-in-swift/)文章里觉得树形结构要遵照`SequenceType`有点头疼的原因主要是因为递归。如果用线性(非递归)表示会很 easy: + +```swift +extension Trie { + public var contents: [[Element]] { + return children.flatMap { + (head: Element, child: Trie) -> [[Element]] in + child.contents.map { [head] + $0 } + (child.endHere ? [[head]] : []) + } + } +} +``` + +然后,这当然也可以作为字典树的生成方法。这个看上去有点不合适,就好像仅仅通过遍历的方式就把一种数据结构转换成了另一种数据结构。而我们真正需要的是按需生成每个元素。 + +然而,有时候需要手写一些虽然用手写很不 nice 的代码,而且还要用一些 trick (比如:闭包)。无论如何,看代码吧: + +```swift +public struct TrieGenerator : GeneratorType { + private var children: DictionaryGenerator> + private var curHead : Element? + private var curEnd : Bool = false + private var innerGen: (() -> [Element]?)? + private mutating func update() { + guard let (head, child) = children.next() else { innerGen = nil; return } + curHead = head + var g = child.generate() + innerGen = {g.next()} + curEnd = child.endHere + } + public mutating func next() -> [Element]? { + for ; innerGen != nil; update() { + if let next = innerGen!() { + return [curHead!] + next + } else if curEnd { + curEnd = false + return [curHead!] + } + } + return nil + } + private init(_ from: Trie) { + children = from.children.generate() + update() + } +} +``` + +这里跟之前使用的`lazy flatMap`逻辑类似。 + +playground 版的代码可以在[这里](https://github.com/oisdk/SwiftTrie)下载,SwiftSequence 里有一些测试,代码在[这里](https://github.com/oisdk/SwiftSequence)。 + + + + + + + + + + + + + + + + + + + + +