├── 2017-01 ├── 深度学习与App的魔幻结合.md └── 面向对象的UITableViewCells.md ├── 2017-09 └── 轻松管理Swift项目中的各种环境.md ├── 2017-10 ├── Swift 4中的泛型.md └── 使用"Core"架构iOS Apps.md ├── LICENSE └── README.md /2017-01/深度学习与App的魔幻结合.md: -------------------------------------------------------------------------------- 1 | > This post have got access from the original author [Avihay Assouline](https://medium.com/@avihay) to translate into Chinese. If you are interest in this article and want to repost it, you should add the refer link to this article in your repost. 2 | > 3 | > 这篇文章已从英文原作者[Avihay Assouline](https://medium.com/@avihay)那里得到翻译授权,如果你对本文感兴趣而且想转发,你应该在转发文章里加上本文的[链接](https://github.com/britzlieg/translate_post/blob/master/2017-01/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%8EApp%E7%9A%84%E9%AD%94%E5%B9%BB%E7%BB%93%E5%90%88.md)。 4 | 5 | > [英文原文链接](https://medium.com/@avihay/bring-magic-to-your-mobile-app-with-deep-learning-184d9062d7fc#.2e0i9zu79) 6 | 7 | # 深度学习与App的魔幻结合 8 | 9 | 打造你自己的自动驾驶汽车的第一步,是教会你的App从18000张图片中识别交通灯 10 | 11 | ![](https://cdn-images-1.medium.com/max/2000/1*Id147TcfIDDGf6BKz5Misw.jpeg) 12 | 13 | 机器学习是发展最快和最令人兴奋的领域之一,而其中最有代表性的是深度学习。自从我回到大学,我一直专注于这个领域。然而,在2009年的时候,软硬件和数据并不能达到实现深度学习的要求。但是,自从那以后,这些都不再是问题。 14 | 15 | 在这个简短的入门指南中,我将会通过深度学习的一些基本步骤,示范一下如何自己入门深度学习。我们将开发一款使用现场相机数据来识别交通灯的app。 16 | 17 | 在我们开始学习之前,我想说明一下一些事情。这个入门教程会跳过一些概念和难点,以便于降低入门门槛和尽快实现真实测试。另外,我们将只使用深度学习中的图片分类,以使整个入门过程变得更加直接和简单。 18 | 19 | [Youtube视频1](https://youtu.be/agAFdlaJ6uo) 20 | 21 | ## 简介 22 | 在这里我假设你对深度学习有一定了解。你可能见过一些使用了深度学习的产品,比如Facebook的脸部识别,Apple的Siri,Mobileye的车辆碰撞识别等等。然而,随着最近深度学习框架的开源,不仅仅巨头们可以开发这些产品,创业公司现在也可以参与其中。例如, [Clarifai](https://www.clarifai.com/) 为你的应用提供了视觉能力, [AIDoc](http://www.aidoc.com/)  对医学放射工业发起了挑战。这些可以看出使用场景正在改变。 23 | 24 | ### 深度学习 25 | 什么是深度学习?什么是神经网络?我可以在这里讨论一下这些。但是,我更希望你看看这个由吴恩达讲解的深度学习的视频。虽然这段视频有点长,但是吴恩达提供了一些很好的例子和讲解。 26 | 27 | 如果你没有时间去看完这段视频,这里有一些里面说的的重点:在过去,为了让我们的电脑能够执行不同的任务,我们不得不为每个小问题单独编写算法。机器学习的强大之处在于,它能够使用同一种算法进行学习。它能够自己发现问题的关键和尝试自己去解决问题。在这个教程中你可以看到,除了提供数据给算法,我们几乎没有改变任何东西,然而它却能自己学习一个新的概念:交通灯。 28 | 29 | [吴恩达深度学习视频](https://www.youtube.com/watch?v=CLDisFuDnog) 30 | 31 | ### 训练过程 32 | 训练我们自己的深度学习神经网络,是实践学习算法的一步。我们提供一个图片分类的数据集给算法,希望它学习如何分类不属于训练数据集的新图片。 33 | 34 | 假设我们能够获得所需要的数据(这里称为“训练集”),用于训练我们的算法。然而,像人类一样,算法在训练期间是需要对其行为进行反馈的。为了实现对算法的行为反馈进行验证,我们需要为算法提供一个叫“验证集”的独立数据集。 35 | 36 | 在完成训练以后,我们将使用“训练集”中没有的数据作为输入数据,并看看它的表现如何。你猜对了,我们将需要第三个被称为“测试集”的数据集,这个数据集将会帮我们测试出算法的识别精确度。 37 | 38 | 我们总共需要3个不同的数据集:训练,验证,测试。 39 | 40 | ### 迁移学习 41 | 42 | 在实践中,我们不会经常从零开始训练我们的深度学习网络。因为现实中,很难找到足够大的数据集来做一些复杂任务,例如图片分类。 43 | 44 | ![](https://cdn-images-1.medium.com/max/1600/1*KlHrcrcH1Zl9Hdn4Sd8htA.jpeg) 45 | 46 | 一般来说,我们会直接使用经过大量数据训练的网络作为初始的深度学习网络。在我们这个例子中,我们使用了一个经过一百万张图片训练的预训练神经网络,并用它来从20000张图片中学习新的分类。 47 | 48 | 这个小技巧确实有效,作为分类问题的一部分是共通的,例如识别边缘,颜色,甚至形状。 49 | 50 | ### 移动设备的机遇 51 | 52 | 如果你对现今的世界足够关注,你就能理解数据才是深度学习中最重要的东西。没有足够数量的数据,我们的算法将不能根据问题去学习并产生令人满意的结果。 53 | 54 | 这里有一个对于移动开发者来说非常巨大的机遇。全世界超过20亿设备不断提供各种不同类型的数据,这是非常有可能去开发一款应用去收集高质量数据,用于训练和学习。很多成功的创业公司都是围绕这个想法而建立的。 55 | 56 | ## 使用Caffe进行深度学习 57 | ### Caffe 58 | [Caffe](http://caffe.berkeleyvision.org/) 是一个由Berkeley Visio和学习中心([BVLC](http://bvlc.eecs.berkeley.edu/))以及社区贡献者一同开发的深度学习框架。这个框架被世界范围内的计算机视觉研究者使用。尽管这是一个主要为计算机视觉开发的框架,然而他可以做其他很多深度学习的工作。 59 | 60 | ### DIGITS 61 | 英伟达的 [DIGITS](https://developer.nvidia.com/digits) 使用了web交互界面简化了深度学习的一般步骤,例如: 62 | 63 | - 数据集管理 64 | - 在多GPU系统中设计和训练神经网络 65 | - 通过先进的虚拟化进行实时性能管理 66 | 67 | DIGITS 交互非常简单,开发者只需要专注于神经网络的设计和训练,不需要投入太多精力到编程和调试。 68 | 69 | ### 硬件要求 70 | 训练深度神经网络是一项计算密集型的工作。虽然你可以只通过只有CPU的机器来运行,但是事实上你更需要一个拥有GPU(例如英伟达的GTX系列,$500 - $700)的性能强大的机器来运行神经网络。 71 | 72 | 如果你没有这样一个性能强大的机器,你可以从亚马逊那里租一个。例如你可以从[spotinst](https://spotinst.com/)这里获取相关的服务,而且只需要 0.2 美元/小时。如果你需要更多的帮助,可以联系我。 73 | 74 | ## 开发一个识别交通灯的App 75 | 这里我们开始开发一个你之前在视频中看到的能识别交通灯的app。没什么需要担心的,所有的代码都在[Github](https://github.com/asavihay/TrafficLights-DeepLearning-iOS)上开源。 76 | 77 | 为了继续这个教程,你应该先安装Caffe和DIGITS。在这个教程中,我们将直接使用DIGITS的web交互界面,因此不需要在学习其他任何语言或框架。 78 | 79 | 通过使用Caffe & DIGITS,我们在一个预训练的网络中实施迁移学习,并教会它红绿交通灯的新概念。然后,我们将把经过训练的网络应用到我们一个简单的移动应用中。 80 | 81 | ### 交通灯数据 82 | 很多创业公司和有开创精神的组织开源了一些非常有用的训练集。其中有一些设立了一些能赢取奖金的比赛,例如[Udacity](https://www.udacity.com/self-driving-car),[Kaggle](https://www.kaggle.com/)等。 83 | 84 | 为了让这个简单应用运行起来,我将会使用Nexar的 [Challenge #1](https://challenge.getnexar.com/challenge-1) 数据集。这个数据集包括 18,659 个标有交通灯的图片。针对每张图片,有人手动标记了包含的是红灯还是绿灯或者没灯。在这个简单应用中,我们只关注图片是否存在交通灯,不关心交通灯的位置或其他。 85 | 86 | ![](https://cdn-images-1.medium.com/max/1600/1*-l3uxQT4on3U6CB3DAZDaA.png) 87 | 88 | ### 训练 89 | 为了这个应用能正常使用,我们将从[GoogLeNet](https://www.cs.unc.edu/~wliu/papers/GoogLeNet.pdf)做一次迁移学习。幸运的是,DIGITS的标准网络中已经包含了这个。我们只需下载预训练[caffmodel文件](http://dl.caffe.berkeleyvision.org/bvlc_googlenet.caffemodel)并用DIGITS打开即可。 90 | 91 | 非常幸运,我有权限使用一个强大的机器,这个机器专门用来做VR体验,而且拥有一个运行在Ubuntu 14.04 平台上的GTX 1070 GPU。对于一些人来说,看着训练过程一定是非常无聊的,特别当你看到精度不断提高时。 92 | 93 | ![](https://cdn-images-1.medium.com/max/1600/1*-7LeZYJ5KCjJ2kS_9SjgeQ.png) 94 | 95 | 第一步,建立训练/验证/测试 数据集。DIGITS 提供一个便捷的方法。DIGITS根据它们的标签和你将要检验和测试的数据划分百分比,将所有样本,划分到不同的目录。在我们的例子中,我们直接将DIGITS 一个目录划分为三个子目录:红,绿和背景。 96 | 97 | ![](https://cdn-images-1.medium.com/max/1600/1*s0qh8buXd4oS_RAFlJY0ww.png) 98 | 99 | 第二步,建立模型。DIGITS 已经包含了我们想要的GoogLeNet神经网络。GoogLeNet已经通过训练能够将图片分成1000个不同的类别。在我们的例子中,为了让它能识别3种新的类型,我们需要对它进行一些改进。不要担心,这些只是一些简单的文本改动。 100 | 101 | ![](https://cdn-images-1.medium.com/max/1600/1*wRVzaHh6BC2LKV142j9Iaw.png) 102 | 103 | 我们要做的改动是非常简单的: 查找/替换 所有出现loss1/classifier, loss2/classifier 和 loss3/classifier,并加上后缀‘/retrain’。例如 loss{x}/classifier 变成 104 | loss{x}/classifier/retrain— 。 我们做这一步是为了让我们的神经网络重新学习,因为这新的部分与之前1000种分类都不同。 105 | 106 | 为了能运行迁移学习,我们将使用预学习网络信息(又称权重),而这只需让DIGITS 下载 [bvlc_googlenet.caffemodel](http://dl.caffe.berkeleyvision.org/bvlc_googlenet.caffemodel) 这个预训练 caffe 模型即可。 107 | 108 | 值得庆贺,你现在已经做好了训练网络的准备了。 109 | 110 | 根据默认的训练设置, DIGITS 迁移学习能生成一个在验证集中精度达到 93.1% 的神经网络。难道这不令人兴奋吗?我们很难做到这一点:) 111 | 112 | 训练完成之后,我们可以通过DIGITS下载我们的模型,然后获取我们使用该模型开发移动应用的必要数据。 113 | 114 | ### 开发应用 115 | 116 | 幸运地,由于[Aleph7](https://github.com/aleph7)和[noradaiko](https://github.com/noradaiko)做了一些基础集成 ,我很容易的就可以将 Caffe 运行在iOS上。除了使用这些基础集成来作为我们的项目的开始,我们还要改进一下,以便它在相机的现场视频流中能正常运行。 117 | 118 | 这段代码非常简单,让我们来看一下 ViewController.mm 文件中最重要的部分: 119 | 120 | ``` 121 | NSString *netDefinition = [NSBundle.mainBundle pathForResource:@"deploy" 122 | ofType:@"prototxt" 123 | inDirectory:@"model"]; 124 | 125 | NSString *netWeights = [NSBundle.mainBundle pathForResource:@"model" 126 | ofType:@"caffemodel" 127 | inDirectory:@"model"]; 128 | 129 | NSString *datasetMean = [NSBundle.mainBundle pathForResource:@"mean" 130 | ofType:@"binaryproto" 131 | inDirectory:@"model"]; 132 | 133 | NSString *netLabels = [NSBundle.mainBundle pathForResource:@"labels" 134 | ofType:@"txt" 135 | inDirectory:@"model"]; 136 | 137 | string net_definition = string([netDefinition UTF8String]); 138 | string net_weights = string([netWeights UTF8String]); 139 | string dataset_mean = string([datasetMean UTF8String]); 140 | string net_labels = string([netLabels UTF8String]); 141 | 142 | classifier = new Classifier(net_definition, 143 | net_weights, 144 | dataset_mean, 145 | net_labels); 146 | ``` 147 | 148 | 149 | 我们通过DIGITS下载的文件model.caffemodel, mean.binaryproto, labels.txt 和 deploy.prototxt ,创建一个新的分类器。 150 | 151 | ``` 152 | cv::Mat src_img, bgra_img; 153 | UIImageToMat(image, src_img); 154 | 155 | cv::resize(src_img, src_img, cv::Size(224, 224)); 156 | cv::cvtColor(src_img, bgra_img, CV_RGBA2BGR); 157 | 158 | vector result = classifier->Classify(bgra_img, 3); 159 | ``` 160 | 161 | 我们需要输入各种尺寸的数据进行分类。为了做到这一点,我们需要把我们的UIImage对象转换为OpenCV的矩阵(第2行),改变其大小以适应我们的网络,然后把颜色序列从 RGBA 转换成 BGR。因为Caffe需要这样的输入格式,所以我们需要先做这个转换。另外,你可能注意到,当进行分类时,这里有一个魔术数字‘3’。这表示的是你数据集分类的数量 - 在我们这个例子中,表示:绿,红,背景。 162 | 163 | ``` 164 | for (vector::iterator it = result.begin(); it != result.end(); ++it) { 165 | NSString* mylabel = [NSString stringWithUTF8String:it->first.c_str()]; 166 | NSNumber* probability = [NSNumber numberWithFloat:it->second]; 167 | 168 | if (it == result.begin() && probability.floatValue > 0.6) { 169 | _lightImage.image = [UIImage imageNamed:[labelToImage valueForKey:mylabel]]; 170 | } 171 | } 172 | ``` 173 | 174 | 175 | 接下来,我们只需重复识别和捕获最高确定性的识别。如果它通过了一些阀值(当前设定为 60% 确定性)我们将用正确的交通灯图片更新我们的UI。 176 | 177 | 准备去让你的数据跑起来吗?非常好! 178 | 179 | 你必须做的的所有事情,只是改变了一开始训练的样本和改变分类的数目。其他所有的东西都是不变的。 180 | 181 | 这是关于这个app的另外一段短视频: 182 | 183 | [Youtube视频2](https://youtu.be/3QsU70MbYt0) 184 | 185 | ### 我能在我的线上App上用它吗? 186 | 187 | 当然,最好不。至少我现在不推荐。这个有点例子太过于笨拙以至于不适合直接使用。 188 | 189 | 你应该在你桌面之外使用这个项目,去体验和测试你的算法,这是一种不需要编码就能测试你的POC项目的好方法。你也可以用它来开发快速应用,去收集你在训练过程中输入的新数据。 190 | 191 | 一旦你掌握了这个训练过程,你接下来将很容易的过渡到学习[TensorFlow](https://www.tensorflow.org/),或其他需要对深度学习有更深入理解的框架,例如 [BrainCore](https://github.com/aleph7/BrainCore)。 192 | 193 | ### 如果你喜欢这篇文章,并且想继续关注,请点击 ♥ (喜欢) - 这将是我继续下一篇的动力! 194 | 195 | ### 你也可以在[Twitter](https://twitter.com/avihayas)中关注英文原文作者。 196 | 197 | > 如果你喜欢这篇译文,记得在[简书中给我点赞并关注我哦](http://www.jianshu.com/u/64d47b1e0fc9) 198 | > 199 | > 本文[Github地址](https://github.com/britzlieg/translate_post/blob/master/2017-01/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%8EApp%E7%9A%84%E9%AD%94%E5%B9%BB%E7%BB%93%E5%90%88.md) -------------------------------------------------------------------------------- /2017-01/面向对象的UITableViewCells.md: -------------------------------------------------------------------------------- 1 | # 面向协议的UITableViewCells 2 | > 这是我基于英文原文翻译的译文,如果你对本文感兴趣而且想转发,你应该在转发文章里加上本文的[链接](https://github.com/britzlieg/translate_post/blob/master/2017-01/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%9A%84UITableViewCells.md) 3 | > 4 | > [英文原文链接](https://medium.com/ios-os-x-development/protocol-oriented-uitableviewcells-6efa7ef8c45b#.vqr68egqf) 5 | 6 | 这篇文章将展示如何通过面向协议编程(POP)而不是通过类继承,来实现不同UITableViewCell对象的展示。准备好了吗?让我们一起来看看具体怎么做! 7 | 8 | cells展示! 9 | 10 | 当我开发[Cast Player](http://castplayerapp.com/)这个app的时候,我需要添加很多设置页面来让用户调整app的一些设置,其中就包括一个反馈表格和一些关于界面。下面是这个界面的展示: 11 | 12 | ![](https://cdn.rawgit.com/britzlieg/asset-respo/gh-pages/traslate_post/translate_post_%E9%9D%A2%E5%90%91%E5%8D%8F%E8%AE%AE%E7%9A%84UITableViewCells_01.gif) 13 | 14 | 从上面的界面展示可以看到,我们要定义6个不同类型的cells: 15 | 16 | ![](https://cdn-images-1.medium.com/max/800/1*EK1gWLRyT7DhBVpOznAchQ.png) 17 | 18 | 总而言之,这些cells都有以下的特点(或行为): 19 | 20 | - Cell 高亮 21 | - 展示一个标题 22 | - 展示一个可读的计数 23 | 24 | 我们怎么创建能代表这6种不同类型的cells类呢?第一步应该要做要做的,就是使用表格列出所有cell的类型和特点。 25 | 26 | ![](https://cdn-images-1.medium.com/max/800/1*C3T72IQvaOldegtV-U8EMw.png) 27 | 28 | 从表格中我们可以看到: 29 | 30 | - 没有一个特性满足所有的cells。 31 | - 一些特性满足一些cells,但是并不满足其他的cells 32 | 33 | 这就意味着通过类继承来实现一个UITableViewCell并不是一个好的选择。事实上,这些cell 类型中没有一个适合作为基类。你认为呢?🤔 34 | 35 | ## 协议 & 扩展!Yay! 36 | 37 | 幸运的,[这篇文章](http://machinethink.net/blog/mixins-and-traits-in-swift-2.0/)给我们提供了一个解决的思路。对于这种使用场景,协议和扩展将会非常有用,但是应该怎么去使用它们呢? 38 | 39 | 这个做法是这样的,我们可以使用协议为每一个特性定义接口,并通过扩展提供接口默认的实现。然后,我们可以创建一个轻量级的UITableViewCell子类,并让它遵循所需要的接口规范。在一篇叫[《面向协议的MVVM介绍》](https://realm.io/news/doios-natasha-murashev-protocol-oriented-mvvm/)中有一个非常类似的问题。我们可以看看这个具体是怎么做的! 40 | 41 | 使用协议和扩展,我们可以写第一个特性的代码,TitlePresentable: 42 | 43 | ``` 44 | protocol TitlePresentable { 45 | var titleLabel: UILabel! { get set } 46 | func setTitle(title: String?) 47 | } 48 | 49 | extension TitlePresentable { 50 | func setTitle(title: String?) { 51 | titleLabel.text = title 52 | } 53 | } 54 | ``` 55 | 56 | 第二个特性,BytesCountPresentable使用一样的模式: 57 | 58 | ``` 59 | protocol BytesCountPresentable { 60 | var bytesCountLabel: UILabel! { get set } 61 | func setBytesCount(bytesCount: Int64?) 62 | } 63 | 64 | extension BytesCountPresentable { 65 | func setBytesCount(bytesCount: Int64?) { 66 | bytesCountLabel.text = bytesCountString(bytesCount) 67 | } 68 | private func bytesCountString(bytesCount: Int64?) -> String { 69 | if let bytesCount = bytesCount { 70 | return bytesCount.bytesFormattedString() // defined elsewhere 71 | } 72 | return "N/A" 73 | } 74 | } 75 | ``` 76 | 77 | 通过让这两个特性分成两个不同的协议,BytesCountTitleTableViewCell的实现就变得简单了。 78 | 79 | 80 | ``` 81 | class BytesCountTitleTableViewCell: UITableViewCell, TitlePresentable, BytesCountPresentable { 82 | @IBOutlet var titleLabel: UILabel! 83 | @IBOutlet var bytesCountLabel: UILabel! 84 | } 85 | ``` 86 | 87 | 通过遵循TitlePresentable和BytesCountPresentable协议,BytesCountTitleTableViewCell能够继承通过扩展添加的行为。其他类可以通过一样的接口来继承同样的行为。这真的非常强大!💪💪 88 | 89 | 注意到类需要重新声明titleLabel和bytesCountLabel两个属性,以便于能够实现对应的协议,但我们也需要指定这些IBOutlet变量,方便我们可以通过Interface Builder来链接它们。 90 | 91 | ## highlighting特性怎么办? 92 | 我们先从这个类开始: 93 | 94 | ``` 95 | class HighlightableTableViewCell: UITableViewCell { 96 | override func setHighlighted(highlighted: Bool, animated: Bool) { 97 | self.contentView.backgroundColor = highlighted ? UIColor(white: 217.0/255.0, alpha: 1.0) : nil 98 | } 99 | } 100 | ``` 101 | 102 | 这里我们选择这样做,通过复写UITablViewCell的setHighlighted()方法来实现cell 高亮。 103 | 104 | 根据上面总结的方法,我们可以定义一个HighlightableView的协议和扩展: 105 | 106 | ``` 107 | protocol HighlightableView { 108 | func setHighlighted(highlighted: Bool, animated: Bool) 109 | } 110 | 111 | extension HighlightableView { 112 | func setHighlighted(highlighted: Bool, animated: Bool) { 113 | print("extension highlighted") // not printed 114 | } 115 | } 116 | ``` 117 | 118 | 我们可以尝试创建一个实现HighlightableView 协议的UITableViewCell的子类,并看看高亮特性是否可以实现。但这次没那么幸运了。🚫 119 | 120 | 我认为这是因为当setHighlighted()方法被调用的时候,那些UITableViewCell的基类方法将会被调用,而不会调用协议扩展方法。 121 | 122 | 在一般情况下,协议扩展是为了在已有的类中加入新的行为,而不能复写已有方法。 123 | 124 | 为了Cell的高亮,再来一遍! 125 | 126 | 为了让cell的高亮特性能正常展示,如果必要的话,我们可以保留HighlightableTableViewCell的定义并从它那里派生出一个子类。例如: 127 | 128 | ``` 129 | class DisclosureTitleTableViewCell: HighlightableTableViewCell, TitlePresentable { 130 | @IBOutlet var titleLabel: UILabel! 131 | } 132 | 133 | class DisclosureBytesCountTitleTableViewCell: HighlightableTableViewCell, TitlePresentable, BytesCountPresentable { 134 | @IBOutlet var titleLabel: UILabel! 135 | @IBOutlet var bytesCountLabel: UILabel! 136 | } 137 | ``` 138 | 139 | 当我们选择要使用HighlightableTableViewCell或UITableViewCell作为基类时,取决于我们是否需要cell高亮特性,并根据需要使用TitlePresentable和BytesCountPresentable的特性。 140 | 141 | 类的层级如下图所示: 142 | 143 | ![](https://cdn-images-1.medium.com/max/800/1*zxVU-JAV1c-5J6L8wpiV_A.jpeg) 144 | 145 | 在代码中是这样的: 146 | 147 | ``` 148 | class HighlightableTableViewCell: UITableViewCell 149 | 150 | class DisclosureTitleTableViewCell: HighlightableTableViewCell, TitlePresentable 151 | 152 | class BytesCountTitleTableViewCell: UITableViewCell, TitlePresentable, BytesCountPresentable 153 | 154 | class DisclosureBytesCountTitleTableViewCell: HighlightableTableViewCell, TitlePresentable, BytesCountPresentable 155 | 156 | class ActionTitleTableViewCell: HighlightableTableViewCell, TitlePresentable 157 | 158 | class ValuePickerTableViewCell: UITableViewCell 159 | 160 | class ValuePickerLabelTableViewCell: ValuePickerTableViewCell, TitlePresentable 161 | ``` 162 | 163 | 注意:HighlightableTableViewCell 是唯一的通过子类实现,并充当其他三个额外cell类型基类的类。一旦一个给定的类型被选择作为基类,额外的特性只能通过协议扩展添加。 164 | 165 | ## 结论 166 | 167 | Swift的协议和扩展,是一种能添加行为(特性)的方法。这种方法在类设计上很有效,能保持类层级扁平化。🚀主要优势如下: 168 | 169 | - 更扁平化的类层级 170 | - API/类更容易扩展 171 | - 相对修改代码,修改协议更容易 172 | - 更少的代码重复 173 | 174 | ## 反馈 175 | 176 | 文章中展示的解决方法,对于我和我的特殊使用场景来说非常有效果 - 我希望这个实践例子能够帮助大家理解协议扩展是怎么使用的,最好对比下我刚开始做的那个继承例子。如果你知道更好的实现方法,可以在我的评论下留言。 177 | 178 | 注意:这篇[文章](http://bizz84.github.io/2016/06/18/Protocol-Oriented-UITableViewCells.html)最早发表于我的博客中,时间为2016年6月18日。 179 | 180 | 如果想看更多类似的文章,请订阅我的[邮件订阅](http://eepurl.com/cmi6rD)。 181 | 182 | 如果你喜欢这篇文章,请点击喜欢(💚)以便让更多人在Medium中看到。 183 | 184 | 还有,不要忘记下载我的[Cast Player](http://castplayerapp.com/)应用哦. 😇 185 | 186 | > 如果你喜欢这篇译文,记得在[简书中给我点赞并关注我哦](http://www.jianshu.com/u/64d47b1e0fc9) 187 | > 188 | > 本文[Github地址](https://github.com/britzlieg/translate_post/blob/master/2017-01/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%9A%84UITableViewCells.md) -------------------------------------------------------------------------------- /2017-09/轻松管理Swift项目中的各种环境.md: -------------------------------------------------------------------------------- 1 | # 轻松管理Swift项目中的各种环境 2 | > 这是我基于英文原文翻译的译文,如果你对本文感兴趣而且想转发,你应该在转发文章里加上本文的[链接](https://github.com/britzlieg/translate_post/blob/master/2017-09/%E8%BD%BB%E6%9D%BE%E7%AE%A1%E7%90%86Swift%E9%A1%B9%E7%9B%AE%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E7%8E%AF%E5%A2%83.md) 3 | > 4 | > [英文原文链接](https://medium.com/flawless-app-stories/manage-different-environments-in-your-swift-project-with-ease-659f7f3fb1a6) 5 | 6 | ![](https://cdn-images-1.medium.com/max/2000/1*Rk8JulyapCiTCUtLsnsEcQ.png) 7 | 8 | 想象一下当你完成开发和测试,准备提交到生产环境时的场景时,你会遇到这么一个问题:所有你的API keys,URL,icon或者其他启动配置都是开发环境的。所以在你发布你的app的时候,你不得不为了适应生产环境而把他们全部改过来。显然,这做法不太好。有时候,由于你的app太复杂,你可能会忘记一些改动,从而导致服务不能正常工作。 9 | 10 | 不像这些糟糕的做法,本文的做法将会根据我们的需要去配置不同的环境,并且改动配置非常简单。现在,我们将讨论几种最常见的环境配置管理的方法: 11 | 12 | - 1、使用注释 13 | - 2、使用全局变量或枚举 14 | - 3、通过一个全局标记flag使用配置和schemes 15 | - 4、通过 *.plist 文件使用配置和schemes 16 | 17 | ### 1、使用注释 18 | 19 | 当你有两个独立的环境,你的应用需要知道它应该使用那个环境。想象一下,你有生产,开发,演示三个环境和API接口。那么环境切换的最快最简单方法就是注释其他两个环境。 20 | 21 | ```swift 22 | // MARK: - Development 23 | let APIEndpointURL = "http://mysite.com/dev/api" 24 | let analyticsKey = "jsldjcldjkcs" 25 | 26 | // MARK: - Production 27 | // let APIEndpointURL = "http://mysite.com/prod/api" 28 | // let analyticsKey = "sdcsdcsdcdc" 29 | // MARK: - Staging 30 | // let APIEndpointURL = "http://mysite.com/staging/api" 31 | // let analyticsKey = "lkjllnlnlk 32 | ``` 33 | 34 | 这个方法非常暴力,混乱,让人看到都想哭。有时不需要关心代码质量和可维护性的时候,我会使用这种黑科技。换句话说,我强力建议不要使用这种方法。 35 | 36 | ### 2、使用全局变量或枚举 37 | 38 | 另一种流行的做法,是通过一个全局变量或枚举(这个会好一点)来处理不同的配置环境。你需要在某个地方(例如AppDelegate)声明您的三个不同环境的枚举和设置它们的值。 39 | 40 | ```swift 41 | enum Environment { 42 | case development 43 | case staging 44 | case production 45 | } 46 | 47 | let environment: Environment = .development 48 | 49 | switch environment { 50 | case .development: 51 | // set web service URL to development 52 | // set API keys to development 53 | print("It's for development") 54 | case .staging: 55 | // set web service URL to staging 56 | // set API keys to development 57 | print("It's for staging") 58 | case .production: 59 | // set web service URL to production 60 | // set API keys to production 61 | print("It's for production") 62 | } 63 | ``` 64 | 65 | 这种做法保证你可以只改变一处代码就能配置不同的环境。与之前的方法不同,这个会好很多。这种做法非常灵活,可读性更强,但它有很多局限性。首先,在不同环境下运行都只有一个相同的Bundle ID。这就意味着你讲不能同时装上不同环境的相同app。这种做法其实也是不太合适。 66 | 67 | 除此以外,这种做法也不能根据不同的环境改变app的icons。而且,如果你在提交app之前忘记更改这个全局变量,你肯定会出问题。 68 | 69 | --- 70 | 71 | 我们再看看其他两种更好的实现方法。这些方法对于新项目和旧项目都适合。跟着这个教程,你可以很容易将它应用到就项目中。 72 | 73 | After applying these approaches your app will have the same codebase for every environment, but you will be able to have different icons and different Bundle ID’s for every configuration. The distribution process also will be very easy. And what is the most important, your managers and testers will be able to have all your environments as separated apps on their devices. So they will fully understand which version they’re trying out. 74 | 75 | 使用这些方法后,你的app只有一份代码库,但你可以根据不同环境配置有不同icons和Bundle ID。这会让你的发布变得非常简单。最重要的是,你的老大和测试可以有多个不同环境的相同app,因此他们非常清楚他们正在测试的是那个版本的应用。 76 | 77 | ### 3、通过一个全局标记flag使用配置和schemes 78 | 79 | 在这种做法中,我们将创建三个不同的配置,三个不同的schemes和与schemes相关的配置。为了演示这种做法,我将新建一个“Environments”项目,你也可以创建一个新项目或者在老项目中做。 80 | 81 | 在项目导航的面板中找到项目设置,在targets的列表中右击已存在的target,然后选择 Duplicate 来复制当前target。 82 | 83 | ![](https://cdn-images-1.medium.com/max/1600/0*kJt7iX0pJ_OCbYH7.) 84 | 85 | 现在我们有不止一个的target和叫‘Environments copy’的build scheme。我们先把它们改成对应的名字。点击您的新target和按下“Enter”键,改target的名字为“Environments Dev”。然后,去到“Manage Schemes…”,选择刚才新创建的scheme,按下“Enter”,重命名你的target,避免命名混乱。 86 | 87 | ![](https://cdn-images-1.medium.com/max/1600/0*pAV3RMB8AJBsTIgL.) 88 | 89 | 90 | 同样的,我们可以新建一个icon asset,这样测试和老大就能知道打开的是哪个app配置。去到Assets.xcassets,点击 “+” ,选择“New iOS App Icon”。修改它的名字为“AppIcon-Dev”。 91 | 92 | ![](https://cdn-images-1.medium.com/max/1600/0*Wuq-Rd6IHVMAgTm0.) 93 | 94 | 现在我们要建立新的icons asset和我们开发环境的联系。去到“Targets”,点击你的开发环境,找到“App Icon Source”选择新的icons asset。 95 | 96 | ![](https://cdn-images-1.medium.com/max/1600/0*LyxuDi3gg8Ca69p7.) 97 | 98 | 就是这样,现在你有每个环境的不同icons。请注意,当我们新建第二个配置时,第二个 *.plist 文件也会生成出来。 99 | 100 | 值得注意,我们现在有两种不同的做法去处理两种不同的配置: 101 | 102 | - 1、添加一个预编译宏标记去区分生产和开发targets。 103 | - 2、在 *plist 文件中添加一个变量。 104 | 105 | 我们尝试第一种方法。我们添加一个标记变量去标记选择的开发环境。在“Build Settings”中,找到“Swift Compiler — Custom Flags”这个选项。设置 -DEVELOPMENT 来标记这个target是开发的build。 106 | 107 | ![](https://cdn-images-1.medium.com/max/1600/0*Henhnxiv07NEtDkk.) 108 | 109 | 然后配置的代码就像如下所示: 110 | 111 | ```swift 112 | #if DEVELOPMENT 113 | let SERVER_URL = "http://dev.server.com/api/" 114 | let API_TOKEN = "asfasdadasdass" 115 | #else 116 | let SERVER_URL = "http://prod.server.com/api/" 117 | let API_TOKEN = "fgbfkbkgbmkgbm" 118 | #endif 119 | ``` 120 | 121 | 现在,如果你选择 Dev 的scheme运行,你就自动使用开发配置来启动你的app了。 122 | 123 | ### 4、通过 *.plist 文件使用配置和schemes 124 | 125 | 在这种做法中,我们将重复之前那个创建新scheme的步骤。在做完这个步骤后,不像之前一样添加一个全局标记,我们将把必要的值添加进我们的*.plist中。同样的,我们将添加一个 serverBaseURL 字符串变量到每个 *.plist 文件中,并把URLs作为值进行设置。现在每个*.plist文件都包含一个URL,我们可以通过代码来调用它。我认为,为我们的Bundle创建一个extension是一个好主意,像下面的代码一样: 126 | 127 | ```swift 128 | extension Bundle { 129 | var apiBaseURL: String { 130 | return object(forInfoDictionaryKey: "serverBaseURL") as? String ?? "" 131 | } 132 | } 133 | 134 | //And call it from the code like this: 135 | let baseURL = Bundle.main.apiBaseURL 136 | ``` 137 | 138 | 个人认为,我更喜欢这种方式多一点,因为你不需要在你的代码中检查你的配置。你仅仅需要选择你的Bundle,然后它就会返回他自己当前的配置。 139 | 140 | 当使用多个targets时需要注意一些情况 141 | 142 | - 记住在一个 *.plist 文件中你的数据存储能被读取是很可能不安全的。解决方法就是,把你的敏感的值放到代码中,只把不敏感的放到 *.plist 文件中。 143 | - 当添加一个新的文件时,不要忘记添加到所有的target中,以保证你的代码在所有配置环境中都是一样的。 144 | - 如果你使用持续集成服务,例如 Travis CI 或 Jenkins,不要忘记正确配置它们。 145 | 146 | Conclusion 147 | 148 | ### 结论 149 | 150 | 一开始用一种可读性强且灵活的方法,去区分不同app环境,是很有必要的。甚至是那种我们避免使用的,最简单的处理不同配置的方法,都能很好的提高我们的代码质量。 151 | 152 | 我们从最简单的开始,讨论了几种不同的做法。但是,还有很多其他不同环境配置方法我们没有涉及到,我很希望在下面评论中看到其他的做法。 153 | 154 | 感谢大家的阅读 :) 155 | 156 | > 本文[Github地址](https://github.com/britzlieg/translate_post/blob/master/2017-09/%E8%BD%BB%E6%9D%BE%E7%AE%A1%E7%90%86Swift%E9%A1%B9%E7%9B%AE%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E7%8E%AF%E5%A2%83.md) 157 | -------------------------------------------------------------------------------- /2017-10/Swift 4中的泛型.md: -------------------------------------------------------------------------------- 1 | # Swift 4 中的泛型 2 | > 这是我基于英文原文翻译的译文,如果你对本文感兴趣而且想转发,你应该在转发文章里加上本文的[链接](https://github.com/britzlieg/translate_post/blob/master/2017-10/Swift%204%E4%B8%AD%E7%9A%84%E6%B3%9B%E5%9E%8B.md) 3 | > 4 | > 译者:[britzlieg](https://github.com/britzlieg) 5 | > 6 | > [英文原文链接](https://theswiftpost.co/generics-swift-4/) 7 | 8 | ![](https://user-gold-cdn.xitu.io/2017/10/13/3bb62edfdcc6fe74542689c21be79093) 9 | 10 | 作为Swift中最重要的特性之一,泛型使用起来很巧妙。很多人都不太能理解并使用泛型,特别是应用开发者。泛型最适合libraries, frameworks, and SDKs的开发。在这篇文章中,我将用不同于其他教程的角度来讲解泛型。我们将使用餐馆的例子,这个餐馆能从SwiftCity的城市理事会中获得授权。为了保持简洁,我将内容控制在以下四个主题: 11 | 12 | - 1、泛型函数和泛型类型 13 | - 2、关联类型协议 14 | - 3、泛型的Where语句 15 | - 4、泛型下标 16 | 17 | 我们接下来看看具体怎么做! 18 | 19 | ## 泛型函数和泛型类型 20 | 21 | ### 开一家Swift餐馆 22 | 23 | 让我们新开张一家餐馆。当开张的时候,我们不仅关注餐馆的结构,也关注来自城市理事会的授权。更重要的,我们将关注我们的业务,以便于它功能化和有利可图。首先,怎么让一家公司怎么看上去像一个理事会?一个公司应该要有一些基础的功能。 24 | 25 | ```Swift 26 | 27 | protocol Company { 28 | func buy(product: Product, money: Money) 29 | func sell(product: Product.Type, money: Money) -> Product? 30 | } 31 | 32 | ``` 33 | 34 | *```buy```*函数把商品添加到库存中,并花费公司相应的现金。```sell```函数创建/查找所需花费的该类型商品,并返回出售的商品。 35 | 36 | ### 泛型函数 37 | 38 | 在这个协议中,```Product```如果是一个确定的类型的话不太好。把每一个```product```统一成一个确定的商品类型是不可能的。每个商品都有自己的功能,属性等。在这些各种类型的函数中,使用一个确定的类型是一个坏主意。让我们回到理事会那里看看。总而言之,不管是哪个公司,它都需要购买和卖出商品。所以,理事会必须找到适合这两个功能的一种通用的解决方案,以适合于每家公司。他们可以使用泛型来解决这个问题。 39 | 40 | ```Swift 41 | protocol Company { 42 | func buy(product: T, with money: Money) 43 | func sell(product: T.Type, for money: Money) -> T? 44 | } 45 | ``` 46 | 47 | 我们把我们原来的确定类型```Product```用默认类型```T```来代替。这个类型参数``````把这些函数定义成泛型。在编译时,默认类型会被确定类型替代。当buy和sell函数被调用时,具体类型就会被确定下来。这使得不同产品能灵活使用同一个函数。例如,我们在Swift餐馆中卖Penne Arrabiata。我们可以像下面一样直接调用```sell```函数: 48 | 49 | ```Swift 50 | let penneArrabiata = swiftRestaurant.sell(product: PenneArrabiata.Self, for: Money(value:7.0, currency: .dollar)) 51 | ``` 52 | 53 | 在编译时,编译器用类型```PenneArrabiata```替换类型```T```。当这个方法在运行时被调用的时候,它已经时有一个确定的类型```PenneArrabiata```而不是一个默认的类型。但这带来另外一个问题,我们不能只是简单的买卖各种类型的商品,还要定义哪些商品时能够被合法买卖。这里就引入where类型约束。理事会有另一个协议```LegallyTradable```。它将检查和标记我们可以合法买卖的商品。理事会强制我们对所有买卖实行这个协议,并列举每一个符合协议的从商品。所以我们需要为我们的泛型函数添加约束,以限制只能买卖符合协议的商品。 54 | 55 | 56 | ```Swift 57 | protocol Company { 58 | func buy(product: T, with money: Money) 59 | func sell(product: T.Type, for money: Money) -> T? 60 | } 61 | ``` 62 | 63 | 现在,我们可以放心用这些函数了。通常,我们把符合```LegallyTradable```协议的默认类型```T```作为我们```Company```协议函数的参数。这个约束被叫做Swift中的协议约束。如果一个商品不遵循这个协议,它将不能作为这个函数的参数。 64 | 65 | ### 泛型类型 66 | 67 | 我们把注意力转移到我们的餐馆上。我们得到授权并准备关注餐馆的管理。我们聘请了一位出色的经理和她想建立一套能跟踪商品库存的系统。在我们的餐馆中,我们有一个面食菜单,顾客喜欢各种各样的面食。这就是我们为什么需要一个很大的地方去存储面食。我们创建一个面食套餐列表,当顾客点套餐的时候,将套餐从列表中移除。无论何时,餐馆会买面食套餐,并把它加到我们的列表中。最后,如果列表中的套餐少于三个,我们的经理将订新的套餐。这是我们的```PastaPackageList```结构: 68 | 69 | ```Swfit 70 | struct PastaPackageList { 71 | var packages: [PastaPackage] 72 | 73 | mutating func add(package: PastaPackage) { 74 | packages.append(item) 75 | } 76 | 77 | mutating func remove() -> PastaPackage { 78 | return packages.removeLast() 79 | } 80 | 81 | func isCapacityLow() -> Bool { 82 | return packages.count < 3 83 | } 84 | } 85 | ``` 86 | 87 | 过了一会,我们的经理开始考虑为餐馆中的每一样商品创建一个列表,以便更好的跟踪。与其每次创建独立列表结构,不如用泛型来避免这个问题。如果我们定义我们的库存列表作为一个泛型类,我们可以很容易使用同样的结构实现创建新的库存列表。与泛型函数一样,使用参数类型``````定义我们的结构。所以我们需要用```T```默认类型来替代```PastaPackage```具体类型 88 | 89 | 90 | ```Swift 91 | struct InventoryList { 92 | var items: [T] 93 | 94 | mutating func add(item: T) { 95 | items.append(item) 96 | } 97 | 98 | mutating func remove() -> T { 99 | return items.removeLast() 100 | } 101 | 102 | func isCapacityLow() -> Bool { 103 | return items.count < 3 104 | } 105 | } 106 | ``` 107 | 108 | 这些泛型类型让我们可以为每个商品创建不同的库存列表,而且使用一样的实现。 109 | 110 | ```Swift 111 | var pastaInventory = InventoryList() 112 | pastaInventory.add(item: PastaPackage()) 113 | var tomatoSauceInventory = InventoryList() 114 | var flourSackInventory = InventoryList() 115 | ``` 116 | 117 | 泛型的另外一个优势是只要我们的经理需要额外的信息,例如库存中的第一种商品,我们都可以通过使用扩展来添加功能。Swift允许我们去写结构体,类和协议的扩展。因为泛型的扩展性,当我们定义结构体时,不需要提供类型参数。在扩展中,我们仍然用默认类型。让我们看看我们如何实现我们经理的需求。 118 | 119 | ```Swift 120 | extension InventoryList { // We define it without any type parameters 121 | var topItem: T? { 122 | return items.last 123 | } 124 | } 125 | ``` 126 | 127 | *```InventoryList```*中存在类型参数```T```作为类型```topItem```的遵循类型,而不需要再定义类型参数。现在我们有所有商品的库存列表。因为每个餐馆都要从理事会中获取授权去长时间存储商品,我们依然没有一个存储的地方。所以,我们把我们的关注点放到理事会上。 128 | 129 | ## 关联类型协议 130 | 131 | 我们再次回去到城市理事会去获取存储食物的允许。理事会规定了一些我们必须遵守的规则。例如,每家有仓库的餐馆都要自己清理自己的仓库和把一些特定的食物彼此分开。同样,理事会可以随时检查每间餐馆的库存。他们提供了每个仓库都要遵循的协议。这个协议不能针对特定的餐馆,因为仓库物品可以改变成各种商品,并提供给餐馆。在Swift中,泛型协议一般用关联类型。让我们看看理事会的仓库协议是怎么样的。 132 | 133 | 134 | ```Swift 135 | protocol Storage { 136 | associatedtype Item 137 | var items: [Item] { set get } 138 | mutating func add(item: Item) 139 | var size: Int { get } 140 | mutating func remove() -> Item 141 | func showCurrentInventory() -> [Item] 142 | } 143 | ``` 144 | 145 | *```Storage```*协议并没有规定物品怎么存储和什么类型被允许存储。在所有商店,实现了```Storage```协议的餐馆必须制定一种他们他们存储的特定类型的商品。这要保证物品从仓库中添加和移除的正确性。同样的,它必须能够完整展示当前仓库。所以,对于我们的仓库,我们的```Storage```协议如下所示: 146 | 147 | ```Swift 148 | struct SwiftRestaurantStorage: Storage { 149 | typealias Item = Food // Optional 150 | var items = [Food]() 151 | var size: Int { return 100 } 152 | mutating func add(item: Food) { ... } 153 | mutating func remove() -> Food { ... } 154 | func showCurrentInventory() -> [Food] { ... } 155 | } 156 | ``` 157 | 158 | 我们实现理事会的```Storage```协议。现在看来,关联类型```Item```可以用我们的```Food```类型来替换。我们的餐馆仓库都可以存储```Food```。关联类型```Item```只是一个协议的默认类型。我们用```typealias```关键字来定义类型。但是,需要指出的是,这个关键字在Swift中是可选的。即使我们不用```typealias```关键字,我们依然可以用```Food```替换协议中所有用到```Item```的地方。Swift会自动处理这个。 159 | 160 | ### 限制关联类型为特定类型 161 | 162 | 事实上,理事会总是会想出一些新的规则并强制你去遵守。一会后,理事会改变了```Storage```协议。他们宣布他们将不允许任何物品在```Storage```。所有物品必须遵循StorableItem协议,以保证他们都适合存储。换句话,它们都限制为关联类型```Item``` 163 | 164 | 165 | ```Swift 166 | protocol Storage { 167 | associatedtype Item: StorableItem // Constrained associated type 168 | var items: [Item] { set get } 169 | var size: Int { get } 170 | mutating func add(item: Item) 171 | mutating func remove() -> Item 172 | func showCurrentInventory() -> [Item] 173 | } 174 | ``` 175 | 176 | 用这个方法,理事会限制类型为当前关联类型。任何实现```Storage```协议的都必须使用实现```StorableItem```协议的类型。 177 | 178 | ## 泛型的Where语句 179 | 180 | ### 使用泛型的Where语句的泛型 181 | 182 | 让我们回到文章刚开始的时候,看看```Company```协议中的```Money```类型。当我们讨论到协议时,买卖中的money参数事实上是一个协议。 183 | 184 | ```Swift 185 | protocol Money { 186 | associatedtype Currency 187 | var currency: Currency { get } 188 | var amount: Float { get } 189 | func sum(with money: M) -> M where M.Currency == Currency 190 | } 191 | ``` 192 | 193 | 然后,再过了一会,理事会打回了这个协议,因为他们有另一个规则。从现在开始,交易只能用一些特定的货币。在这个之前,我们能各种用```Money```类型的货币。不同于每种货币定义money类型的做法,他们决定用```Money```协议来改变他们的买卖函数。 194 | 195 | ```Swift 196 | protocol Company { 197 | func buy(product: T.Type, with money: M) -> T? where M.Currency: TradeCurrency 198 | func sell(product: T, for money: M) where M.Currency: TradeCurrency 199 | } 200 | ``` 201 | 202 | where语句和类型约束的where语句的区别在于,where语句会被用于定义关联类型。换句话,在协议中,我们不能限制关联的类型,而会在使用协议的时候限制它。 203 | 204 | ### 泛型的where语句的扩展 205 | 206 | 泛型的where语句在扩展中有其他用法。例如,当理事会要求用漂亮的格式(例如“xxx EUR”)打印money时,他们只需要添加一个```Money```的扩展,并把```Currency```限制设置成```Euro``。 207 | 208 | 209 | ```Swift 210 | extension Money where Currency == Euro { 211 | func printAmount() { 212 | print("\(amount) EUR") 213 | } 214 | } 215 | ``` 216 | 217 | 泛型的where语句允许我们添加一个新的必要条件到```Money```扩展中,因此只有当```Currency```是```Euro```时,扩展才会添加```printAmount()```方法。 218 | 219 | ### 泛型的where 语句的关联类型 220 | 221 | 在上文中,理事会给```Storage```协议做了一些改进。当他们想检查一切是否安好,他们想列出每一样物品,并控制他们。控制进程对于每个```Item```是不一样的。因为这样,理事会仅仅需要提供```Iterator```关联类型到```Storage```协议中。 222 | 223 | 224 | ```Swift 225 | protocol Storage { 226 | associatedtype Item: StorableItem 227 | var items: [Item] { set get } 228 | var size: Int { get } 229 | mutating func add(item: Item) 230 | mutating func remove() -> Item 231 | func showCurrentInventory() -> [Item] 232 | 233 | associatedtype Iterator: IteratorProtocol where Iterator.Element == Item 234 | func makeIterator() -> Iterator 235 | } 236 | ``` 237 | 238 | *```Iterator```*协议有一个叫```Element``的关联类型。在这里,我们给它加上一个必要条件,在```Storage```协议中,```Element```必须与```Item```类型相等。 239 | 240 | ## 泛型下标 241 | 242 | 来自经理和理事会的需求看起来是无穷无尽的。同样的,我们需要满足他们的要求。我们的经理跑过来跟我们说她想要用一个```Sequence```来访问存储的物品,而不需要访问所有的物品。经理想要个语法糖。 243 | 244 | 245 | ```Swift 246 | extension Storage { 247 | subscript(indices: Indices) -> [Item] where Indices.Iterator.Element == Int { 248 | var result = [Item]() 249 | for index in indices { 250 | result.append(self.items[index]) 251 | } 252 | return result 253 | } 254 | } 255 | ``` 256 | 257 | 在Swift 4中,下标也可以是泛型,我们可以用条件泛型来实现。在我们的使用中,```indices```参数必须实现```Sequence```协议。[从Apple doc](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html)中可以知道,“The generic ```where``` clause requires that the iterator for the sequence must traverse over elements of type ```Int```.”这就保证了在sequence的indices跟存储中的indices是一致的。 258 | 259 | 260 | ## 结语 261 | 262 | 我们让我们的餐馆功能完备。我们的经理和理事会看起来也很高兴。正如我们在文章中看到的,泛型是很强大的。我们可以用泛型来满足各种敏感的需求,只要我们知道概念。泛型在Swift的标准库中也应用广泛。例如,[Array](https://developer.apple.com/documentation/swift/array) 和 [Dictionary](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-ID113)类型都是泛型集合。如果你想知道更多,你可以看看这些类是怎么实现的。 [Swift Language Doc](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html) 也提供了泛型的解析。最近Swift语言提供了泛型的一些说明[Generic Manifesto](https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md)。我建议你去看完所有的文档,以便更好的理解当前用法和未来的规划。感谢大家的阅读!如果你对接下来的文章有疑惑,建议,评论或者是想法,清在 [Twitter](https://twitter.com/candostEN) 联系我,或者评论!你也可以在[GitHub](https://github.com/candostdagdeviren)上关注我哦! 263 | 264 | > 本文[Github地址](https://github.com/britzlieg/translate_post/blob/master/2017-10/Swift%204%E4%B8%AD%E7%9A%84%E6%B3%9B%E5%9E%8B.md) 265 | 266 | -------------------------------------------------------------------------------- /2017-10/使用"Core"架构iOS Apps.md: -------------------------------------------------------------------------------- 1 | # 使用"Core"架构iOS Apps 2 | > 这是我基于英文原文翻译的译文,如果你对本文感兴趣而且想转发,你应该在转发文章里加上本文的[链接](https://github.com/britzlieg/translate_post/blob/master/2017-10/%E4%BD%BF%E7%94%A8%22Core%22%E6%9E%B6%E6%9E%84iOS%20Apps.md) 3 | > 4 | > [英文原文链接](https://theswiftpost.co/architecting-ios-apps-core/) 5 | 6 | 在过去的两年,我有机会经历了不同的架构模式,例如```MVC```,```MVVM```和```VIPER```。这些都有一个通用的```V```组件,再我们的应用中代表的是视图。在一个完美的设计中,视图组件只做以下事情: 7 | 8 | - 1、传递用户行为(触摸事件)到业务层。 9 | - 2、监听状态变化和进行更新。 10 | 11 | 除了这些,没有其他职责。不同架构中,视图组件的功能职责是一致的。这个组件简单,独立,因此很容易被替代。但事实上,真的如此吗?看一下你所有的实现,然后告诉我...再大多数iOS应用中,它们是最难处理的组件。我们把每个智能组件(例如view model,presenter 和 interactor)注入到 视图控制器中。这就意味着如果一个视图控制器挂了,那些智能组件也一样跟着挂掉。视图就像是一个笨重的肌肉男,主宰着整个生命周期的数据流动。难道就没有其他的方案吗? 12 | 13 | 我不认为这是一个架构定义的问题,但我不能说出它们是怎么被实现的。 14 | 15 | ### 我们真的尝试去实现什么吗? 16 | 17 | 如果我们考虑到将来的扩展,一个理想的架构应该看起来是这样的: 18 | 19 | ![](https://i2.wp.com/theswiftpost.co/wp-content/uploads/2017/09/1-WfPvtPKszh_kXUsqtcwi8A.png?w=1600&ssl=1) 20 | 21 | 用户信息流 22 | 23 | 假设UI组件符合预期设计,```Core```包含所有业务逻辑,我们就真的可以移除UI,然后用任何东西去代替...例如一组测试套件。 24 | 25 | ![](https://i1.wp.com/theswiftpost.co/wp-content/uploads/2017/09/1-dG-eIlLIiDW0-yPENib4Bw.png?zoom=2&resize=912%2C366&ssl=1) 26 | 27 | 如果我们能做到这样,我们就可以用一个测试套件模拟每一个用例,而不需要任何的UI。我们看一下登录用例: 28 | 29 | #### 登录(从用户角度): 30 | 31 | - 1、输入用户名和密码。 32 | - 2、点击登录 33 | 34 | #### 登录(测试套件): 35 | 36 | ```core.dispatch(LoginCommand(username: "goksel", password: "123"))``` 37 | 38 | 这看起来很爽吧,对不对? 39 | 40 | ### 进入 “Core” 的世界 41 | 42 | ![](https://i2.wp.com/theswiftpost.co/wp-content/uploads/2017/09/1-xZByvaEcUx-YoSAfezJWrg.jpeg?zoom=2&resize=912%2C608&ssl=1) 43 | 44 | 45 | 我们的目标是设计一个业务层,这个业务层不需要任何UI组件即可展示所有的app状态。显而易见,这不太可能,除非我们不依赖视图的生命周期。所以我们需要另外一层,这层就像我们app的生命之源。我讲这层叫”Core“。在我们的产品中,我们一般有以下功能特性:登录,注册,电影列表,电影细节等等。所有的这些功能特性应该是一个非常理想的自运行的组件,所有的状态变化都由Core来管理。最后整个流程如下图所示: 46 | 47 | ![](https://i1.wp.com/theswiftpost.co/wp-content/uploads/2017/09/1-2Wg7DttPV12OXxmtjUL8bw.png?zoom=2&resize=912%2C522&ssl=1) 48 | 49 | 从上图可以看到: 50 | 51 | - 1、Core是一个能模拟我们app的盒子 52 | - 2、Actions 是发生在系统中的变化。它们贯穿整个Core和触发组件状态改变。 53 | - 3、组件包含功能特性的业务逻辑,并能运行这些actions 54 | - 4、订阅者可以是任何东西。它可以是一个控制台的app,可以是一个iOS的App,也可以是我们的测试套件。 55 | 56 | 简单吧,是不是? 57 | 58 | ### 说是没用的,给我看看你的代码 59 | 60 | 这理论上(总是)听起来很好,但实际上是不是很容易实现的呢?让我们实现一个包括两步认证的简单登录界面。 61 | 62 | ![](https://i0.wp.com/theswiftpost.co/wp-content/uploads/2017/09/1-8-MreAYOUvLE7XWsJ52Ghw.png?zoom=2&resize=563%2C999&ssl=1) 63 | 64 | #### 要求: 65 | 66 | 允许用户有60秒时间去输入安全码。超时则返回。 67 | 68 | - 1、只要用户点击了登录按钮,使用网络验证安全码。 69 | - 2、如果验证成功,跳转进home组件。 70 | - 3、如果验证不成功,弹出一个警告框。 71 | - 4、当网络请求还在进行时,显示一个loading指示器。 72 | 73 | 够简单吧。让我们看看具体怎么搞。 74 | 75 | #### 1 - 定义状态 76 | 77 | > 组件的状态由什么组成?我们需要在屏幕上渲染什么数据? 78 | 79 | - 我们需要一个显示/隐藏loading指示器的标记。 80 | - 我们需要跟踪计时器的状态。 81 | - 我们需要跟踪验证的过程 82 | 83 | ``` 84 | struct LoginState: State { 85 | var isLoading = false 86 | var timerStatus: TimerStatus = .idle 87 | var verificationResult: Result? 88 | } 89 | ``` 90 | 91 | #### 2 - 定义Actions 92 | 93 | 在屏幕上发生了什么了? 94 | 95 | - 计时器计时 96 | - 点击验证按钮 97 | 98 | ``` 99 | enum LoginAction: Action { 100 | case tick 101 | case verifyOTP(String) 102 | } 103 | ``` 104 | 105 | #### 3 - 定义组件 106 | 107 | 现在我们定义组件。所有组件将有一个状态和一个运行函数。当一个action被core派发,所有的组件将收到通知,如果有必要会有所响应。如果把状态改变作为一个被派发的action,订阅了这个action的组件被新状态通知。 108 | 109 | ![](https://i2.wp.com/theswiftpost.co/wp-content/uploads/2017/09/1-P-W0ZcnnDSD39Uz_fet-gg-e1504957137883.gif?zoom=2&resize=472%2C354&ssl=1) 110 | 111 | ***注意***: 状态是组件类中的一个可读属性。然而,它可以通过```commit```这个函数来更新。这样设计是为了避免太频繁的状态更新。所以要改变状态,先创建一个拷贝,然后做出所有的变更,最后调用```commit```函数。这样就能设置一个新的状态,并且通知所有的订阅者。 112 | 113 | ``` 114 | class LoginComponent: Component { 115 | override func process(_ action: Action) { 116 | guard let action = action as? LoginAction else { return } 117 | switch action { 118 | case .tick: 119 | self.tick() 120 | case .verifyOTP(let code): 121 | self.verifyOTP(code) 122 | } 123 | } 124 | // ... 125 | } 126 | ``` 127 | 128 | 129 | 所以我们的组件就是这么简单。通过复写```process```函数,必要时我们可以对传进来的actions做出响应。正如你所想的一样,这个真正的魔术放生在```tick```和```verifyOTP```函数上。 130 | 让我们更深入一下: 131 | 132 | ``` 133 | func verifyOTP() { 134 | // Get a copy of the current state: 135 | var state = self.state 136 | // Start loading before network call: 137 | state.isLoading = true 138 | // Publish state: 139 | commit(state) 140 | // Start network call: 141 | service.verifyOTP { result in 142 | // Update state with response: 143 | state.isLoading = false 144 | state.timerStatus = .finished 145 | state.result = result 146 | // Publish new state (and navigation): 147 | switch result { 148 | case .success(): 149 | // Navigate to home if success: 150 | let nav = BasicNavigation.push(HomeComponent(), from: self) 151 | self.commit(state, nav) 152 | case .failure(_): 153 | self.commit(state) 154 | } 155 | } 156 | ``` 157 | 158 | 虽然这个函数做了同步工作,由于提交了一个新的状态给组件产生状态传递,因此缺少一个完成的block。 159 | 160 | #### 4 - 订阅状态 161 | 162 | 从UI视图的角度看,没有发生同步。每当状态可用时,视图只是得到了一个新的状态并进行更新。例如,LoginViewController看起来是这样的: 163 | 164 | ``` 165 | class LoginViewController: Subscriber { 166 | var component: LoginComponent! // Injected by previous component. 167 | func viewWillAppear(animated: Bool) { 168 | super.viewWillAppear(animated) 169 | component.subscribe(self) 170 | } 171 | func viewDidDisappear(animated: Bool) { 172 | super.viewDidDisappear(animated) 173 | component.unsubscribe(self) 174 | } 175 | // MARK: - Actions 176 | func verifyTapped(field: UITextField) { 177 | core.dispatch(LoginAction.verifyOTP(field.text)) 178 | } 179 | // MARK: - Subscriber 180 | func update(with state: State) { 181 | guard let state = state as? LoginState else { return } 182 | // Update UI here. 183 | } 184 | func perform(_ navigation: Navigation) { 185 | // Perform navigation here. 186 | } 187 | } 188 | ``` 189 | 190 | #### 5- Define “Core” 191 | 192 | ```Core```可以定义成一个全局属性。 193 | 194 | ```let core = Core(rootComponent: LoginComponent())``` 195 | 196 | 就像这样,在这个被定义后,我们可以很容易的派发各种action。🎉 197 | 198 | ### 底线 199 | 200 | 201 | ```Core```可以被看成是一个Redux,Flux和MVVM的混合体。如果我们使用```Core```来作为一个App的架构,我们将拥有类似Redux的组件交互和状态传递。然而,组件本身是非常类似于 MVVM 的 view model,非常直接和容易适应。 202 | 203 | ### ```Core```和```Redux```的主要区别 204 | 205 | - ```Redux``` 是静态的。它需要你在编译时定义一个笨重的管理app状态的架构。如果你有重用多次的controllers,那将会特别困难。特别是,当你的应用数据流依赖于服务器响应,你将很难不通过入侵架构来在编译前定义app状态。```Core```是动态的。你仅仅需要为工作组件定义状态和actions。组件跳转将被动态处理。 206 | - 在```Redux```中,组件间没有一个标准的导航实现。在```Core```中,你可以有原生的导航实现。 207 | - ```Redux```专注于应用状态,而```Core```专注于独立组件状态。在这方面,我发现在一个独立组件中会比在一个笨重的应用中更容易使用。 208 | - 在```Redux```中,因为状态是全局的,所以当一个界面从导航堆栈中被pop时,很容易忘记做状态清理。在```Core```中,因为每个组件存储它自己的状态,当你从一个组件树中移除一个组件的时候,状态会被导航机制自动处理。 209 | 210 | ![](https://i1.wp.com/theswiftpost.co/wp-content/uploads/2017/09/1-_0TFaTTM33jww7OC1fP8BQ.gif?zoom=2&w=1200&ssl=1) 211 | 212 | ### 为什么使用它? 213 | 214 | 因为对比其他架构,你能通过一样的努力获取很多益处。 215 | 216 | - ```分离关注点```: 在```Core```中的每个变量函数都有明确的目的和清晰的定义。 217 | - ```容易理解```: 它强制你模型化你的组件,以便其变得更容易理解. 通过为每个组件定义状态, 你也可以为它们制定文档。 218 | - ```高度可测试```: 在```Core```中,UI只是状态存储的反映,你可以很容易地通过把UI替换成测试类来测试你的组件。 219 | - ```可重用```: 你可以不通过UIKit来开发你的业务逻辑。因此它可以在不同平台上复用(iOS,macOS,watchOS,tvOS)。 220 | - ```bug细节报告```: 你可以通过一个简单的中间件来记录用户行为,然后把行为堆栈记录到bug报告上。这样将会帮助你减少类似的crash,减少查bug的时间。我的意思是,减少很多查bug的时间。 221 | 222 | 然而,这并不是说```Core```比其他架构优越。在软件开发中,没有银弹,只有根据实际的需要做的妥协。 223 | 224 | ![](https://i1.wp.com/theswiftpost.co/wp-content/uploads/2017/09/1-ZYrSaLwfJbcXfS88jyPklg.gif?zoom=2&w=1200&ssl=1) 225 | 226 | 所以,请选择“正确”的架构: 227 | 228 | - 保持自身的技术的更新,但要小心炒作的技术 229 | - 定义好问题并根据问题选择符合实际的解决方案 230 | - 最后...无论如何,请拒绝MVC 231 | 232 | ![](https://i0.wp.com/theswiftpost.co/wp-content/uploads/2017/09/1-5hVDI1CgvGxiC-eyFrKlkA.gif?zoom=2&w=1200&ssl=1) 233 | 234 | ### 感谢阅读 235 | [```Core```](https://github.com/gokselkoksal/Core)的Swift实现现在已经放在Github上了。大家可以去试一下!在这里,我非常欢迎大家分享自己的见解,记得在Github上给我提issues哦。哈哈。❤️ 236 | 237 | > 译者:原文是架构类英文文章,比较抽象,建议结合源码看。[源码地址](https://github.com/gokselkoksal/Core)。 238 | 239 | > 本文[Github地址](https://github.com/britzlieg/translate_post/blob/master/2017-10/%E4%BD%BF%E7%94%A8%22Core%22%E6%9E%B6%E6%9E%84iOS%20Apps.md) 240 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 翻译国外文章 2 | --------------------------------------------------------------------------------