iOS单元测试

基本上,单元测试的思想非常简单: 了解输入并将输出与期望值进行比较,我们可以验证黑盒是否正常工作。 + ———————- + 输入| | 输出量 + ———-> +黑匣子+ ————> | | + ———————- + 尽管经常知道单元在您的项目中实现了什么功能,但是对于单元测试环境仍然未知,这就是为什么在这里使用Blackbox术语的原因。 但是什么是单元测试单元? 嗯,有很多定义,但是它们都以一种或另一种方式将Unit定义为功能单元,我想说是不可分割的功能单元 。 在Swift语言中,功能的不可分割的单元是一个功能。 每个函数都可以带有一些参数(输入),可以返回某个值(输出),因此可以通过单元测试来验证其功能。 您可以编写一个涉及整个类型(类或结构)或什至组合成一个模块的几种类型的单元测试,但是: 将一个单元测试与一个功能绑定在一起可以使您的测试尽可能地适应代码。 好的,让我们玩一下Swift编程吧。 因此,当您具有如下方法或函数时,单元测试的想法非常简单: 为了验证它是否正确运行,您编写了一些传递已知Params代码,并检查返回的Result是否与这些参数所期望的一样。 这个特殊的代码称为单元测试,因为它测试一个单元doSomeWork函数。 这是一个精致的示例,在现实世界中, doSomeWork函数而不是Class或Structure的方法,因此其工作可能取决于其他方法和属性。 尽管通常依赖另一种方法并不危险,但是应注意依赖属性和实例变量,因为它们描述了类型的状态并且在测试之间是持久的,此处的规则: 使您的方法成为函数式编程的纯函数 。 纯函数是不依赖所有者状态且不会产生副作用(更改所有者状态)的函数。 像许多其他现代语言一样,Swift也是面向对象的,它具有具有属性和实例变量的类,这使得不可能将所有方法都当作纯函数使用,特别是如果它是UI应用程序。 万一该方法无法重构为纯函数,您的规则是: 尽可能减少方法对其所有者状态的依赖 使用纯函数(除了使代码可测试以外)的另一个有用效果是,它们将函数式编程的优点引入了您的应用程序。 现在可以安全地构建方法链,并且对于相同的输入参数,其结果将始终相同。 它还通过增加可扩展性和减少耦合来改善应用程序体系结构。 回到无法摆脱对状态的依赖的方法,可以依靠的一种提高其可测试性的技术是依赖注入 , 考虑下一节课: 在这里, Jobs将在doSomeWork方法中执行实际工作的Jobs委托给ConcreteWorker ,由于以下原因,我们不能删除使doSomeWork不利于测试的依赖关系: ConcreteWorker可能依赖于在测试过程中难以设置或无法设置的环境,例如,它可能会使用Simulator无法使用的iOS功能 Jobs可以开始使用ConcreteWorker的子类来覆盖work(:)方法,或者甚至可以在运行时选择适当的工作器 这些是以下事实的结果: Jobs控制了ConcreteWorker实例,而无法从外部进行管理。 为了解决这个问题,我们将使用依赖注入来反转此控件: 请注意,我们还引入了Worker协议,该协议为Jobs使用的工人定义了一个合同,该合同将其与具体的worker实施分离。 单元测试还有另一条规则: 将测试与测试的类隔离开 […]

“让它比Nike +更好”-如何过滤位置(在iOS Vol.5中跟踪高精度位置)

在最后三个会话中,我们在示例代码中添加了背景跟踪引擎+地图绘制。 使用示例应用程序进行短期运行会使您感到它已经像Runtastic这样的正常运行应用程序一样工作。 但是,您可能会遇到一些应用无法正常运行的情况。 尤其是在跑步时.. 在多云的天气下 在被高楼大厦包围的小巷里 在一个有许多树木的森林或公园里 为了在这种情况下使您的应用像Nike +一样更准确地工作,您需要将应用日志设置为“仅限好位置” 。 在这篇博客文章中,我将解释如何制作各种过滤器以仅记录良好的位置。 记录准确的位置不仅可以帮助应用程序不仅在地图上绘制漂亮的路径,而且还可以从这些位置计算出准确的距离或速度。 在当前示例中,我们只是将新位置记录到didUpdateLocation回调中的locationDataArray中。 首先,我们将其替换为对filterAndAddLocation()的调用,如下所示。 左侧轨道(不带过滤器)已在标有蓝色圆圈的区域周围记录了几个不正确的位置。 正确的轨道没有不正确的位置,路径看起来更平滑。 比耐克+更好 我使用过滤器测试了示例应用程序,并将其与Nike +的性能进行了比较。 我在iPhone上同时打开了示例应用程序和Nike +,然后跑了2–3公里。 在标有蓝色圆圈的区域,我们的跟踪算法显示出更好的性能。 后来有一天,我在涩谷的街道上奔跑。 Nike +和我们的示例应用程序的跟踪路径如下。 这两个试验还不足以称为实验,但是我上面解释的过滤器似乎使我们的应用程序具有与Nike +相同甚至更好的性能。 更好的跟踪引擎 还有一些空间可以使此跟踪引擎变得更好。 如果您在iPhone放在背包里的大雨天里奔跑在森林中,则可能看不到您的应用程序跟踪任何内容。 如果您看到这种情况,则说明您的过滤条件可能太严格了。 使跟踪引擎更强大的最后几个步骤是使您的过滤器适应环境。 我不会对此做更深入的介绍,但是这里有一些提示 从平均水平精度知道条件 从位置回调之间的时间间隔了解条件 知道天气 迎接挑战,使您的引擎变得更大!

Swift(3.0)中的协议和代表:

协议定义了特定任务或功能的方法,属性和其他要求的蓝图。 然后,该协议是一项必需的任务,然后可由类,结构或枚举采用以提供这些要求的实现。 当一种类型满足要求时,就称其符合该协议。 句法 协议也遵循与类,结构和枚举类似的语法- 协议SomeProtocol { //协议定义 } 在类,结构或枚举类型名称之后声明协议。 也可以具有单个和多个协议声明。 如果定义了多个协议,则它们必须用逗号分隔,如以下示例所示: struct SomeStructure:Protocol1,Protocol2 { //结构定义 } 当必须为超类定义协议时,协议名称应在超类名称后加上逗号。 例如: class SomeClass:SomeSuperclass,Protocol1,Protocol2 { //类定义 } 协议可以具有存储或计算的属性,实例或变异方法或功能。 但是,如果它们是计算属性,变量实例或变异函数(不带花括号),则不能使用常量而是使用var (变量)来声明它们以符合协议。 此外,协议是类型,其名称以大写字母开头。 根据Apple文档: 协议不仅是一种类型,还可以扩展,它们可能要求特定的初始化程序通过符合类型来实现,并且总体上需要针对符合性类型的更多功能或各种规范。 在实现设计模式或委托时,Swift开发人员经常使用协议。 委托是一种设计模式,使类或结构可以将其某些职责移交给(或委托 )其他类型的实例。 根据Apple文档: 通过定义封装委托职责的协议来实现此设计模式,以确保符合类型(称为委托)可以提供已委托的功能。 委托可用于响应特定操作,或从外部源检索数据而无需了解该源的基础类型。 一个iOS开发人员经常会遇到的协议和委托模式的示例是在将TableView实现为常规ViewController时。 UITableView对象必须具有充当数据源的对象和充当委托的对象。 通常,这些对象是应用程序委托,或更常见的是自定义UITableViewController对象。 尽管它们有许多可选方法,但要完全符合Apple文档的DataSource协议,始终需要两种方法: tableView(UITableView, numberOfRowsInSection: Int) 需要。 告诉数据源返回表视图给定部分中的行数。 func tableView(UITableView, cellForRowAt: IndexPath) 需要。 要求数据源在单元格中插入表格视图的特定位置。 换句话说,当插入,删除或重新排列表的行时,数据源提供UITableView构造表和管理数据模型所需的信息。 除了数据源之外, UITableView对象的委托人还必须采用UITableViewDelegate协议。 委托管理表行的配置和选择,行重新排序,突出显示,附件视图以及编辑操作。 […]

Firebase —初始获取期间数据不同步

退出了一家主要使用专有技术的大公司之后,我获得了所有自由,可以尝试各种出色的工具来为自己的公司构建产品。 我想在这里分享我的学习历程(在所有方面,而不仅仅是编码),我欢迎大家的评论和讨论,因为这也是我的学习方式。 最近,我一直在iOS上使用Firebase。 对于大多数事情来说,这都是令人惊奇的,我特别喜欢脱机功能。 基本上,它使您的应用程序可以脱机运行,并且在您恢复网络后,数据将立即同步到db。 它还“聪明地”(稍后Database.database().isPersistence首先从缓存中查找数据以提高加载性能,而仅用一行代码Database.database().isPersistence获得全部Database.database().isPersistence 。 一切工作正常,除了在初始访存时遇到神秘的数据不同步问题。 如果进行“如何刷新Firebase缓存”或“ Firebase中的缓存中的过时数据”之类的搜索,您将得到诸如此类,此类或此类的零碎讨论。 它们中的许多有用,但没有一个能提供完整的图片。 我真的想知道发生了什么,所以我做了很多实验,让我在这里总结一下自己的理解。 (TL; DR;) 有2种主要的Firebase API用于检索数据: ref.observe(.value, with: { // completion block } ref.observeSingleEvent(of: .value, with: { // completion block } 第一个API是事件监听器。 它返回初始结果,并监听ref更改。 第二个API不监听更改,它只检索一次数据,因此它似乎是用于初始提取的正确API。 但是不幸的是,如果持续打开它就无法工作。 无论调用多少次, observeSingleEvent都将仅返回本地缓存的数据。 如果您的本地数据超过默认的最大10MB,问题将变得更加严重,Firebase将启动逐出缓存的数据(似乎是随机的)。 因此,如果您离线或关闭应用程序并重新启动,则仅通过使用observeSingleEvent获取数据可能会丢失一些数据。 这看起来像是一个常见问题,我希望有一些简单的解决方案,但令人惊讶的是,我确实找不到解决该问题的好方法。 经过大量的阅读和实验后,我提供了一些替代方案来解决或缓解此问题,并让我与您分享。 ref.observe 始终使用 ref.observe 这是最推荐的方法,也是Google小组的Firebase工程师推荐的方法。 使用observe将使本地数据保持同步,如果使用它来获取数据,它将首先从本地获取数据,并最终与服务器同步(具有一个或多个回调)。 但是,使用它初始获取时有两个问题: 您无法确定何时从初始服务器状态获取数据。 如果您真的想立即进入服务器状态,请停止加载微调器,而不是让数据不断更新和重新加载,则不能这样做。 它不适用于列表数据。 对于列表数据,您可能会使用.childAdded或.childRemoved因为.value仅在更改时为您提供整个Blob数据。 它效率不高,大多数时候不是很有用。 由于您不知道它何时确切返回初始服务器状态,因此,如果您只想将其用于初始提取,则无法确定何时删除.value侦听器。 ALT2 —调用 […]

Jual Walkie —对讲机HT宝峰GT-3TP Mark III 8瓦双频

Selama Postingan ini masih tayang barang akan selalu ada 宝峰GT3TP Mark III 8瓦双频 准备好了,斯托克·特尔巴塔斯(stok terbatas),订购了sekarang。 100%BARU,garansi mesin 1布兰 Harga PAS,没有问题。 * Fitur Utama: 功率麦克斯8瓦 3(tiga)功率级别:低,中,高 防水IP55(Tahan Hujan / Lembab) Antena Disempurnakan,Lebih敏感性 * Spesifikasi Teknis: 频率范围:136–174 / 400–520MHz 双频显示,双频。 显示,双待机 RX音频大声和清晰 输出功率:8瓦特 128路 50个CTCSS和104个CDCSS 内置VOX功能 1750Hz突发音 FM收音机接收器(65.0MHz-108.0MHz) 内置LED大反光LED手电筒 高/中/低射频功率 宽/窄FM模式:25KHz / 12.5KHz 紧急警报 电池电量不足警报 省电 超时计时器 […]

核心数据学习笔记

我还有另一个只有25分钟的视频(单击此处:https://www.youtube.com/watch?v=klZfcT087L4),介绍了如何在iOS开发中使用核心数据来保存用户的输入数据。 我不会解释什么是核心数据以及为什么要使用它。 编写该故事的目的只是为了便于学习,或在我的后续工作中使用快速教程来快速设置环境并开始使用核心数据。 生成新项目,选择核心数据: 3.从“添加实体”中添加实体名称(托管类),并为每个类添加属性(如果属性是可选的,请确保选中此框)。如果您的属性是托管类,请选择“可转换”作为dataType 4.确保以这种方式设置设置: 5.并为此托管类生成swift,然后检查要生成的属性。 6.然后,您需要另一个swift文件来生成persistentContainer,并在需要时保存saveContext()函数,如果您有多个托管对象,则将persistentContainer与其他容器分开。 确保名称“ RecipeModel”与您的核心数据文件名称匹配。 7.将实体添加到核心数据persistentContainer 8.从核心数据persistentContainer获取对象 context.delete(recipesArray [index])//使用delete方法删除数组中的元素之一。 总结:如果需要保存一组托管对象,最好使用核心数据,但是如果只需要保存一个或两个对象,则userDefault可能是一个很好的解决方案,因为您无需遍历所有这里的代码。 请记住:如果选择保存复杂的类,则核心数据和userDefault都必须具有与NSCoding相关的功能。 因此,两者的准备工作是相似的。 更多代码: 核心数据类:https://www.raywenderlich.com/3444-beginning-core-data

iOS 2018系列:破解iOS采访或成为iOS专家(6)

第6章:DispatchGroup和OperationQueue 最近,我不得不做一堆异步任务,我只想在所有任务完成时得到通知。 该博客将介绍两种简单的方法:DispatchGroup和OperationQueue。 在这篇文章中,我们将同时介绍它们。 派遣组 如果您正在开发一个不使用Operations的项目,并且不想重构任何现有代码,则DispatchGroup是一个功能强大的API,可以将这些任务组合为一个任务。 情况1 :计算三个不同线程上三个数组的总和,一旦所有线程完成其执行,我想使用该结果。 在考虑解决方案之前,让我们学习一些与调度组相关的API。 enter () —明确指示一个块已进入该组。 leave () -明确表示该组中的一个块已经完成。 func notify (qos:DispatchQoS =默认值,标志:DispatchWorkItemFlags =默认值,队列:DispatchQueue,执行工作:@escaping @convention(block)()-> Swift.Void)– 当一组先前提交的块对象完成时,将要计划的块调度到具有指定服务质量类和配置的队列中。 解决方案 :这三个API将帮助我处理上述情况,让我们学习如何使用它们。 我在下面写了添加数组元素并返回的函数 此函数使用DispatchQueue.global()。async创建线程,并使用DispatchQueue.concurrentPerform创建循环以添加infoArr的所有元素(类似于for循环),然后返回名为block()的完成处理程序。 下面的函数将创建三个并行计算总和的线程,一旦线程完成,它将调用dispatchgroup的notify函数。 调用notify函数后,我们将拥有所有三个数组的和,并且我们可以将其传递给completeizationBlock()。 操作和操作队列 它是一个抽象类,您需要对其进行子类化才能使用。 如果您只想执行一小段代码或调用一个方法,则可以使用BlockOperation和NSInvocationOperation代替子类化Operation。 情况2:使用操作计算三个不同线程上三个数组的总和,一旦所有线程完成其执行,我都想使用该结果。 解决方案:创建OperationQueue实例以添加操作,创建三个BlockOperation以添加数组的总和,然后在操作之间添加依赖项(因为我们需要获得实际上不依赖的最终总和),一旦我们以任何顺序将这三个块操作添加到操作队列中,这无关紧要,在第三块上注册完成块以获取最终总和。 输出:下图显示-调用块的顺序和最终求和结果 有障碍的并发队列 并发队列中的任务可以任何顺序执行,并且可以同时启动。 与上述串行队列相比,这是非常快的。 但是,由于我们可能在写书的同时阅读,所以我们会遇到读者-作家问题。 如果有一种方法可以确保在读取时不进行写入操作,而在使用并发写入时不进行读取操作,该怎么办? 好吧,有一种方法可以使用带有Barrier的Concurrent Queue 。 如果我们采用上面的并发代码,并为写操作插入一个障碍,那么我们将确保在执行队列中的所有读取之后进行写操作,并且确保在写入时不会发生任何读操作。 使用带有障碍的并发队列可以帮助我们改善和加快代码的速度,同时消除读写器问题,这对于单例来说也很重要。 奖励时间:#import和@class之间的区别 “ #import”将整个有问题的头文件带入当前文件; 还包括该文件#imports的所有文件。 另一方面 , @class (当单独使用带有某些类名的行时)告诉编译器“嘿,您很快就会看到一个新令牌; 这是一堂课,所以要这样对待)。 当您有可能使用“ […]

iOS / Swift中的OpenCV入门

对于不认识的人,OpenCV是计算机视觉开放源代码库。 它是用C ++编写的,这意味着您几乎可以在任何地方使用它。 从桌面应用程序到移动应用程序。 简而言之,OpenCV是: OpenCV(开源计算机视觉库)是一个开源计算机视觉和机器学习软件库。 OpenCV的构建旨在为计算机视觉应用程序提供通用的基础结构,并加速在商业产品中使用机器感知。 作为BSD许可的产品,OpenCV使企业可以轻松地使用和修改代码。 尽管创建新项目并添加pod或框架不是通常的任意任务,但考虑到它的好处,这是一个相当简单的过程。 首先,您可以克隆包含我们将在此处执行的操作的存储库。 在这个项目中,我们将从手机的摄像头获取视频流,并对其应用一些简单的色彩效果,仅用于演示目的。 因此,您需要将必需的权限添加到您的info.plist文件(即NSCameraUsageDescription 。 我们将添加OpenCV框架并将其与Swift一起使用。 首先创建一个项目(du!)。 然后初始化pods: $ cd project_root_directory $ pod init 然后将框架添加到您的Podfile : pod’OpenCV’,’〜> 3.1.0.1′ 不要忘记打开由CocoaPods创建的工作区,而不是打开您创建的项目。 OpenCV对称为Mat的n维密集数组数据类型(而不是UIImages 。 因此,我们需要一种在两者之间进行转换的方法。 为此,我们UIImage添加扩展 UIImage + OpenCV.h UIImage + OpenCV.mm 不要错过.mm扩展名。 它表明它是一个Objective-C ++文件,因此XCode以此进行编译。 否则它将无法正常工作。 接下来,我们将创建一个类,用于从相机获取图像流以进行处理: OpenCVCam.h OpenCVCam.mm 上面的类初始化相机,并处理每一帧。 它没有做任何花哨或复杂的事情,但对于初学者来说,这很好。 好了,图像已处理。 现在怎么办? 现在,我们必须在视图控制器中获取图像,或者将其实际使用或呈现。 为此,我们定义了一个委托(在OpenCVCam.mm ): OpenCVCamDelegate.h 到目前为止并不难,不是吗? 好的,我们差不多完成了。 不过,有一个很小的问题。 […]

从Xcode 8中的Swift 2.3到Swift 3

不久前,我将Xcode转换为Xcode 8,这最终意味着我可以选择将整个项目转换为Swift3。虽然大多数转换是为您完成的(其中很多是对您而言),但是仍然有一些组件Xcode不能更改,您必须手动检查代码以自行查找和转换它们。 由于Swift 3上个月才问世,因此您在网上找不到太多的文档/语法,因为大多数人仍在使用它或尚未转换。 我发现有些东西与Swift的早期版本和Swift 3有所不同。其中之一是大多数“ NeXTSTEP”类都消失了。 “ NeXTSTEP”或NS — — —是Objective-C类,是可可框架的原始代码。 在进行API调用时,我记得在转换为Swift 3之前,我必须使用NSURL来获取URL并使用NSURLSession来创建互联网会话。 现在有了Swift 3,API调用的NS部分已经消失了,现在仅使用URL和URLSession。 我发现,尽管仍然有一些使用NS的类(例如NSDictionary,NSArray等),但Apple试图让他们的语言Swift摆脱NS。 我必须自己手动更改的另一个组件是实现核心数据时。 在Swift 3之前,我记得在获取数据时,我们必须将获取请求置于需要获取的任何对象上。 func fetchData() { var错误:NSError? =无 让userRequest = NSFetchRequest(entityName:“ Users”)做{ 让对象=尝试ManagedObjectContext.executeFetchRequest(userRequest)作为? [用户]如果对象?.count> 0 { 如果让对象=对象 { 个体=对象[0] } } }赶上{ 打印(错误) } 在Swift 3中,他们在获取请求后使用该胡萝卜符号,有点像将其包围在需要的对象上。 现在,将项目转换为任何版本的Swift或Xcode时最令人沮丧的一件事是,如果您在项目中使用了CocoaPods。 短期内,CocoaPods是来自其他程序员的第三方代码段,以多种方式帮助您的项目,例如动画,设计,颜色,方法调用,API,约束等等。 在项目中使用CocoaPods的问题在于CocoaPod必须支持您正在使用的版本。 因此,如果您在Swift 2.3中处理项目,那么CocoaPod也必须在Swift 2.3中。 由于Swift 3还是相对较新的版本,因此并没有很多CocoaPod更新到Swift 3,这使得您的项目无法运行,直到他们对其进行更新,或者您在项目中手动进入其源代码并将其转换为Swift 3。 最终,所有Swift程序员都将不得不转换为Apple提供给我们的最新版本,即使您遇到190编译错误,我们作为程序员也必须咨询这些更改。 不要害怕更改,这是非常有益的,因为您可以从Apple获得最新版本和更新,并且Apple所做的这些更改旨在帮助我们Swift程序员使您的程序运行起来更流畅,更容易编码。

如何从XIB文件实例化视图

如前一篇文章中所述,在进行团队开发时,故事板可能不是最佳解决方案。 但是如果没有情节提要,您如何实例化视图? 这是我将在本文中讨论的主题。 如何从XIB文件实例化视图 创建一个“ Xibview.swift”文件。 这将是一个便利类,能够从xib文件创建视图。 从现在开始我们创建的每个UIView都将实现“ Xibview”。 “ initViewFromName”负责创建视图。 它会… 接收一个UIView类,并提取其类名。 从类名生成一个笔尖。 从笔尖实例化视图。 对于那些想知道笔尖/ XIB有何不同的人,它们在功能上是相同的。 笔尖是二进制文件,而xibs是xml格式,因此在集成诸如GitHub之类的版本管理系统时,xibs是首选。 创建一个xxxView.swift,它将是您的xib文件的文件所有者(我们将在下一步中创建)。 类名无关紧要,但是请确保它继承了XibView,即我们上面刚刚创建的类。 创建一个xib文件。 将我们在上面创建的类指定为文件的所有者。 该文件的所有者(通常)是用于在运行时访问xib文件的类。 更改背景颜色可能是一个好主意,以便您知道何时正确显示视图 意识到! 确保“ xib文件”和“类名”具有相同的名称。 为什么? 因为XibView(我们创建的)只能从其类名实例化xib文件。 准备完毕! 就这样! XibView将处理所有实例化并将全尺寸视图添加到ViewController。 以下是如何创建此视图的示例: 初始化此ViewController时,还将初始化我们创建的视图。 视图初始化器不是用类本身编写的,因此XibView会处理它。 通过在loadView()中设置self.view,我们告诉ViewController要显示哪个视图。 一些陷阱 从xib创建视图时有一些陷阱。 例如… 这是一个坏主意。 你能猜出为什么吗? 好吧,让我们看看如果运行该程序会发生什么。 EXC_BAD_ACCESS…。 这并没有多大帮助,但请看一下堆栈跟踪。 我们有一个整齐的无限循环! 当为“视图”本身(而不是文件的所有者)分配了自定义视图类时,将在每个xib实例化时调用自定义视图类中的初始化代码。 但是在初始化代码中,我们正在从xib实例化一个新视图。 实例化此xib时,将调用我们自定义视图的init代码。 但是在初始化代码中,我们正在从xib实例化一个新视图。 实例化此xib时,将调用我们自定义视图的init代码。 但是在初始化代码中……。 你得到其余的。 摘要 在本文中,我解释了 如何从xib文件创建视图 […]