Tag: 面向协议

在Swift中扩展枚举

我从几年前就开始使用Swift进行开发,甚至还用它编写了一个应用程序(Knowtrail),但是最近我开始通过使用Swift进行编码来谋生。 但是直到现在我才真正尝试过的一件事就是扩展枚举。 那是因为我还没有意识到Swift中有更多强大的枚举! 我必须解决的问题是我想编写一个管理器对象,该对象可以在蓝牙外围设备上写属性。 这些属性具有有限数量的可能值,因此使用枚举。 但是,每个属性具有一组不同的值,因此需要不同的枚举类型。 但是我仍然希望我的经理不管所有属性和类型如何,都以相同的方式处理所有属性,从而节省了代码行并获得了代码质量和整洁度。 主要是我想要一种集中式的方法来检查属性是否具有有效值(以我的蓝牙外围设备的角度而言),并且还从枚举中将该值导出为适合通过蓝牙写入的格式(在iOS中,这是Data类)。 知道我们在Swift方面取得的巨大进步之一就是它的态度:一切都应该是可扩展的并且可以协议化。我难以置信地半信半疑地试图在枚举中编写一些方法,以扩展它们并使之发挥作用! 我嵌入了一个大致示例的示例: 由于我希望所有枚举都能独立于其类型进行处理,因此Swift的实现方法是通过协议。 因此,我们可以看到上面定义的两个协议(Invalidable和Exportable),这些协议允许符合它们的任何对象提供一种方式,让任何人知道该对象的值是否有效,以及一种以所需格式获取其值的方法。 Environments枚举同时符合这两个协议,并且在isValid()方法中可以看到,枚举在所有方面都是对象。 我们可以访问自我及其属性! 它还支持类继承,在我们的示例中是UInt8。 通过继承UInt8,我们可以得到很好的副作用:根据其顺序,所有可能的值都将获得UInt8值(例如.indoors为0,.semiOutdoors为1等) 使用协议的另一个优点是它可以被非枚举对象采用,我们仍然可以导出它们的值并检查它们是否有效。 在我的情况下,我具有一些属性,其类型实际上是无限数量的选项(例如32位整数),因此我围绕UInt32创建了包装器自定义对象,并使它符合两种协议。 这样,我可以让其他经理使用相同的属性来处理我的所有属性,而不管它们的枚举类型如何,也不管它们是否是枚举!

迅捷泛型

Hola技术人员,直到现在我们所有人都已经阅读了许多有关最酷的主题泛型的博客和文章,但仍然有模糊的想法吗? 让我们尝试再次擦拭灰尘。 通用功能使程序更灵活,可重用和可伸缩,可以与任何类型的数据类型一起使用。 泛型是Swift非常强大的功能。 大多数基于泛型的库和框架。 甚至我们在日常编码生活中都使用了泛型而不了解它。 示例:数组和字典都是swift的通用集合类型,它们可以容纳任何类型的数据类型,例如Int,String,CGPoint等。让我们不必过多谈直接进入一些实际的东西。 让我们编码… 假设我们有一个电子商务应用程序,并且必须在该应用程序上的大部分位置显示金额标签,并且金额也来自服务器,而该服务器也不是特定的数据类型,有时是Int,Float,Double并可能是String本身。 因此,我们通常要做的是。 每次都厌倦了类型转换,如果我们得到一个函数,可以传递任何类型的数据类型,该函数会将其转换为带有$($)的字符串,以便将其直接传递给我们的UILabels或UIButtons。 是不是很酷。 在上面的扩展中,我们符合我们的协议,在该协议中,我们还将返回Double和String的转换,并附加了美元货币。 我们必须对所有数据类型实施相同的操作。 现在将在我们的Utilities类或Global类中编写一个全局函数amountText ,可以在整个项目中访问它而无需创建任何实例。 将使此函数公开,以便它也可以在模块外部使用,默认情况下,它将在内部使用,因此不能在定义模块之外使用。 此函数将采用通用数据类型,该数据类型均符合我们的协议AmountConversion 。 现在,使用我们的标签和按钮检查它是否可以接受。 来吧,我们来创建一个UILabel和一些带有若干数据类型的实例。 这是可以预期的数量。 并尝试直接将其与标签文本一起传递。 在这里,我们在协议中创建了一个初始化程序,这将有助于我们在通用方法中返回默认值,将在下一节中讨论它。 再次来到您的Utility或Global类,并编写一个通用方法。 它将接受所有符合我们的协议UnwrapOptional的任何类型的参数。 现在让我们创建一些可选的变量,并检查我们的函数是否给出了预期的结果。 下面一些变量具有值,而另一些则为零,让我们看看函数返回给我们什么值。 哇哈哈! 我们得到了期望的输出。 现在将进一步扩展此功能。 如果我们有任何自定义类或自定义类,在其中也有可选的var可以使用,那么它也可以是可选的链接。 能行吗 我们来看看 我有电影类,现在有一些可选变量尝试用电影类型的实例解开可选对象。 YOOO,之所以起作用,是因为最终我们试图解包已经符合我们的UnwrapOptional协议的字符串或指定的数据类型。 现在查看是否尝试解开Movie类的实例。 没运气! 它将出现错误“在参数类型’电影?’中,’电影’与预期的类型’UnwrapOptional’不符” 因此,在这里我们必须使我们的类符合协议。 并且它将为类添加必需的init,因为我们的UnwrapOptional协议具有自己的构造函数,可返回自定义类的默认值。 是的,任务完成了。 现在,我们可以尝试使用可选链接获取值。 抢啤酒! 干杯! 它的工作原理,足以满足一天的学习需求。 好了,我们了解了泛型如何与协议握手,并解决了许多问题。 泛型有很多附带功能。 欢迎反馈。

Swift中的Override协议扩展默认实现

您可能需要阅读https://team.goodeggs.com/overriding-swift-protocol-extension-default-implementations-d005a4428bda 这是我在Medium写作中缺席了很长时间(可能是四个月)之后的下一个故事,所以请耐心等待您。 我读过人们在使自己的Swift代码更紧凑并遵循面向协议的编程方面遇到问题。 两者都可以实现,但也有一些局限性。 让我告诉你我的情况。 在上面的代码中,我有一个协议X ,我想让UIView实现它。 然后,要实现X , UIView必须提供方法x() ,我们知道。 但是,假设我们要override UIStackView的x()实现,它是UIView子类。 问题来了。 扩展不能被覆盖,因为Swift实现扩展的方式是使用静态分派,这意味着在编译时就可以解决。 阅读https://www.raizlabs.com/dev/2016/12/swift-method-dispatch/ 好的,如果您的目标是编译代码,那么上面的代码实际上可以使用一些技巧进行编译。 首先,由于编译器声明我们不能覆盖扩展中的non-objc声明,因此我们必须使要实现的方法成为@objc方法。 该解决方法使该方法成为动态调度。 在运行时解决! 可是等等! 它运行正常吗? 啊,它在运行时有效! 我们快乐,可以实现子类中最重要的扩展! 但是有局限性。 使用@objc意味着您的协议要求签名应与@objc兼容。 如果您根据与@objc不兼容的类型提出了要求,该怎么@objc ? 当然,您不能做太多事。 局限性令人恐惧! 让我们看看另一种方法。 自我约束协议扩展 使用此方法,您不需要向@objc公开任何内容,因为此解决方法是完全Swift的。 但是,让我给您一个线索,这不适用于动态调度。 这样可以编译,但是如果您调用隐藏在其超类类型下的方法,则代码将存在错误。 是的,它可以正确编译。 但是它在运行时有效吗? 如您在Debug输出中所见。 当我们在隐藏在UIView下的UIStackView中调用x()时,就好像我们@objc上面使用@objc 。 那是因为s在编译时解析为使用where Self: UIView实现来调用x() 。 结论 如果您的实现应该动态地分派,则不得使用此override来与协议扩展一起使用。 您总是有机会使用@objc及其局限性,或者可以使用constrained protocol extension ,而@objc担心隐藏在客户端代码中的错误。 明智的朋友。 PS:我已经在此库ViewDSL中实现了@objc版本

Swift:在PAT上实现动态调度(具有关联类型的协议)

面向协议的编程功能强大而强大,我们对此毫无疑问! 但是,Swift仍然还不够完善(至少在撰写本文时),我今天要谈论的Swift的缺点之一是在使用PAT(关联类型的协议)时无法执行动态调度以及如何解决此缺陷并使PAT支持动态调度。 什么是PAT? 关联类型是未知类型的占位符,而PAT是具有一个或多个关联类型要求的协议。 如下所示的Animal协议是PAT的示例。 如果您想了解有关PAT的更多信息,建议您阅读NatashaTheRobot的这篇很棒的文章。 什么是动态调度? 动态调度是选择在运行时调用哪种多态操作(方法或函数)的实现的过程。 —维基百科 下面的示例代码演示了如何使用协议(没有关联的类型)实现动态调度。 上面的代码将生成两行输出: 我的老虎在丛林中行走。 我的牛在农场里散步。 这表明walk()函数调用已动态分配给所需的具体类型实现。 PAT的缺点 为了演示PAT的缺点,让我们在Animal协议中添加一个相关的类型要求FoodType 。 我们还将在Animal协议中引入eat(food: FoodType)功能要求。 如果将上面的示例代码粘贴到Xcode游乐场,则会出现如下所示的错误。 错误:协议“动物”只能用作一般约束,因为它具有“自我”或相关类型要求 这是什么意思? 基本上,这意味着如果我们的Animal协议包含一个或多个相关的类型要求,就不能再将Animal协议用作类型。 但为什么??? 😢 让我们看一下下面的代码片段。 从逻辑上讲,我们可以说上面的代码是正确的,因为我们将Meat喂给Tiger ,将Grass类喂给Cow 。 但是,编译器无法知道我们是否将正确的FoodType类型传递给eat函数。 myTiger和myTiger都是Animal类型,因此编译器不知道哪个是Cow类型,哪个是Tiger类型。 这种歧义解释了为什么编译器阻止我们使用Animal作为类型。 因此,在这种情况下无法进行动态调度。 解决方法💡 要解决此问题,我们将使用一种称为类型擦除的技术来隐藏类型内的动态调度。 要了解有关类型擦除的更多信息,这是gwendolyn weston的精彩演讲,您不可错过! 回到我们的解决方法,我们将要做的是停止使用Animal协议作为类型,并引入另一个新的具体类型来帮助我们执行动态调度。 在这里,我们将命名新的具体类型AnyAnimal ,它是一个符合Animal协议的枚举。 上面的代码是很容易解释的,您可以想象AnyAnimal枚举是一个调度程序,负责调度对所需Animal具体类型的函数调用。 请注意,此处没有强制使AnyAnimal符合Animal协议,但是这样做可以降低代码的健壮性。 想象一下将来将新功能需求添加到Animal协议的情况,如果我们忘记更新AnyAnimal协议,则会触发编译错误。 如果我们不遵守Animal协议,则不会触发任何错误。 说够了! 让我们看一下正在运行的AnyAnimal枚举… 在上面的代码片段中,我们使用AnyAnimal代替Animal作为我们的animalArray元素类型。 这样我们就可以避免使用Animal作为类型,从而避免我们先前遇到的错误。 接下来,让我们看一下通过枚举animalArray每个元素得到的输出。 我的老虎在丛林中行走。 我的老虎吃肉 我的牛在农场里散步。 我的牛吃草 我们可以看到, AnyAnimal枚举正在正确调度对期望的Animal具体类型的walk()和eat(food: […]

协议编程,第1部分:2D空间。

在假期期间,我最终有很多空闲时间,没有电话也没有电脑,只能随身携带iPad。 从这个实验中,我学到了两件事: 如果没有在计算机上编程,我将无法花几天的时间 使用Apple的Playground应用程序编写和编译Swift很高兴 因为我已经很长时间没有写Swift了,所以我认为这是一个很好的实验,看看自从他们引入条件一致性以来我能走多远。 动机来自尝试编写线性代数库的尝试。 这朝着另一个方向发展,但是结果是一组有用的协议,这些协议封装了复数(ℂ),2d向量(ℝ²)和其他线性空间的行为。 这篇博客文章中有一个Swift游乐场的副本,您可以在这里从iPad或Mac下载。 我强烈建议您尝试一下,因为您将能够运行此处示例所示的代码,并进行一些小的调整和更改。 常用操作 向量空间定义一个字段 ,一个字段具有相当大的一组属性(可交换性等)。 不幸的是,Swift的类型系统无法检查这些属性。 相反,我们将专注于我们需要的几个属性,而Swift允许我们实现并假设它们将是足够的。 我们将使用的属性是: 将两件事加在一起,并具有要添加的标识值。 将两件事相乘,并具有用于相乘的标识值。 二维对象的抽象。 将二维对象与标量相乘。 计算二维对象的大小。 我们的目标是,一旦我们将所有这些机器编织在一起,将我们的功能添加到类型中就将变得非常容易。 让我们开始吧。 多重复制 乘法协议在两个相同类型的值之间定义一个函数* ,并返回相同类型。 它还具有一个标识值,例如a * id = a 。 正如我们将看到的,对于Int和Double ,标识值分别为1和1.0但对于更复杂的类型则不是这种情况。 “ lhs”和“ rhs”分别代表“左手侧”和“右手侧”。 鉴于此,我们可以为Int类型实现乘法。 由于Int类型已经具有适合协议定义的*运算符,因此我们只需要定义multId值。 添加剂 加性协议与加性标识的值和加法运算符+相似。 并将其实现为Int : 这两个协议是monoid 二维 为了简化我们的未来工作,我们将抽象一些类型具有二维的事实。 这样,他们将需要能够返回每个组件并根据两个组件值创建自己。 该协议将使我们能够为具有二维的类型定义默认实现。 我们没有符合该协议的任何类型,但我们已经可以为其实现Additive 。 的确,任何类型都是可Additive只要它具有两个维并且其成分也是可Additive 。 至于addId ,由于Additive是通过将每个组件加在一起来实现的,我们可以通过创建一个具有每个组件标识值的2D对象来对其进行定义。 如我们所见,加法标识不是1而是一个使用其组件类型的标识值的值。 大小 […]

快速进行面向协议的编程

你们都有关于POP的知识:面向过程的编程&OOP:面向对象的编程。 如果您了解C,那么您必须了解POP。 我心爱的Objective C也遵循OOP概念。 现在,SWIFT。 它遵循面向协议的编程(Protocol OP)。 有人说Swift使用了太多协议,所以它是Protocol OP。 大声笑。 这绝对是错误的概念。 协议 首先让我们了解协议。 协议不过是接口,或者您可以说它是属性和功能的蓝图,任何类或结构都可以采用它来执行特定任务。 让我们看看它是如何工作的。 协议播放器{ var song:字符串{获取设置} var artist:字符串{获取设置} var audioLink:字符串{获取设置} func play() func pause() } 我创建了一个简单的Player协议 ,该协议具有三个属性和两个功能。 下面,我有一个用Player协议装饰的AudioPlayer类 。 采用协议将迫使您实现它具有的所有属性和功能。 类AudioPlayer:播放器{ var song:字符串 var艺术家:字符串 var audioLink:字符串 init(歌曲:String,艺术家:String,audioLink:String){ self.song =歌曲 self.artist =艺术家 self.audioLink =音频链接 } func play(){ //播放音频的代码 } func pause(){ //暂停音频的代码 }} 只能为类类型设计协议。 […]

协议编程,第2部分:依赖向量

在上一部分中,我们使用了协议以便对我们感兴趣的某些属性进行建模,并找到了适合该模型的多个示例(2D矢量,复数和线性函数)。 但是这些仅限于二维类型。 这太局限了,我们想操纵2个以上的维度。 这将需要确保我们正在操纵相同尺寸的值。 像第1部分一样,您可以在一个快速的游乐场中跟随,您可以在此处下载iPad或在Mac下载 免责声明 紧随其后的内容被视为“请勿在家中尝试”,纯粹是出于学术兴趣而非在Swift中的实际应用。 我们将使用协议来实现依赖类型。 N维向量,第一次尝试 为了更好地理解该问题,我们将尝试使用一个简单的数组来实现n维向量。 现有的大多数实现都使用数组来表示n维向量,但是它们有很大的缺陷: 不幸的是,我们无法实现NVector Additive ,因为它没有携带有关其大小的足够信息。 我们需要以某种方式对向量的大小进行编码。 这将使我们既可以重用代码,又可以避免出现以下错误: 为了解决此问题,我们将需要使用Size类型参数及其所持有的值的类型声明NVector结构。 我们的+函数看起来像这样: 由于第一个类型参数是数组的大小,因此我们强制要求两个加法向量的值相同。 事实证明,Swift已经拥有足够的技术来实现这种功能。 首先,我们将定义一个新的类型Nat以表示自然数 纳兹世界 “ Nat”是“自然数”的简写,自然数是从0到无穷大的整数的名称。 使用这两个定义来递归定义它们: 零是自然数。 自然数的任何后继也是自然数。 可以这样用Swift编写: 现在,我们可以定义几个自然数: 如您所见,数字是根据先前的数字递归定义的。 您还可以看到我们正在使用没有值的空enum声明。 这意味着Nat仅作为类型而不是value存在。 类型Three没有值 。 但是,我们可以创建一个函数,给定类型为Nat的函数可以返回它表示的Int值。 这样我们就可以从类型Three获得值3 (类型为Int )。 方法如下: 1.首先我们需要一个协议,该协议将声明符合它的类型具有Int表示形式 该协议声明符合它的类型具有与之关联的静态Int值。 2.然后,我们需要为Zero类型和Succ类型实现此协议。 Zero直接实现: Zero由数字0表示。 3.最后,我们需要为Succ实现相同的协议。 Succ有点棘手,因为它是递归定义的。 因此,我们需要利用条件一致性来要求内部N类型也具有Int表示形式。 包装的类型N实际上是我们当前正在计算的类型的前身。 因此, Succ返回1加N的Int值,它是前身。 这样,类型Two返回1加上1整数值,后者本身返回1 + Zero整数值0 。 总计为1 […]

协议关联类型-PAT

简而言之,我们都知道面向协议的编程的功能。 要在蛋糕上添加樱桃,我们在协议中提供了“关联类型”。 让我们创建一个简单的Stack功能: 协议栈{var count:Int {get}变异func push(item:Int)变异func pop()-> Int}结构容器:Stack {var items = [Int]() var count:Int { 返回items.count }更改func push(item:Int){ items.append(item) }更改func pop()-> Int { 返回items.removeLast() } } var intContainer =容器(项目:[]) intContainer.push(项目:10) intContainer.push(项目:20) intContainer.push(项目:30) intContainer.push(项目:40) intContainer.push(项目:50)print(intContainer.count) 打印(intContainer.pop()) 但是,这对我们当前的Stack协议很有用,但是有一个主要的缺点。 只有处理Int的容器才能符合此协议。 我们可以使用“关联类型”功能来消除此限制。 关联类型像泛型一样工作。 为了演示,让我们更改Stack协议以利用关联的类型: 协议栈{ 关联类型ItemType var count:Int {get} 变异func push(项目:ItemType) 变异func pop()-> ItemType } 现在,堆栈协议允许存储任何类型的项目(整数,字符串或任何其他自定义类型) struct Container […]

Swift协议扩展方法调度

今天,我想写一个我发现的错误。 当我接触CoreValue时,我正在研究它。 CoreValue是一个框架,允许您使用CoreData保留值类型。 该错误导致数据库中的条目重复 。 该错误也很有趣,因为我学到了一些东西。 它使用语言功能作为隐藏的斗篷。 一旦找到了怪物,我就能看到这种语言特征并对其进行了解。 然后,我决定写这则短篇小说。 我学到了什么? 协议扩展有2种方法。 需求 方法和静态 方法 ( “没有需求支持”)。 需求方法使用动态调度 。 静态方法使用静态调度 。 知道它们之间的差异非常重要,这样您就不会产生错误。 我将通过首先解释该错误来说明为什么它很重要。 您可以跳过此部分,并了解以下每种方法。 我将不使用CoreValue示例,因为它很难解释。 所以,我要编一个故事。 英雄中隐藏的错误 这是一个关于不太聪明的英雄的故事。 英雄就是战士。 和战士,他们只是fight(). 那是必要条件 。 战斗人员在战斗时只会掷punch() 。 这是用扩展方法编码的,因为它是所有Fighter的共同行为: 协议战斗机{ func fight() //要求 } 扩展战斗机{ //需求 func fight(){ self.punch() } //静态方法(协议中不需要) funcpunch(){ 打印( “打拳” ) } } 英雄不是简单的战士,他们是MagicalFighters。 当魔法战士猛击时,他们首先施法: 协议MagicalFighter:战斗机{ […]

面向协议的编程为继承的上层编程打开了大门。

当讨论架构时,OOP在界面的帮助下是足够开放的。 但是程序员总是必须在子类中实现它。 在不知不觉中, OOP似乎鼓励我们专注于较低继承级别的编程 。 当我们认为自己的架构足够好时,顶级世界立即冻结了,但是一两个月之后,事实并非如此。 例如,下面是OOP中接口的常用实现 类狗扩展动物工具可移动{ func move(){ //狗的运动 } } 但是, 面向协议的编程为上层编程打开了一扇门 。 您可以在上层而不是下层子类中实现。 这是一个完全新鲜的想法。 在POP中实施协议 扩展名可移动自我:狗{ func move(){ //狗的运动 } } 很难说POP比OOP好得多,但是很明显,凭借上层编程的力量,POP更加灵活。 享受面向协议的编程,不要过度使用它。