Tag: 软件工程

使用Swift属性观察器清理对象

在上一篇文章中,我们浏览了如何使用委托模式将视图与控制器分离。 今天,我们正在深入研究属性观察者的世界,以了解如何使用它们使对象更易于处理。 什么是财产观察员? 财产观察员观察并响应财产价值的变化。 每次设置属性值时都会调用属性观察器,即使新值与属性的当前值相同也是如此。 – Swift编程语言指南 以上解释几乎说明了一切。 在声明属性观察器时,我们这样做是因为我们有兴趣知道何时更改某个值。 引言告诉我们,“更改”并不一定意味着实际值已更改。 这个想法实际上是观察何时将变量写入其中,这可能是非常有用的事情。 除了观察并通知我们更改之外,我们还可以通过选择willSet (写之前)和/或didSet (写之后)观察者来选择希望在什么时间通知我们。 让我们看一些示例,这可以如何帮助我们清理代码并简化其他想要使用我们的实现的开发人员。 假设我们正在实现一个自定义进度条,该进度条将在您的应用程序的几个地方使用。 它由一个典型的蓝色条组成,该蓝色条随进度进行动画处理。 它还具有一个标签,用于向用户显示进度的四舍五入百分比值。 控制和强制变量的域 为了跟踪我们的进度,我们将使用Double类型的规范化值。 在这种情况下,归一化意味着我们将存储一个数字x,使得0≤x≤1。这种方法具有很多优点,尤其是在处理图形时。 它还负责检查设置的值,以使它们保留在域中。 让我们看看属性观察器如何帮助我们做到这一点: 类ProgressBar { public var progress:Double = 0 { didSet { self.progress = min(max(self.progress,0),1) print(“将新进度设置为\(self.progress)。”) } } } var p = ProgressBar() p。进步= 2 //打印“将新进度设置为1.0” 进步= 0.2 //打印“将新进度设置为0.2” p。进步= -100 //打印“将新进度设置为0.0” 很好 通过使用didSet观察器,我们可以确保进度值始终保持在其范围内。 […]

使用Swift处理部分中的可见标题

是否曾经注意到Apple的“照片”应用程序将其标题更改为发粘时变得模糊? 如您所见,一旦页眉到达其粘性位置,背景就会变为模糊。 如果您使用的是Objective-C,那么有使用VlaueForKey的漂亮解决方案 我们需要的是visibleCells属性,但对于visibleHeadersInSection… 快速浏览一下UITableView文档,我们可以得到indexPathsForVisibleRows,并将其与map结合起来可以得到我们需要的数组。 将其附加到scrollViewDidScroll可以得到以下内容: 为了这个例子,我将强制解开可选 让我们看一下实现它的代码: 我们的ColorChangeable协议: 我们的HeaderColorChange 扩展名(用于颜色): 让我们看一下第23行中的iterativeBackgroundChange ,我们首先采用旧颜色(第24行),提取其颜色(包括alpha)(第26行),然后返回相同的颜色,但在其alpha值上添加0.1即可得到我们想要的效果。 我们的DataController: (显然) TableController持有一个DataController类型的实例,以便获取表视图委托和数据源的相关数据。 虚构类型SomeEntity可以是您决定的任何类型,但是,如果它符合ColorChangeable协议(第5行),则意味着此对象在表视图中具有对应的标题,该标题应更改并保存其颜色状态我们所做的。 您可以在此处找到示例项目。 希望你像我一样喜欢它 如果您有任何疑问/评论/启示或任何相关的内容,我很乐意在 Twitter上 或什至在评论部分 与您讨论 。 不要犹豫🙂

简要介绍:实现MVVM-C模式

您可以阅读它或尝试与我们创建一个简单的pp。 我们建议使用后者。 我们已经发布了有关MVVM模式的文章: MVVM体系结构:分步指南 ViewModel有什么不同。 essays.decode.agency 以及关于使用协调器的一项: 与协调员配合应用流程 向庞大的ViewController告别。 essays.decode.agency 因此,唯一合乎逻辑的下一步就是将两者放在一起,并在一个实际示例中引导您实现MVVM-C模式。 我们将其称为“红色和绿色”应用程序。 基本结构 我们将在语义上将“红色与绿色”应用程序的屏幕分组为单位。 每个单元包括以下组件: 视图 ViewController 视图模型 ViewModelProtocol ViewModelProtocol用于进一步封装ViewController和ViewModel之间的通信。 我们的应用程序有一个主屏幕,可以选择红色或绿色。 选择红色将我们带到红色屏幕,绿色将我们带到绿色屏幕。 每个VC通过两个组件初始化: mainView —定义屏幕的可视部分并在其上配置操作 viewModel —分离UI部件的逻辑; 在MVVM-C模式中,它还负责与协调器进行通信 让我们深入了解并定义主屏幕。 我们将其称为MainVC: 在LoadNibFromEnum中,您会看到我们使用了自定义函数来调用Nibs: 视图是通过相应的XIB定义的,其中有两个按钮,这些按钮在屏幕上显示,因此可以定义对它们的操作。 ViewModelProtocol定义了可以在VC-VM关系上执行的所有动作。 在这种特定情况下,我们只需要在上述两个按钮上定义动作,因此我们将这样做: 可以以多种方式定义动作,但是出于代码易读性的考虑,我们仅通过块定义动作。 ViewModel可以是符合ViewModelProtocol的类或结构,这意味着在ViewModelProtocol中定义的所有方法或动作也必须在ViewModel中定义。 然后,我们最终可以将视图中的按钮动作与ViewModel中定义的动作连接起来: 我们的MainView是公开的,因此我们可以在viewDidLoad中的按钮上定义所有动作,并将每个动作转发到ViewModel中的块。 这样,ViewController不必知道导航接下来会发生什么,并且无需承担控制UI的责任。

在Swift中编写轻量标记解析器

这篇文章的完整代码可以在这里找到 。 最近,我不得不编写一个轻量级的标记解析器,以在我们的iOS应用程序中提供文本格式。 要求与您在其他富通讯应用程序中可以找到的要求类似: 为了强调单词或句子,用户可以用*号包围文本以创建粗体文本,或用_underscores_表示斜体文本。 要在消息中显示更正,用户可以用〜波浪号〜包围文本以删除文本。 用户可以组合格式选项。 因此,以下文本: 那个*快速*,〜红褐色的狐狸跳过了_ *懒狗* _。 应采用以下格式: 敏捷的棕狐狸跳过了一条懒狗 。 另一个附加要求是字内格式化不应该被允许。 例如,以下文本: 计算_6 * 4 * 8_。 _Quick_zephyrs_blow_。 应采用以下格式: 计算6 * 4 * 8 。 Quick_zephyrs_blow 。 我考虑了实现解析器的几种方法,包括Parser Combinators ,但是最后,我决定从头开始编写它。 这样做可以让我完全控制性能。 我们可以将标记文本格式分解为以下步骤: 令牌化,这是将输入字符串分解为令牌(格式定界符和文本)的过程。 解析 ,这是解释标记以生成格式化文本的抽象表示的过程。 渲染 ,包括将抽象表示形式转换为NSAttributedString 。 让我们详细了解实现每个步骤的细节。 实施分词器 我们的标记格式具有三种不同的标记: text , left delimiter和right delimiter 。 每个令牌应携带上下文信息,例如定界符或实际文本。 让我们创建一个enum来建模: 枚举MarkupToken { […]

使用面向协议的编程进行通用JSON解析

澄清 : Codable,本文是关于实现可与Codable或您可能选择的任何东西一起使用的体系结构的 每个(Swift)软件开发人员最终都将需要为其iOS应用程序(或macOS)解析JSON。 在我的整个职业生涯中,我已经看到了许多关于如何解析JSON的体系结构方法。 您应该经常问自己(开发的任何功能): 您构建的体系结构是否是一种演化系统,可以在不修改基础代码/类/结构的情况下适应任何给定的更改( 如果不是,则不敏捷 ) 。 如果您的方法基于OOP,您是否保持SOLID原则不变? 服务是否为松耦合? 在最近的一次聚会中,我也参加了有关体系结构方法和原理的会议,他们展示了一张带有引号的幻灯片: 开发人员知道可以做什么。 应该做的。 那么应该怎么做……? 让我们分解一下JSON解析,即解析JSON涉及的内容: 调用一个请求到服务器并从闭包中获得响应 (例如)。 在大多数项目中,服务器端以不同的合法性响应我们的请求,因此,我们需要一个验证器来验证JSON响应的合法性,而不管我们需要解析的实际数据如何。 4xx我们可以验证JSON,否则,我们将返回错误。 解析 JSON并反序列化相关对象 从验证器 返回相关错误 。 没什么新鲜的,简单明了的🙂 我们的主要目标是编写一个小型且集中的代码,以分离上述所有步骤,并且当然要使用POP方式( 面向协议的编程 )。 我是Alamofire的忠实拥护者,因此我们假设响应是标准的DataResponse : Alamofire.request(…)。responseJSON {(resp:DataResponse )in …} 我们需要一个协议,该协议将描述我们先前细分的方法集。 我们将需要3种方法: 封装运行整个“显示”的实际逻辑的方法。 验证JSON的方法-此方法将返回内部JSON(稍后将显示一个示例)。 一种将解析我们的数据并创建实际对象的方法,该对象应该位于内部JSON内部。 公共协议ParseProtocol { ? ? )-> 吗? 扩展ParseProtocol { ? } 那返回类型呢? 由于方法begin将封装“整个”逻辑,因此它应该返回我们解析的对象以及一个Error类型,然后返回一个元组如何: func begin(forResponse响应:DataResponse […]

避免使用遏制和子视图控制器的大规模视图控制器

View Controller是提供基本构建块的组件,我们以此为基础来构建iOS开发中的应用程序。 在Apple MVC世界中,它充当View和Model之间的中间人,充当两者之间的协调器。 它以控制器作为观察者开始,该观察者对Model的更改做出反应,更新View,使用Target Action接受来自View的用户交互,然后更新Model。 作为iOS开发人员,即使我们使用MVVM,MVP或VIPER之类的体系结构,在很多情况下我们也将面临处理Massive View Controller的问题。 有时,View Controller的职责太多,无法在一个屏幕中处理。 它违反了SRP(单一责任原则),在模块之间建立了紧密的耦合,并使得重用和测试每个组件变得困难。 我们可以以下面的应用屏幕截图为例。 您可以在一个屏幕上看到至少3个职责: 显示电影列表; 显示可以选择应用于电影列表的过滤器列表; 清除所选过滤器的选择。 如果我们要使用Single View Controller构建此屏幕,则可以确保View Controller会变得非常庞大和肿胀,因为它在一个单一View Controller中处理过多的职责。 我们如何解决这个问题? 解决方案之一是使用View Controller Containment和Child View Controller。 使用此解决方案的好处如下: 将电影列表封装到MovieListViewController ,后者仅负责显示电影列表并对电影模型中的更改做出反应。 如果我们只想显示没有过滤器的电影列表,那么我们也可以在另一个屏幕中重用此MovieListViewController 。 将过滤器逻辑的列表和选择封装到FilterListViewController ,后者仅负责显示和处理过滤器的选择。 当用户选择和取消选择过滤器时,我们可以使用委托与父级View Controller通信。 将主View Controller缩小为一个ContainerViewController ,该ContainerViewController只是负责将“过滤器列表”中的选定过滤器应用于MovieListViewController的Movie模型。 它还设置布局,并使用容器视图添加子视图控制器。 您可以在下面的GitHub Repository中查看完整的项目源代码。 alfianlosari / Filter-MVC-iOS 使用子级View Controller进行封装,可重用,并避免使用Massive View Controller… github.com 使用Storyboard组成View Controller 根据上述情节提要,以下是我们用于构建“筛选器”屏幕的视图控制器: ContainerViewController […]

使用Swift实现基于堆的优先级队列

在计算机科学中存在很多问题,其中使用优先级队列作为基础数据结构可以极大地提高算法的时间复杂度。 Dijkstra的最短路径算法就是一个例子,该算法使用优先级队列在图形中搜索两个顶点之间的最短路径。 遗憾的是,Swifts标准库没有默认的优先级队列实现,因此我们将研究如何自行实现基于堆的优先级队列。 要继续使用自己的IDE,请 单击此链接 以获取源代码 ! 什么是优先队列? 优先级队列是一种数据结构,可以对具有相对优先级的对象进行有效的排序。 您可以将一堆对象放入队列,然后根据它们之间相互比较的重要性将它们一一递回。 假设您已经为计算机创建了许多任务,以便它们在将​​来的某个特定时间运行。 将它们添加到优先级队列将使您的计算机使任务出队,并在仍在等待其截止日期的任务之前获得应执行的对象。 为了实现我们的队列,我们​​将使用堆结构! 什么是堆? 可以将堆看作一棵树,其中每个节点最多有2个子节点。 堆还具有以下限制:它需要将所有新节点添加到顶层,并尽可能地添加到最左边。 看一下下面的图片: 堆还维护与每个节点的相对大小有关的属性。 最小堆(这是我们将要使用的堆)保持以下属性:每个节点都小于其两个子节点,而最大堆则相反。 为了能够维护此属性,我们将需要进行一些操作以获得正确的节点顺序。 当我们插入一个新节点时,我们将其添加到树顶部左侧的第一个可用位置。 如果完成此操作后min min属性不成立,我们将开始与该节点的父节点交换节点,直到达到再次拥有min堆的状态。 下图显示了将2插入到现有的最小堆中时发生的情况:

MVC做对了

或一些想法,以更好地设计您的MVC应用 我一直在写有关MVP或MVVM之类的出色架构模式的文章,但是我们软件开发人员必须问自己一个非常重要的问题:我们是否按照预期的方式使用MVC? 我们是否真的研究过MVC还是只是使用了MVC?我们是在一些古老的Objective-C教程中学到的,只是将ViewControllers像神一样实现了所有功能? MVC:什么是MVC? 如果您了解自己的知识,并且不需要任何人提醒您有关MVC的全部信息,请随时跳到下一部分。 我将撰写有关MVC内容的简短概述。 MVC在1970年代开始在著名的Xerox Parc Alto Research实验室中使用。 当苹果家伙从Altos机器上窃取UI时,他们忘记了窃取MVC。 MVC提出的方案是将您的程序分为三层,这两层相互交互,从而自然地分离出软件的职责。 值得一提的是,MVC主要用于具有UI以及与该UI进行用户交互的应用程序。 例如,在后端实施这种模式没有多大意义。 另一个常见的错误是将整个程序视为MVC(实际上并没有错),而是一个更具哲学意义的问题,而不是一个具体的实现方法。 每个视图都应复制MVC三重奏,并且此组件应相互通信。 模型 :模型表示我们在应用程序中使用的抽象。 这可以是用户,销售,物品等。如果我们使用某种网络层,则模型可能在后端启动,并最终由我们的程序继承。 View :View代表实际的用户界面。在iOS / MacOS开发中,我们使用Xcode的Interface Builder(曾经是一个单独的工具,但在最近几年中已成为Xcode的一部分)创建Views。 尽管我们可能实际上并未编写View的代码, Controller :(或实际上是ViewController )如图2所示,这是Model-View-Controller模式的核心组件。 控制器是您应用程序内部结构的基础。 每个应用程序至少都有一个视图控制器,大多数应用程序都有多个。 每个视图控制器管理应用程序用户界面的一部分,以及该界面与基础数据之间的交互。 ViewControllers是我们实现MVC控制器部分的重要部分,但不是唯一的一部分。 我们将在本文中进一步讨论其他类型的Controller。 如Apple文档所述: 对于iOS应用程序,视图控制器提供了应用程序数据与其外观之间的重要链接。 了解何时以及如何使用视图控制器对于iOS应用程序的设计至关重要。 视图控制器是“模型-视图-控制器”设计范例中的传统控制器对象,但它们还有很多其他功能。 在iOS应用程序中,视图控制器提供了管理基本应用程序行为所需的大部分逻辑。 例如,视图控制器管理屏幕上内容的呈现和删除,并响应于设备方向的变化来管理视图的重新定向。 可以合并由和对象扮演的MVC角色,例如,使一个对象同时满足控制器和视图角色的要求,在该角色中,它被称为视图控制器。 MVC并不是真正地构建整个应用程序,它专注于与用户交谈时进行结构化以及用户如何使用它。 如果您正在使用数据库密集型应用程序,那么这可能不是最佳架构。 MVC在每个视图中实现 要牢记的一件重要事情(我实际上与我的一位同事进行过讨论,他的经验非常丰富,所以我想这可能会对其他人有所帮助)是MVC并不是您应用于应用程序的模式作为一个整体,而是在每个应用程序视图中重复执行的操作。 正如我们在一个假设的应用程序的图3上看到的那样,MVC模式在每个View上不断重复。 MVC面临着“大型视图控制器”综合症或问题:我们实际上拥有这些庞大的ViewController,它们可以处理所有事情:网络呼叫,业务逻辑,数据解析, 人类牺牲,猫狗共处……歇斯底里! 一切都交织在一起,解析器在解析完成后更新了UI(为什么不呢?) 如果这个问题没有道理,答案也不是:任何显然不是数据或显然是图形的东西都会被放入无定形的“控制器”集合中,这最终将整个代码库吸入内部,就像黑洞在其下方塌陷一样。自重” Graham Lee,Inside-Out Apps ViewControllers是您的朋友 避免使用这种可怕的,巨大的ViewController的一个好主意是让多个ViewController控制显示给用户的实际View,而不是使用一个由较小ViewController组成的复合ViewController,自iOS 5起可用 […]

异国同情@ Scout24:与海瑟姆·阿博莎巴见面

我们的新系列《 不同国家 》的第二个故事,同样的激情@ Scout24的特色是Haitham Reda,几年前他从埃及搬到德国工作。 让我们看看他对他的新家有何评价,他在柏林工作和生活的经历,并找出沙拉三明治和咖喱香肠与它有什么关系。 1. 嗨,海瑟姆,你从开罗搬到了柏林。 您为什么决定在另一个国家开始新的生活? 好吧,对我来说,这并不是一个艰难的决定,因为我想成为技术中心的核心,并积极参加各种会议,聚会和大规模工作。 因此,我决定在2014年移居柏林,因为柏林正成为下一个要去的地方和欧洲的技术中心,对此我感到非常高兴。 2. 您正在Scout24担任高级iOS工程师。 您的工作最有意义的是什么? 在加入Scout24之前,我已经使用ImmobilienScout24应用程序找到了自己的公寓,那时我周围的人也是如此。 我加入Scout24就是那种影响和观点,因为这意味着我可以为改变人们最重要的决定之一-居住地做出贡献。 加入Scout24使我有机会与才华横溢的团队合作,无论是技术,产品还是数据分析。 另外,我可以看到我们的工作对数百万用户的影响,并从他们那里得到及时的反馈。 3. 到目前为止,您工作中遇到的一些挑战性时刻是什么,您如何应对它们? 大规模开展工作并每月两次向数百万用户发送该产品并不容易。 有时候,我们在保持和提高应用质量,速度和可靠性以及交付新功能以及及时交付它们方面遇到了技术难题。 在这样的时刻,最好还是退后一步,再次查看具有挑战性的问题,并专注于可交付成果,分解并确定优先级,并在不影响质量的情况下逐一解决它们。 保持镇定和专注是克服挑战的关键。 4. 在埃及工作与在德国Scout24工作之间最大的区别之一是什么? 沙拉三明治与咖喱香肠。 两者都达到目的,但具有不同的成分。 因此,例如在埃及,我们倾向于优先考虑交货速度,而不是其他许多方面,这需要在发货后进行多次迭代以确保质量。 同时,在德国,精度更高,计划时间更长,因此预测更多。 5. 对于正在考虑前往柏林工作的人们,您有什么建议吗? 我会说“你不会后悔”。 柏林是一个非常宜居的城市,具有多元文化。 您将被来自世界各地的才华横溢的人包围,并且您将在会议,聚会和类似活动中与他们交流想法。 此外,这座城市充满了艺术和您想要的所有活动。 一路顺风!

UITests中的模拟网络请求

联网是iOS开发人员必须面对的长期问题。 几乎没有应用程序,不需要它。 在测试中,我们需要可重现且稳定的结果,而网络对此不做任何保证。 理想情况下,我们希望将Mac抛入地下100m的暗洞中并运行自动化测试。 没有网络连接不应该成为测试失败的原因。 由于应用程序依赖于从后端获取数据,因此这需要我们方面的一些工作。 在单元测试的存根网络调用中,我们确保控制了单元测试。 作为一个不错的副作用,它还提供了暗洞选项。 因此,无事可做。 概括地说,OHHTTPStubs用于返回NSURLRequest级别的数据。 遗憾的是,此技巧不适用于任何不与应用程序共享流程的UITest框架(EarlGrey可以工作;)。 继续使用OHHTTPStubs进行UI测试 您的第一个想法可能是:“为什么不在应用程序中使用OHHTTPStubs?” 这是可能的,但是有一些我不喜欢的原因: 很难控制需要退还什么。 它改变了应用程序内的许多逻辑。 它添加(测试)代码,该代码不属于生产代码。 控制后端 在编写UI测试时,您需要考虑要测试的内容。 您是要仅测试您的应用程序,还是要测试系统? 我工作过的公司倾向于测试整个产品。 这只是一个拐杖,因为他们对后端的正常工作没有信心。 测试您的系统听起来不错,但是错误并不能真正告诉您什么地方坏了。 您将不得不四处看看。 所以我建议 您 为您的应用程序使用UI测试。 为此,您必须为测试创建自己的后端。 它必须在同一台计算机上运行,​​因此不存在网络依赖性。 在一台机器上设置整个后端听起来有些矫kill过正,所以让我们跳过这一点。 相反,我建议创建一个模拟后端服务器,您可以在该服务器上控制测试所需的响应。 模拟后端的基本结构 我们对测试后端有什么要求? 我们可以控制,它返回什么 它在我们的应用程序之外运行 最小化我们的生产代码 这些只能通过编写我们自己的后端来实现。 最基本的想法是为每个请求类型提供一个带有字典的程序。 该词典将路径映射到响应。 因此,每当我们调用“ GET / hello”时,它将返回“ 200 OK – world”。 如果您希望将此后端编写为独立应用程序,则可以这样做。 也许有某种网络API,例如: “ / getRoute?path = hello&result = […]