Tag: 函数式编程

使用FunctionalTableData构建应用程序(第1部分)

Shopify构建的FunctionalTableData库采用另一种方法来解决先前的问题。 通过创建我们所有元素必须遵循的可重用的封装CellConfigType协议。 我们可以在UITableView和UICollectionView显示这些项目,而无需更改其中的任何一行代码。 使用CellConfigType 名为HostCell,的库提供了CellConfigType即用型实现HostCell,我们将在这些文章中使用它来降低学习难度。 当使用FunctionalTableData时, CellConfigType实例化实现是您唯一关心的问题,其余的工作将为您完成。 如果您感到好奇并想在内部进行窥视,欢迎您深入研究代码库。 HostCell由给定视图的UIView,状态和布局组成。 如果我们以应用预览(图1)为例, LabelCell的HostCell实现将类似于以下内容。 它可能看起来很多,但是一旦我们开始阅读代码,它就非常简单。 我们将State定义为表示视图的信息,在此示例中,UILabel可以具有文本,字体,以及是否为多行形式的State 。 图1的顶部单元很容易用此State表示。 我们可以利用LabelState初始化程序中的默认值,并创建一个LabelCell如下所示:

枚举的许多面孔

对我来说这已经是忙碌的几个星期了,很高兴终于回到我可以适应例行程序的地步。 从单身派对到一个可怕的寒冷一周,再到哈德逊山谷度过一个愉快的假期,我再一次发现自己在布鲁克林,在计算机前编写代码(与之相对的另一种纸张和纸本一支钢笔)。 所以我们到了! Swift有了对Objective-C的许多改进(对于那些久经考验的真正的Objective-C开发人员,我很容易受到侮辱),从面向协议的编程到功能更强大的编程方法,甚至是围绕泛型的更好的API。 Swift中的许多功能使其成为令人愉悦的开发体验。 我个人发现Swift中的枚举很棒。 它们一直是我最常用的语言功能之一,以至于我可能对它们有些执迷,这是一个错误。 但是,我将介绍一些我认为枚举可能非常强大的案例,特别是 澄清看似任意的值 状态的封装 错误包装 澄清看似随意的值 枚举最适合用来处理似乎晦涩难懂,难以记忆的价值并为其应用含义。 这主要是因为它们是可读值。 我们每天在语言中使用的单词具有很多含义,我们可以在有些令人困惑的值之上轻松地传递该含义。 让我们以CAGradientLayer为例。 CAGradientLayer具有两个属性,即startPoint和endPoint ,它们都是CGPoint.实例CGPoint. 苹果将​​这些文件记录如下 绘制到图层的坐标空间中时,渐变的起点和终点。 起点对应于第一个渐变停止点,终点对应于… 这是对这两个属性的不必要的冗长描述。 这两个点都代表渐变开始的x和y坐标,范围从0到endPoint和endPoint的默认值endPoint为[0.5, 0] endPoint [0.5, 0]和[0.5, 1] endPoint [0.5, 1] 。 很难想象这意味着什么,但这是从上到下的垂直渐变。 如果要构建水平渐变层,则需要执行以下操作。 func horizo​​ntalGradientLayer()-> CAGradientLayer { 让渐变= CAGradientLayer() gradient.startPoint = CGPoint(x:0,y:0.5) gradient.endPoint = CGPoint(x:1,y:0.5) 返回梯度 } 即使现在只输入一次,我也必须考虑一下startPoint和endPoint可以正确定义水平渐变的不同可能性。 这种想法容易出错。 使用枚举定义渐变方向可以改善我们对渐变实际外观的可视化。 枚举GradientDirection { 垂直案例 案例水平 […]

将视图模型建模为函数

注意:本文假设您了解RxSwift的基础知识或对MVVM有了一般的了解。 如果您不这样做,Internet上有大量令人难以置信的资源(包括下面链接的资源)可以帮助您入门。 另一个注意事项: Point-Free的 Brandon Williams 和 Stephen Celis 最初对这个想法睁开了眼睛 。 如果您尚未签出,则应该这样做。 他们发布的内容对于Swift开发人员来说绝对是必不可少的,订阅可能是您今年做出的最有价值的投资。 介绍 关于如何将RxSwift与MVVM配对,已经写了成千上万的单词,并进行了数小时的讨论。 在Grailed,我们一直热衷于与社区进行创新,以改善我们的代码并为我们的消费者创造更好,更可靠的产品。 出于这个目标,我们一直在使用一种MVVM形式,该形式通过函数式编程和RxSwift来提供可靠性,可测试性和稳定性。 我们喜欢它的许多方面,但是遇到了大多数MVVM开发人员都会熟悉的一系列问题。 问题所在 有时可能不清楚如何组织代码。 在互联网上漂浮的数十种MVVM变体中,每个视图似乎对视图层如何与其视图模型进行交互都有不同的看法。 缺少清晰的模式可能会使MVVM中的开发感到特别和不一致,从而可能导致可维护性问题。 视图模型也可能变得非常冗长。 由于许多版本的MVVM中缺乏结构,其他人选择为视图模型编写更为明确的输入和输出合同,但传统上这是以大量的样板为代价的。 同时,其他版本也不能防止视图模型的使用者错误地使用其API。 从视图层订阅视图模型的输入众所周知是一种反模式,但这是我在多个生产代码库中看到的。 理想情况下,编译器将阻止我们完全犯此错误。 由于Swift的类和结构初始化规则,视图模型也可能变得难以设置。 例如,当您必须将输出Observable设置为类或结构的属性时,这些输出取决于输入Subject 。 有时您会遇到这样的情况:在完成所有属性的初始化之前,您不能引用self ,但是由于需要引用self ,因此无法初始化属性。 忘记绑定视图模型的输入或输出也很容易,而且编译器在做错事情时也不会帮助我们找出问题。 编译器是我们的朋友,如果它可以帮助我们,那就太好了,这样我们就不会犯这种愚蠢的错误。 一种解决方案 现在,我们已经解决了RxSwift带来的MVVM的一些痛点,让我们以一个流行的MVVM风格编写一个简单的代码示例,并研究如何改进它。 所有笔触的开发人员都知道,编写纯函数可以解锁可测试性和可理解性的级别,否则,即使不是不可能,也很难实现。 问题在于我们编写的许多类型的代码无法完全适合纯函数,因此我们努力为尽可能多的模型建模纯函数。 这种见解驱使许多MVVM开发人员创建他们的视图模型,以具有一组明确的输入和输出,以便我们可以更类似于功能地对待它们。 输入是函数调用或Subject ,输出是回调,可变变量或Observable ,并且视图的业务逻辑在视图模型内部建模为输入到输出的转换,主要是在视图模型的初始化程序中。 Kickstarter开源应用可能是第一个,也是最知名的迭代。 尽管它是用ReactiveSwift编写的,但它们的想法几乎是相同的。 这个开源的应用程序使许多开发人员(包括我自己)对我们如何使用MVVM实现应用程序的可测试性和稳定性打开了眼睛。 让我们以经典的RxSwift为例,使用用户名和密码字段以及登录按钮的简单登录表单。 我们只希望在用户名和密码均填写后才启用该按钮,并且当用户单击该按钮时,我们希望显示一些有关其成功登录的消息(我们将在此处对其进行硬编码,在以后的文章中将详细介绍如何处理网络)。 这是一个示例,说明如果遵循Kickstarter应用程序中列出的模式,该如何编写。 如此小的屏幕在这里有很多事情要做,所以花一点时间来消化它。 我非常喜欢这种风格的几件事: 视图模型创建了一个非常明确的契约,关于它的功能以及应如何使用它 很少有错误使用视图模型API的方法 视图模型没有副作用 从外部查看此视图​​模型并了​​解如何对其进行测试非常容易 […]

在Swift上编写函数

简要回顾概念 当我们谈论功能的组成时,我们采用的是两个功能,其中一个的输出值是另一个的输入值。 他们都必须等待一个参数(这将是复合函数的输入),也就是说,它们没有被完全应用。 (此过程称为部分申请) (雾)(x)= f(g(x)) 在谈论部分应用程序时,特别是在讨论部分应用程序功能时:如果传递的参数少于期望的接收数量,则该函数将返回一个新函数,该函数将等待其余参数。 f(x,y,z):w / f(x,y)->(g(z)-> w) 没有另外的基本概念,局部应用就不可能存在。 咖喱化是将等待几个参数的函数转换为一系列接受一个参数并返回等待下一个参数的函数的过程。 考虑到这一点,部分应用是计算直到特定参数N的过程。 f(x,y,z):w / f(x)->(g(y)->(h(z)-> w)) (我没有使用数学符号,尽管这样做是正确的,因为这些概念属于数学。 所有这些概念在这里都有更好的解释,以及如何在Swift中应用。

基于协议的通用网络-第2部分JSONEncoder和Swift中的Post请求可编码。

自从我在那篇文章中写了“在Swift 4中使用JSONDecoder和Decodable的基于协议的通用网络”以来,我一直在谈论如何使用Decodable,协议和泛型来创建可重用的网络层。 效果很好,但API仅限于GET HTTP方法,这就是为什么在本教程中我想将其扩展为使用POST方法,我将尽力向您展示如何使用枚举来完成这项工作,我假设您没有读过第1部分,因此在新项目中我将从零开始,您可以随时返回第一部分以了解更多详细信息。 我们将从GenericAPI类开始,创建一个新文件并复制并粘贴… 协议GenericAPIClient {var session:URLSession {get} func fetch (带有请求:URLRequest,解码:@escaping(Decodable)-> T ?,完成:@escaping(Result )-> Void )}扩展名GenericAPIClient {typealias JSONTaskCompletionHandler =(Decodable ?, APIError?)->无效的私有函数解码任务(带有请求:URLRequest,decodeType:T.Type,completionHandler的完成:@转义JSONTaskCompletionHandler)-> URLSessionDataTask {let任务= session.dataTask(with:request){数据,响应,防护错误让httpResponse =响应为? HTTPURLResponse否则{completion(nil,.requestFailed(description:error?.localizedDescription ??“无描述”))return} guard httpResponse.statusCode == 200 else {完成情况(nil,.responseUnsuccessful(description:“ \(http(Response.statusCode )“))return}保护let data = data else {complete(nil,.invalidData); return} do {letgenericModel = try JSONDecoder()。decode(decodingType,from:data)complete(genericModel,nil)}捕获let err { }} return task} ///成功响应在主线程上执行。 func fetch […]

学习RxSwift(第1部分)

如今Rx蓬勃发展,我认为如果每个开发人员都使用RxSwift会更好。 Rx =反应式编程,这是一个与数据流和变化的传播有关的声明式编程范例,例如,在命令式编程中,将a = b+c设置a = b+c将a的结果赋给a b+c ,然后是b的值 和c 可以更改而不会影响a的值,但是在反应式编程中,a的值 只要b的值自动更新 或c 更改,而程序不必重新执行语句a:=b+c 确定a的当前赋值。 维基百科 ReactiveX是来自以下方面的最佳创意的组合 观察者模式,迭代器模式和功能编程。 ReactiveX不仅仅是一个API,它是编程的想法和突破。 它启发了其他几种API,框架,甚至是编程语言。 在ReactiveX中,观察者订阅了一个Observable。 然后,该观察者对可观察对象发出的任何项目或项目序列做出反应。 这种模式有助于并发操作,因为它在等待Observable发出对象时不需要阻塞,而是以观察者的形式创建了一个哨兵,随时准备在Observable以后的任何时间做出适当的反应。 在ReactiveX中,许多指令可能会并行执行,并且随后会捕获其结果,您以“可观察”的形式定义了一种用于检索和转换数据的机制,然后为它订阅了一个观察者,在此之前,当观察员站岗时,定义好的机制便开始行动起来,以随时捕获并响应其排放。 这种方法的优点是,当您有一堆彼此不依赖的任务时,可以同时启动所有任务,而不必等每个任务都完成才开始下一个任务-这样,您的整个完成任务捆绑包所需的时间与捆绑包中最长的任务一样长。 热点观察 在创建项目后立即发出项目,因此以后订阅该Observable的任何观察者都可以开始观察中间位置的序列。 冷观测 等到观察者订阅它之后才开始发射项目,因此可以保证这样的观察者从一开始就可以看到整个序列。 RxSwift功能强大且功能强大,可节省大量开发人员的生命和精力,可用于: 1-绑定 Observable.combineLatest(firstName.rx.text,lastName.rx.text){$ 0 +“” + $ 1} .map {“问候语,\($ 0)”} .bind(发送至:greetingLabel.rx.text) 2-代表 而不是进行乏味且无表情的操作: 公共功能scrollViewDidScroll(scrollView:UIScrollView){[弱自我] self?.leftPositionConstraint.constant = scrollView.contentOffset.x } …写 self.resultsTableView .rx.contentOffset .map {$ 0.x} .bind(发送至:self.leftPositionConstraint.rx.constant) […]

功能反应式编程(FRP)的基本构建模块:IOS

每个大型结构都有基本单元,这些基本单元组合在一起,对整个结构有意义。 例如,砖块,水泥,油漆,混凝土等构成建筑物上的基本构建块。 同样地,在我们继续讨论功能性反应式编程的广泛领域之前,如果我们了解将要结合在一起的基本构建模块,以便使我们创建的大量应用程序有意义,那将是非常棒的。 FRP的基本构建模块,从事件流开始。 事件流可以定义为随着时间推移发生的一系列事件。 您可以将其视为异步数组。 下图显示了事件流的简单描述: 如您所见,我们在箭头上表示了时间,该箭头从左到右对齐,向前移动到右边,事件随时间而发生。 在时间轴上间歇性地绘制的彩色气泡(由其名称指示)表示事件 。 我们可以在整个序列中添加一个事件侦听器,并且每当事件发生时, 我们都可以通过做一些事情来与之互动 ; 那是主要思想! 在Swift中,我们还有许多其他类型的序列,例如数组: 假设我们有一个eventStream数组: var eventStream = [“ 1”,“ 2”,“ abc”,“ 3”,“ 4”,“ cdf”,“ 6”] 让我们尝试将eventStream与数组进行比较; 数组是空间中的序列,这意味着eventStream数组中的所有项目现在都存在于内存中; 另一方面,eventStreams没有该属性。 事件可能随着时间而发生,您甚至都不知道所有可能发生的事件以及何时发生。 S o,如果我们必须在数组和事件流之间建立联系,那么我们可以断言,如果[[1],“ 2”,“ abc”,“ 3”,“ 4”,“ cdf”,“ 6” ]值会在一段时间内发生,并且不只是从头开始存在于内存中,前面的数组将像事件流一样,其中事件“ 1”可能在第一秒发生,事件“ 2”可能在第四秒发生,事件“ abc”可能会在第10秒发生,依此类推。 在这里,请注意,事件发生的时间和事件的类型都不是事先知道的。 事件仅在发生时解决。 事件流的好处是它们具有类似于数组的功能。 假设我们的问题是在给定数组中添加所有数字。 数组的解决方案 :如您所见,数组中的元素不是数字; 它们是字符串,因此我们必须在此处进行一些转换,然后遍历循环以过滤掉无法转换为数字的字符串,然后将其余部分(如果它们是有效数字)相加。 我们可以使用for循环遍历数组,但是当我们遍历for循环内的数组时,我们将使用map和filter操作来提供问题的解决方案,这是功能性方法: 第一步 : 让result = […]

命令式与声明式编程

所以我在研究RxSwift,结果发现 “ Rx允许以声明的方式构建应用程序。” 我对此没有给予太多关注,我们称之为“ 范式 ”,因为我习惯了逐步编程(命令式编程)。 我不知道这种情况的存在,也对此一无所知,但是实际上了解编程范例实际上是有帮助的,因此,作为开发人员,当我们进行编码时,我们可以知道应该采用哪种编程范例。 所以,让我们开始吧, 在研究“命令式和声明式编程”时,通常会遇到类似的定义,即, 命令式编程告诉我们如何做,而声明式编程告诉我们我们想发生的事情。 那是什么意思呢? 让我们更深入地了解一些简单的代码片段 声明式编程描述了您试图完成的工作,而没有定义如何执行。 那么,您观察到了什么? 对于第一个命令式编程示例来说,它通常具有更多的行代码,这是非常明显的区别。 另一个是,它明确遵循分步指令以从数组中获取偶数。 在声明式编程方法中,它利用了一个现有函数filter 。 命令式编程提供了灵活性,但带来了复杂性,声明式编程隐藏了复杂性并提供了简单性 应用命令式和声明式编程的编程语言 所以回到我学习RxSwift的研究时, 如果Swift是命令式语言,并且RxSwift允许我们以声明性方式进行编码,那怎么可能呢? 基本上,声明式编程是对函数的抽象,而该函数所基于的则是命令式实现。 这种编程通常更加安全和规范,这使我们的程序员可以编写易于理解的代码。 因此,即使某些语言是必需的,我们仍然可以以声明性的方式进行编码,以便当我们作为团队进行编程时,它使我们能够以更加安全和可扩展的方式进行编码。

使用懒惰延迟计算

作为iOS开发人员,您可能已经听说过用于初始化的lazy关键字-除非有人真的想要与之交互,否则不会创建惰性组件。 那真lazy 作品:推迟工作直到需要做为止。 实际上,我们可以做更多的事情。 不仅用于属性初始化,还用于Swift函数编程中的计算。 让我们看看它是如何工作的。 假设我们有一个由整数组成的数组,并且我们要求结果的值是原始数组的一个元素的两倍。 这是示例代码: 让数组= [1,2,4,5,3,7] let element = array.map {$ 0 * 2} [3] 打印(元素) 您可能会注意到,我们只需要第3个元素,而我们将数组中所有6个元素的值加倍,这是多余且无用的。 如何解决? 使用lazy 。 Swift提供了一个名为LazySequenceProtocol的协议   ,并且在其扩展名中有一个惰性变量来推迟计算(例如map 和filter 在函数式编程中。 定义如下: ///避免创建多层“ LazySequence”包装器。 ///符合“ LazySequenceProtocol”的所有内容都已经是懒惰的。 扩展LazySequenceProtocol { ///与`self`相同。 public var lazy:自我{得到} } 随着lazy   ,我们可以修复原始代码以使其高效: 让数组= [1,2,4,5,3,7] 让element = array.lazy.map {$ 0 * 2} [3] 打印(元素) […]

Swift中的面向铁路的编程

在本文中,我们将讨论处理函数执行的两个轨道:快乐路径和错误路径。 几乎每个编程任务都由两个用户的轨迹或路径组成。 当一切顺利时,我们称其为“幸福之路”。 但是每次发生某些事情时,尤其是在移动应用程序中。 验证错误,互联网消失,系统杀死我们的应用程序,硬件报告错误,服务器不可用等。 通常,错误路径需要付出更大的努力。 我们应该以某种方式处理所有预期的和意外的错误。 铁路定向编程(ROP)可以帮助我们完成此例程。 该术语的最初发明者是Scott Wlaschin。 他是https://fsharpforfunandprofit.com/的创建者,并且是《 Domain Modeling Made Functional》一书的作者(强烈推荐给所有人)。 在他很少使用F#语言讨论ROP之后,许多社区开始对所有其他语言(而不仅仅是功能语言)采用这种方法。 现在,我想展示一下我们如何在Swift中做到这一点。 让我们从问题开始。 假设我们有一项添加基本用户注册功能的任务。 用户必须提供电子邮件和密码,它们必须有效,并且系统中不应有任何重复的电子邮件。 好的,让我们为注册功能建模。 我们为输入数据和用户创建简单的结构。 CustomDebugStringConvertible协议将有助于打印输出,而UserError枚举描述了注册过程中所有预期的和意外的错误。 这非常简单明了,让我们继续。 验证之后,我们希望将用户保存到数据库或将其发布到Web服务。 再次,为简单起见,我决定使用一个模拟保存到数据库的函数。 在saveToDb(_ 🙂函数中,我们尝试创建新用户并将其放入商店,并在发现重复的情况下引发错误。 好,准备完毕。 现在让我们做第一个尝试编写寄存器功能的尝试。 首先,让我们看一下签名(UserInput)引发-> User。 我们希望接收输入并返回一个新用户。 但是我们也想以某种方式返回所有可能的错误。 一种方法是抛出错误 。 但是通常抛出错误意味着意想不到的事情刚刚发生。 我们期望电子邮件可能无效吗? 是。 我们期望密码可能无效吗? 是。 我们是否期望可能已经存在具有相同电子邮件的用户。 是。 那么,为什么不只将用户和预期错误之一一起返回呢? 人们称这种类型为元组。 Swift可能支持元组。 新的签名是(UserInput)->(User ?, UserError?) 更好,但是现在我们必须对User和UserError使用Optionals 。 这是因为我们可以返回用户或错误,但不能同时返回两者。 熟悉函数式编程的人可以说:“我们在这里需要求和类型!”。 他们将是正确的。 而且我们不必发明新的东西,为此已经存在良好的抽象。 […]