Tag: 软件工程

LiveCollections第4部分:轮播表

这是一个足够具体的案例,我认为它值得保留自己的内容。 如果您曾经使用过内容驱动型应用程序(例如Scribd),在其中选择了许多令人兴奋的个性化类别,那么您以前可能已经与该结构进行了交互。 如前所述,在Scribd中,您在应用程序中看到的每个类别都是UITableView的一行,而水平轮播是UICollectionView 。 这导致听起来与情况3和4相似的情况,但是增加了一些复杂性。 主要区别在于我们将动画应用于需要协调的两种不同类型的视图对象。 这种安排的部分技巧是我们不想执行UITableView重新加载操作。 即使您传递animationStyle = .none ,该单元仍然可以闪烁。 更糟糕的是,重新加载单元格会导致出队操作,因此在重建单元格时,请告别任何不错的UICollectionView操作。 另一个要考虑的因素是,我们要确保基础表格视图在考虑其自己的动画完成之前,等待所有大学视图动画完成。 方案7:轮播表 在这种情况下,我们需要解决的主要问题是我们有一个UITableView和N个UICollectionViews ,我们需要它们以协调的方式进行动画处理。 也就是说,我们希望能够对表格视图行上的插入,删除和移动进行动画处理,但是对于任何重新加载,我们都不希望表格视图来处理它,而是触发集合视图动画。 为此,我们首先创建一个新的数据结构,如下所示: struct CarouselRow:相等{ let标识符:字符串 放电影:[电影] }扩展名CarouselRow:唯一标识{ typealias RawType = CarouselRow var uniqueID:字符串{返回标识符} var hashValue:Int { 返回movies.reduce(identifier.hashValue){$ 0 ^ $ 1.hashValue} } } 由于我们没有使用多个部分,因此我们的CarouselRow结构只需要符合UniquelyIdentifiable ,并且可以使用CollectionData更新表视图。 还要注意,它包含[Movie]数组,该数组也符合UniquelyIdentifiable 。 这将传递给一个子控件,该子控件将控制UICollectionView并将使用CollectionData保留其自己的数据。 那么,我们如何协调所有这些呢? 面临的挑战是让拥有UITableVIew主控制器在正确的时间更新每个子控制器,但是如何? 这将通过两种关键方式进行处理: 表格视图和集合视图的所有数据更新将首先通过CollectionData和collectionData.update(carouselRows)通过主表格视图控制器。 主表视图控制器将采用委托CollectionDataManualReloadDelegate ,该代理将为其提供更新子控制器所需的定时挂钩。 这是我们将在CollectionDataManualReloadDelegate使用的两个相关函数: func willHandleReload(在indexPath:IndexPath)-> Bool func […]

LiveCollections第5部分:数据工厂,非唯一数据和高级功能

最后要看的重要功能是数据工厂,稍后我将对其进行解释,然后我将介绍一些我尚未介绍的其他功能(因为我只是不够擅长于其他功能)将它们放入前面的示例中)。 数据工厂:这是什么?为什么要使事情变得更复杂? 当我开始这个项目时,这是我没有想到的功能,但是在Scribd中用LiveCollections制作食物后,这项功能成为必需的功能。 必需的是, CollectionData中的计算基于采用UniquelyIdentifiable通用类型,因此,使用默认的对象相等性函数定义相等性比较。 这是有问题的,因为我们必须对每种数据类型的均等性进行单一定义。 尽管这对于数据管理是正常的,但对于该数据的视图表示而言却不是理想的选择。 平等本质上是定义视图中行/项目的重载 。 这种不平等告诉我们发生了变化。 问题是我们可能不想在数据的两个不同表示形式中使用相同的相等概念 。 具体来说,我们可能需要考虑其他信息,这些信息会修改我们的数据,但它们是扩展的元数据,而不是根属性。 有两种方法可以解决此问题: 只需创建一个包装类/结构来定义数据类型和其他元数据的元组。 创建相同的包装类/结构,但使用采用UniquelyIdentifiableDataFactory协议的对象来支持它,并将为您构建包装器。 解决方案1在有限的情况下完全适用,而解决方案2隐藏了映射逻辑以转换数据,并允许您公开标准化的更新方法(您可能需要这种方法来实现自定义协议一致性,注入和可测试性)。 如果包装类型是标准类型Movie MovieWrapper ,则这是方案1的更新函数: func update(_数据:[ MovieWrapper ],完成:(()-> Void)?) 对于解决方案2: func update(_数据:[ Movie ],完成:(()->无效)?) 用法和抽象方面的微小但重要的变化,因为它与到目前为止我们在所有示例中一直使用的相同API相匹配。 好的,我们如何建立数据工厂? 我们将在最终方案中对此进行回答… 方案9:使用数据工厂 让我们从一个简短而简单的情况开始。 数据对象将是Movie ,而我们要添加的元数据是boolean isInTheaters: Bool 。 本质上,即使电影结构上的所有数据保持完全相同,但是电影结束了其戏剧性的放映,我们仍要在视图中重新加载该单元格(大概是删除横幅或徽章)。 为此,我们需要一个将在CollectionData使用的新结构。 我们可以将其称为DistributedMove以表示已拾取要在剧院上映的电影。 看起来像这样: struct DistributedMovie:哈希{ 让电影:电影 let isInTheaters:布尔 } 扩展名DistributedMovie:UniquelyIdentifiable { var rawData:电影{返回电影} var […]

为初级移动工程师寻求第一份工作的建议

新的移动工程师经常问我如何找到他们的第一份工作。 事实是:这真的很难。 如果您刚从训练营出来或是自学成才,就没有需求。 几乎没有公开招聘的初级职位。 每个人都想要一个高级工程师(或至少一个具有CS学位的人。) 但这不是不可能的! 这只需要时间,坚韧和精心计划。 不要放弃 我本人的职业生涯非常迷人,没有CS学历。 我实际上来自戏剧世界,这是一件更加残酷的事情。 演员们说:“每个工作都要进行100次试镜。”当您是一名初级工程师时,对您来说也是如此,面试很难。 但是经过1-2年的扎实经验,您需要的面试次数将减少到10次。然后,当您更高级时,您必须关闭LinkedIn通知,您将有很多次面试。提供。 那么,您有什么可以做的来最大化您获得工作的机会? 是! 很多。 多年来,无论是作为一名招聘经理还是一名导师,我都看到很多糟糕的招聘数据包和投资组合。 (而且总是相同的错误。)本文将重点介绍每个人都可以对其简历进行的修复,示例应用程序和代码示例。 将其视为初级移动工程师求职的优化清单。 免责声明:这些是我的意见。 这是我的文章。 它不代表我的雇主或除我以外的任何其他实体的意见。 您需要做所有这些事情吗? 不! 人们没有做我在此列表上所做的任何事情而被录用。 我将为示例应用程序推荐的很多东西实际上都是高级的或利基的,不一定每项工作都需要。 但是,您无法接受“足够”的位置。 您需要使所有其他申请人脱颖而出 。 这些优化将使您的应用程序对更多的招聘经理(最终决定将您加入团队的公司人员)印象更为深刻。 对中级学生也有好的建议吗? 当然。 有意见吗? 阅读免责声明。 适用于Android吗? 当然。 我的职业生涯大部分时间都在iOS和Flutter上,但是这个好东西应该对任何移动工程师都有帮助。 (尽管,我将在此处使用iOS示例。)这会带来工作吗? 没有保证。 我不能给你一份工作甚至领导。 您必须自己完成这项艰苦的工作。 但是,执行所有这些操作,您可能会更轻松。 如果要申请FAANG,这是应该做的吗? 没有。 关于向大型科技公司(以及模仿其正式流程的公司)进行申请的文章已经很多。 作为没有CS学位的人, 您最好的选择是在代理机构和初创公司 (通常比理论知识更重视应用技能的公司)。 如果您想在大公司工作,那是可能的! 我有,我曾经在你的位置。 在这些公司的职业网站上查找有关此信息。 只是知道,这是另外一回事。 好。 关于建议。

反对第三方依赖

“这不是每天的增加,而是每天的减少。 摆脱不必要的东西。” 李小龙(Jeet Kune Do)的道 介绍 请考虑以下情形:一群开发人员正在从头开始进行短期项目。 客户很着急,因此他们决定不重新发明轮子,而是使用人类尽可能多的外部依赖项。 发展的步伐是荒谬的。 几周后,吉拉(Jira)有许多封闭票。 真好 大家都很开心 几个月后,开发人员可以完全控制很多第三方依赖项,并且只有很少的代码。 几年后,有一个项目很难维护。 不再支持某些依赖项。 语言版本已更改,客户需要支付叉子维护费用。 哦,与此同时,这十七个外部框架之一存在一个小的安全问题…… 三个问题 第三方开源库很棒。 (通常)将它们抛光到可能的极限。 使用它们(通常)是一种乐趣。 (通常)它们是由比我聪明得多的人开发的,我对此非常赞赏。 Chapeau为那些将自己的时间花在使其他开发人员的生活变得更轻松的开发人员上而起。 但是,使用它们总是会带来一些风险-这可能是个问题。 如果不再支持该库怎么办? 您准备好承担维修货叉的费用了吗? 还是迁移到另一个解决方案并进行大规模重构会更便宜? 如果存在安全漏洞会怎样? 这不是一个不现实的场景-足以回顾流行的AFNetworking的情况。 添加第三方代码始终带来增加潜在的未检测到的漏洞的风险。 如果您的要求在几个月后发生变化,会发生什么? 您的项目在变化,第三方框架也在变化。 在某些时候,重叠的路径可能会明显分开。 您可能需要已经集成的第三方不提供的功能,或者以无法接受的方式提供的功能。 那你要怎么办 摘要 我不反对使用第三方库,我反对失去对代码的控制并承担不必要的风险。 在将另一个第三方依赖项添加到您的项目之前,请考虑是否确实必要。 想想如果它不再被支持会发生什么。 迁移到另一个解决方案或分叉维护的成本可能是多少。 不要误会我的意思-第三方代码不是万恶之源。 它很有用,有助于更快地交付产品并提高整体生产率。 所有这些都是在风险与收益之间取得平衡。 如果您决定使用第三方依赖关系,请使用适当的设计和合理的抽象层来保护自己。 就像古代的斯多葛式的一样-始终牢记最坏的情况。

测试驱动开发:开发人员魔术棒

让我们讲一个故事。 “不久以前,我已经为即将上线的测试仪提供了我的应用程序的构建。 我已经对其进行了一些修复,根据我的开发人员健全性测试,这些构建已使生产准备就绪。 但是经过测试,情况完全不同。 在某些情况下,由于某些较早的修复程序,我遇到了错误。 搞什么鬼??? 除了使我的构建稳定之外,我使它更加不稳定。” 我不是唯一遇到这种情况的人,但是许多开发人员也遇到了这种情况。 那么我们该怎么做呢? 答案是测试驱动开发(TDD)。 我们要学什么? TDD上有很多博客。 我将列出您可以在其中找到的一些最佳参考。 我们不会讨论理论上的TDD概念,而是将重点放在我们如何从开发人员的角度计划实现TDD。 什么是TDD? 为什么选择TDD? 如何规划TDD? TDD的警告。 您需要TDD吗? 看起来太多了吗? 不用担心,我们会做的很快而简短。 😉 什么是TDD? 测试驱动开发是美国软件工程师肯特·贝克(Kent Beck)提出的开发程序,在编写代码的同时,我们还记录了测试用例。 从而允许我们在继续开发的同时测试我们的代码。 下图描述了传统开发与TDD之间的区别。 在传统开发中,我们首先开发代码,完成功能并进行手动测试。 但是在TDD中,我们首先写下测试用例,然后相应地开发代码。 这有助于我们最大程度地减少代码失败或错误的机会。 如果不更新旧的测试用例,则开发的任何新功能也应尊重现有的测试用例。 TDD基于RGR的概念,即红色,绿色和重构。 红色:我们首先编写失败的测试用例。 绿色:我们编写通过测试所需的最少代码。 重构:如果需要测试代码,我们也会重构代码。 为什么选择TDD? 最大限度地减少代码失败的机会。 最小化团队中任何新开发人员在代码库上工作的机会。 由于引入了新功能或错误修复,破坏现有功能的机会较小。 提高产品知识。 改进编码标准。 如何规划TDD? 百万美元问题来了? 我们都知道什么是TDD。 但是我们如何计划TDD? 我们将如何决定需要编写哪些测试用例? 我要编写并开发该功能,然后为它们编写测试还是将来如何? 困惑? 了解正确实施的功能。 让我们以以下要求为例: 要求: 构建一个程序,该程序将从用户那里获取城市的人口输入,并返回该城市所属的类别。 以下是城市类型及其人口范围: 小(5,000至10,000) 中(10,000至50,000) […]

以正确的方式处理Swift第1部分

扩展,委托,黑盒,覆盖 不要这样 MyViewController类:UIViewController,UITableViewDelegate,UITableViewDataSource,UIPickerViewDelegate,UIPickerViewDataSource {…} 为什么? 这是不好的编码,您可能会继续向MyViewController添加更多的委托类,并使该类变得可怕。 记住规则:模块必须少于400行代码,包括注释。 像这样 制作一个名为MyViewControllerTableExt的新Swift文件,并将其添加 扩展MyViewController:UITableViewDelegate,UITableViewDataSource {…} 制作另一个名为MyViewControllerPickerExt的新Swift文件,并将其添加 扩展MyViewController:UIPickerViewDelegate,UIPickerViewDataSource {…} 为什么? 这些文件的名称将使代码更易于阅读和调试。 在小模块中跟踪错误比千行模块容易得多。 这也是良好的编码习惯。 几个月后,回顾一下代码,您会发现它们仍然易于阅读和维护。 不要这样 从父控制器 self.present(childViewController,animation:true){} 然后从该子控制器中按一个按钮 self.dismiss(动画:true){} 为什么? 这是错误的编码。 父母失去了控制。 它打电话给孩子,直到孩子决定解散自己才知道工作何时完成。 在最坏的情况下,父母可能会叫几个孩子,却不知道哪一个仍在记忆中。 这是不对的。 除非子控件是类似于box的单按钮屏幕,否则不要使用它。 像这样 创建一个名为globalStruct的快速文件,并添加此文件 协议ChildDelegate:类{ func childResult(数据:字符串) } 在子viewController中,添加委托 class ThisChildViewController:UIViewController { 弱var委托:ChildDelegate? … } 当孩子完成工作后,将控制权和数据传回给父母,然后它将做 自委托。 childResult(结果); 请注意, 孩子不会调用self.dismiss。 然后从父目录创建一个名为MyViewControllerChildExt的新Swift文件,并执行以下操作 扩展MyViewController:ThisChildDelegate { func childResult(data:String){ self.dismiss(animated:true){//父级在其控件中关闭子级 […]

正确地使用Swift第2部分

虚拟屏幕,注释,模板,命名,静态表格… 上一部分在这里 您可能会注意到,在连接速度较慢的情况下,您的Facebook应用程序屏幕上充满了上图所示的虚拟对象。 看起来很奇怪,但比旋转的加载迹象要少刺激性。 Tumblr应用程序,存储和加载您最后存储在本地存储中的卡。 这比上面的怪异屏幕要聪明得多。 为什么? 在惰性癌症综合征中,人们可以做这样的事情 func getUserData(回调:@转义(_结果:字符串)->()){ let myUrl = URL(string:“ https://www.google.com”); var request = URLRequest(URL:myUrl!) 让会话= URLSession.shared; request.httpMethod =“获取” request.addValue(“ application / json”,对于HTTPHeaderField:“ Content-Type”) request.addValue(“ application / json”,对于HTTPHeaderField:“ Accept”) // request.httpBody = jsonData DispatchQueue.global()。async(){ let task = session.dataTask(with:request){数据,响应,错误 警卫让结果=数据,错误==无其他{ print(“ \(错误?.localizedDescription ??“未知错误”)”)); 返回; } 如果让httpStatus = response为? HTTPURLResponse,httpStatus.statusCode!= 200 { print(“ […]

iOS App体系结构-第1部分:构建屏幕

多年来,我们已经在iOS和Android团队的Nubank上调查并尝试了许多不同的架构模式。 我们创建了项目,并且像许多初学者一样,开始编写满足我们需求的简单MVC代码,并实现了当时所需的功能。 Apple的MVC对初学者来说是一种简单的体系结构,但是在您的项目开始扩展时就不够了。 尽管我们确实希望简单,但是我们也希望能够在开发新功能时在我们的团队中共享工作,而当您所有屏幕行为都在一个类ViewController中时,这很难做到。 随着我们继续扩展以及项目变得越来越复杂,对于我们来说很明显,我们需要一个不同的工具来完成这项工作,因此,我们承担了创建自己的应用程序体系结构的任务。 在开发一种模式来尝试解决我们的问题时,我们的主要驱动力和关注点是: 我们正在从单人代码库过渡到团队 我们需要模块化并支持可重用性 我们希望有更少的班级,更少的责任 我们需要使测试更容易编写和更有用 在构建我们的架构模式时,我们进行了大量研究和实验,研究现有结构如何解决不同的问题,以及它们的优缺点在日常活动中如何表现出来。 这样的架构为我们带来了很大的启发,是Bob叔叔提出的“清洁架构”,它提出了一种构建具有一些目标的应用程序的方法: 清洁架构的建议与我们要实现的目标之间存在很大的重叠。 但是,我们觉得完全按照鲍勃叔叔的建议会给我们的特定工作环境带来过多的开销。 例如,将作品分成太多类可能会使新来的人感到恐惧,因为很难理解每个部分的作用以及这种分离的直接好处是什么。 我们也不希望这种分离妨碍开发人员的工作,并导致生产力下降,因为需要创建多个文件并始终在它们之间进行切换。 因此,我们选择了Clean Architecture的中间应用程序,该应用程序为我们提供了原始提案的去耦,可测试性和单一责任,但并没有增加太多开销,并且使我们可以更快地构建功能。 为了实现所需的职责分离和分配,我们决定创建额外的层,以渐进地抽象UIView和UIViewController 。 让我们使用一个虚拟屏幕来说明我们的模式,该屏幕旨在引导注册过程,例如我们的信用卡“奖励计划”,用户可以随时选择加入该计划。 如果卡当前active ,则此屏幕应显示一条消息,邀请用户注册,但如果卡被阻止,则应指示用户首先解除卡的blocked 。 我们的控制器是控制单元的抽象。 它提供了有关特定用户交互的外界接口,并执行了与该交互有关的可能影响应用程序状态的工作。 Controller根据从上下文接收到的输入来生成视图状态规范(称为ViewModel),并根据操作或输入更改来协调对该视图状态的任何更改。 例如,假设我们正在构建一个简单的屏幕,该屏幕应对客户卡的状态做出反应。 例如,我们可以使用以下enum来表示这种状态: 视图是对发生的事件控制最少的元素,仅充当内容的容器,以适当的布局显示。 ViewController具有更多控制权,并通过填充来自中间状态表示的内容并以与布局无关的方式公开可能的操作来管理View。 中间表示是我们的ViewModel,这是一个不变的纯数据结构。 最后,控制器拥有逻辑并知道动作的效果。 它接受事件的抽象版本,并使用它来创建ViewModel。 它不知道ViewModel的应用方式,也不知道应如何向用户显示信息。

2018年1月和2018年2月iOS独立开发思考和更新

嗨,朋友们, 简直无法相信一月和二月来了又快。 希望您今年有个开门红。 我想回顾一下过去两个工作量 一月份发生了很多事情。 Daily Vibes可在Apple App Store中获得。 如果尚未下载,请试一试 为Beta测试人员添加了一份注册表: 制作和编辑并上传视频 改善上述截图 改善上述视频 在撰写本文时,所有这些想法都浮出水面。 我希望将它们与下一个版本一起发布,希望在下周的某个时候发布。 然后,我将不得不等待,看看分析。 单个应用程序需要大量工作。 我开始欣赏独立开发者及其成功的产品。 您确实需要戴上能成功开展业务的所有帽子comfortable。 对我来说,营销是一个未知领域,我很犹豫开始学习它。 但是现在来看我,我可以制作图标,促销视频,并开始对增长具有战略意义 。 这是我享受的旅程,但充满挑战。 那是什么呢? 刚开始 。 今天就开始做一件小事。 并做到每一件事。 单。 天。 并且不要忘记这些小小的胜利是累积的。 我也铺平了道路,并做了所有必要的书面工作以添加应用程序订阅。 现在我要弄清楚我在这方面的策略😉 哦,在结束语中,我想保持一个宽松的2周出货周期……到目前为止,我一直坚持定期更新的时间表=)我希望我能保持这一水平! 感谢您的阅读,直到下次我的朋友。 真诚的 亚历克斯

在Swift 5中编写可扩展的API客户端

对于本文,我们决定为Marvel API编写一个客户端。 在深入研究代码之前,让我们解释一下为什么选择它: 它是开放的 。 去获得一个API密钥! 它很大 。 否则,为什么要建立一个可扩展的客户端? 是真的 。 迫使我们处理丑陋的现实细节。 这是一致的 。 好的,我们不需要太多丑陋的细节,我们也需要系统的实现。 它有一个很棒的API测试器 。 文档非常丰富,此在线工具使测试我们的代码变得非常容易。 太酷了 。 😎 在本文的其余部分,我们将使用Swift Playgrounds对该API进行试验。 如果您真的想对它有用,请推荐使用Karumi的MarvelApiClient。 功能性无状态核心周围的有状态外壳 定义组件接口时,一个很好的规则是使您的设计基于无状态类型,且其值语义不会产生任何副作用。 拥有该核心后,您可以创建另一个将这些类型映射到实际副作用的类型。 我们的API客户端组件将围绕三种类型构建: APIRequest :将创建JSON请求的值类型。 APIResponse :将从JSON响应中创建的值类型。 APIClient :将接收请求,将其发送到服务器,然后通过回调通知调用方。 最后一种似乎比其他类型更混乱! 好吧,这就是我们的有状态外壳,它将是处理这种混乱状态的外壳。 实际上,该组件将与大多数细节无关,而您几乎不需要更改它。 APIClient初始接口 基于先前的想法,我们可能会从以下内容开始: 让我们从更改APIRequest协议开始: Decodable协议为我们做了很多工作! 您是否注意到ID是非可选的? 因此,如果我们遇到格式不正确的ComicCharacter ,则会引发错误,并且我们将能够以我们认为合适的任何方式对其进行处理。 但是,那里还有另一种类型。 查看Image类型? 在任何Apple框架中都没有定义,这是我们定义的自定义类型。 让我们看一下它的代码: 由于某种原因,Marvel API决定发送分为路径和扩展名的图像URL。 我们不希望将此解析细节公开给我们的业务逻辑层,因此我们尝试从该输入中构建正确的URL。 由于我们的输入和输出不匹配,因此我们必须走很长的路,并完全实现Decodable 。 这意味着定义一个CodingKey枚举和一个init 。 […]