嗨,我是Patrick-Mercari的iOS工程师。 在Mercari,我们的客户工程团队使用反应式编程范例。 近年来,这种范例已经发展并在开发社区中变得越来越流行。 在本文中,我想简要介绍一下使用ReactiveSwift的反应式编程。 为了演示这些概念,我在本文旁边提供了一个示例项目供您参考。 当按下按钮时,它只是显示随机的颜色,但是它说明了如何使用反应式编程。 请注意,本文假定您已熟练掌握Swift。 在Mercari以及此示例项目中,我们将Model-View-ViewModel(MVVM)架构与ReactiveSwift一起使用。 ReactiveSwift库使我们可以更轻松地应用反应式编程范例。 反应式编程的核心是流的概念—流只是发送值,当我们观察它们时,我们可以采取行动。 在ReactiveSwift中,流的这一概念由Signal类表示。 我们可以将用户交互建模为信号。 这与我们选择的架构MVVM完美匹配。 ViewModel是MVVM最重要的部分之一。 必须执行的任何逻辑都应存在于ViewModel内部。 这显然将整个应用程序中的职责分开:ViewModels处理逻辑,而视图和ViewControllers根据此逻辑的输出进行更新。 在ViewModel层中将这种模式表示为输入和输出很容易。 这是一个大致的视图: 输入项 您可以将输入视为已执行的操作,无论是由ViewController本身( viewDidLoad )还是由用户(轻击和轻扫)执行; 我们希望对他们采取行动以执行一些工作。 在示例项目中,我们可以看到如何捕获输入。 如下所示, RandomColorViewModelInputs协议声明执行某些操作时将调用的函数: 协议RandomColorViewModelInputs { func viewDidLoad() func newColorButtonTapped() } 例如,当viewDidLoad方法在我们的ViewController内部触发时,将调用viewDidLoad() 。 我们可以使用ReactiveSwift的.pipe()使这些函数在实现内部具有反应性。 这使我们能够创建一个Signal ,我们可以通过该Signal发送输入并观察结果输出。 在这里,我们可以看到viewDidLoadIO的定义(我们的管道,用于输入/输出的IO )和提供输入的viewDidLoad() (来自我们的协议): 私人让viewDidLoadIO = Signal .pipe() func viewDidLoad(){ viewDidLoadIO.input.send(value:()) } viewDidLoadIO是使用.pipe()的信号。 它发送一个Void值,并具有NoError作为关联的错误类型(本质上意味着它永远不会出错)。 在函数viewDidLoad()我们可以看到Void输入正在通过viewDidLoadIO发送。 这是通过viewDidLoadIO的整体流程的外观: 产出 输出是采取措施的结果。 […]
这将是我在我的应用程序Health Up Display上进行的一系列工作中的第一步。 我从社区中的其他开发人员那里汲取了灵感,这些开发人员已经针对自己的应用做了类似的博客。 请查看Brent Simmon的旧版Vesper Sync日记或Curtis Herbert撰写的Slopes Diaries,以获取一些示例。 启动此系列文章有一些目标。 当我通过这些事情进行推理时,一种与自己大声交谈的方法。 可能从已经解决类似问题的其他人那里寻求帮助。 在学习过程中,向我后面的其他人提供相同的帮助。 使用公众责任感作为脚下的火,以提供保持专注并最终发布此应用所需的动力。 在我仍将脚趾浸入博客水域时,请提供稳定的内容。 在开始之前,请先了解一下背景知识。 可惜的是,Health Up Display是我几年来一直在从事的健康和健身跟踪应用程序。 我想要一个将Health.app的数据库查看方面与iOS上Activity.app中的UI更相似的设计样式相结合的应用程序。 我还想解决我认为这两个应用程序都存在的缺点。 我喜欢将此应用程序视为这两个应用程序(如果有孩子)的产品。 我在该项目上的进展还没有达到我想要的水平,但是尽管如此,我还是取得了进展,如果您想看看的话,可以使用一个非常有用的beta。 您可以在这里注册尝试一下。 顺便说一句,我们可以继续讨论今天的日记条目。 与HealthKit进行交互的更好的API 正如我提到的,我已经在这个项目上工作了一段时间。 有些代码确实很旧。 就像旧的Swift 2.0。 另外,我只是在学习Swift,而实际上大部分只是用Swift编写的Objective-C代码。 这在一定程度上是我的错,因为我还没有熟悉这种语言,并且在那时,它的风格仍在迅速发展。 这也部分是由于这样的事实,即系统框架尚未(在许多情况下仍未真正为Swift进行过现代化)。 对于HealthKit框架尤其如此。 从好的方面来说,这是一个非常使用闭包的框架,并且在这方面非常适合Swift,但是这些闭包实际上是旧的Objective-C块。 实际上,大多数HealthKit API是使用闭包来异步处理其结果的查询。 不幸的是,在大多数情况下,这些闭包通常具有用于可选的有效查询结果和同时可选的错误的参数。 大多数Swift开发人员并不真的喜欢那样,我们也不认为它非常迅速 。 我们更喜欢使用可靠的Result类型之类的东西,以简单的switch语句清楚地区分成功还是失败。 如果您需要查看内容,请阅读这篇文章。 我的应用程序具有一些旨在为我构建这些查询的类。 它们还为我的应用程序与HealthKit交互提供了一个集中的位置和包装器。 我使用这些实例从HealthKit数据库检索运行状况统计信息,然后将它们存储在我的应用程序自己的数据缓存中,以便UI可以在启动后立即准备好您的最新统计信息。 您确实不需要每次启动应用程序时都等待查询返回。 当查询最终在以后返回较新的数据时,我将更新缓存和应用程序的UI层。 这些类充满了前面提到的并非Swifty的代码实例,而且由于我对Swift风格的了解以及对如何使用HealthKit以及与HealthKit交互的知识越来越多,现在也HealthKit 。 另外,我希望很快就可以开始使用该应用程序的Apple Watch组件,并且我还需要与该目标共享大量此类代码。 我还有一个有趣的宠物项目应用程序,名为Mowing Meter,它使用iPhone的运动协处理器芯片在修剪院子时跟踪您的步距和距离。 我目前正在将其转换为Swift,并将其也转换为“ 真正的 […]
Gameboy是(现在仍然是)非常出色的设备。 不仅从游戏玩家的角度,而且从程序员的角度,程序员都希望了解更多有关计算机如何在更基础的水平上工作的信息。 汇编语言,CPU操作码,内存映射的I / O,所有这些都是计算机工作的基础,但是由于我们要进行更高级别的抽象,因此在日常工作中我们并没有真正使用太多东西现在。 但是了解我们日常设备在幕后的工作方式既有趣又有用。 结合起来,复古计算和游戏总是很有趣! 就像我说的,Gameboy就是一个完美的装置。 它于1989年发布,具有基于Z80的8位CPU(与您可能知道的Intel 8080不太相似),具有16位地址空间和1 MHz的时钟速度,以及大约16kb的RAM 。 可执行文件随盒带提供,盒带的大小从32kb到数兆字节不等。 墨盒本身只是一块硬件,因此它还可以具有额外的RAM,电池供电的RAM以保存游戏,甚至还可以提供更疯狂的物品(例如相机)。 而且很容易找到用于运行和调试程序的仿真器。 在本文中,我们将编写一个简单的Gameboy游戏,该游戏仅在屏幕上显示一个精灵。 那不是什么大游戏,但是要达到这一点还需要学习很多东西。 我们将使用我在Swift中编写的汇编程序,您可以在Github上找到适用于macOS和Linux的汇编程序。 本文将首先说明Gameboy CPU的工作方式,然后介绍如何编写汇编程序,然后在Gameboy上图形如何工作,最后,我们将把所有这些部分组合成一个工作程序。 让我们开始学习一下Gameboy的工作原理! CPU和地址空间 理解Gameboy以及实际上任何计算系统的工作原理,主要有两部分:CPU,它接收指令并执行它们,以及地址空间,该地址空间用于访问不同的硬件组件。 当一条指令在CPU上运行时,它会以某种方式修改系统的状态。 通过内存地址访问的CPU内部状态或某些硬件组件的状态。 Gameboy具有16位地址空间,这意味着您可以访问0到65535(十六进制的$ ffff)的内存地址。 Gameboy的硬件已将这些地址中的每一个都映射到某种硬件。 它可能是盒式ROM,声音芯片或某些通用RAM。 编译汇编代码时,它将转换为二进制文件,即一系列操作码。 操作码是一个数字,表示CPU可以执行的一条指令。 例如,操作码$ 00表示NOP,这是一个空操作。 $ 82是一条ADD指令,它将两个数字加在一起。 您的二进制文件是这些操作码的一长串列表,它们位于盒带的ROM中。 盒式ROM存储器映射到Gameboy存储器空间的前半部分,这意味着从$ 0000到$ 7fff的所有地址(这些值更容易用十六进制数字表示。16位值为4密码十六进制数字)。 这意味着,如果您读取介于$ 0000和$ 7fff之间的任何地址,它将从盒带中读取一个字节。 请注意,地址空间是16位的,这意味着到内存位置的每个地址都在$ 0000和$ ffff之间,但是存储在这些位置中的值是8位,因此在$ 00和$ ff之间。 Gameboy CPU将其状态保存在内部寄存器中。 每个寄存器都是一个8位或16位值,直接存储在CPU内部,因此读写非常快,因为它不必通过地址总线与任何其他硬件组件通信。 Gameboy有八个8位寄存器和两个16位寄存器: 8位寄存器:A,F,B,C,D,E,H,L 16位寄存器:SP(堆栈指针),PC(程序计数器) SP寄存器跟踪堆栈的大小(我们将在后面介绍),而PC寄存器跟踪CPU当前在代码中的位置。 就像地址空间一样,这两个寄存器是16位的。 […]
这是谷歌地图上的导航示例。 在这里,标记会随着车辆在其应用程序中的uber一样转弯而移动。 使用旧的和新的坐标动画值轴承,标记正在移动。 入门 要运行此演示项目,您需要添加带有Key的最新Google Maps SDK。 您可以下载Google Maps SDK,只需将其拖放到该项目中即可。 Google Maps API-获取Google Maps iOS SDK 先决条件 要使用iOS版ARCarMovement构建项目,您需要7.3版或更高版本的Xcode。 用法 如下所示将Google Maps API密钥添加到AppDelegate.m : [GMSServices provideAPIKey:@”YOUR_API_KEY”]; 将以下导入语句添加到您的ViewController.h中 ,如下所示: #import “ARCarMovement.h” 在ViewController.h中创建一个ARCarMovement属性: @property (strong, nonatomic) ARCarMovement *moveMent; //alloc // self.moveMent = [[ARCarMovement alloc]init]; self.moveMent.delegate = self; 所需的数据源方法是 /** * assign the specified details to be work with […]
响应式编程是定义响应异步事件的应用程序行为的有用工具。 它将异步操作处理代码从iOS中常见的委托模式转移到更具声明性的形式,使异步性更易于推理。 我没有使用响应式框架从头开始构建应用程序的经验,但是随着我逐渐增加对所涉及概念的理解,我发现在很多情况下,大量的响应式编程已使工作变得更加整洁。更具可读性的代码。 我一直在使用的框架是RxSwift,它是ReactiveX API的Swift实现,但是还有其他可用选项。 在本文中,我们将简要地探讨一个示例,该示例演示如何使用RxSwift创建一个可观察的序列来替换原本会分散在多个不同回调中的代码。 对于我们的示例,要求如下所示,我们必须显示一个垂直滚动的项目列表,每个项目都有一个要显示的图像,该图像必须通过网络异步加载。 我们不能简单地同时从网络中获取所有图像,因为我们的列表可能很长,并且用户甚至可能没有浏览到最后,因此最终可能会因不必要的网络调用而浪费能量和数据。 另一个限制是,当设备连接到wifi时,我们希望浏览的行为不同于连接到蜂窝网络时的行为。 当连接到wifi时,我们希望在用户滚动之前加载图像,以便在屏幕上滚动显示单元时就可以显示图像了。 当连接到蜂窝网络时,我们只希望在用户停止滚动时加载图像,以防止在滚动列表时使用过多的数据。 为了实现此行为,我们需要遵循几种协议,它们是UITableViewDelegate , UITableViewDataSourcePrefetching和UIScrollViewDelegate 。 下面的代码给出了在视图控制器中外观的想法。 考虑到我们需求的复杂性,这并不是一个不好的结果,尽管它确实使我们一眼就很难看出图像加载行为是如何工作的,而且如果这些回调分散在一个大文件中(或几个文件)。 另一个复杂的因素是,这些委托回调通常还需要执行其他功能,这在阅读和理解图像获取行为时会产生更大的背景噪音。 反应式编程如何提供帮助? 我们可以使用反应式编程来定义可观察的事件流,这些事件驱动我们的loadImagesIfNecessary(for:)函数。 每当我们要加载一组图像时,此可观察对象将发出一个由索引路径数组组成的事件。 我们将通过组合和转换与我们感兴趣的委托回调相对应的可观察变量来构造此可观察变量(这些可观察变量在RxSwift的子组件RxCocoa中实现)。 以下是我们如何设置和使用此事件序列的示例。 从功能上讲,这应该与我们最初探讨的传统委托回调实现相同,但是,我认为它使我们的代码更易于理解,因为我们现在可以在一处读取所有图像提取逻辑,而背景噪音却更少。 这是我发现应用简单的反应式编程模式以便组合在一起并简化代码中的复杂逻辑的一种方法。 它确实要求读者对反应式编程原理有基本的了解,并向代码引入了第三方依赖性(反应式编程框架,在此示例中为RxSwift)。 您如何看待这种方法? 以这种方式进行逻辑合并值得额外的开销吗? 如果您有任何建议或问题,请给我@RowbotNZ发消息!
大约七个月后,Cycles取得了令人难以置信的成功: 在顶级生产力应用程序中,周期已达到第3位。 循环已被许多站点(例如Mashable和 AppAdvice) 使用和审查 。 周期已从App Store下载32,000多次,并且仍在增长。 是的,没错,我每天都在不断地研究自行车。 虽然我确实认为该应用程序具有巨大的初始启动能力,但始终存在改进的空间。 得出我最后的建议: 您的应用程序永远不会感到完成,那就可以了! 您为此花费了很多时间和精力,因为您对此充满热情。 我想花时间说谢谢您阅读本文,希望我以一种或多种方式为您提供帮助。 我很乐于回馈社区,因此,如果您有任何疑问,疑虑,反馈,或者只是想说声嘿,请随时通过社交媒体(下面链接)与我联系。 我也喜欢Twitch上的流媒体! 我已经开始在Cycles和其他副项目上流式传输更新进度。 请随时出来打个招呼或询问任何开发问题。 看我流!
“反应式编程具有陡峭的学习曲线。” 在学习函数式反应式编程(FRP)时,我一直在听到这些。 从命令式编程的背景出发,我努力掌握了FRP的各种概念。 从最基本的角度来看,FRP的好处是它使您可以建模时间。 但这很难使您动脑子。 为了帮助您克服学习难题,我将在“征服ReactiveSwift”系列中分享我的学习经验。 我的目的是为初学者提供一个循序渐进的指南,以学习ReactiveSwift。 这是该系列的第一篇文章。 它介绍了功能性反应式编程,并说明了它与命令式编程的区别。 命令式与功能式反应 为了理解这些编程范例之间的区别,让我们举一个例子。 假设我们要实现一个用户界面,该用户界面具有一个UILabel (我们称之为label )和一个UITextView (我们称之为textView ),其中UILabel反映了在UITextView中输入的文本。 为了实现这种行为,我们编写如下代码: func textViewDidChange(_ textView: UITextView) { label.text = textView.text } 上面的代码可以正常工作。 我们一直在这样做。 那是什么问题呢? 让我们调查一下。 考虑一下旨在更新标签文本的语句: label.text = textView.text 这是一个赋值语句。 这是什么意思? 这意味着在label.text时, label.text等于label.text 。 该语句不封装有关分配点之前或之后的label.text状态的任何信息。 换句话说,一旦将时间概念引入模型,语句label.text is equal to textView.text不一定成立。 因此,我们需要将此语句封装在委托方法textViewDidChange ,以使label的状态保持一致。 以命令方式,很难优雅地表示这样的关系。 欢迎使用反应式绑定 在反应式编程中(特别是在ReactiveSwift中),可以通过绑定解决相同的问题。 label.reactive.text <~ textView.reactive.continuousTextValues 该语句暗示在label的整个生命周期内, label的文本与textView的文本相等。 […]
您如何将故事板拆分为多个较小的故事板? 那你为什么呢? 在本文中,我将告诉您为什么要这样做,一路遇到什么问题以及如何解决这些问题。 问题:大型情节提要使iOS开发人员陷入困境 随着FINN应用的发展,最近的故事板也不断发展。 使用情节提要意味着您将获得一些有用的功能,并且可以直观地了解应用程序的工作原理。 我说“可以”,因为不一定是这种情况。 另外,使用情节提要具有一些怪异之处,当多个开发人员在同一个项目上工作时,这些怪异之处并不总是被理解。 对情节提要的任何小的更改都可能意味着对情节提要的xml进行了几处更改。 除此之外,XCode只是因为打开文件而具有重新计算几个情节提要项的坐标的烦人趋势! 还有一个烦人的问题是,故事板越大,处理起来就越慢。 通常,我们至少要等待5秒钟才能打开文件,而且操作通常比较缓慢。 因此,iOS团队很久以前就决定将其拆分,但这项工作不一定容易。 还是小。 甚至有趣。 而且,Jira的任务还指出“当心示波器蠕变……”。 是的,它爬了起来。 FINN应用:一些历史 当前的FINN应用程序于2013年8月启动。在这三年中,该应用程序当然有所增长,在过去的一年左右的时间里,使用Swift添加了一些新功能,其中相当一部分是较旧的Objective-C类已在Swift中重写。 但是,大多数代码库仍然是Objective-C。 因此,Swift和Objective-C之间的互操作性至关重要。 快速查看文件数量可知,我们当前在项目中有171个Swift文件和349个.m文件(当然,这还不包括第三方代码)。 这些文件包含> 44,000行代码,其中包括30,000行Objective-C代码和> 14,000行Swift代码(不包括注释和空格)。 从小开始 我们决定从小处着手,首先将应用程序的名为“ Min FINN”(我的FINN)的部分提取到其自己的故事板上。 这是应用程序的一个相当自治的部分,尽管不是很完整。 该应用程序此部分中的大多数导航都在该功能的内部,但是从应用程序其他部分进入该功能的一些入口点。 某些导航是通过segue完成的,但是有多个位置可以直接通过情节提要实例化场景的视图控制器。 当然,这意味着这样的代码会在整个代码库中乱七八糟: 这样做有两个问题:1.引用“ self.storyboard”,这意味着只有在视图控制器与当前视图控制器位于同一个故事板上时,它才会起作用。2.它使用硬编码字符串来引用故事板标识符“ resultListViewController”,容易出错 我们需要更好的方法。 当我们开始时,这就是MainStoryboard_iPhone的样子: 是的,有点混乱,不一定超级可管理。 从概念上讲,它也不是很有用。 那么我们如何进行拆分呢? 有了XCode 7,我们有了一个不错的新功能-重构到情节提要…: 您只需选择要提取的所有场景,然后此重构功能都将为您创建一个新的情节提要,并将旧情节提要和新情节提要中的场景之间的所有连接连接起来。 但是,如果您支持iOS 8,并且正在使用关系查询(即来自UITabBarController的查询),则此方法将不起作用。 当然,这就是我们的情况。 但是,至少它为我们提供了将场景提取到新的故事板上的便捷捷径。 我们只是删除了得到的故事板参考。 此外,对于非关系情况,我们还是要靠自己。 玩弄两个故事板 好的,现在我们有了一个新的故事板,其中只有“ Min FINN”场景: 分散在代码周围的大多数“ […]
我是一名新的iOS程序员。 我距Swift已有3年了,所以我在iOS编程中的第一门语言是Swift。 尽管那里有很多库可以帮助我开发应用程序,但它不像Swift库那么简单,Swift库仅在“ pod安装”完成后无缝地导入其模块。 为了更清晰,在这里我谈论的是如何从通过Cocoapods注入的库中导入Objective C标头。 轻松将库手动注入到Swift项目中。 您可以将其文件从外部文件夹拖放到您的项目中,Xcode将为您提供为您生成Objective C桥接标头的选项。 或者,您可以创建一个新的Objective C头文件“ .h”,并将其命名为: -Bridging-Header.h 在我们开始之前,我想使它成为一个简单的教程,而不仅仅是有经验的程序员记住某些东西的方式。 这意味着,虽然实现起来很短,但是one line code ,但是我想分享一下我如何通过反复试验来理解这一点。 因此,像往常一样,让我们创建一个新的Xcode项目。 随意命名,忽略核心数据,UI测试和单元测试。 就像您每天一样,如果您每天制作一个新应用程序。 然后,我需要您至少知道如何使用Cocoapod。 如果您不知道Cocoapod是什么,那么花一点时间来了解iOS开发者社区为我们提供的如此出色的工具。 您将要访问其官方网站,该网站还提供了我们找到所需图书馆的方式。 前往http://cocoapods.org 新,我想您已经掌握了Cocoapod。 然后,我们将使我们的项目使用将从Cocoapod获取的外部库。 转到项目根文件夹,如果您不知道什么是根文件夹,则只需转到包含我们的。xcodeproj文件的文件夹。 在终端上运行pod init 。 Podfile Pod完成后,让我们打开Podfile声明要导入的Pod库。 让我们找到一个仅用Objective C编写的好的库。我的意思是,这个库在Swift中还没有或者没有。 我们有MagicalRecord,它将帮助我们管理CoreData的使用。 通过将pod ‘MagicalRecord’,’~> 2.3’放在…行的#Pods下pod ‘MagicalRecord’,’~> 2.3’将MagicalRecord声明为Podfile。 保存Podfile,然后在终端上运行pod install 。 我们将获得由pod发送的一堆日志,以告知我们项目中的当前操作。 等到完成。 现在,我们在根文件夹中具有。xcworkspace和.xcodeproj。 如果我们使用Cocoapod作为第三个库管理器,则每次处理项目时都希望使用.xcworkspace。 如果您想替代Cocoapod,我们可以选择Carthage和Swift Package Manager。 打开.xcworkspace。 我们的项目保持不变,只不过我们在Xcode导航器窗格中注册了两个项目。 一个是我们自己的项目,第二个是Pod项目。 这就是为什么我们需要使用xcworkspace来始终加载Pod项目的原因。 […]
当您拥有需要在公司内部的iOS开发人员之间共享而无需公开的框架时,可以使用私有的CocoaPods存储库来实现。 在这篇博客文章中,我将逐步向您展示如何做到这一点。 您应该已经安装了CocoaPods,否则请安装它: 须藤宝石安装cocoapods 在公司的Github,Bitbucket或Gitlab上创建私有存储库: 2.提供所有iOS团队成员的访问权限。 3.添加您的私人规格: pod repo add specsName https://code.company.com/podspecs.git 注意 :它将添加到通行证~/.cocoapods/repos 4.准备好吊舱后,将CocoaPod规格推到远程位置: pod repo push specsName MyPod.podspec 要么 pod repo push –allow-warnings specsName MyPod.podspec 5.当您需要使用内部Pod时,只需将您的私有podspecs源和期望Pod添加到Podfile中: source ‘https://code.company.com/podspecs.git’ source ‘https://github.com/CocoaPods/Specs.git’ platform :ios, ‘11.0’ target ‘MayApp’ do use_frameworks! pod ‘MyPod’ end