最近,我们刚刚发布了一个新的基于Swift的应用程序,当时苹果公司大力推荐它,并且已经获得了相当多的用户。在这篇文章中,我们想分享这些经验,提出我们对新语言的想法,并指出Swift中的新特性,这些特性使我们能够编写更好的程序。这不是一本Swift入门指南,本文的读者是对Swift不是很熟悉并且对Swift在实际编程过程中是什么样子的开发者感到好奇。我们将参考一些技术概念,并在适当的地方提供入门指南和文档的链接。首先,我们将简要描述这个新应用程序的作用以及我们的主要目标是什么。新应用程序您可能已经熟悉我们的主要应用程序Duolingo,这是一款非常受欢迎的语言学习应用程序,拥有超过6000万用户(截至2014年12月),它还被Apple评为2013年年度应用程序。如果您想学习作为一种新语言,Duolinggo将成为您iPhone或iPad上的首选应用程序。随后,我们发布了DuolingoTestCenter(以下简称TestCenter),这是一个非常好用的应用程序,可以让你测试你的语言知识。例如,如果你是一个在美国或英国的大学找工作的外国人,这些工作通常需要你有一些官方证书来证明你的英语水平。这个应用程序的用户可以通过一些测试来让用户确定他们的语言水平,并且为了防止作弊,会有真人监督测试。这款应用自发布以来,在50多个国家的APPStore的“最佳新应用”中被苹果公司推荐。在目标性能方面,测试中心对性能要求不高。大多数应用程序是一些静态内容和少量控件。另外,为了防止作弊,整个考试过程都会被录像,这是基本的考点。我们在使用Swift的过程中没有遇到任何性能问题,但是我们还是要注意性能。对我们来说更重要的是应用程序的稳定性和健壮性。由于测试持续约20分钟并且它们是收费的,因此在测试中途崩溃会导致相当差的用户体验1。此外,一旦测试开始,您必须完成它(也就是说,用户不能暂停或退出应用程序;这样做是为了防止作弊)。所以,我们需要尽量减少崩溃的可能性。对Swift的普遍看法当Swift刚发布时,许多人开始将它与其他语言进行比较,仅通过查看其语法就可以得出结论。有人说他们现在“不必忍受Objective-C语法”,可以直接进行iOS开发。老实说,这种认识是错误的。谁在乎语法(只要它不古怪)?对于一门语言来说,还有很多比语法更重要的东西,比如它能让你更轻松地表达你的想法,它不会鼓励不良行为。Swift比Objective-C或任何其他语言更能激发我们的灵感。如果您在Twitter上关注一些Swift作者,您就会知道他们从其他地方吸收了很多非常好的概念,包括函数式编程,并且他们抛弃了很多现有的(但不理想的)概念。由于我们习惯于使用Objective-C进行编程,因此Swift对我们来说是一个很好且友好的进步。如果您的原始语言是Haskell(或类似语言),您可能会觉得Swift仍有改进的空间。同时,我们也很期待在未来的Swift版本中会带来哪些更多的改进。优点Swift支持很多开发者在使用其他语言的过程中已经习惯的新特性,比如自定义运算符和函数重载。值类型(包含文字值的类型,例如Swift结构)可以使您的代码更易于理解。我们也非常喜欢在Swift中使用更强大的静态类型系统和类型推断。特别是当Objective-C中没有泛型时,在Swift中我们终于有了类型安全的集合,而不是仅仅希望将某种类型的对象存储在NSArray中。接下来,让我们仔细看看我们在Swift中发现的非常有用的特性。没有异常到目前为止,Swift中没有错误处理。我们不知道Swift的作者是否故意设计了没有错误处理的语言,或者仅仅是因为当时没有足够的时间。无论如何,我们觉得没有错误处理是一件非常好的事情,因为(unhandled)exceptions使代码更难阅读(well-handledexceptions可以使代码更清晰,让开发人员知道什么时候哪里会发生异常,但是他们太麻烦了,反正Objective-C不支持异常处理)。事实上,在我们最常遇到应用程序崩溃的原因中,第七是因为苹果提供的一个方法抛出异常(-[AVAssetWriterInputHelpermarkAsFinished])。这个方法没有被标记为抛出异常,也没有在文档中注明,所以我们不知道它会这样,直到我们真正看到这个崩溃报告,当一些用户的应用程序崩溃时。有经验的Cocoa开发人员会知道,虽然Objective-C提供了异常抛出和处理的机制,但它只在极少数情况下使用,而且那些情况通常是不可恢复的情况(尽管有一些例子)。在这种情况下,更好的解决方案可能不是捕获和处理异常,而是改进代码以便根本不会抛出异常。可能有人会争辩说,这样的异常似乎是一种失败的断言方法,但也许这个概念就是设计成这样的,那为什么要用assert()和fatalError()呢?通常,我们希望防止自己忘记处理错误,理想情况下,我们希望在编译时捕获所有问题,而不是在我们的应用程序崩溃之后。异常只是让它变得困难,那么为什么我们甚至需要在Swift中使用它呢?OptionalSwift中有很多非常重要的基本概念,Optional(你可能知道这很像Haskell中的Maybe类型)就是其中之一。Apple的文档说:Optional是一个枚举类型,有两个值None和Some(T),分别代表没有值和有值。所有类型都可以显式(或隐式)转换为Optional类型。同时,Swift为使用Optional类型提供了简单方便的语法糖,比如在None的情况下使用nil,特殊的扩展语法,运算符等等。此外,Optional链还允许您编写包含多个Optional依赖项的简单明了的代码。那么我们如何使用它呢?Optional是一种很好的表达“该值可能为空”的方式,你可以将它作为函数的返回值类型,来表示该函数可能不返回任何结果(只要你不好奇为什么会这样)是)为什么这比在Objective-C中将null分配给指针更好?因为这样编译器(在编译时)可以保证我们在正确的类型上操作。换句话说,不是Optional的值在Swift中永远不能为空。此外,由于Swift中的Optionals不仅仅是简单的指针类型,它们的用途更广泛。这里有一个关于Optional的使用的小例子:在Objective-C中,所有返回直接指针类型的方法,比如对象初始化方法(比如-init),都可能合法返回nil(比如当一个对象不能被初始化时).一个明显的例子是+(UIImage*)imageNamed:(NSString*)name,光看方法名,你不能确定它是否会返回nil。但是在Swift中你可以。Apple在Swift中引入了failableinitializers的概念,这样可以方便地在类型级别表达方法不会返回nil。在Swift中,同样的例子是这样的:init?(namedname:String)->UIImage,注意这里有个问号,意思是如果找不到标识符为name的变量,init方法可能会返回零。我们在适当的地方经常使用这个特性(我们试图避免显式或强制解包Optionals)。如果表达式可能返回nil(例如失败)并且我们不需要知道原因,则Optional是好的。Result如果你有一个可能会失败的函数调用,而你想知道它失败的原因,那么你可以使用Swift提供的Result(对于函数式编程开发者来说,他就像是Either的子类型),这将是一个简单而实用的选择。与Optional类似,Result允许你在类型层面表达,某物可能是一个类型的值,或者是一个NSError).通常,成功枚举值将包含您感兴趣的正常值,如果出现错误,您将得到一个.Failure和一个描述性的NSError。与Optional不同,Result不是Swift标准库的一部分,也就是说,你必须自己定义它。(在这个阶段,编译器缺少一些相关的功能,你需要找到一个解决方法。)我们在网络通信、I/O、代码分析模块的很多地方都用到了Result。这个解决方案比旧的NSError指针传入和传出函数,或者通过一个完成块来包含成功的值要好得多,比错误指针(或者使用布尔返回值和NSError指针的更复杂的方案)要好得多,Result是一个非常优雅的解决方案,可以让您编写更好、更干净、更安全的代码。在我们的应用程序中,任何可能失败的表达式(非致命失败)都将返回一个Optional或Result。与Objective-C的互操作性在设计Swift时,与Objective-C的互操作性是一个重要的考虑因素。如果Apple只是发布了一种新的编程语言,然后试图用Swift实现完全替换所有以前的代码库,那是行不通的——至少现在还行不通。另外,开发社区中还有大量的Objective-C代码。如果与Objective-C没有良好的互操作性,可能没有人愿意使用Swift。幸运的是,Swift和Objective-C之间的互操作性相当简单,我们已经在小范围内进行了试验,并取得了良好的效果。但值得注意的是,一些Swift概念(例如枚举)在Objective-C中并不直接可用。例如,我们的应用程序中有一个小功能组件需要操作PDF文件。我们用Swift编写了这个组件,然后我们想在用Objective-C编写的主应用程序中使用这个模块。唉,有一些方法使用了仅在Swift中可用的特性,这意味着这些方法不能在Objective-C中自动桥接。为什么要绕过这个问题,我们干脆给Swift的方法做了一个wrapper方法,可以在Objective-C2中使用。当然,在Swift中直接使用我们主应用中已有的Objective-C代码也很简单。为此,您只需将那部分代码从应用程序中取出(或者更好的是,一个单独的模块本身),然后通过桥接标头将其导入到您的Swift代码中。缺点尽管Swift与Objective-C相比有了很大的改进,但仍有一些地方需要改进。例如,新语言缺乏其他现代语言常见的一些高表现力。但作为一种新语言,这可能很快就会改变。Apple保证兼容性,但也表示他们可能会在适当的时候修改语言的某些功能(实际上,他们已经这样做过几次)。这意味着在更新编译器之后,您可能必须修改代码,否则将无法编译。我们知道这种事情会发生并且不在乎,幸运的是我们现有的代码以前运行良好,通常不需要太多时间来“修复”它们。我们对Swift最大的挫败感——以及我们挫败感的根源——可能不是语言本身,而是它附带的工具。在Xcode(Apple的Objective-C和SwiftIDE)上使用Swift还不是很好的体验。在我们开发的过程中,Xcode经常运行很卡或者直接崩溃。大部分时间没有(或非常慢)代码提示,基本上没有调试器,不稳定和不可靠的语法突出显示,缓慢的编辑器(一旦项目达到一定大小),并且没有重新加载构建工具。此外,编译器报告的错误信息往往难以理解,编译器中存在一些错误和缺失的特性(如类型推断经常出错)。自从我们开始使用Xcode以来,它已经走了很长一段路,它大部分都很好,只有一些小东西会破坏编程体验。我们希望苹果能够更加重视并不断完善这一开发工具。苹果在2014年6月的WWDC大会上发布了Swift的一些数字,同年7月底,我们推出了TestCenter,这是我们第一个只用Swift语言开发的应用,然后我们在11月中旬发布了它.花了3个多月才到1.0版本(一个程序员;Android版本和web版本那时已经存在,所以我们那时已经有了完整的后端和设计)。正如我们之前所说,健壮性和稳定性对我们来说非常重要,所以让我们看看我们是如何做到的。崩溃在写这篇文章的时候,TestCenter已经发布了大约两个半月的时间,并且已经拥有了相当数量的下载量和用户(可能是苹果推荐的结果)。与任何其他第一版一样,我们遇到了很多以前从未见过的问题,但幸运的是,我们似乎没有忽视任何严重的错误。截止到今天,考点的崩溃率大概是0.2%,看起来还不错3。如果您仔细查看崩溃组(由于相同原因导致的崩溃):排名第一的崩溃组(导致大约30%的崩溃)是由于外部Objective-C库造成的。事实上,前五组中有四组是由于Objective-C(第五组是由于我们忘记在最终发布版本中关闭的失败断言)。另外值得注意的是,排在第七位是因为前面提到的Apple提供的Objective-C函数有时会抛出异常,而文档中并没有体现出来(-[AVAssetWriterInputHelpermarkAsFinished])。我们将如此低的崩溃率归因于我们可靠的软件架构和我们对一些良好编程原则的遵守。但是,Swift的优秀设计也减少了很多bug出现的可能性,这对我们构建我们的软件来说是非常重要的。架构很有帮助。例如,使用Swift的类型系统,许多错误可以在编译时发现,而不是在发布的产品运行时发现。编译器性能我们不得不问一个问题,对于我们这种规模的项目,编译器是如何编译的。根据sloc的数据,我们现在的项目中有10634行实际代码(不包括空行、注释等)。清除Xcode缓存需要2分钟,然后运行timexcodebuild-configurationRelease命令,调试运行大约需要30秒编译。所有测试均在2013年年中的RetinaMacBookPro上完成。需要注意的是,编译xib也需要一定的时间,并不是所有的Swift5。你肯定能感觉到Xcode会随着你项目的增长而变得越来越慢,而且不仅仅是我们有这个问题。循环时间(更改代码后从按下CMD+R到应用程序在模拟器中打开的时间)也比Objective-C长。在一个简单的测试中,向代码中添加一行需要14秒来编译,具体取决于这行代码中所做的事情,而在Objective-C项目中进行类似更改只需要2、3秒。当然,这不是一个复杂的编译器基准测试,因此可以对这些数字持保留态度。希望您至少对今天的编译器性能有一个大概的了解。结论对于长期使用Objective-C的开发人员——尤其是那些对现代编程语言感兴趣的开发人员——Swift是一个受欢迎且令人兴奋的开发项目,而且,由于(当前)开发工具的存在,它有时也会令人沮丧。我们已经证明(至少在我们的同类应用程序中)Swift可用于编写稳定、健壮和高容量的应用程序。我们的主要应用程序Duolingo已经使用了一些Swift代码,我们计划在未来更多地使用它。那你为什么会选择Swift?只要您拥有最新的用户(您只能支持iOS7+)并且在开发大型项目时有耐心,Swift提供了一种全新的、结构良好的编程语言选择。我们真诚地建议您尝试一下,尤其是为了了解Apple想要提倡的编程哲学。如果您使用的是Objective-C,则切换到Swift相对简单明了。您可以像在Objective-C中编程一样使用Swift。当您使用Swift中的一些新概念时会很有趣。特别是现在似乎有一种拥抱函数式编程的趋势,我们认为这很好。如果你已经有一个基于Objective-C的应用程序,你可能不想完全重写整个应用程序来使用Swift,但你可以考虑在添加模块时使用Swift。如果你能回到过去并且你不得不重写这个应用程序,你还会使用Swift吗?会议。
