Mobile development. Swift: the mystery of the protocols

    Today we continue the cycle of publications on mobile development under iOS. And if last time it was about what you need and do not need to ask at the interviews, in this material we will touch on the subject of protocols, which is important in Swift. It will be about how the protocols are arranged, how they differ from each other, and how they are combined with Objective-C interfaces.



    As we said earlier, Apple continues to actively develop the new language, and most of its parameters and features are clearly indicated in the documentation. But who reads the documentation when you need to write the code here and now? So let's go through the main features of the Swift protocols right in our post.

    To begin with, it is worth making a reservation that Apple's protocols are an alternative term for the term “Interface”, which is used in other programming languages. In Swift, protocols serve to designate patterns of certain structures (the so-called blueprint) with which you can work at an abstract level. In simple terms, the protocol defines a number of methods and variables that a certain type must necessarily inherit.

    Further in the article the points will be gradually revealed as follows: from simple and often used to more complex. In principle, the interviews can be given questions in this order, since they determine the level of competence of the applicant - from the level of June to the level of seniors.

    What are the protocols in Swift for?


    Mobile developers often do without protocols at all, but they lose the ability to work with some entities abstractly. If we highlight the main features of the protocols in Swift, we will have the following 7 points:

    • Protocols provide multiple inheritance
    • Protocols cannot store state
    • Protocols can be inherited by other protocols.
    • Protocols can be applied to structures (struct), classes (class) and enums (enum), defining type functionality
    • Generic protocols allow setting complex dependencies between types and protocols during their inheritance.
    • Protocols do not define “strong” and “weak” references to variables.
    • In extensions to protocols, you can describe specific implementations of methods, and calculated variables (computed values)
    • Class protocols allow themselves to be inherited only by classes.

    As you know, all simple types (string, int) in Swift are structures. In the standard Swift library, for example, it looks like this:

    publicstructInt: FixedWidthInteger, SignedInteger{

    In this case, the types of collections (collection), namely, array, set, dictionary - can also be packaged into a protocol, because they are also structures. For example, a dictionary is defined as follows.

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

    Usually in object-oriented programming, the concept of classes is used, and everyone knows the mechanism of inheritance of methods and variables of the parent class by a descendant class. At the same time, no one forbids him to contain additional methods and variables.

    In the case of protocols, you can create a much more interesting hierarchy of relationships. To describe the next class, you can use several protocols at the same time, which allows you to create quite complex structures that will satisfy many conditions simultaneously. On the other hand, the limitations of different protocols allow you to create some object that is intended only for narrow use and contains a certain number of functions.

    The implementation of the protocol in Swift is quite simple. Syntax implies a name, a series of methods and parameters (variables) that it will contain.

    protocolEmployee{
    funcwork()var hours: Int { get }
    }

    In addition, Swift protocols can contain not only the names of methods, but also their implementation. Method code in the protocol is added via extensions. In the documentation you can find many references to extensions, but with respect to the protocols in the extensions you can place the function name and the function body.

    extensionEmployee{
    funcwork() {
    print ("do my job")
    }
    } 

    The same can be done with variables.

    extensionEmployee{
    var hours: Int {
    return8
    }
    }

    If we use the object associated with the protocol somewhere, we can set a variable in it with a fixed or transmitted value. In fact, the variable is a small function without input parameters ... or with the possibility of direct parameter assignment.

    Expansion of the protocol in Swift allows you to implement the variable body, and then it will actually be a computed value - a calculated parameter with the functions get and set. That is, such a variable will not store any values, but will either play the role of a function or functions, or will play the role of a proxy for some other variable.

    Or if we take some class or structure and implement the protocol, then we can use an ordinary variable in it:

    classProgrammer{
    var hours: Int = 24
    }
    extensionProgrammer: Employee{ }
    

    It is worth noting that the variables in the protocol definition cannot be weak. (weak - this is a variant of the implementation of the variable).

    There are more interesting examples: you can implement an expansion of an array and add a function associated with the data type of the array. For example, if an array contains integer values ​​or has the format equitable (suitable for comparison), the function may, for example, compare all the values ​​of the cells of the array.

    extensionArraywhereElement: Equatable{
        var areAllElementsEqualToEachOther: Bool {
            if isEmpty {
                returnfalse
            }
            var previousElement = self[0]
            for (index, element) inself.enumerated() where index > 0 {
                if element != previousElement {
                    returnfalse
                }
                previousElement = element
            }
            returntrue
        }
    }
    [1,1,1,1].areAllElementsEqualToEachOther

    A small note. Variables and functions in protocols can be static.

    Using @objc


    The main thing you need to know in this matter is that @the Swift objc protocols are visible in the Objective-C code. Strictly speaking, for this “magic word” @objc exists. But everything else remains unchanged.

    @objcprotocolTypable{
        @objcoptionalfunctest()
        functype()
    }
    extensionTypable{
        functest() {
            print("Extension test")
        }
        functype() {
            print("Extension type")
        }
    }
    classTypewriter: Typable{
        functest() {
            print("test")
        }
        functype() {
            print("type")
        }
    }


    Protocols of this type can only be inherited by classes. For enumerations and structures this cannot be done.

    That is the only way.
    @objcprotocolDummy{ }
    classDummyClass: Dummy{ }


    It is worth noting that in this case it is possible to define optional functions (@obj optional func), which, if desired, can be not implemented, as for the test () function in the previous example. But conditionally optional functions can be implemented with the help of protocol extension with empty implementation.

    protocolDummy{
        funcohPlease()
    }
    extensionDummy{
        funcohPlease() { }
    }


    Type inheritance


    By creating a class, structure, or enumeration, we can designate the inheritance of a particular protocol — in this case, the inherited parameters will work for our class and for all other classes that inherit this class, even if we do not have access to them.

    By the way, in this context one very interesting problem appears. Suppose we have a protocol. There is some class. And the class implements the protocol, and there is a work () function in it. What will happen if we have protocol extension, which also has a work () method. Which one will be called when the method is accessed?

    protocolPerson{
         funcwork()
    }
    extensionPerson{
        funcwork() {
            print("Person")
        }
    }
    classEmployee{ }
    extensionEmployee: Person{
        funcwork() {
            print("Employee")
        }
    }

    A class method will be launched - such are the features of method dispatching in Swift. And this answer is given by many applicants. But the question of how to make it so that the code does not implement the class method, but the protocol method, only a few know the answer. However, there is also a solution for this task — it implies removing the function from the protocol definition and calling the method as follows:

    protocolPerson{
    //     func work() //спрячем этот метод в определении протокола
    }
    extensionPerson{
        funcwork() {
            print("Person")
        }
    }
    classEmployee{ }
    extensionEmployee: Person{
        funcwork() {
            print("Employee")
        }
    }
    let person: Person = Employee()
    person.work()
    //output: Person


    Generic Protocols


    Swift also has generic protocols with abstract associated types that allow you to define type variables. Such a protocol can be assigned additional conditions that are superimposed on associative types. Several such protocols allow you to build complex structures necessary for the formation of the application architecture.

    However, you cannot implement a variable in the form of a generic protocol. It can only be inherited. These constructs are used to create dependencies in classes. That is, we can describe some abstract generic class to define the types used in it.

    protocolPrinter{
        associatedtypePrintableClass: Hashable
        funcprintSome(printable: PrintableClass)
    }
    extensionPrinter{
        funcprintSome(printable: PrintableClass) {
            print(printable.hashValue)
        }
    }
    classTheIntPrinter: Printer{
        typealiasPrintableClass = Int
    }
    let intPrinter = TheIntPrinter()
    intPrinter.printSome(printable: 0)
    let intPrinterError: Printer = TheIntPrinter() // так нельзя

    It should be remembered that generic protocols have a high level of abstraction. Therefore, in the applications themselves, they may be redundant. But at the same time generic protocols are used when programming libraries.

    Class Protocols


    In Swift, there are also class-bound protocols. Two types of syntax are used to describe them.

    protocolEmployee: AnyObject{ }

    Or

    protocolEmployee: class{ }

    According to the developers of the language, the use of these syntax is equivalent, but the class keyword is used only in this place, unlike AnyObject, which is a protocol.

    In the meantime, as we see during interviews, people often cannot explain what a class protocol is and why it is needed. Its essence lies in the fact that we are able to use some object that would be a protocol, and at the same time would function as a reference type. Example:

    protocolHandler: class{} 
    classPresenter: Handler{ weakvar renderer: Renderer?  }
    protocolRenderer{}
    classView: Renderer{ }

    What is the salt?

    IOS uses automatic reference count memory management, which implies strong and weak references. And in some cases, it should be taken into account which particular - strong (strong) or weak (weak) - variables are used in classes.

    The problem is that when using a certain protocol as a type, when describing a variable (which is a strong reference), cyclic connections (retain cycle) can occur, leading to memory leaks, because objects will be held everywhere by strong references. Also, trouble can arise if you still decide to write code in accordance with the principles of SOLID.

    protocolHandler{} 
    classPresenter: Handler{
     var renderer: Renderer?  
    }
    protocolRenderer{} 
    classView: Renderer{ 
    var handler: Handler? 
    }

    To avoid such situations, Swift uses class protocols that allow you to initially set "weak" variables. The class protocol allows you to keep an object a weak reference. An example where this is often considered is called a delegate.

    protocolTableDelegate: class{}
    classTable{
        weakvar tableDelegate: TableDelegate?
    }

    Another example where class protocols are worth using is an explicit indication that an object is passed by reference.

    Multiple Inheritance and Method Dispatching


    As stated at the beginning of the article, protocols can be inherited multiplely. I.e,

    protocolPet{
        funcwaitingForItsOwner()
    }
    protocolSleeper{
        funcsleepOnAChair()
    }
    classKitty: Pet, Sleeper{
        funceat() {
            print("yammy")
        }
        funcwaitingForItsOwner() {
            print("looking at the door")
        }
        funcsleepOnAChair() {
            print("dreams")
        }
    }

    This is useful, but what pitfalls are hidden here? The fact is that the difficulties, at least at first glance, arise from method dispatching (method dispatch). In simple terms, it may not be clear which method will be called — the parent or from the current type.

    A little higher, we have already revealed the topic of how the code works, it calls the class method. That is, as expected.

    protocolPet{
        funcwaitingForItsOwner()
    }
    extensionPet{
        funcwaitingForItsOwner() {
            print("Pet is looking at the door")
        }
    }
    classKitty: Pet{
        funcwaitingForItsOwner() {
            print("Kitty is looking at the door")
        }
    }
    let kitty: Pet = Kitty()
    kitty.waitingForItsOwner()
    // Output: Kitty is looking at the door

    But if you try to remove the method signature from the protocol definition, then “magic” happens. As a matter of fact, this is a question from the interview: “How to make the function from the protocol be called?”

    protocolPet{ }
    extensionPet{
        funcwaitingForItsOwner() {
            print("Pet is looking at the door")
        }
    }
    classKitty: Pet{
        funcwaitingForItsOwner() {
            print("Kitty is looking at the door")
        }
    }
    let kitty: Pet = Kitty()
    kitty.waitingForItsOwner()
    // Output: Pet is looking at the door

    But if you use a variable not as a protocol, but as a class, everything will be fine.

    protocolPet{ }
    extensionPet{
        funcwaitingForItsOwner() {
            print("Pet is looking at the door")
        }
    }
    classKitty: Pet{
        funcwaitingForItsOwner() {
            print("Kitty is looking at the door")
        }
    }
    let kitty = Kitty()
    kitty.waitingForItsOwner()
    // Output: Kitty is looking at the door

    It's all about static dispatching methods when extending the protocol. And this must be taken into account. And here multiple inheritance? But with it: if you take two protocols with implemented functions, such code will not work. In order for the function to be performed, it will be necessary to explicitly make a caste to the desired protocol. Such is the echo of multiple inheritance from C ++.

    protocolPet{
        funcwaitingForItsOwner()
    }
    extensionPet{
        funcyawn() {
    print ("Pet yawns")
    }
    }
    protocolSleeper{
        funcsleepOnAChair()
    }
    extensionSleeper{
        funcyawn() {
    print ("Sleeper yawns")
    }
    }
    classKitty: Pet, Sleeper{
        funceat() {
            print("yammy")
        }
        funcwaitingForItsOwner() {
            print("looking at the door")
        }
        funcsleepOnAChair() {
            print("dreams")
        }
    }
    let kitty = Kitty()
    kitty.yawn()

    A similar story will be if one protocol is inherited by another, where there are functions that are implemented in extensions. The compiler will not allow him to collect.

    protocolPet{
        funcwaitingForItsOwner()
    }
    extensionPet{
        funcyawn() {
    print ("Pet yawns")
    }
    }
    protocolCat{
        funcwalk()
    }
    extensionCat{
        funcyawn() {
    print ("Cat yawns")
    }
    }
    classKitty:Cat{
        funceat() {
            print("yammy")
        }
        funcwaitingForItsOwner() {
            print("looking at the door")
        }
        funcsleepOnAChair() {
            print("dreams")
        }
    }
    let kitty = Kitty()

    The last two examples show that you should not completely replace the protocols with classes. You can get confused in static dispatch.

    Generics and protocols


    We can say that this is a question with an asterisk, which you don’t need to ask at all. But coders love super-abstract constructs, and of course, a couple of unnecessary generic classes must be in the project (where without it). But the programmer would not be a programmer if he did not want to wrap it all into another abstraction. And Swift, being a young but dynamically developing language, gives such an opportunity, but in a limited way. (yes, this is not about mobile phones).

    First, a full-fledged check for possible inheritance is only in Swift 4.2, that is, only in the fall will it be possible to use it normally in projects. On Swift 4.1, a message is issued that the feature is not yet implemented.

    protocolProperty{ }
    protocolPropertyConnection{ }
    classSomeProperty{ }
    extensionSomeProperty: Property{ }
    extensionSomeProperty: PropertyConnection{ }
    protocolViewConfigurator{ }
    protocolConnection{ }
    classConfigurator<T> whereT: Property{
        var property: T
        init(property: T) {
            self.property = property
        }
    }
    extensionConfigurator: ViewConfigurator{ }
    extensionConfigurator: ConnectionwhereT: PropertyConnection{ }
    [Configurator(property: SomeProperty()) asViewConfigurator]
        .forEach { configurator in
        iflet connection = configurator as? Connection {
            print(connection)
        }
    }

    For Swift 4.1, the following is displayed:

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

    Whereas in Swift 4.2 everything works as expected:

    __lldb_expr_5.Configurator<__lldb_expr_5.SomeProperty>
    connection

    It is also worth noting that you can inherit the protocol with only one type of links. If there are two types of links, then inheritance will be prohibited at the compiler level. A detailed explanation of what is possible and what is not is shown here .

    protocolObjectConfigurator{ }
    protocolProperty{ }
    classFirstProperty: Property{ }
    classSecondProperty: Property{ }
    classConfigurator<T> whereT: Property{
        var properties: T
        init(properties: T) {
            self.properties = properties
        }
    }
    extensionConfigurator: ObjectConfiguratorwhereT == FirstProperty{ } // тут будет ошибка:// Redundant conformance of 'Configurator<T>' to protocol 'ObjectConfigurator'extensionConfigurator: ObjectConfiguratorwhereT == SecondProperty{ }

    But, despite these difficulties, working with generic connections is quite convenient.

    Summarizing


    Protocols were provided in Swift in its current form to make development more structural and to provide more advanced inheritance models than in the same Objective-C. Therefore, we are confident that the use of protocols is a justified move, and be sure to ask the candidate candidates that they know about these elements of the Swift language. In the next posts we will touch on dispatching methods.

    Only registered users can participate in the survey. Sign in , please.

    Do you use protocols in Swift?


    Also popular now: