Tag: 软件构架

在iOS上使用MVVM进行路由

我在多个项目中使用MVVM已有一段时间了,我非常喜欢它的简单性。 特别是,如果您像许多人一样从MVC移出,则只需要在体系结构中增加一层即可; 查看模型。 如果您发现太多复杂的图层,这确实使事情变得容易。 这是一个好的开始,但是这种简单性并不总是很好。 在MVVM中,将业务逻辑移出视图控制器(VC),然后意识到它仍然很胖。 视图模型(VM)现在具有业务逻辑,但是表示数据(格式)或路由又如何呢? 它们仍然停留在VC中,我们需要将它们移出。 样品流量 假设我们正在实现一个登录屏幕,如下所示。 路线清单: 登录>主屏幕 注册>注册屏幕 忘记密码(?)>忘记密码屏幕 这似乎是一个简单的屏幕,可以使用带有3个脚本的情节提要实现。 但是请相信我,事实并非如此。 例如,您通常会在登录时打开主屏幕。 但是在这种情况下,用户的密码可能已过期,您需要实现重定向以更改密码屏幕。 因此,登录路径变为: 登录>主屏幕或更改密码屏幕 这是情节提要路由失败的地方。 它只是无法应付这种动力。 因此,通常要做的就是让VC处理它: func loginButtonTapped(){ //开始网络请求… //回应后: 如果viewModel.shouldChangePassword { performSegue(id:“ ChangePasswordScreen”,发送者:nil) }其他{ performSegue(id:“ HomeScreen”,发送者:nil) } } 这是路由逻辑,不应在VC中使用。 如果要使用轻量级VC,请在编写if语句之前三思。 他们是决定,不属于那里。 以我的理解,VC仅应具有与视图相关的代码和粘合代码。 永远没有决定。 让我们定义一个路由器协议,并将这些if语句从VC中删除。 我们会需要: 路由ID :字符串标识符,例如segue ID。 上下文 :要从其路由的当前视图控制器。 可选参数 :过渡所需的临时数据。 (点击的行索引等) 协议路由器{ 功能路线( 到routeID:字符串, 来自上下文:UIViewController, […]

具有构建模块化SDK的经验

在conichi中,我们正在努力改善酒店的客人体验:加快入住和退房流程,带来移动支付的可能性等。 为了构建此基础架构,我们一直在研究三个主要项目,其中最重要的一个是我们的移动SDK,我们正在将其集成到合作伙伴的应用程序中。 我们一直在构建SDK时,要牢记它应该是模块化的。 所谓模块化,是指每个合作伙伴都应该可以使用他们所需的唯一功能来构建其SDK。 我们如何使代码成为可能 首先,我们分离了核心功能,并基于对配置对象的依赖注入进行了初始化过程: @interface CNISDKConfiguration:NSObject @属性(非原子,副本,只读)NSString * apiKey; @属性(非原子,副本,只读)NSString * apiSecret; @property(非原子,赋值,只读)CNISDKLogLevel logLevel; +(instancetype)configurationWithBlock:(void(^)(id 配置))configurationBlock; @结束 该对象允许基于块的可变初始化,并且遵循NSCoding协议,以避免任何引用的相关问题。 我从Parse框架实现中汲取了这个想法。 下一步是为“工具包”创建统一的界面: @protocol CNISDKKit +(void)enableWithConfiguration:(CNISDKConfiguration *)配置; +(instancetype)sharedInstance; -(无效)开始; @结束 该协议要求使用以下方法来启用Kit并对其进行配置,启动基础Kit的任务以及能够访问共享实例。 每个Kit都有自己的初始化参数,开发人员还可以在初始化期间将其作为依赖项注入传递 @protocol CNISDKKitConfiguration @结束 不幸的是,我还没有为Kit的配置对象提供任何统一的属性或方法,但是这个简单的协议至少可以为我们提供类型安全性和有关此对象外观的高级信息。 由于唯一的核心模块应该初始化Kit,因此我们创建了一个对象,该对象表示Kit及其配置之间的一对一关系 @interface CNISDKKitBundle:NSObject @property(非原子,强,只读)Class 套件; @property(非原子,强,可为空,只读)id 配置; +(instancetype)bundleWithKit:(Class )工具包配置:(nullable id )配置; @结束 并为核心配置添加了一个新属性-一系列工具 @interface CNISDKConfiguration:NSObject 。 。 。 @property(非原子,强,只读,可为空)NSArray * […]

MVVMC —在Runtastic适应MVVM设计模式

为什么我们关心建筑 在Runtastic,我们已经创建了38个iOS应用程序,我们的团队已经发展到20个iOS开发人员,并且我们的iOS代码库包含超过700,000行代码。 不断壮大的团队会导致代码库不断壮大,从而导致更高的复杂性和更多的代码依赖性。 除非您关心体系结构并在创建软件组件时遵循一些规则,否则这可能会以灾难告终: 遵循单一责任原则 可测试性设计 有明确的依赖性 保持代码可读性和可维护性 我们都从苹果公司的MVC开始,它可以变成Massive View Controller(在那完成了)。 尝试运行`find。 型f -exec wc -l {} + | 在项目的根目录中对-n`进行排序 。 您可能会像我们一样找到具有数千行代码的ViewController。 意识到使用MVC遵守上述规则时,我们面临着越来越多的挑战,因此我们着手寻找其他设计模式。 作为iOS社区中的当今热门话题,您可以在各种设计模式之间进行选择,包括MVC,MVP,MVVM或VIPER。 经过研究后,我们决定采用MVVM,但仍未解决一些问题,例如路由(显示新屏幕)或数据绑定。 因此,我们还仔细研究了VIPER,它在解决这些问题方面确实有很好的想法。 但是在VIPER中,我们经历了不利的一面,即存在大量的样板代码以及不习惯这种模式的开发人员的陡峭学习曲线。 最后,我们将从VIPER中学到的知识整合到我们自己的MVVM设计模式中。 MVVMC —什么是C? C代表“calçots”,这是加泰罗尼亚语起源的一种特定类型的洋葱,与我们的同事和这种模式的发起者相同。 在Runtastic博客上阅读全文

VIPER-S:编写您自己的体系结构以了解其重要性(第1部分)

最初发布于ThinkAndBuild:http://www.thinkandbuild.it/viper-s-writing-your-own-architecture-to-understand-its-importance-part-1/ 在为我的应用程序使用VIPER几个月后,我开始研究自己的体系结构:我想为自己的需求创建更好的东西。 然后,我开始与同事Marco交流思想。 他站在Android的一面,但我们需要讨论以找到共同点,并取得一致的结果。 我们“有点”失败了,最终得到了与VIPER真正相似的东西,但是! 我现在正在应用程序中使用的是这个VIPER的修订版,因此我不会认为这是一次失败的尝试。 更像是VIPER的自定义版本。 通过这一途径,我学到了很多关于体系结构的知识,因此我决定与一系列文章分享经验。 我想重点关注两件事: •为获得完整架构而做出的决策,突出了基本原理和疑点(其中有些仍然存在) •我最终使用的体系结构显示了代码和一些实际示例。 从现在开始,我们将此结构称为VIPER-S 。 这里的S代表语义,因为我试图获得一种更清晰的命名方式,赋予角色和通信更多的意义,并添加了一些规则来提高代码的可读性和编写性。 让我们的建筑师 让我们开始一个问题的旅程。 为什么需要架构? 这个问题有很多不同的答案,但最相关的是: •清楚了解我们的代码(并简化其维护) •轻松分配职责(并简化团队工作) •提高可测试性(并简化您的生活) 牢记这些答案,并怀着深远的目标感,我们可以开始规划架构。 我是“ divide-et-impera”的忠实拥护者:对我来说,这是一种生活方式。 这就是为什么我将首先确定所有领域和角色 ,将在这些领域工作的参与者以及这些参与者之间如何交流的原因。 这些元素将定义我们的体系结构的支柱,因此,清楚地了解它们是什么真的很重要。 域是一个大集合,其中包含职责范围的所有逻辑。 角色只是这个角色的一小部分,它更加具体,可以确定确切的需求。 角色是实现所有功能以满足角色的代码元素。 让我们列出并描述我为构建VIPER-S而确定的领域和角色。 体系结构域:用户界面 通过用户界面域,我们向用户显示信息并与他们进行交互。 让我们看看该域的角色。 角色:显示UI信息 这是一个真正的“愚蠢”角色。 到达该域的数据已准备就绪,可以使用,无需进一步处理。 它只需要向下发送到最终的UI元素,并具有以下功能: func display(date:String){label.text = date} 如您所见,在上一步中,date属性可能已从Date转换为String。 我们仅在此处显示现成的信息 。 标签显示一个字符串,因此我们希望收到一个字符串。 角色:处理UI事件 这是另一个不太主动的角色,实际上,我们在这里仅拦截用户交互或应用程序生命周期事件。 通常在UI target-action中调用负责该角色的参与者 : @IBAction func save(){eventsHandler.onSave()} 建筑领域:数据 […]

VIPER建筑

在软件开发中,我们遇到许多问题。 难以测试的代码,重复的代码或几乎没有分离的代码几乎每天都会让我头疼。 模式尝试解决某些用例的常见问题。 单例是一个简单的例子。 它解决了一个类只必须具有一个状态的问题。 设计模式(例如单例)和体系结构模式之间的区别在于,体系结构模式解决了与关注点分离相关的问题。 问题 我们创造了惊人的应用程序。 以及出色的应用程序,出色的用户界面和复杂的功能。 这意味着我们的功能将随着它们的增长而变得复杂。 我们必须考虑如何使所有内容清晰,可扩展,可维护且易于理解。 解决方案 VIPER正是解决了这个问题:它描述了我们零件的责任以及它们之间如何相互作用。 我将App PursCreate重构为VIPER,并开始在其他一些较大的项目中使用它。 这就是我学到的: 一切都有特定的位置。 您完全知道例如在路由器或演示器中放置了功能。 结构变得更加复杂,但是添加新功能并使它们与其他功能分离非常容易 我减少了副作用 为我的业务逻辑编写UnitTests更容易 让我简要介绍一下VIPER的各个部分: 视图 启动应用程序时,您看到的就是视图。 是带有圆角半径和阴影或某些渐变色药丸的红色按钮。 主持人 表示逻辑处理所有必要的操作,以便在视图中显示所有必需的信息(向数据的交互器询问) 。 它还可以处理所有用户交互,例如显示新屏幕,直观地进入编辑模式或自定义反向操作。 互动者 交互器包含业务逻辑或充当业务逻辑的基础。 在我最近的项目中,常见的用例是CoreData访问,在产品列表(购物应用程序)中过滤产品或触发HTTP请求。 实体 实体代表您的DataModel。 这可能是ManagedObjectModel(用于CoreData)或包含HTTP响应的结构。 路由器 路由器处理所有显示和导航逻辑。 例如,当用户单击一个按钮时:演示者将呼叫路由器并说:displayMyView()。 为什么要提取这种简单的逻辑? 因为它迅速增长。 在我的App PursCreate中,我遇到以下情况: 单击单元格时,将显示您的目标概述。 有两种目标类型。 因此,您必须确定这是经典任务还是连胜纪录。 现在,用户可以编辑他的目标。 如果他进入编辑模式,则必须再次确定类型,但是这次打开对应任务而不是概览的编辑。 当应用增长时,这部分可能会变得非常复杂。 复杂 VIPER具有许多优势。 但是,它带有很多复杂性。 这可能会导致不必要的过度设计。 作为程序员,我们的工作是确定哪种架构模式适合我们的用例。 也许MVC或MVVM非常适合简单的用例。 在更复杂的情况下,VIPER得益于其分离性和可伸缩性。 […]

基于合同的软件架构模式

再次简化软件架构 营销中最重要的规则之一就是倾听其客户需求。 让我们考虑MVVM架构中View客户端的真实需求,注册模型变更通知,以便为他们提供最佳的编码体验,并定义一个架构合同,该合同将满足他们的需求,以严格遵守合同规则。 定义这组需求是为了通过减少此通知调用的外部可变性来减少软件的复杂性:为了获得流畅的体验,当上下文完全由以下内容确定时,只有一种可能的方法可以调用目标函数数据和线程的观点。 此外,该合同应完整并考虑与模型的所有交互:这样,合同就保护了目标功能背后的代码不受应用程序其余部分的影响。 与其编写复杂的代码来管理所有意外的可能情况以解决所有可能的执行情况,不如通过设计减少到唯一的情况并通过合同来强制执行,来减少可能的情况数量。 实施名义上的方案就足够了。 确定每个客户的需求 从市场细分的角度来看,有几种类型的客户: 1)查看内容客户端: 视图内容客户端是视图控制器的功能,负责基于某些模型属性渲染视图。 客户端需要通过其视图模型从模型中读取一组属性:因此,当这些读取属性发生更改时,必须通知客户端。 当所有读取属性都稳定时,客户端要求该通知仅在主线程事件循环中一次 同步发生。 稳定意味着这些读取属性在事件循环结束之前不会更改。 这样可以保证所显示的内容与模型的当前状态完全匹配,而不是其先前状态的过时副本。 客户端可能还需要编写一些模型属性,其他View Content客户端将使用这些模型属性。 在属性写入后,这些订阅的View Content客户应收到其通知。 书面财产应作为合同的一部分。 不允许客户读取或写入不属于合同一部分的任何财产,因为这可能会破坏其他客户的“财产稳定”规则。 在渲染视图时,所有这些都发生在主线程中。 客户端还要求在事件循环结束之前不得删除其视图控制器,这意味着将在接收到此通知之前执行视图层次结构渲染。 该客户端还需要独立于模型对象的存在而注册模型属性,以便与模型对象创建/删除分离:每个模型属性均由源自根模型对象的唯一键路径标识。 这组需求是通知合同的基础: “当这组现在稳定的READ属性中的至少一个发生更改时,我只希望同步地被通知一次,并且我可以读/写其中一些WRITE属性。” 因此,通知调度程序应基于这些联系来计算模型属性之间的依赖关系:如果合同读取A并写入B,则B依赖于A,这将创建模型属性顺序。 因此,通知调度程序应将合同级别计算为读取集的最高属性级别,并按此顺序进行调度。 2)查看层次结构客户端: 视图层次结构客户端是一种视图控制器功能,负责基于一组模型“视图层次结构”属性,通过创建/删除子视图层次结构来修改其子视图层次结构。 该客户端不应向其子视图控制器提供模型属性,而应仅向子视图控制器用于从模型中获取其模型属性的不可变配置数据。 由于视图层次结构更新将从根到叶发生,因此每个视图层次结构客户端均应提供与其层次结构级别相同的优先级编号: 根视图控制器=优先级1 其子级=优先级2 它的子孙=优先级3 等等… 通知调度程序将首先按优先级顺序发送基于优先级的通知,然后再发送与数据相关的通知(例如:查看内容)。 视图层次结构客户端可以写入任何不稳定的视图内容或视图层次结构属性,即不属于优先级相等或较低的优先级合同。 因此,合同如下: “当该组现在稳定的READ(视图层次结构)属性中的至少一个发生更改时,我希望仅在此优先级级别上仅被同步通知一次,并且我可以读/写任何非稳定属性。” 3)标准化,协调,计算数据客户端: 规范化客户端负责解决跟随模型写事务的模型中的任何不一致问题。 协调客户端负责在高层确定应用程序/文档的视图层次结构和上下文,并设置视图层次结构属性。 计算数据客户端负责计算一组属性,这些属性可以由多个视图模型共享,并且应在呈现视图层次结构之前进行计算。 这些客户端具有基于优先级的合同,带有硬编码的负优先级,可以在View Hierarchy客户端之前安排它们。 归一化=优先级-3 协调=优先级-2 计算数据客户端=优先级-1 查看层次结构…优先级为正 查看内容…无优先级/数据驱动 我们可以为客户提供优质服务吗? 当前有许多架构模式被大肆宣传,很明显,它们都试图满足其中一些需求。 据我所知,最接近上述需求的是单向Elm […]

场景

今天,我想谈谈“场景”,我的名字是一个简单的结构,用于将View / ViewController / ViewModel集合的相关位收集在一起。 让我们通过看一个例子来开始,然后我们将逐步进行介绍。 struct MenuScene:场景 { typealias依赖关系= HasAppBroker和HasStore typealias控制器= MenuViewController typealias视图= MenuView typealias交互器= MenuInteractor 让config = PresentationConfig.menu 让控制器:控制器= MenuViewController() 让视图:视图 init(有依赖关系:依赖关系) { self.view = self.controller.menuView Interactor.configureBindings(场景:自我, 依赖关系:依赖关系) } } 它符合具有一些关联类型的Scene协议-Controller , View和Interactor ,并且如您在init的最后一部分所看到的那样, interactor具有某些依赖性。 插入器只是具有单个暴露的静态函数configureBindings的结构的名称。 依赖关系是通过一系列协议创建的,每个协议一个。 在这种情况下,为HasAppBroker和HasStore 。 协议HasAppBroker { var appBroker:AppBroker {获取} } 协议HasStore { var store:存储{get} } 我们将它们组合在一起以创建Dependencies类型。 然后,只要我们发布到场景init中的所有内容都符合那些协议,就可以满足相关性。 视图设置为我们要处理的主视图; […]

代码最佳实践:为您的未来自我编写代码

2015年11月,我踏上了一个激动人心的新旅程,构建一个iOS应用,将iPhone上的图像和视频投射到连接Chromecast的电视上。 在Swift中,作为一个单独的开发人员,作为一个辅助项目,所有这些都在其中。 在这篇文章中,我将分享一些考虑因素和实践,这些因素和实践已帮助我在3.5个月内将代码扩展到4000行以上,同时又将技术负担降至最低,并最大程度地提高了可维护性。 快速预览: 就上下文而言,第一个版本的功能已完成约50%,因此我希望代码库最终会翻倍。 与我以前的所有应用程序和客户端项目相比,我估计在编写更多模块化代码而更少编写模块化代码的同时,我可以更快地增加价值- 至少两倍 。 事不宜迟,这就是我的工作方式: 口头禅:为维护性和未来自我而设计 力争绝不重复代码 始终将源文件保持在200行以下 代码本地化获胜。 凝聚 不要分散属于同一类的多个类代码。 使用类内部的扩展对确实属于一起的功能进行分组。 这使得在需要时更容易将事情排除在外。 适当地命名事物 最小化可变状态 仔细考虑对象图中的所有权 , 尤其是对于异步调用 使用ViewModels驱动UI更新 在添加新功能后始终进行重构 添加新功能后,请务必进行广泛的测试-在添加新代码之前先修复AKA错误 默认将所有内容设为私有 在Interface Builder中完成所有UI和布局。 代码不是您的表示层所属的位置! 错误处理对于应用程序设计至关重要 使错误对开发人员和用户友好 程序员错误应立即消除 奖励 :将开发容器用于我计划开源的自包含代码,例如这样。 到目前为止我还没有做 ReactiveSwift :我很想尝试一下,并在应用程序中利用Observables的功能 ,但是还没有机会。 TDD / BDD /单元测试-尽管我已经对依赖注入进行了仔细的考虑,并且上面的约束迫使我保持​​班级小。 集成测试。 由于我总是在添加新功能之前修复错误,因此这并不是一个强烈的要求。 但是,我为接收器应用程序构建了一个伪造/存根 ,以便在没有可用的Chromecast设备时可以测试大多数应用程序。 另外,我可以在运行时在假/真实接收器之间切换。 其他注意事项 对于这样的附带项目,我发现尝试每天取得一些进步是有益的,并且如果不进行任何工作 ,通常连续三天不能超过。 这使我始终专注于产品,并且我可以计划新的工作或考虑如何持续解决问题。 在单独构建此文件时,我可以使用一个Google文档来跟踪进度,错误,待办事项,里程碑和日常任务。 在现阶段,我发现这比拥有专用于每种情况的专用工具要简单,这在大多数组织中都是如此。 最后,我选择将其作为辅助项目进行构建,同时将大部分时间用于客户服务。 好处: […]

带有RxSwift的MVVM

背景 在开发iOS应用程序时,您知道用于开发iOS应用程序的体系结构是MVC(模型-视图-控制器),每个应用程序组件按其职责分开。 在MVC架构上,模型负责提供数据或控制与数据访问有关的所有事物。 Person或PersonDataAccess类是模型的示例。 View负责将数据显示到用户界面。 并且,最后一个是Controller,负责更改模型并准备要呈现给View的数据。 根据Apple MVC的概念,视图将用户操作发送到控制器,然后控制器使用从视图发送的数据更新模型,然后模型将模型中的数据更改通知控制器,最后一个是控制器更新视图。 看起来很简单,对吧? 但是现实与上面的概念并不相同。 大多数iOS开发人员(包括me😂)倾向于制作Massive View Controller。 为什么将其视为问题? 因为视图和控制器紧密耦合,很难说它们是分开的。 在这种情况下, UIViewController子类被视为View。 因此,您倾向于将所有这些职责(网络调用,事件处理,数据更改,显示数据等)写入视图控制器( UIViewController子类)。 似乎易于实现,但有很多后果。 由于表示和数据操作合为一层,因此很难测试业务逻辑(视图) 难以维护 膨胀的UIViewController 继续… 由于以上所有这些问题,我开始学习一种替代的体系结构,该体系结构解决了这些问题。 认识MVVM 因此,这是视图控制器庞大性的拯救。 有一个名为ViewModel的组件。 那么,ViewModel的职责是什么? 就像MVC架构上的Controller一样,ViewModel负责更改模型并准备要呈现给视图的数据。 但是,有许多差异。 ViewModel提供了到View的数据绑定机制,因此,如果ViewModel上有数据更改,则View将自动更新,并且ViewModel没有对View的引用。 就个人而言,由于数据绑定机制,我喜欢这种体系结构,因此如果ViewModel发生更改,则View可以更新自身,并且由于职责分离,因此易于测试模型和业务逻辑。 因此,将MVVM应用于iOS开发的好处是 易于测试业务逻辑和模型 视图几乎是被动的,因为存在数据绑定,尽管如此,我们仍然使用视图控制器执行segue(作业,请分开此😂) 轻松更改UI而不弄乱业务逻辑 您可以将业务逻辑或网络调用放入ViewModel,这样视图控制器就变得不那么肿了 ViewModel的经验法则 无参考资料 没有import UIKit 没有引用任何UIKit的组件,例如UIView , UIButton , UITextField等 只是一个数据。 JSON,字典或其他数据结构 RxSwift ReactiveX提供的一种反应式编程框架。 这是Rx的Swift版本。 它依赖于可观察的模式,如果Observable发生了数据更改或事件,则观察者可以执行某些操作。 在这里可以找到RxSwift的更多详细信息。 在这里,我在MVVM体系结构上使用RxSwift提供从ViewModel到View的数据绑定,并在Model发生更改时通知ViewModel。 […]

书评:App Architecture:Swift中的iOS应用程序模式

自大约11年前的2008年3月iOS推出以来,核心的iOS MVC架构并未发生实质性变化。 多年来,随着应用程序变得越来越复杂,MVC和UIKit的局限和缺点促使开发人员开发和使用替代体系结构。 App体系结构:Swift中的iOS应用模式研究了在iOS上构建软件的不同体系结构方法。 尽管所有体系结构都稍有不同,但本书描述了关注点分离,不变性,单向数据流和可测试性的通用主题,这些主题是这些体系结构的基础概念,所有开发人员在构建高质量软件时都应努力争取。 虽然这本书与概念本身无关,但概念是从本书中摘取的最重要的内容。 体系结构来来去去,但是概念是常青的。 与以往一样,在处理建筑时,请遵循建筑的黄金法则: 保持简单,愚蠢 。 记住:建筑只是达到目的的一种手段。 专注于最后。 关注客户,用户。 关于建筑 标准很棒,每个人都应该有自己的标准。 克里斯·科恩(Chris Koehnen) 建筑—繁重的术语 术语“架构”有点像是四个字母的单词。 在考虑软件设计,体系结构或Rx之类的库以某种方式思考时,软件开发人员会变得非常热情和/或防御。 他们使人两极分化。 使它们成为可能的体系结构和工具可以成为许多人的信仰。 在研究体系结构之前,重要的是我们要全神贯注,不要陷入陷阱! 客户不会对所使用的模式或架构的“清洁度”有所了解。 然后是建筑的近亲-“模式”。 模式本身通常是有用的设计。 像体系结构一样,如果将模式的关注点放到了极致,则冒着从为客户的角度设计软件到为设计的目的设计软件的风险。 一切都与软件在内部的实用程度,软件的“纯”程度等有关。同样,不要忽视客户 。 专注于概念 在这样的背景下,我想说的是,本文将不着重于本书中描述的每种特定体系结构的优点。 实际上,这本书的作者并没有主张任何一种架构都优先于另一种架构。 明智之举。 没有*正确*的架构。 不要挂在一个特定的体系结构上。 没有“正确的”架构。 但是好的架构通常遵循通用的概念和模式。 这些概念跨越了整个架构。 了解这些基础知识,您将轻松理解任何体系结构。 这些概念,而不是它们的单个特定实现,对您而言,最重要的是要摆脱本文。 我们将重点关注的基本概念包括: * 关注点分离。 *不变性。 *单向数据流。 *与UIKit和MVC的交互。 建筑很重要。 代码前设计! 专家提示:体系结构很重要。 高质量的软件并非偶然发生。 设计中考虑了思想。 真正的黑客会仔细考虑他们的设计,将其视为一种艺术形式。 想要跳进去,手指到键盘,启动代码是很常见的。 但是帮自己一个忙。 停下来想一想。 […]