Swift的核心我们可以通过方程的传递性来理解Swift:Swift的核心是面向协议的编程。面向协议编程的核心是抽象和简单。所以swift的核心就是抽象和化简。你可能会对我的标题感到惊讶。我不是说子类没有价值,尤其是在使用单继承(singleinheritance)的时候,类和子类当然是强大的工具。然而,我要说的是,日常iOS开发的问题是类和继承的过度使用。作为一个面向对象的程序员(object-orientedprogrammer,后来统一称为OOPprogrammer;object-orientedprogramming后来统一简称为OOP),我们总会很自然地倾向于使用引用类型和类来解决问题,但我个人我仍然认为应该反过来,倾向于使用值类型而不是引用类型。我们仍将编写模块化、可扩展和可重用的代码,这一点不会改变。swift中强大的值类型可以帮助我们在不过度依赖引用类型的情况下实现这一点。我认为不仅面向协议编程(protocolorientedprogramming,后来被POP取代)可以帮助我们实现这一点,其他两类编程也可以,两者的核心思想都是抽象化简。这两种类型是:面向价值的编程(value-orientedprogramming,后来被VOP所取代)和函数式编程(functionalprogramming)。需要说明的是,我绝不是这类编程(POP、VOP和函数式编程)方面的专家。和你一样,我从MMM(手动内存管理-手动内存管理)时代起就是一名OOP程序员。通过自学,我从一开始就很重视价值抽象化简的思想。没想到我是一个偏向函数式编程(functionalprogramming)的OOP程序员,经常用到VOP和POP的思想。这可能就是我第一天兴高采烈加入swift浪潮的原因吧。在整个WWDC的整个星期里,swift的核心理念与我认为编程应该是怎样的非常契合,这种感觉一直充斥着我的脑海。通过这篇文章,希望能帮助各位(OOP程序员)打开思路,思考如何以更Non-OOP(非OOP)的方式解决问题。OOP的问题(以及为什么我必须学习它)我会第一个说:没有OOP很难制作iOS应用程序。Cocoa的核心是OOP。没有OOP就无法编写iOS应用程序。有时我幻想这不是真的。如果您不同意,请证明我错了。我真的需要这个,请证明我错了!无论哪种方式,您总是会遇到必须解决对象和引用类型问题的时候,然后由于Cocoa的规则,您不得不使用类。你在这种情况下遇到的问题是一个我们都知道和喜欢的问题:传递一个类的实例似乎总是有一种不可思议的能力:当你想使用一个实例时,让实例的状态(state)和你所期望的一样是不同的。(这是由于可变状态,你的对象的另一个所有者可以在它认为合理的时候改变这个对象的属性。)如果你不需要多重继承,从一个伟大的类派生一个类从而获得它的扩展功能阻止您无法使用其他一些很棒的类的附加功能,并且还会增加复杂性。(例如,尝试将UITextField的两个子类组合起来,产生一个具有两者特性的超UITextField。)上述项目的另一个问题是它会导致意外行为(unexpectedbehavior)。如果您遇到与上述类似的情况,您就陷入了依赖问题:您连接了两个超类的特性,对其中一个超类的更改可能会对另一个超类产生不利影响。这就是众所周知的类间紧耦合导致的问题。在单元测试中模拟。有些类与系统中的环境状态紧密耦合,以致于完全测试这些类需要您为每个类创建伪造的表示。我什至不需要告诉你,你本质上并没有真正测试过这个类,你只是在假装测试它。更不用说许多Mocking库使用运行时技巧来创建一个假类。并发问题。这与上面提到的可变状态密切相关。如果同时从多个线程更改一个引用,就会导致这个问题,运行时对象之间的同步会出现异常。我真的不需要告诉你这件事。它很容易导致诸如Godclasses(上帝类-承担许多子类需要的重要高级代码的所有责任),Blobs(具有太多权限的类),LavaFlow(因为它包含太多非法代码,任何人Daretotouchclasses)等等这几种反模式(antipatterns)。POP面向协议的编程特别容易陷入OOP反模式。大多数时候我们(包括我)都懒得去文件>新建文件。事实证明,向现有类添加功能是如此容易,以至于我们不想从头开始一个新类。如果你一直这样做,并且你一直懒惰地从一个“非常重要”的类中派生子类,那么你已经得到了上帝类/死星类。实际上,我之前已经这样做过:我已经为应用程序中的每个视图控制器添加了功能,该应用程序会显示指向导航控制器的导航栏的错误视图。哦,我是多么愚蠢。直到我想改变错误神一样的行为,我不得不再次改变整个应用程序。那不聪明,您真的应该检查那些错误。一个控制一切的类=bug到处飞。如果使用POP,ErrorGod类可以很容易的进行很大程度的抽象,方便以后的改进。(顺便说一下,如果你想学习POP,我强烈推荐你观看这个视频)。想想很有趣,因为在这段视频中苹果自己说:“从一个协议开始,而不是从一个类开始。”——DaveAbrahams:DestroyYourThreeViewsProfessor这是一个展示(之前的方法)有多么残酷的例子:,backgroundColor:UIColor=ColorSalmon,withSizesize:CGSize=CGSizeZero,canDismissByTappingAnywherecanDismiss:Bool=true){//写了复杂脆弱的代码}}//说吧,有100个类继承这个类EveryViewControllerInApp:PresentErrorViewController{}随着项目的推进,马上就明白不是每个UIViewController都需要这个错误逻辑,或者真的需要这个类提供的每一个功能。我团队中的任何人都可以很容易地在这个超类中更改一些东西,这会影响整个应用程序。这使得代码变得脆弱。它也使得代码多态化,当子类应该决定自己的行为时,超类在这里帮助决定。以下是我们如何使用POP在swift2.0中更好地构建此代码:到ErrorPopoverRenderer协议有presentError的默认实现funcpresentError(message:String,withArrowshouldShowArrow:Bool,backgroundColor:UIColor,withSizesize:CGSize,canDismissByTappingAnywherecanDismiss:Bool){//添加呈现错误视图的默认实现}}classKrakenViewControl:UIViewController,ErrorPopoverRenderer{//DroptheGodclassandmakeKrakenViewControllerconformtothenewErrorPopoverRendererProtocol.funcmethodThatHasAnError(){//...//抛出错误,因为Kraken海怪今天吃人会不舒服。presentError(/*blahblahblah很多参数*/)}}看,这里发生了一些很酷的事情。我们不仅消除了上帝类的存在,而且我们还使代码更加模块化,增强了它的可扩展性。通过创建ErrorPopoverRenderer协议,任何符合该协议的类都将能够呈现ErrorView。不仅如此,我们的KrakenViewController类不必实现presentError函数,因为我们扩展了UIViewController以提供默认实现。哦,但是等等!有一个问题!每次我们想要呈现一个ErrorView时,我们都必须实现每个参数。这有点烦人,因为我们不能在协议函数声明中为参数提供默认值。我有点喜欢这些参数!更糟糕的是,我们在使代码更加模块化的过程中引入了复杂性。我们继续用swift2.0新的小技巧来做一些补偿:protocolErrorPopoverRenderer{funcpresentError()}extensionErrorPopoverRendererwhereSelf:UIViewController{funcpresentError(){//这里添加默认实现,并提供ErrorView的默认参数。}}classKrakenViewController:UIViewController,ErrorPopoverRenderer{funcmethodThatHasAnError(){//…//抛出错误,因为Kraken海怪今天吃人会不舒服。presentError()//哇哦!没有更多的参数!我们现在有一个默认实现!}}好吧,现在看起来不错。我们不仅去掉了这些烦人的参数,还利用swift2.0的新特性,让presentError在协议层面默认使用了Self实现。使用Self意味着当且仅当协议的追随者继承自UIViewController时,此扩展才有效。这允许我们将ErrorPopoverRenderer真正视为UIViewController而无需扩展后者!更好的是,从现在开始,Swift运行时使用静态分派而不是动态分派调用presentError()方法。大致意思就是我们在函数调用点增强了presentError()方法的性能。哎,不过还是有问题。我们的POP之旅到此暂时告一段落,但对它的完善不会停止。我们的问题是,如果我们只想对某些参数使用默认值,而对其余参数不使用怎么办?使用POP在这方面帮助不大,但我们可以找到另一种方法。现在,让我们使用VOP。#p#VALUE-ORIENTEDPROGRAMMING你看,POP和VOP总是一起出现。在上面的WWDC视频链接中,Crusty做出了一些大胆的断言:我们可以用struct和enum类型做类能做的一切。我在很大程度上同意这一点,但不是那么极端。在我看来,协议本质上是将VOP粘合在一起的粘合剂,在这一点上我同意Crusty的观点。实际上,既然我们已经涵盖了Swift和VOP的核心概念,我想向您展示一张从AndyMatuschak就Swift中的VOP主题进行的精彩采访中截取的精彩图片:您可以??在库中看到Swift的标准,只有4个类,其余95个struct和enum实例共同构建了Swift函数的核心。Andy是这样解释的:用Swift编程时,我们不得不考虑使用很薄的对象层和很厚的值类型层。类有它的位置,但我想尽量认为它们的位置应该只是在对象层的一个非常高的层次,在值类型层通过操作逻辑来管理各种行为。“将逻辑与行为分开”-AndyMatuschak如您所知,值类型被分配给变量或常量,或者在将其值作为参数传递给函数时被复制。这通过允许值类型在任何时候只有一个所有者来降低复杂性。与引用类型相反,引用类型在赋值期间有许多所有者,其中一些您甚至没有意识到。在任何时候使用引用都会有一些副作用:引用的所有者会耍花招并在幕后更改引用。类=高复杂性,值=低复杂性。利用值类型的简单性,我们来实现前面提到的默认参数的设计。我们使用BrianGesiak的价值选项范式方法:蓝色}}structErrorOptions{letmessage:StringletshowArrow:BoolletbackgroundColor:UIColorletsize:CGSizeletcanDismissByTap:Boolinit(message:String="Error!",shouldShowArrow:Bool=true,backgroundColor:Color=Color(),size:CGSize=CGSizeZero,canDismissCanappy:Bool=true){self.message=messageself.showArrow=shouldShowArrowself.backgroundColor=backgroundColorself.size=sizeself.canDismissByTap=canDismiss}}使用上面的optiontypestruct(它是一个值类型!)让我们的POP带来一些VOP的颜色是如下:protocolErrorPopoverRenderer{funcpresentError(errorOptions:ErrorOptions)}extensionErrorPopoverRendererwhereSelf:UIViewController{funcpresentError(errorOptions=ErrorOptions()){//这里添加默认实现,提供ErrorView的默认参数。}}classKrakenViewController:UIViewController,ErrorPopoverRenderer{funcfailedToEatHuman(){//…//抛出错误,因为Kraken海怪今天吃人会不舒服。presentError(ErrorOptions(message:"Ohnoes!Ididn'tgettoeattheHuman!",size:CGSize(width:1000.0,height:200.0)))//哇哦!没有更多的参数!我们现在有一个默认实现!}}正如你所看到的,为了使用视图控制器来做错误处理,我们给它一个完全抽象的、可扩展的和模块化的方式,而不是强制所有的视图控制器继承一个上帝类。当你有一个具有不同功能的神类时,上面的例子特别有用。另外,在使用该方法实现与上述错误函数类似的其他功能时,可以将实现该功能的代码放在任何地方,无需进行过多的重构或更改代码框架。让我们用函数式编程来解决这个问题。我也是函数式编程的新手,但我知道一件事:这种范式(paradigm)需要一种编程风格,鼓励程序员避免可变数据和改变状态(changingstate)。类似于数学函数,函数式编程由一些输出只依赖于输入参数的函数组成,函数的输出不会受到本体外依赖的影响。这就是众所周知的“datain,dataout”,意思是每次传入一个值,传出时这个值必须始终相同。想想单元测试吧!如果我们用函数式思维来写代码,我们可以将VOP和函数式编程结合起来,利用它的很多优点,包括但不限于:另一个线程不能更改其并行线程中的变量)。更详细的单元测试单元测试不再需要使用mocks(有了值类型变量,不需要重新构建必须使用mock对象的环境,只需要测试少量的函数。本质上,通过初始化从任意依赖项中抽象出来的功能,您可以重建任何您想要的东西。)代码更清晰(老实说,就像瓷器一样精美)。让你的朋友们目瞪口呆,非常酷,让海妖疯狂地崇拜你。什么时候使用子类,什么时候应该使用子类?答案是当你别无选择时。例如:当系统需要时。许多CocoaAPI要求您使用类,您不应该用值类型来对抗系统。UIViewController需要被子类化,否则你的应用程序将一无所有。不要和系统作对!当你需要一些东西来帮助你管理其他类实例之间的值类型变量,并且还需要与这些值类型变量进行通信时。AndyMatuschak为这种情况举了一个很好的例子:使用一个类来获取值类型绘图系统计算的值,并将其传递给Cocoa类以将绘图系统绘制到屏幕上。当您需要或想要在许多所有者之间进行隐式共享时。这种情况的一个例子是CoreData。数据持久化是善变的。在使用CoreData的时候,使用子类来同步很多需要同步的owner是非常有效的。但要注意并发问题!这是您在处理此类问题时必须做出的权衡。当您不知道它的副本对引用类型意味着什么时。你复制一个单例吗?惯于。你会复制一个UIViewController吗?惯于。一个窗口?绝对不。(你可以,这是你的特权。)当一个实例的生命周期与外部影响相关时,或者当只需要一个稳定的身份时。单例是一个特别典型的例子。结论作为OOP程序员,我们习惯于使用类来解决问题。长期以来我们开发了很多模式来弥补引用类型带来的劣势。我的观点是,在编程中采用不同的思维方式可以有效地减轻这种权衡的使用。如果我们真的重视可伸缩性和可重用性,我们就必须接受模块化编程作为必经之路。使用值类型结合Swift2.0中新的和改进的协议功能可以轻松实现这一点。虽然以前的OOP思维方式会让我们很难用VOP和POP的方式去思考,但是如果我们用swift写的多了,VOP和POP的方式就会开始成为我们的第二天性。我们的大脑可能需要我们多写一点代码来适应这种思维方式,但我相信整个iOS社区都可以采用这些做法,这将大大降低我们日常解决问题的难度。Swift的核心是一个极其强大的值类型系统。坦率地说,我们应该从一开始就以VOP的理念来磨练自己,以发挥这一价值体系的优势。希望这篇文章能或多或少地帮助到你,让你每天都能写出更详细、更本质安全的代码。祝码友编程愉快!
