Swift features

In this article I wanted to talk about the features and difficulties of Swift that I encountered when I first met. To write the article, the language version 2.0 was used. It is assumed that you have already read the documentation and have basic knowledge for developing a mobile application.

Generic Protocols


By this term I mean any protocols that have open typealias (associatedtype in Swift 2.2). There were two of these protocols in my first Swift application: (for example, I simplified them a bit)

protocol DataObserver {
    typealias DataType
    func didDataChangedNotification(data: DataType)
}
protocol DataObservable {
    typealias DataType
    func observeData (observer: TObserver)
}

DataObservable is responsible for tracking data changes. It does not matter where this data is stored (on the server, locally or whatever). DataObserver receives alerts that data has changed. First of all, we will be interested in the DataObservable protocol, and here is its simplest implementation.

class SimpleDataObservable : DataObservable {
    typealias DataType = TData
    private var observer: DataObserver?
    var data: DataType {
        didSet {
            observer?.didDataChangedNotification(data)
        }
    }
    init(data: TData) {
        self.data = data
    }
    func observeData(observer: TObserver) {
        self.observer = observer
    }
}

Everything is simple here: we save the link to the last observer, and call the didDataChangedNotification method on it when the data changes for some reason. But hey ... this code does not compile. The compiler throws the error "Protocol 'DataObserver' can only be used as a generic constraint because it has Self or associated type requirements." This is because generic protocols can only be used to impose restrictions on generic parameters. Those. Declaring a variable of type DataObserver will fail. This situation did not suit me. Rummaging around a bit, I found a solution that helps me deal with the current problem, and his name is Type Erasure.

This is a pattern that is a small wrapper over a given protocol. First, let's introduce the new AnyDataObserver class, which implements the DataObserver protocol.

class AnyDataObserver : DataObserver {
    typealias DataType = TData
    func didDataChangedNotification(data: DataType) {
    }
}

The body of the didDataChangedNotification method is left empty for now. Go ahead. We introduce the generic init class (for what I need it I will tell you a bit below):

class AnyDataObserver : DataObserver {
    typealias DataType = TData
    func didDataChangedNotification(data: DataType) {
    }
    init(sourceObserver: TObserver) {
    }
}

The sourceObserver parameter of type TObserver is passed to it. It can be seen that restrictions are imposed on TObserver: firstly, it must implement the DataObserver protocol, and secondly, its DataType must exactly match the DataType of our class. Actually sourceObserver this is the original observer object that we want to wrap. And finally, the final class code:

class AnyDataObserver : DataObserver {
    typealias DataType = TData
    private let observerHandler: TData -> Void
    func didDataChangedNotification(data: DataType) {
        observerHandler(data)
    }
    init(sourceObserver: TObserver) {
        observerHandler = sourceObserver.didDataChangedNotification
    }
}

Actually, this is where all the “magic” happens. A private observerHandler field is added to the class, which stores the implementation of the didDataChangedNotification method of the sourceObserver object. In the didDataChangedNotification method of our class itself, we simply call this implementation.

Now rewrite SimpleDataObservable:

class SimpleDataObservable : DataObservable {
    typealias DataType = TData
    private var observer: AnyDataObserver?
    var data: DataType {
        didSet {
            observer?.didDataChangedNotification(data)
        }
    }
    init(data: TData) {
        self.data = data
    }
    func observeData(observer: TObserver) {
        self.observer = AnyDataObserver(sourceObserver: observer)
    }
}

Now the code compiles and works great. I can note that some classes from the Swift standard library work on a similar principle (for example, AnySequence).

Self Type


At a certain point, I needed to introduce a copy protocol into the project:

protocol CopyableType {
    func copy() -> ???
}


But what should the copy method return? Any? CopyableType? Then with each call I would have to write let copyObject = someObject.copy as! SomeClass, which is not very good. In addition, this code is unsafe. The keyword Self comes to the rescue.

protocol CopyableType {
    func copy() -> Self
}


Thus, we inform the compiler that the implementation of this method must return an object of the same type as the object for which it was called. Here you can draw an analogy with instancetype from Objective-C.

Consider the implementation of this protocol:

class CopyableClass: CopyableType {
    var fieldA = 0
    var fieldB = "Field"
    required init() {
    }
    func copy() -> Self {
        let copy = self.dynamicType.init()
        copy.fieldA = fieldA
        copy.fieldB = fieldB
        return copy
    }
}


To create a new instance, use the dynamicType keyword. It serves to get a reference to an object type (all this resembles the class method from Objective-C). After receiving the type object, init is called (to ensure that init without parameters is indeed in the class, we enter it with the required keyword). After that, we copy all the necessary fields into the created instance and return it from our function.

As soon as I finished copying, there was a need to use Self in one more place. I needed to write a protocol for the View Controller, in which there would be a static method for creating a new instance of this View Controller itself.

Since this protocol was not directly connected with the UIViewController class, I made it quite general and called AutofactoryType:

protocol AutofactoryType {
    static func createInstance() -> Self
}


Let's try to use it to create a View Conotroller:

class ViewController: UIViewController, AutofactoryType {
    static func createInstance() -> Self {
        let newInstance = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController")
        return newInstance as! ViewController
    }
}

Everything would be fine, but this code will not compile: “Cannot convert return expression of type ViewController to return type 'Self'” The fact is that the compiler cannot convert the ViewController to Self. In this case, ViewController and Self are the same thing, but in the general case this is not so (for example, when using inheritance).

How to make this code work? To do this, there is a not entirely honest (with respect to strict typing), but quite a working way. Add function:

func unsafeCast(sourceValue: T) -> E {
    if let castedValue = sourceValue as? E {
        return castedValue
    }
    fatalError("Unsafe casting value \(sourceValue) to type \(E.self) failed")
}

Its purpose is the conversion of an object of one type to another type. If the conversion fails, then the function simply fails.

We use this function in createInstance:

class ViewController: UIViewController, AutofactoryType {
    static func createInstance() -> Self {
        let newInstance = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController")
        return unsafeCast(newInstance)
    }
}

Thanks to automatic type inference, newInstance is now converted to Self (which could not be done directly). This code compiles and works.

Specific extensions


Type extensions in Swift would not be so useful if you could not write specific code for different types. Take, for example, the SequenceType protocol from the standard library and write such an extension for it:

extension SequenceType where Generator.Element == String {
    func concat() -> String {
        var result = String()
        for value in self {
            result += value
        }
        return result
    }
}

In the extension, a restriction on the element of the sequence is introduced; it must be of type String. Thus, for any sequence consisting of strings (and only for them), it will be possible to call the concat function.

func test() {
    let strings = [“Alpha”, “Beta”, “Gamma”]
    //printing “AlphaBetaGamma”
    print("Strings concat: \(strings.concat())")
}

This allows you to put a significant part of the code into extensions, and call it in the right context, while receiving all the advantages of reuse.

Implement protocol default methods.


Implement protocol default methods.

protocol UniqueIdentifierProvider {
    static var uniqueId: String { get }
}

As follows from the description, any type implementing this protocol must have a unique identifier uniqueId of type String. But if you think a little, it becomes clear that within the framework of one module for any type, its name is a unique identifier. So let's write an extension for our new protocol:

extension UniqueIdentifierProvider where Self: UIViewController {
    static var uniqueId: String {
        get {
            return String(self)
        }
    }
}

In this case, the Self keyword is used to impose restrictions on the type object. The logic of this code is approximately the following: “if this protocol is implemented by the UIViewController class (or its descendant), then the following uniqueId implementation can be used.” This is the default protocol implementation. In fact, you can write this extension without any restrictions:

extension UniqueIdentifierProvider {
    static var uniqueId: String {
        get {
            return String(self)
        }
    }
}

And then all types implementing UniqueIdentifierProvider will get uniqueId out of the box.

extension ViewController: UniqueIdentifierProvider {
   //Nothing
}
func test() {
   //printing "ViewController"
   print(ViewController.uniqueId)
}

The beauty is that a class may have its own implementation of this method. And in this case, the default implementation will be ignored:

extension ViewController: UniqueIdentifierProvider {
   static var uniqueId: String {
        get {
            return "I’m ViewController”
        }
    }
}
func test() {
   //printing "I’m ViewController"
   print(ViewController.uniqueId)
}

Explicit Generic Argument


In my project, I used MVVM, and the method was responsible for creating the ViewModel:

func createViewModel() -> TViewModel {
    let viewModel = TViewModel.createIntsance()
    //View model configurate
    return viewModel
}


Accordingly, this is how it was used:

func test() {
   let viewModel: MyViewModel = createViewModel()
}

In this case, MyViewModel will be supplied to the createViewModel function as a generic argument. This is because Swift itself takes types out of context. But is it always good? In my opinion, this is not so. In some cases, it can even lead to errors:

func test(mode: FactoryMode) -> ViewModelBase {
   switch mode {
   case NormalMode:
      return createViewModel() as NormalViewModel
   case PreviewMode:
      return createViewModel() //забыли as PreviewViewModel
   }
}


In the first case, the NormalViewModel is substituted into the createViewModel method.
In the second, we forgot to write “as PreviewViewModel”, which is why the ViewModelBase type is substituted into the createViewModel method (which in the best case will lead to an error in runtime).

Therefore, it is necessary to make the type indication explicit. To do this, in createViewModel we will add a new parameter viewModelType of type TViewModel.Type. Type here means that the method does not accept an instance of a type as a parameter, but a type object itself.

func createViewModel(viewModelType: TViewModel.Type) -> TViewModel {
    let viewModel = viewModelType.createIntsance()
    //View model configurate
    return viewModel
}

After that, our switch-case looks like this:

func test(mode: FactoryMode) {
   let viewModel: ViewModelBase?
   switch mode {
   case NormalMode:
       return createViewModel(NormalViewModel.self)
   case PreviewMode:
       return createViewModel(PreviewViewModel.self)
   }
}

Now, the arguments NormalViewModel.self and PreviewViewModel.self are passed to the createViewModel function. These are type objects NormalViewModel and PreviewViewModel. Swift has a rather strange feature: if a function has one parameter, you can skip self.

func test(mode: FactoryMode) {
   let viewModel: ViewModelBase?
   switch mode {
   case NormalMode:
       return createViewModel(NormalViewModel)
   case PreviewMode:
       return createViewModel(PreviewViewModel)
   }
}


But if there are two or more arguments, the keyword self is necessary.

PS


I hope that this article will be useful to someone. It is also planned to continue about Swift (and not only).

Also popular now: