Tag: 工程

iOS 9升级:为什么我的单元测试停顿了?

关于为什么从iOS 8升级到9 SDK导致测试缓慢的调查。 由 艾伦•芬伯格 ( Alan Fineberg)撰写 。 抬起头,我们已经搬家了! 如果您想继续了解Square的最新技术内容,请访问我们的新家https://developer.squareup.com/blog 将Square Register从iOS 8升级到iOS 9时,我们发现单元测试行为发生了难以诊断的变化。 我们调查了此问题,并追溯了我们的步骤以找到根本原因。 最终,我们发现潜在的问题不仅是Square特有的,而且可能会影响任何iOS开发人员。 现在测试缓慢,不可靠 iOS版Square Register已有将近7年的历史,它是一个庞大的应用程序,其中包含超过一百万行代码。 从iOS 8或iOS 9迁移基本SDK似乎很简单,但这种变化的规模却带来了许多挑战。 这些挑战中最主要的是应用程序的测试套件。 实施更改后不久,我们注意到我们的单元测试套件(每个构建都运行了数千个测试)已经大大减慢了速度,最终将失败。 这些测试失败在单独运行时从未发生,但通常在较大的测试套件中失败,并且并非总是以相同的方式失败。 由于没有一个单一的测试有缺陷,所以一个合理的解释表明共享测试环境受到污染。 这是主队列! 下一步是减少草垛的大小。 我们开始注释测试以缩小范围。 在文件变得乏味之后逐一注释掉-我们提供了使生活更轻松的类别技巧(下)。 不幸的是,不存在一种更好的方式来运行与iOS中的命名模式匹配的测试子集: 令人沮丧的是,此Xcode功能仅启动了Instruments的惰性实例,而不是附加到进程。 相反,要描述测试运行,我们必须: 在代码中设置一个断点, 运行测试, 发射仪器, 通过进程ID小心地附加到正确的进程(下拉列表中可能有重复项) 然后点击“记录”。 泄漏工具不认为NSManagedObjectContext(下面的屏幕快照中为SDManagedObjectContext)是泄漏,因为它最终被拆除了。 但是它确实报告了保留/释放对的列表。 因此,我们查看了单个对象超过6000个保留/发布的列表。 当时,我们没有发现问题的根源,因此它又回到了通过lldb进行调试的过程。 经过大量调试并打印出[context keepCount]后,我们发现上下文的save:方法添加了一个保留,并且,如果我们注释掉了这一行,则按预期方式清理了上下文(!),并且泄漏为堵塞。 我回到了Instruments,用此新信息对它的报告进行了交叉检查,并且肯定的是: 罗,未配对的保留。 导致泄漏的保持架的详细视图: Instruments的详细信息视图:堆栈跟踪包括save:,CFRetain和_registerAyncReferenceCallback(sic) 诊断 为了调试,我覆盖了[NSManagedObjectContext(_NSCoreDataSPI)performWithOptions:andBlock]并设置了一个符号断点(基于上面的Instruments输出)。 我的汇编语言不太流利,但是我可以通过阅读以下注释进行跟进: 在第91行,我们增加了保留人数。 在第132行,我们将异步工作添加到队列中。 我们推断出,在调用save:时,上下文正在排队一些工作并同时保留其自身(逐步执行并调用p(int)[$ […]

从零到英雄:ImmobilienScout24 III上本地应用程序开发的故事

这是由Immobilienscout24的移动工程师Iyad Tamer Agha撰写的由三部分组成的本地应用程序开发@ immobilienscout24系列的最后一部分 。 随时阅读第1部分和第2部分,以获取对该主题的更多见解。 非技术措施 技术方法和工具对于确保移动应用程序的质量至关重要,尽管某些非技术性措施可能会导致检测软件错误的时间更早或根本没有发生。 结对编程:我们团队中已建立的一种实践称为结对编程,结对编程是指两个程序员共同工作以共同承担编程任务。 四眼原理导致对编写代码的更多控制和更少的错误。 通过成对工作,将对代码进行更详细的讨论,从而获得更可靠的软件。 协同工作还意味着可以在团队中更好地共享知识。 经验表明,结对编程的另一个副作用是,与单独工作的同事相比,串联工作的同事被打扰的可能性较小。 Mob编程:我们团队中也使用过的成对编程的扩展形式,即mob编程,其中不仅有两个开发人员来完成一项任务,而且整个团队也一起来处理代码。 当团队希望将新功能集成到应用程序中时,该方法被证明是成功的。 这将导致团队内部就该功能代码的共同确定原则达成共识。 代码审查:确保代码质量的另一种方法是代码审查。 实施一项新功能后,团队中的一个或多个开发人员将仔细检查代码。 目的是识别任何编程错误,并检查是否遵守了编写代码的内部准则。 这也鼓励团队内部的知识共享。 我们劳动的成果 首次发布仅一年后,我们的努力就获得了回报。 发布后的第一年,我们有849,000个应用程序下载,这个数字比上一年增加了15%。 我们的App Store评分从3星提高到4星,反映了用户满意度的提高。 这些年来,团队学到了很多东西。 关键课程是: 质量不仅仅是一个技术问题 :结对编程和代码审查之类的实践与对软件质量进行持续改进的技术措施同样重要。 用户永远是对的:如果用户决定应用程序应提供哪些功能,则提供错误内容的风险非常低。 少即是多:一个好的应用不是包含所有可能功能的应用。 删除很少使用的功能并改进常用功能是值得的。 四个星期的发布周期:尽管该应用适合每两周冲刺后发布新功能,但事实证明,最好是四个星期。 这为用户提供了足够的时间来评估新功能,并且团队有机会评估发布的成功并采取适当的措施。 我们的未来方向 大多数基于软件的公司开始与小型团队一起开发移动应用程序。 但是,随着时间的流逝,它们的使用变得如此广泛,以至于对于某些产品来说,大多数流量已经从移动应用程序中产生了,例如ImmobilienScout24就是这种情况。 由于移动应用程序的快速增长,负责移动开发的中央团队的模型已不再足够。 因此,我们的未来计划是在功能团队中扩展移动开发。 我们将iOS应用程序划分为单独的软件组件(模块),以便不同的团队可以彼此独立地开发自己的应用程序需求。 我们期望减少日益重要的移动应用程序的上市时间,因为速度是使自己在竞争中脱颖而出的关键因素。

代客击败OS X钥匙串访问控制列表零日漏洞

我们如何使用测试驱动的开发来复制攻击向量并针对它加强代码 抬起头,我们已经搬家了! 如果您想继续了解Square的最新技术内容,请访问我们的新家https://developer.squareup.com/blog 由 Dan Federman 撰写 。 世界着火了 6月17日,The Register报告说,iOS和OS X钥匙串中存在一个零日漏洞,该漏洞破坏了钥匙串中存储的安全数据。 该文章声称,写入到钥匙串的数据可以被恶意应用程序读取。 在Square,我们编写可转移资金的iOS代码。 安全始终是我们的头等大事,我们非常重视这些要求。 如果这篇文章的主张是正确的,那么世界就着火了。 在太平洋标准时间上午8点过后,当我们意识到该漏洞时,我们立即打开了描述该攻击的文件。 我们发现该攻击的工作方式如下: 恶意应用会在钥匙串中搜索良性应用编写的密钥。 恶意应用程序可以看到密钥存在,但无法读取关联的秘密值。 恶意应用会删除良性应用编写的密钥。 然后,恶意应用将密钥重新添加到不包含值的钥匙串中,并将自身和良性应用添加到密钥的访问控制列表(ACL)中,从而允许恶意应用读取良性应用随后写入这些密钥的所有机密信息。 在这一点上,我们可以松一口气:访问控制列表仅存在于Mac OS X上,而不存在于iOS上,因此尽管出现了头条新闻,但我们的应用程序并不容易受到攻击。 但是,两周前,我们刚刚开放了开源的Valet,这是一个跨平台的钥匙链包装器,其OS X组件很容易受到攻击。 这是我们无法接受的,因此我们加强了代客抵御这种攻击的能力。 有毒的工具 Apple的钥匙串仅提供三种用于更新钥匙串的工具:添加,更新和删除。 但是我们现在知道在OS X上,更新本质上是不安全的。 我们不能相信钥匙串中的现有钥匙没有受损的访问控制列表。 因此,我们仅需添加和删除即可。 解决方案似乎很明显。 更改钥匙串中某项的值时,与其更新现有的钥匙串条目,不如删除现有的项然后添加一个新项。 但是Apple的文档非常清楚:请勿删除然后添加-始终更新。 为什么? 因为“ [删除钥匙串项目时,您将丢失用户或其他应用程序添加的所有访问控制和信任设置。”虽然听起来不祥,但这正是我们想要的效果。 更好的是,此警告不适用于Valet,后者使用安全的共享访问组而不是ACL在OS X和iOS上共享钥匙串值。 检验假设 我们的第一步是编写一个单元测试来检验我们的假设。 我们希望测试尽可能地模仿攻击。 因此,我们首先在ACL中具有多个应用程序的钥匙串中插入一个钥匙。 使用与测试VALValet相同的基本查询来添加此泄露密钥,以便我们的代客能够读取和写入该泄露密钥。 VALValet * valet = [[VALValet分配] initWithIdentifier:@“ MacOSVulnTest”可访问性:VALAccessibilityWhenUnlocked]; NSString […]

Shazam实时歌词功能的幕后花絮

作为重新设计的一部分,我有机会研究了一些非常令人兴奋且在技术上具有挑战性的部分,包括新版本的实时歌词同步功能,或者我们喜欢称其为“同步歌词”的方式。 你们中的某些人可能已经使用Shazam很久了,他们知道此功能不是新功能,我们确实在一段时间前就在应用程序中使用了此功能。 这就是我们交付给用户的魔力的全部。 不久之后,我们将其重新带回去,并使其变得更好。 在这篇博客中,我将分享一些有关开发过程的见解以及我们已经做出的一些决定。 设计! 设计! 设计! 当我们在应用程序中构建新功能时,我们的设计师始终会提供他们如何想象其工作原理的原型。 我们确实关心细节,因此确保每个细节正确都是很重要的。 归根结底,这会让我们的设计师感到满意,但更重要的是,我们的用户也会感到满意。 在歌词同步的情况下,原型是开发过程的基本要素。 在下面,您可以看到我所学习和复制的那本书。 这部影片上发生的事情很少值得注意。 在突出显示的线和未聚焦的线之间有动画,以及实际的运动。 为简化起见,我将其称为白色文本行 , 突出显示的行 。 这条线将失去重点 之后的路线将成为重点。 现在,将它们分解为关键动画: 在行改变的任何给定时刻,突出显示的行的alpha值从1.0更改为0.0 。 (1个动画)。 高亮显示的行必须在屏幕上完全相同的位置居中。 列表滚动一行(1个动画)。 在滚动过程中,即将成为焦点的线(意味着它必须从列表中淡出)将alpha值从1.0更改为0.0 ,而旧的突出显示的线则出现在逆动画的顶部(淡入到清单)。 想象一下,就像您在突出显示的行周围有一个安全区域一样,在列表滚动期间,您必须淡出刚击到安全区域底部的行,然后淡出将保留在顶部的行。 请参见下图,其中红色是突出显示的线条(2个动画)周围的安全区域。 在动画方面,并不复杂。 总共4个动画,它们全部更改一些alpha值。 容易吧? 除了动画之外,使设计更加复杂的是突出显示的行始终是静态的,而其余的行(称为歌词列表)仅是移动的,至少这就是我们要复制的效果。 我与Android团队进行了一些内部讨论,以了解实现这种行为的方法。 在我开始实施之前,考虑了两种可能性。 同步歌词#1 一种可能是将歌词列表视为两个单独的集合视图[1]和一个静态视图。 我喜欢将此静态视图称为焦点视图 。 在这种情况下,我们将一个集合视图视为已通过的歌词,将焦点视图视为突出显示的行,将第二个集合视图视为将要出现的歌词。 优点 焦点视图在两个集合视图之间是静态的,因此我们无需计算视图的位置并同步其后的文本。 易于在视图上设置布局约束。 缺点: 我们将歌词列表视为两个单独的集合视图,因此我们需要从底部集合视图中删除第一行,并将突出显示的行重新插入到顶部集合视图中。 在调试和编写漂亮的代码时,这可能会引起头痛。 该功能的要求之一是在屏幕上实现用户交互(稍后进行介绍)。 对于两个集合视图,这将非常困难。 我们可能需要创建第三个集合视图…pphhhh,什么令人头痛,对吗? 此外,另一个要求是在两行文本之间插入一个广告(稍后再阅读)。 使用这种布局很难在同步过程中跳过广告。 同步歌词#2 另一种可能性,即我们实际实现的可能性,将歌词列表视为单个集合视图,并将焦点视图置于其上方。 […]

自定义UICollectionViewLayout自动布局和动态类型

Gousto的iOS团队对其所有UI使用自动布局,因此我们能够容纳可变内容,动态类型和不同大小级别的设备。 当我们想将食谱列表切换到中等规模的设备上的网格时,我们很难找到有关如何使用UICollectionView和用于UICollectionViewCells的XIB实现此目的的任何文档。 这篇博客文章讨论了我们遇到的问题以及如何使用自定义UICollectionViewLayout实现它。 为什么? 自从引入动态类型以来,我们认为支持它对我们的用户将是有益的(这意味着响应用户在其设备上设置的字体比例)。 当要求重新设计我们的主要配方列表以添加另一种具有不同高度的电池时,我们认为这是一个绝佳的机会,应该不会太痛苦。 苹果公司说“在开始构建自定义布局之前,请考虑这样做是否真的必要”,这也总是很棒。 在整个这篇文章中,我将展示一些代码,但是我还将附加一个演示项目,以便您可以看到它的工作! 固定约束 以前,我们的单元格都具有相同的高度,而我们的食谱标题标签都具有高度限制,且字体大小最小,因此字体大小会缩小以容纳文本,在极端情况下,标题会被截断。 我们要做的第一件事是删除所有不必要的约束并使所有变量可变。 我们拥有的单元格布局非常复杂,带有许多子视图(有时是不必要的),因此我们对此进行了整理,并尝试使其尽可能平坦,并针对不同的场景使用了不同的单元格。 介绍自定义布局 如果要创建自定义布局,则基本上必须自己做好所有事情,这可能就是Apple不真正推荐它的原因。 在布局中,有三种主要方法: func prepare() 最初,我认为该方法一开始只会被调用一次,但是它被频繁调用,因此,如果layoutAttributesForItems为空(这是我们用于单元的缓存),我们只想计算估计的单元大小。 第一次调用时,我们的缓存为空,因此我们创建了初始布局。 在initialLayout中,我们实际上将执行我们的像元大小估计。 首先确定我们需要多少列,因为根据窗口的宽度,我们有1、2或3。 上面的代码创建了两个数组,对于我们的X和Y单元格位置,我们仅用列数的位置(例如三列)来初始化y数组 yOffset = [0,0,0] 我们基于列再次完成xOffset数组,因此如果我们有一个1024像素宽度的设备和三列,我们将拥有 xOffset = [0,341,682,0,341,682,…],依此类推。 接下来,我们需要遍历每个单元并为每个单元创建布局属性,并将其存储在本地缓存中。 在创建框架的最后,我们需要添加下一个yPosition,以便该列下面的单元格知道它的开始位置。 我们还将为下一遍设置该列。 最后,我们需要用新的高度更新contentHeight属性。 contentHeight + = collectionView.contentInset.bottom var collectionViewContentSize:CGSize 这只是返回我们在prepare方法中刚刚计算出的collectionView内容区域的大小。 func preferredLayoutAttributesFitting(_ layoutAttributes:UICollectionViewLayoutAttributes)-> UICollectionViewLayoutAttributes 这可能是使布局正常工作的最困难的部分。 我们发现没有多少文档对我们有帮助,因此为此进行了大量的尝试和错误。 在准备好布局之后,我们像往常一样将一个单元出队,并会自动调用preferredLayoutAttributesFitting。 这是单元格有机会指示其首选属性(包括尺寸)的位置,我们使用自动布局来计算这些属性。 我们将垂直fittingSize设置为压缩,因为我们希望像元在满足其约束的情况下尽可能达到最小高度。 该方法的关键是systemLayoutSizeFitting…因为我们的collectionView垂直滚动,所以我们可以将水平优先级设置为.required-这意味着它只能与预先计算的宽度一样大。 垂直优先级设置为.defaultLow,因为我们需要单元能够增长。 最初,我们弄错了这一点,最终导致一些单元会增长到1000像素…… 现在,布局将调用invalidate,在其中我们检查自originalAttributes之后单元格的高度是否已更改。 如果没有的话,我们会忽略它,但是如果有…… func […]

measureBlock:性能测试如何在iOS中工作?

抬起头,我们已经搬家了! 如果您想继续了解Square的最新技术内容,请访问我们的新家https://developer.squareup.com/blog 我在Square的一个小型项目中工作,涉及iOS的性能单元测试-本质上,研究如何引入性能单元测试,我们的选择是什么以及如何在CI(持续集成)上扩展。 在关注Apple作为其单元测试套件的一部分提供的一种神奇的measureBlock方法时,问题是:它如何工作? 而且,这对我们和CI流程都有效吗? 什么是measureBlock? 对于那些不知道measureBlock,有一点背景知识:当您在XCTest编写单元测试时,可以使用一项功能来测量执行代码块所需的时间。 在Objective-C中看起来像这样: 它是如何工作的? 基础 不幸的是,在我偶然发现2014年WWDC会议的一些旧幻灯片之前,Google的业绩还很低。 本文档说明measureBlock 运行您的块10次,并计算运行块所需的平均时间。 然后将该平均值用作基线。 第一次运行测试时,它会失败,因为尚未建立基线,因为该基线是在第一次运行时计算得出的。 您可以手动修改该基准。 在随后的测试运行中, measureBlock仍然会运行您的模块10次,但这一次它将把运行时间的标准偏差与基线进行比较。 如果向上或向下的折扣超过10%,则您的测试将失败。 所有这些设置也可以手动更改。 基线与平均值 Xcode显示了一个弹出窗口,它同时显示了基线和平均值。 两者之间的区别是:平均是上次运行测试运行代码块所花费的时间。 基线是您选择的固定设置(如果不执行,则由Xcode自动设置)。 将标准偏差与基线进行比较; 弹出窗口中显示的平均值对您的测试没有任何影响。 为什么使用标准偏差 以下图表显示了给定代码块10次运行的运行时间: 平均时间为1秒。 (摘自Apple WWDC幻灯片) 现在,这是第二个图表,其中平均时间也是1秒: 显然,平均水平并不能说明全部情况。 这就是measureBlock将标准偏差与基线进行比较的原因-因为标准偏差告诉我们有关测量范围的信息。 基线存储在哪里? 因此,对于那些在多台计算机上运行CI的大公司工作的人来说,现在面临的主要问题是:基线存储在哪里? 我只是通过使用git,添加性能单元测试并查看文件diff来弄清楚这一点。 Xcode将基线存储在project.xcodeproj/xcshareddata/xcbaselines/…下的项目文件包中。 此文件夹将包含一个.plist列出给定主机+运行目标组合的所有性能测试设置,以及一个包含所有主机列表的Info.plist 。 基线特定于运行测试的主机和目标设备(例如,iPhone 7模拟器)。 Xcode生成唯一的UUID来识别组合(机器+目标),并将所有性能设置绑定到该组合。 该组合由计算机的规格定义-因此,如果您在具有完全相同的规格的另一台计算机上运行性能测试,则将拉取相同的基准(请参见下面的屏幕快照,了解用于定义组合的规格) 。 索引所有主机和目标组合的Info.plist如下所示: 这是给定主机性能测试设置的.plist的示例: 因此,当将这些检查到您的代码存储库中时,每台机器都必须具有自己的设置。 这是合理的,因为主机之间的性能会有所不同,模拟器也会有所不同。 但是,如果您在一家大型公司拥有数百台虚拟机,则可能会变得棘手。 问答环节 这是什么版本的Xcode? Xcode 9.2 您是如何找出这些人的? […]

借助新的iMessage集成,可以更轻松地进行团体订购

作者:软件工程师Andy Mai 今年早些时候,在苹果全球开发者大会上,我们为DoorDash客户演示了一种直接从新iMessage应用程序下订单的方法。 在过去几个月中完善了该功能并成功推出了适用于iOS10的新iMessage之后,我们很高兴看到新的DoorDash iOS应用现在可以使用适用于iMessage的DoorDash。 现在,您可以与同事共进午餐,计划足球前的后挡板,或为整个家庭订购晚餐,所有这些都可以通过现有的iMessage群组聊天进行。 那么它是怎样工作的? 首先,请确保已为iMessage启用了DoorDash应用。 为此,请在iMessage中单击应用程序商店图标,然后在“管理”选项卡下选择添加商店,并将DoorDash应用程序滑动至“打开”。 将应用程序安装到iMessage之后,创建群组消息,单击应用程序商店图标,然后选择DoorDash以查看您喜欢的餐厅的列表,就像在DoorDash应用程序中一样。 从那里开始,就像发送短信一样简单。 一旦您从DoorDash的出色选择中选择了一家餐馆,您的对话中就会出现一条新的聊天消息,并且可以立即进行小组订购。 只需将消息发送给iMessage群聊中的其他参与者,他们就可以出发了。 通过点击聊天气泡,已经拥有DoorDash应用程序的人将被带到商店的菜单页面,他们可以在其中添加项目到群组订单中。 然后,他们可以通过更新聊天气泡将更新(即Jeff添加了3个项目)发送给其他参与者。 没有安装DoorDash应用的参与者将被提示直接在iMessage中下载它,然后可以继续将他或她选择的项目添加到组订单中。 一旦每个人都选择了自己的食物并将其添加到购物车中,小组订单的创建者就可以随时准备好提交订单。 瞧,已经为团队午餐定购了食物! 在DoorDash,我们一直在寻找使交付比以往更轻松的方法,我们很高兴看到这种集成使DoorDash与大多数人每天使用的应用程序无缝地协作。 因此,下次您要招待一群朋友共进晚餐时,请不要着急获得每个人的订单。 只需发送一个小组iMessage,让他们做出您想要晚餐的艰难决定。 然后坐下来,让DoorDash处理物流。

从行程到小部件

Skyscanner应用程序动态结果页面的故事 由Zsombor Fuszenecker 到目前为止的故事… 在Skyscanner,我们会不断地在我们的应用程序上进行迭代,以帮助旅行者找到适合您旅行的最佳航班,酒店或租车服务。 毫不奇怪,搜索结果页面是应用程序最重要的部分之一。 这是我们的用户将大部分时间用于比较结果和更改搜索参数的地方。 最初,航班搜索结果页面仅用于显示航班行程。 但是,在发布具有以下结果列表的新版本的应用程序后,我们意识到可以改进来自同一提供商的具有多个选项的路由。 请注意,上面的结果非常相似(相同的承运人,价格相差不大,飞行时间略有不同),并且像这样的列表在小屏幕上显示有大量物品并不容易浏览。 我们的目的是使比较变得简单,因此我们最近修改了此页面的外观,以确保比较飞行选项在尽可能小的屏幕上进行浏览。 我们迅速从了解问题转向研究可行的解决方案,然后迭代许多原型。 最后,我们认为我们找到了最佳解决方案:我们没有显示具有很多直航的航线的几乎相同的航班列表,而是按航空公司对列表进行了分组。 通过这样的概述,可以很容易地比较直接承运人及其价格。 我们称这个额外的内容为小部件 。 随着时间的流逝,我们想要添加到结果列表中的事物数量越来越多。 警告消息,推荐小部件和赞助广告只是结果列表中许多其他项目的一部分。 每次我们添加新类型的内容时,更改应用程序的代码都不是一件容易的事。 有一个巨大的文件,其中包含许多条件和边缘案例处理逻辑。 我们还想进行试验,这意味着我们的数据源甚至充满了条件。 创建新的小部件时,开发人员必须经过数百行的If语句。 没有我们的全力支持,一切都容易被打破,其他团队也无法做出贡献。 因此,在意识到我们无法在结果列表上进行足够快的迭代之后,我们开始计划在其背后的新架构。 设计目标: 快速迭代新的小部件并使其更容易进入列表 最小化创建新小部件会破坏另一个小部件的风险 使实验变得容易; 添加新的小部件应该不难,也不需要列表后面的团队的全面支持。 可以并行或顺序运行小部件计算代码。 使列表在后端可配置,以便独立于应用发布周期发布 我们称该项目为“ Widgetify”。 从构思到生产: 我们的第一步只是绘图。 我们自己坐下来,做了一些建筑草图。 然后我们提出了自己的想法,并达成了团队共识。 在初步感觉到“它在理论上可行”之后,我们创建了Feature标志并将其推到我们的主分支(尽管我们并未在生产中启用该功能,甚至在内部也未启用)。 然后,我们创建了基类,其中第一个调用端口是显示默认内容的列表。 我们能够对列表进行过滤和排序,所有内容(甚至分析)都可以像以前一样工作。 在此阶段,我们默认情况下在内部启用此功能,以便尽快捕获错误 接下来,我们创建了一些虚拟小部件,并确定了该项目可以证明未来。 有时,我们发现一个旧的小部件无法在新平台上正常工作,因此我们不得不关闭该功能并修复该特定的小部件提供商(在Skyscanner,我们现在每个平台执行2周的发布周期)。 最终,当我们觉得有东西要向其他人展示时,我们开始在内部推广该平台并收集其他团队的反馈。 为此,我们在团队旁边坐了三天,帮助他们创建自己的内容。 作为回报,我们获得了宝贵的反馈,并且基于集体反馈,我们反复进行以使构建新内容变得更加容易。 卡和提供者 Widgetify的工作方式如下:我们要在列表中显示的所有新内容(卡片)类型都必须具有provider 。 我们可以基于功能标记注册此提供程序以启用内容类型。 但是提供商并不总是需要退回卡。 例如,如果您是美国公民或永久居民,或者您位于美国境内,则只有在您的旅行符合美国政府批准的十二个类别之一的情况下,才可以前往古巴。 这是法律要求。 我们在每次搜索时都会启动Cuban警告提供程序 ,但是,只有在搜索条件和用户符合条件时,它才会返回一张卡片。 […]

笨拙的UI是一个好的UI:在iOS中使用Swift的MVP

由Mohamed Iyad Tamer Agha撰写 在开发iOS应用程序时,Model-View-Controller是一种常见的设计模式。 通常,视图层由以编程方式定义或在xib文件中定义的UIKit元素组成,模型层包含应用程序的业务逻辑,而由UIViewController类表示的控制器层则是模型和视图之间的粘合剂。 这种模式的一个很好的部分是将业务逻辑和业务规则封装在模型层中。但是,UIViewController仍然包含与UI相关的逻辑,这意味着: 调用业务逻辑并将结果绑定到视图 管理视图元素 将来自模型层的数据转换为UI友好格式 导航逻辑 管理UI状态 和更多 … 承担所有这些责任,ViewController经常变得庞大,并且难以维护和测试。 因此,现在该考虑改进MVC来解决这些问题了。 我们将此改进称为模型视图演示者MVP。 MVP模式是由Mike Potel于1996年首次提出的,多年来讨论了多次。 Martin Fowler在他的GUI体系结构文章中讨论了这种模式,并将其与其他用于管理UI代码的模式进行了比较。 MVP有很多变体,但它们之间的差异很小。 在这篇文章中,我选择了似乎在当今的应用程序开发中最常用的通用示例。 此变体的特征是: MVP的视图部分同时包含UIViews和UIViewController 该视图将用户交互委托给演示者 演示者包含处理用户交互的逻辑 演示者与模型层进行通信,将数据转换为UI友好格式,并更新视图 演示者不依赖于UIKit 视图是被动的(转储) 以下示例将向您展示如何实际使用MVP。 我们的示例是一个非常简单的应用程序,它显示了一个简单的用户列表。 您可以从此处获取完整的源代码:https://github.com/iyadagha/iOS-mvp-sample。 让我们从一个简单的用户数据模型开始: [js] struct用户{ 让firstName:字符串 让lastName:字符串 让电子邮件:字符串 年龄:整数 } [/ js] 然后,我们实现一个简单的UserService,它异步返回用户列表: [js] 类UserService { //服务延迟交付模拟数据 func getUsers(callBack:([[User])-> Void){ let users = [User(firstName:“ […]

苹果手表架构

去年12月,我们发布了FanDuel Apple Watch应用程序,为玩家提供了一种快速简便的方法来检查他们的阵容在移动中的表现。 创建应用程序给我们带来了许多有趣的挑战,包括如何干净地设计应用程序。 我们练习了测试驱动的开发(TDD),并允许这种方法来驱动体系结构。 事后看来,我们当时做出的某些决定变得更加容易,但我认为准确地安排思考过程是很有用的。 我们从解决与iOS应用程序最不通用的区域开始; 手表和手机之间的消息传递。 尽管watchOS 2可以直接在手表上编写网络代码,但我们无法利用这一点。 我们的iOS应用程序的联网部分相当复杂,并且向用户(尤其是那些玩实况游戏的用户)发送数据是一项时间紧迫的操作,因此,利用我们已经在电话上拥有的联网堆栈更加有意义。 然后,我们研究了实时消息传递,以及当用户打开手表应用程序或查看Glance时是否发送网络请求以下载用户的最新数据。 为此,我们发现watchOS 2中的WCSession类引入的实时消息传递功能非常适合。 此外,由于我们必须包装WCSession并为其提供委托,因此有必要创建一个类坐在它与应用程序的其余部分之间。 在电话上,我们创建了WatchConnector类,在手表上,我们创建了PhoneConnector类作为其相反的数字。 这些类的功能非常相似。 他们通过充当代表从WCSession接收消息,并在收到指示时将消息发送到WCSession。 一旦我们对如何构造两个设备之间的通信有了一个大致的了解,就该开始着眼于将它们与watch应用程序中的屏幕连接起来了。 对于实际的屏幕架构,我们能够从FanDuel手机应用程序中大量借用。 基本上,这意味着将所有表示逻辑从UIViewControllers引入Presenters,并将所有业务逻辑从ViewControllers和Presenters引入Interactors。 看起来类似于以下内容: 由于watch应用程序需要绝对最少的设备上业务逻辑,因此我们决定在Presenter层和Interactor层之间插入消息传递机制,并有效地使用上述架构,但是会在两个设备之间传播。 此时,实际上只剩下两个问题需要回答: 演示者如何告诉我们的连接器发送消息? 我们的连接器如何通知演示者他们收到了他们感兴趣的消息? (当然,这是在电话端镜像的,交互器与连接器通信,反之亦然)。 我们决定在Presenters和Connector之间包括一个Requester类,该类将接收来自Presenter的事件信息,并将其转换为请求消息,以供Connector发送到电话。 同样,我们决定在电话上开设一个名为Responder的类,该类负责获取请求消息的主体并将其路由到用于网络和业务逻辑的正确Interactor。 从理论上讲,既然我们可以有多个请求者一次请求数据,或者有多个响应者同时返回数据,但是每边只有一个WCSession,那么这似乎是实现观察者模式的理想场所。 也就是说,请求者将注册为PhoneConnector的观察者,该观察者将在收到消息时通知他们,从而允许他们在必要时采取措施。 这就是我们最终的架构,如下所示: 对于我们的FanDuel Apple Watch应用程序来说,这感觉像是一种非常灵活且明智的体系结构,尤其是考虑到我们遇到并克服的最初挑战。 总体而言,这是一个相当干净的体系结构,其中每个部分都有明确定义的单一职责,并且可以轻松地进行隔离测试。 我敢肯定,还有待进一步改进,我迫不及待地想要进行更多的实验。 Matthew Healy,iOS工程师和Scrum Master