In this article I wanted to talk about the features and difficulties of Swift, which I encountered when I first met. For writing, the language version 2.0 was used. It is assumed that you have already read the documentation and have the 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). In my first application on Swift there were two such protocols: (for example, I simplified them a little)
protocol DataObserver { typealias DataType func didDataChangedNotification(data: DataType) } protocol DataObservable { typealias DataType func observeData<TObserver: DataObserver where TObserver.DataType == DataType> (observer: TObserver) }
DataObservable is responsible for tracking data changes. It does not matter where this data is stored (on the server, locally or else how). 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<TData> : DataObservable { typealias DataType = TData private var observer: DataObserver? var data: DataType { didSet { observer?.didDataChangedNotification(data) } } init(data: TData) { self.data = data } func observeData<TObserver : DataObserver where TObserver.DataType == DataType>(observer: TObserver) { self.observer = observer } }
Everything is simple: save the reference to the last observer, and call it didDataChangedNotification method when the data is changed for some reason. But hey ... this code doesn't compile. The compiler gives the error "Protocol 'DataObserver' can be used. This is because generic protocols can only be used to impose restrictions on generic parameters. Those. It is not possible to declare a variable of type DataObserver. I was not satisfied with this state of affairs. Having a little rummaged in a network, I have found the decision which helps to understand with the developed problem, and a name to it Type Erasure.
')
This is a pattern that represents a small wrapper over a given protocol. To begin with, we introduce a new class AnyDataObserver, which implements the DataObserver protocol.
class AnyDataObserver<TData> : DataObserver { typealias DataType = TData func didDataChangedNotification(data: DataType) { } }
The body of the didDataChangedNotification method is left empty for now. Go ahead. We enter into the generic init class (for which I’m going to tell you just below):
class AnyDataObserver<TData> : DataObserver { typealias DataType = TData func didDataChangedNotification(data: DataType) { } init<TObserver : DataObserver where TObserver.DataType == DataType>(sourceObserver: TObserver) { } }
The sourceObserver TObserver parameter is passed to it. It can be seen that restrictions are imposed on TObserver: first, it must implement the DataObserver protocol, secondly, its DataType must exactly match the DataType of our class. Actually, sourceObserver is the source observer object that we want to wrap. And finally the final class code:
class AnyDataObserver<TData> : DataObserver { typealias DataType = TData private let observerHandler: TData -> Void func didDataChangedNotification(data: DataType) { observerHandler(data) } init<TObserver : DataObserver where TObserver.DataType == DataType>(sourceObserver: TObserver) { observerHandler = sourceObserver.didDataChangedNotification } }
Actually here all the "magic" happens. The private field observerHandler 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 we will rewrite SimpleDataObservable:
class SimpleDataObservable<TData> : DataObservable { typealias DataType = TData private var observer: AnyDataObserver<DataType>? var data: DataType { didSet { observer?.didDataChangedNotification(data) } } init(data: TData) { self.data = data } func observeData<TObserver : DataObserver where TObserver.DataType == DataType>(observer: TObserver) { self.observer = AnyDataObserver(sourceObserver: observer) } }
Now the code is compiled and works great. I can note that some classes from the standard library Swift work on a similar principle (for example, AnySequence).
Self type
At a certain point I needed to enter the copy protocol into the project:
protocol CopyableType { func copy() -> ??? }
But what should the copy method return? Any? CopyableType? Then at each call I would have to write let copyObject = someObject.copy as! SomeClass, which is not very good. In addition to this, this code is not secure. 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 invoked. 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 (it all resembles the class method from Objective-C). After receiving the type object, init is called to it (to ensure that init with no parameters really exists in the class, we enter it with the required keyword). After that, we copy all the required fields into the created instance and return it from our function.
As soon as I finished copying, it became necessary to use Self in another place. I needed to write a protocol for the View Controller, in which there was a static method for creating a new instance of this very View Controller.
Since this protocol was not directly related to the UIViewController class, I made it fairly general and called AutofactoryType:
protocol AutofactoryType { static func createInstance() -> Self }
Let's try using 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 the type ViewController to return type 'Self'” The fact is that the compiler cannot convert ViewController to Self. In this case, ViewController and Self are the same thing, but in general, this is not the case (for example, when using inheritance).
How to make this code work? For this, there is not quite honest (in relation to strict typing), but quite a working method. Add a function:
func unsafeCast<T, E>(sourceValue: T) -> E { if let castedValue = sourceValue as? E { return castedValue } fatalError("Unsafe casting value \(sourceValue) to type \(E.self) failed") }
Its purpose is to convert an object of one type to another type. If the conversion fails, the function simply fails.
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 the following extension for it:
extension SequenceType where Generator.Element == String { func concat() -> String { var result = String() for value in self { result += value } return result } }
The extension introduced a restriction on the element of the sequence, it must be of type String. Thus, for any sequence consisting of strings (and only for them), it will be possible to call the function concat.
func test() { let strings = [“Alpha”, “Beta”, “Gamma”]
This allows a significant part of the code to be put into extensions, and called in the right context, while gaining all the advantages of reuse.
The implementation of the default protocol methods.
The implementation of the default protocol methods.
protocol UniqueIdentifierProvider { static var uniqueId: String { get } }
As follows from the description, any type that implements this protocol must have a unique identifier unique of type String. But if you think a little, it becomes clear that within one module for any type its unique identifier is. 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 successor), 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 that implement the UniqueIdentifierProvider will receive a uniqueId out of the box.
extension ViewController: UniqueIdentifierProvider {
The beauty is that the 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) }
Explicitly Specifying a Generic Argument
In my project I used MVVM, and the method responsible for creating the ViewModel was:
func createViewModel<TViewModel: ViewModelType>() -> TViewModel { let viewModel = TViewModel.createIntsance()
Accordingly, it was used this way:
func test() { let viewModel: MyViewModel = createViewModel() }
In this case, the MyViewModel will be supplied to the createViewModel function as a generic argument. All because Swift itself deduces types from a context. But is it always good? In my opinion, it is not. In some cases it may even lead to errors:
func test(mode: FactoryMode) -> ViewModelBase { switch mode { case NormalMode: return createViewModel() as NormalViewModel case PreviewMode: return createViewModel()
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 at best will lead to an error in runtime).
Therefore, it is necessary to make the type indication explicit. To do this, we will add a new viewModelType parameter of type TViewModel.Type to createViewModel. Type here means that the method takes as an argument not a type instance, but an object-type itself.
func createViewModel<TViewModel: ViewModelType>(viewModelType: TViewModel.Type) -> TViewModel { let viewModel = viewModelType.createIntsance()
After this, 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 CreateViewModel function is passed the arguments NormalViewModel.self and PreviewViewModel.self. These are the objects of type NormalViewModel and PreviewViewModel. In Swift there is a rather strange feature: if a function has one parameter, you can not write 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 self keyword is required.
PS
I hope that this article will be useful to someone. It is also planned to continue about Swift (and not only).