移动开发。 Swift:协议之谜

今天,我们继续进行有关iOS移动开发主题的系列出版物。 如果最后一次是关于面试中您需要什么和不需要什么的,那么在本文中,我们将探讨协议的主题,这在Swift中很重要。 这将涉及协议的安排方式,彼此之间的差异以及它们如何与Objective-C接口结合。



正如我们之前所说,新的Apple语言正在不断发展,其大多数参数和功能在文档中都有明确说明。 但是,当需要在此处和现在编写代码时,谁会阅读文档? 因此,让我们直接在我们的帖子中介绍Swift协议的主要功能。

首先,值得一提的是Apple协议是“接口”概念的替代术语,该接口在其他编程语言中使用。 在Swift中,协议用于指示可以在抽象级别上工作的某些结构的模式(所谓的蓝图)。 简而言之,协议定义了一定类型必须继承的许多方法和变量。

在本文的后面,将逐步揭示出一些时刻,包括从简单且经常使用到更复杂的时刻。 原则上,在面试中,您可以按顺序提出问题,因为它们决定了申请人的能力水平-从6月的水平到老年人的水平。

Swift需要哪些协议?


移动开发人员通常根本不使用协议,但是他们失去了抽象处理某些实体的能力。 如果我们强调Swift中协议的主要功能,我们将获得以下7点:

  • 协议提供多重继承
  • 协议无法存储状态
  • 协议可以被其他协议继承。
  • 协议可应用于结构(struct),类(class)和枚举(枚举),定义类型功能
  • 泛型协议允许您在继承期间指定类型和协议之间的复杂依赖关系
  • 协议未定义“强”或“弱”变量引用
  • 在协议的扩展中,可以描述方法和计算值的特定实现
  • 类协议仅允许类继承

如您所知,Swift中的所有简单类型(字符串,整数)都是结构。 在Swift标准库中,例如,如下所示:

public struct Int: FixedWidthInteger, SignedInteger { 

同时,集合类型(集合),即数组,集合,字典,也可以打包到协议中,因为它们也是结构。 例如,字典定义如下

 public struct Dictionary<Key, Value> where Key: Hashable { 

通常在面向对象的编程中,使用类的概念,并且每个人都知道从后代类继承父类的方法和变量的机制。 同时,没有人禁止他包含其他方法和变量。

就协议而言,您可以创建更有趣的关系层次结构。 为了描述下一类,您可以同时使用多种协议,这使您可以创建相当复杂的设计,这些设计可以同时满足许多条件。 另一方面,不同协议的局限性使得有可能形成仅用于狭窄应用且包含一定数量功能的某些对象。

Swift中协议的实现非常简单。 该语法包含一个名称,许多方法和将包含的参数(变量)。

 protocol Employee { func work() var hours: Int { get } } 

另外,在Swift中,协议不仅可以包含方法的名称,还可以包含其实现。 协议中的方法代码通过扩展添加。 在文档中,您可以找到许多扩展的提及,但是对于扩展中的协议,您可以放置​​函数的名称和函数的主体。

 extension Employee { func work() { print ("do my job") } } 

您可以对变量执行相同的操作。

 extension Employee { var hours: Int { return 8 } } 

如果我们在某处使用与协议关联的对象,则可以在其中设置具有固定值或传输值的变量。 实际上,变量是一个很小的函数,没有输入参数...或者可以直接分配参数。

在Swift中扩展协议允许您实现变量的主体,然后实际上它将是一个计算值-具有get和set函数的计算参数。 也就是说,这样的变量将不存储任何值,但是将扮演一个或多个函数的角色,或者扮演某些其他变量的代理角色。

或者,如果我们采用某些类或结构并实现协议,则可以在其中使用常规变量:

 class Programmer { var hours: Int = 24 } extension Programmer: Employee { } 

值得注意的是,协议定义中的变量不能弱。 (弱是一种变体实现)。

还有更多有趣的示例:您可以实现数组的扩展并在其中添加与数组的数据类型相关的函数。 例如,如果数组包含整数值或具有相等的格式(适合比较),则该函数可以例如比较数组单元格的所有值。

 extension Array where Element: Equatable {   var areAllElementsEqualToEachOther: Bool {       if isEmpty {           return false       }       var previousElement = self[0]       for (index, element) in self.enumerated() where index > 0 {           if element != previousElement {               return false           }           previousElement = element       }       return true   } } [1,1,1,1].areAllElementsEqualToEachOther 

一句话。 协议中的变量和函数可以是静态的。

使用@ objc


在这件事上,您需要了解的主要事情是@ objc Swift协议在Objective-C代码中可见。 严格来说,对于这个“魔术词” @ objc存在。 但是其他一切都保持不变

 @objc protocol Typable {   @objc optional func test()   func type() } extension Typable {   func test() {       print("Extension test")   }   func type() {       print("Extension type")   } } class Typewriter: Typable {   func test() {       print("test")   }   func type() {       print("type")   } } 


此类协议只能由类继承。 对于列表和结构,无法完成。

那是唯一的方法。
 @objc protocol Dummy { } class DummyClass: Dummy { } 


值得注意的是,在这种情况下,可以定义可选函数(@obj optional func),如果需要的话,可以像之前示例中的test()函数那样实现该函数。 但是有条件的可选功能也可以通过用空实现扩展协议来实现。

 protocol Dummy { func ohPlease() } extension Dummy { func ohPlease() { } } 


类型继承


通过创建类,结构或枚举,我们可以表示某种协议的继承-在这种情况下,即使我们无权访问继承的参数,该参数也适用于我们的类以及继承该类的所有其他类。

顺便说一下,在这种情况下出现了一个非常有趣的问题。 假设我们有一个协议。 有一些课。 并且该类实现了协议,并且具有work()函数。 如果我们扩展了协议,同时又具有work()方法,会发生什么情况。 调用该方法时将调用哪一个?

 protocol Person {    func work() } extension Person {   func work() {       print("Person")   } } class Employee { } extension Employee: Person {   func work() {       print("Employee")   } } 

将启动class方法-这些是Swift中调度方法的功能。 而这个答案是由许多申请人给出的。 但是,在如何确保代码中没有类方法而是协议方法的问题上,只有少数人知道答案。 但是,对于此任务也有解决方案-它涉及从协议定义中删除该函数并按以下方式调用该方法:

 protocol Person { //     func work() //      } extension Person {   func work() {       print("Person")   } } class Employee { } extension Employee: Person {   func work() {       print("Employee")   } } let person: Person = Employee() person.work() //output: Person 


通用协议


Swift还具有带有关联类型的通用协议,可让您定义类型变量。 可以为此类协议分配附加在关联类型上的条件。 这些协议中的几种使您可以构建应用程序体系结构形成所必需的复杂结构。

但是,您不能将变量实现为通用协议。 它只能被继承。 这些构造用于在类中创建依赖关系。 也就是说,我们可以描述一些抽象的通用类来确定其中使用的类型。

 protocol Printer {   associatedtype PrintableClass: Hashable   func printSome(printable: PrintableClass) } extension Printer {   func printSome(printable: PrintableClass) {       print(printable.hashValue)   } } class TheIntPrinter: Printer {   typealias PrintableClass = Int } let intPrinter = TheIntPrinter() intPrinter.printSome(printable: 0) let intPrinterError: Printer = TheIntPrinter() //   

应当记住,通用协议具有很高的抽象水平。 因此,在应用程序本身中,它们可能是多余的。 但是同时,在对库进行编程时会使用通用协议。

类协议


Swift还具有类绑定协议。 两种语法用于描述它们。

 protocol Employee: AnyObject { } 



 protocol Employee: class { } 

根据该语言的开发人员的说法,这些语法的用法是等效的,但是class关键字仅在此位置使用,这与协议AnyObject不同。

同时,正如我们在访谈中看到的那样,人们通常无法解释什么是班级协议以及为什么需要它。 它的本质在于这样一个事实,即我们有机会使用某个对象,该对象将是一个协议,同时将用作引用类型。 一个例子:

 protocol Handler: class {} class Presenter: Handler { weak var renderer: Renderer?  } protocol Renderer {} class View: Renderer { } 

什么盐?

IOS通过自动引用计数方法使用内存管理,这意味着存在强链接和弱链接。 在某些情况下,您应该考虑在类中使用哪些变量(强(强)或弱(弱))。

问题在于,当使用某种协议作为类型时,在描述变量(这是一个强链接)时,会发生保留周期,从而导致内存泄漏,因为对象将通过强链接保存在任何地方。 另外,如果您仍然决定按照SOLID原则编写代码,则可能会引起麻烦。

 protocol Handler {} class Presenter: Handler { var renderer: Renderer? } protocol Renderer {} class View: Renderer { var handler: Handler? } 

为了避免这种情况,Swift使用了类协议,该协议允许您最初设置“弱”变量。 类协议允许您将对象保留为弱引用。 一个经常值得考虑的示例称为委托。

 protocol TableDelegate: class {} class Table {   weak var tableDelegate: TableDelegate? } 

应当使用类协议的另一个示例是显式指示对象是通过引用传递的。

多方法继承和分派


如本文开头所述,协议可以被多次继承。 那就是

 protocol Pet {   func waitingForItsOwner() } protocol Sleeper {   func sleepOnAChair() } class Kitty: Pet, Sleeper {   func eat() {       print("yammy")   }   func waitingForItsOwner() {       print("looking at the door")   }   func sleepOnAChair() {       print("dreams")   } } 

这很有用,但是这里隐藏了哪些陷阱? 问题是,至少乍看之下,由于方法的分配(方法分配)而产生了困难。 简而言之,可能不清楚将调用哪个方法-父方法还是当前方法。

就在上面,我们已经讨论了代码如何工作的主题;它调用了类方法。 也就是说,正如预期的那样。

 protocol Pet {   func waitingForItsOwner() } extension Pet { func waitingForItsOwner() { print("Pet is looking at the door") } } class Kitty: Pet { func waitingForItsOwner() { print("Kitty is looking at the door") } } let kitty: Pet = Kitty() kitty.waitingForItsOwner() // Output: Kitty is looking at the door 

但是,如果您尝试从协议定义中删除方法签名,则会发生“魔术”。 实际上,这是访谈中的一个问题:“如何从协议中调用功能?”

 protocol Pet { } extension Pet { func waitingForItsOwner() { print("Pet is looking at the door") } } class Kitty: Pet { func waitingForItsOwner() { print("Kitty is looking at the door") } } let kitty: Pet = Kitty() kitty.waitingForItsOwner() // Output: Pet is looking at the door 

但是,如果您不是将变量用作协议,而是用作类,那么一切都会好起来的。

 protocol Pet { } extension Pet { func waitingForItsOwner() { print("Pet is looking at the door") } } class Kitty: Pet { func waitingForItsOwner() { print("Kitty is looking at the door") } } let kitty = Kitty() kitty.waitingForItsOwner() // Output: Kitty is looking at the door 

扩展协议时,所有这些都是关于静态调度方法的。 并且必须考虑到这一点。 这是多重继承? 但是这样做:如果您采用两个已实现功能的协议,那么这样的代码将无法工作。 对于要执行的功能,您将需要显式转换为所需的协议。 这就是从C ++的多重继承的回声。

 protocol Pet {   func waitingForItsOwner() } extension Pet {   func yawn() { print ("Pet yawns") } } protocol Sleeper {   func sleepOnAChair() } extension Sleeper {   func yawn() { print ("Sleeper yawns") } } class Kitty: Pet, Sleeper {   func eat() {       print("yammy")   }   func waitingForItsOwner() {       print("looking at the door")   }   func sleepOnAChair() {       print("dreams")   } } let kitty = Kitty() kitty.yawn() 

如果您从另一个协议继承一个协议,其中有一些在扩展中实现的功能,也会发生类似的情况。 编译器不会让它编译。

 protocol Pet {   func waitingForItsOwner() } extension Pet {   func yawn() { print ("Pet yawns") } } protocol Cat {   func walk() } extension Cat {   func yawn() { print ("Cat yawns") } } class Kitty:Cat {   func eat() {       print("yammy")   }   func waitingForItsOwner() {       print("looking at the door")   }   func sleepOnAChair() {       print("dreams")   } } let kitty = Kitty() 

最后两个示例表明,用类完全替换协议是不值得的。 您可能会对静态调度感到困惑。

泛型和协议


我们可以说这是一个带星号的问题,根本不需要问。 但是编码人员喜欢超级抽象的构造,当然,项目中必须有几个不必要的泛型类(没有类的话)。 但是,如果程序员不想将其全部包装在另一个抽象中,那么他就不是程序员。 Swift是一门年轻而又不断发展的语言,它以有限的方式提供了这样的机会。 (是的,这与手机无关)。

首先,只有在Swift 4.2中才对可能的继承进行全面测试,也就是说,只有在秋天,才有可能在项目中正常使用它。 在Swift 4.1上,出现一条消息,提示该机会尚未实现。

 protocol Property { } protocol PropertyConnection { } class SomeProperty { } extension SomeProperty: Property { } extension SomeProperty: PropertyConnection { } protocol ViewConfigurator { } protocol Connection { } class Configurator<T> where T: Property {   var property: T   init(property: T) {       self.property = property   } } extension Configurator: ViewConfigurator { } extension Configurator: Connection where T: PropertyConnection { } [Configurator(property: SomeProperty()) as ViewConfigurator]   .forEach { configurator in   if let connection = configurator as? Connection {       print(connection)   } } 

对于Swift 4.1,将显示以下内容:

 warning: Swift runtime does not yet support dynamically querying conditional conformance ('__lldb_expr_1.Configurator<__lldb_expr_1.SomeProperty>': '__lldb_expr_1.Connection') 

而在Swift 4.2中,一切都按预期工作:

 __lldb_expr_5.Configurator<__lldb_expr_5.SomeProperty> connection 

还值得注意的是,您只能通过一种关系来继承协议。 如果有两种类型的链接,则在编译器级别将禁止继承。 这里显示了什么是不可能的详细解释。

 protocol ObjectConfigurator { } protocol Property { } class FirstProperty: Property { } class SecondProperty: Property { } class Configurator<T> where T: Property {   var properties: T   init(properties: T) {       self.properties = properties   } } extension Configurator: ObjectConfigurator where T == FirstProperty { } //   : // Redundant conformance of 'Configurator<T>' to protocol 'ObjectConfigurator' extension Configurator: ObjectConfigurator where T == SecondProperty { } 

但是,尽管有这些困难,使用泛型连接还是很方便的。

总结一下


Swift以当前形式提供了协议,以使开发比同一个Objective-C更具结构性并提供更高级的继承模型。 因此,我们有信心使用协议是合理的举动,并且一定要问开发人员应聘者他们对Swift语言的这些要素了解多少。 在以下文章中,我们将介绍调度方法。

Source: https://habr.com/ru/post/zh-CN420239/


All Articles