Swift Features
В этой статье хотелось рассказать об особенностях и трудностях Swift, с которыми я столкнулся при первом знакомстве. Для написания статьи использовалась версия языка 2.0. Предполагается, что вы уже читали документацию и обладаете базовыми знаниями для разработки мобильного приложения.
Generic протоколы
Под этим термином я подразумеваю любые протоколы, в которых есть открытые typealias (associatedtype в Swift 2.2). В моем первом приложении на Swift было два таких протокола: (для примера я немного упростил их)
protocol DataObserver < typealias DataType func didDataChangedNotification(data: DataType) >protocol DataObservable < typealias DataType func observeData(observer: TObserver) >
DataObservable отвечает за отслеживание изменения данных. При этом не важно, где эти данные хранятся (на сервере, локально или еще как). DataObserver получает оповещения о том, что данные изменились. В первую очередь нас будет интересовать протокол DataObservable, и вот его простейшая реализация.
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 >>
Тут все просто: сохраняем ссылку на последний observer, и вызываем у него метод didDataChangedNotification, когда данные по какой-то причине изменяются. Но погодите… этот код не компилируется. Компилятор выдает ошибку «Protocol ‘DataObserver’ can only be used as a generic constraint because it has Self or associated type requirements». Все потому, что generic-протоколы могут использоваться только для накладывания ограничений на generic-параметры. Т.е. объявить переменную типа DataObserver не получится. Меня такое положение дел не устроило. Немного покопавшись в сети, я нашел решение, которое помогает разобраться со сложившейся проблемой, и имя ему Type Erasure.
Это паттерн, который представляет собой небольшой обертку над заданным протоколом. Для начала введем новый класс AnyDataObserver, который реализует протокол DataObserver.
class AnyDataObserver : DataObserver < typealias DataType = TData func didDataChangedNotification(data: DataType) < >>
Тело метода didDataChangedNotification пока оставим пустым. Идем дальше. Вводим в класс generic init (для чего он нужен расскажу чуть ниже):
class AnyDataObserver : DataObserver < typealias DataType = TData func didDataChangedNotification(data: DataType) < >init(sourceObserver: TObserver) < >>
В него передается параметр sourceObserver типа TObserver. Видно, что на TObserver накладываются ограничения: во-первых он должен реализовать протокол DataObserver, во-вторых его DataType должен в точности соответствовать DataType нашего класса. Собственно sourceObserver это и есть исходный observer-объект, который мы хотим обернуть. И наконец финальный код класса:
class AnyDataObserver : DataObserver < typealias DataType = TData private let observerHandler: TData ->Void func didDataChangedNotification(data: DataType) < observerHandler(data) >init(sourceObserver: TObserver) < observerHandler = sourceObserver.didDataChangedNotification >>
Собственно тут и происходит вся «магия». В класс добавляется закрытое поле observerHandler, в котором хранится реализация метода didDataChangedNotification объекта sourceObserver. В самом методе didDataChangedNotification нашего класса мы просто вызываем эту реализацию.
Теперь перепишем 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) >>
Теперь код компилируется и прекрасно работает. Могу отметить, что некоторые классы из стандартной библиотеки Swift работают по схожему принципу (например AnySequence).
Тип Self
В определенный момент мне потребовалось ввести в проект протокол копирования:
protocol CopyableType < func copy() ->. >
Но что же должен возвращать метод copy? Any? CopyableType? Тогда при каждом вызове пришлось бы писать let copyObject = someObject.copy as! SomeClass, что не очень хорошо. В добавок к тому же этот код небезопасен. На помощь приходит ключевое слово Self.
protocol CopyableType < func copy() ->Self >
Таким образом мы сообщаем компилятору, что реализация этого метода обязана вернуть объект того же типа, что и объект, для которого он был вызван. Тут можно провести аналогию с instancetype из Objective-C.
Рассмотрим реализацию этого протокола:
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 >>
Для создание нового экземпляра используется ключевое слово dynamicType. Оно служит для получения ссылки на объект-тип (все это напоминает метод class из Objective-C). После получения объекта-типа, у него вызывается init (для гарантии того, что init без параметров действительно есть в классе, мы вводим его с ключевым словом required). После чего копируем в созданный экземпляр все нужные поля и возвращаем его из нашей функции.
Как только я закончил с копированием, возникла необходимость использовать Self еще в одном месте. Мне потребовалось написать протокол для View Controller, в котором бы был статический метод создания нового экземпляра этого самого View Controller.
Так как этот протокол никак напрямую не был связан с классом UIViewController, то я его сделал достаточно общим и назвал AutofactoryType:
protocol AutofactoryType < static func createInstance() ->Self >
Попробуем использовать его для создания View Conotroller:
class ViewController: UIViewController, AutofactoryType < static func createInstance() ->Self < let newInstance = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController") return newInstance as! ViewController >>
Все бы хорошо, но этот код не скомпилируется: “Cannot convert return expression of type ViewController to return type ‘Self’” Дело в том, что компилятор не может преобразовать ViewController к Self. В данном случае ViewController и Self — это одно и то же, но в общем случае это не так (например, при использовании наследования).
Как же заставить этот код работать? Для этого есть не совсем честный (по отношению к строгой типизации), но вполне рабочий способ. Добавим функцию:
func unsafeCast(sourceValue: T) -> E < if let castedValue = sourceValue as? E < return castedValue >fatalError("Unsafe casting value \(sourceValue) to type \(E.self) failed") >
Ее назначение — это преобразование объекта одного типа к другому типу. Если преобразование не удается, то функция просто завершается с ошибкой.
Используем эту функцию в createInstance:
class ViewController: UIViewController, AutofactoryType < static func createInstance() ->Self < let newInstance = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController") return unsafeCast(newInstance) >>
Благодаря автоматическому выводу типов, newInstance теперь преобразуется к Self (чего нельзя было сделать напрямую). Этот код компилируется и работает.
Специфичные расширения
Расширения типов в Swift не были бы такими полезными, если бы нельзя было писать специфичный код для разных типов. Возьмем, к примеру, протокол SequenceType из стандартной библиотеки и напишем для него такое расширение:
extension SequenceType where Generator.Element == String < func concat() ->String < var result = String() for value in self < result += value >return result > >
В расширении введено ограничение на элемент последовательности, он должен быть типа String. Таким образом для любой последовательности, состоящей из строк (и только для них), можно будет вызвать функцию concat.
func test() < let strings = [“Alpha”, “Beta”, “Gamma”] //printing “AlphaBetaGamma” print("Strings concat: \(strings.concat())") >
Это позволяет значительную часть кода выносить в расширения, и вызывать его в нужном контексте, получая при этом все плюсы повторного использования.
Реализация методов протокола по умолчанию.
Реализация методов протокола по умолчанию.
protocol UniqueIdentifierProvider < static var uniqueId: String < get >>
Как следует из описания, любой тип реализующий этот протокол, должен обладать уникальным идентификатором uniqueId типа String. Но если немного подумать, то становится понятно, что в рамках одного модуля для любого типа уникальным идентификатором является его название. Так давайте напишем расширение для нашего нового протокола:
extension UniqueIdentifierProvider where Self: UIViewController < static var uniqueId: String < get < return String(self) >> >
В данном случае ключевое слово Self используется для того, чтобы накладывать ограничения на объект-тип. Логика этого кода примерно следующая: «если этот протокол будет реализован классом UIViewController (или его наследником), то можно использовать следующую реализацию uniqueId». Это и есть реализация протокола по-умолчанию. На самом деле можно написать это расширение и без каких-либо ограничений:
extension UniqueIdentifierProvider < static var uniqueId: String < get < return String(self) >> >
И тогда все типы, реализующие UniqueIdentifierProvider, получат uniqueId “из коробки”.
extension ViewController: UniqueIdentifierProvider < //Nothing >func test() < //printing "ViewController" print(ViewController.uniqueId) >
Прелесть в том, что в классе может быть своя реализация этого метода. И в этом случае реализация по-умолчанию будет игнорироваться:
extension ViewController: UniqueIdentifierProvider < static var uniqueId: String < get < return "I’m ViewController” >> > func test() < //printing "I’m ViewController" print(ViewController.uniqueId) >
Явное указание Generic аргумента
В своем проекте я использовал MVVM, и за создание ViewModel отвечал метод:
func createViewModel() -> TViewModel < let viewModel = TViewModel.createIntsance() //View model configurate return viewModel >
Соответственно, так он использовался:
func test()
В данном случае в функцию createViewModel в качестве generic аргумента будет поставляться MyViewModel. Все благодаря тому, что Swift сам выводит типы из контекста. Но всегда ли это хорошо? На мой взгляд, это не так. В некоторых случаях может даже привести к ошибкам:
func test(mode: FactoryMode) -> ViewModelBase < switch mode < case NormalMode: return createViewModel() as NormalViewModel case PreviewMode: return createViewModel() //забыли as PreviewViewModel >>
В первом case в метод createViewModel подставляется NormalViewModel.
Во втором мы забыли написать «as PreviewViewModel», из-за чего в метод createViewModel подставляется тип ViewModelBase (что в лучшем случае приведет к ошибке в runtime).
Значит, необходимо сделать указание типа явным. Для этого в createViewModel мы добавим новый параметр viewModelType типа TViewModel.Type. Type тут означает, что метод принимает в качестве параметра не экземпляр типа, а сам объект-тип.
func createViewModel(viewModelType: TViewModel.Type) -> TViewModel < let viewModel = viewModelType.createIntsance() //View model configurate return viewModel >
После этого наш switch-case выглядит так:
func test(mode: FactoryMode) < let viewModel: ViewModelBase? switch mode < case NormalMode: return createViewModel(NormalViewModel.self) case PreviewMode: return createViewModel(PreviewViewModel.self) >>
Теперь В функцию createViewModel передается аргументы NormalViewModel.self и PreviewViewModel.self. Это объекты-типы NormalViewModel и PreviewViewModel. В Swift есть довольно странная особенность: если у функции один параметр, можно не писать self.
func test(mode: FactoryMode) < let viewModel: ViewModelBase? switch mode < case NormalMode: return createViewModel(NormalViewModel) case PreviewMode: return createViewModel(PreviewViewModel) >>
Но если аргументов два или больше, ключевое слово self необходимо.
P. S.
Надеюсь что данная статья окажется кому-то полезной. Так же планируется продолжение про Swift (и не только).
Swift Typealias: как их использовать и зачем?
В этой статье вы узнаете о типалиях и вариантах их использования в Swift.
Псевдоним типа позволяет вам предоставить новое имя для существующего типа данных в вашей программе. После объявления псевдонима типа псевдоним можно использовать вместо существующего типа во всей программе.
Псевдоним типа не создает новых типов. Они просто дают новое имя существующему типу.
Основная цель typealias — сделать наш код более читабельным и понятным в контексте для человеческого понимания.
Как создать типалиас?
Он объявляется с использованием ключевого слова typealias как:
typealias name = существующий тип
В Swift вы можете использовать typealias для большинства типов. Они могут быть:
- Встроенные типы (например, String, Int)
- Типы, определяемые пользователем (например, class, struct, enum)
- Сложные типы (например, закрытие)
Typealias для встроенных типов
Вы можете использовать псевдонимы для всех встроенных типов данных, таких как String, Int, Float и т. Д.
Например:
typealias StudentName = Строка
Приведенное выше объявление позволяет использовать StudentName везде вместо String . Итак, если вы хотите создать константу типа string, но больше похожую на имя студента. Вы можете сделать как:
пусть имя: StudentName = "Джек"
Без использования typealias вы должны объявить константу типа string как:
пусть имя: String = "Джек"
Над обоими примерами создается константа типа String . Но объявляя с помощью typealias , наш код становится более читабельным.
Typealias для типов, определяемых пользователем
Есть много случаев, когда вам нужно создать свой собственный тип данных. Предположим, вы хотите создать тип, представляющий ученика, вы можете создать его, используя класс как:
class Student ( )
Теперь группу студентов можно представить в виде массива как:
var student: Array = ()
Вышеупомянутое объявление можно сделать более читаемым, создав свой собственный тип для Array использования в typealias качестве:
typealias Студенты = Массив
Теперь мы можем сделать наш код более читабельным:
var студенты: студенты = ()
Typealias для сложных типов
Разберем еще один пример. Предположим, у нас есть метод, который принимает замыкание в качестве входного параметра.
Не волнуйтесь, если вы не знаете о закрытии. Подумайте об этом как об особом типе функции. Мы подробно объяснили это в статье: Быстрые закрытия.
func someMethod(oncomp:(Int)->(String))( )
В приведенном выше примере в качестве входных данных для someMethod . Замыкание принимает Int значение и возвращается String .
Вы можете видеть, что использование слова (Int)->(String) имеет меньший смысл для читателя. Вы можете использовать, typealias чтобы дать ему новое имя:
typealias CompletionHandler = (Инт) -> (Строка)
Теперь вы можете переписать метод как:
func someMethod(oncomp:CompletionHandler)( )
Мы видим, что тот же код выглядит более понятным и удобным для программистов с использованием typealias .
TypeAlias и Tuple в Swift — Введение в Swift
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Об обучении на Хекслете
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар « Как самостоятельно учиться »
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Используйте Хекслет по-максимуму!
- Задавайте вопросы по уроку
- Проверяйте знания в квизах
- Проходите практику прямо в браузере
- Отслеживайте свой прогресс
Задавайте вопросы, если хотите обсудить теорию или упражнения. Команда поддержки Хекслета и опытные участники сообщества помогут найти ответы и решить задачу
Для перемещения по курсу нужно зарегистрироваться
1. Основы Swift ↳ теория
2. TypeAlias и Tuple в Swift ↳ теория
3. Optional в Swift ↳ теория
4. Операторы и серии в Swift ↳ теория
5. Строки в Swift ↳ теория
6. Массивы в Swift ↳ теория
7. Словари в Swift ↳ теория
8. Ветвление в Swift ↳ теория
9. Функции в Swift ↳ теория
10. Серия параметров в Swift ↳ теория
11. Переменные параметры и inout в Swift ↳ теория
12. Тип функции в Swift ↳ теория
13. Пример замыкания в Swift ↳ теория
14. Замыкание в Swift ↳ теория
15. Энумератор (перечисление) в Swift ↳ теория
16. Raw values в энумераторе в Swift ↳ теория
17. Структуры и классы в Swift ↳ теория
18. Value type vs. Reference type в Swift ↳ теория
19. Вычислимые (computed) свойства в Swift ↳ теория
20. Наблюдатели (property observers) в Swift ↳ теория
21. Свойства типа в Swift ↳ теория
22. Методы типа и экземпляра в Swift ↳ теория
23. Мутирующие (mutating) методы в Swift ↳ теория
24. Сабскрипты (subscripts) в Swift ↳ теория
25. Наследование в Swift ↳ теория
26. Инициализаторы в Swift ↳ теория
27. Деинициализаторы в Swift ↳ теория
28. ARC (automatic reference counting) в Swift ↳ теория
29. Расширения (extensions) в Swift ↳ теория
30. Протоколы в Swift ↳ теория
31. Generics в Swift ↳ теория
32. Generic тип в Swift ↳ теория
Поможем, если трудно
Порой обучение продвигается с трудом. Сложная теория, непонятные задания… Хочется бросить. Не сдавайтесь, все сложности можно преодолеть. Рассказываем, как
Не понятна формулировка, нашли опечатку?
Выделите текст, нажмите ctrl + enter и опишите проблему, затем отправьте нам. В течение нескольких дней мы улучшим формулировку или исправим опечатку
Что-то не получается в уроке?
Загляните в раздел «Обсуждение»:
- Изучите вопросы, которые задавали по уроку другие студенты — возможно, ответ на ваш уже есть
- Если вопросы остались, задайте свой. Расскажите, что непонятно или сложно, дайте ссылку на ваше решение. Обратите внимание — команда поддержки не отвечает на вопросы по коду, но поможет разобраться с заданием или выводом тестов
- Мы отвечаем на сообщения в течение 2-3 дней. К «Обсуждениям» могут подключаться и другие студенты. Возможно, получится решить вопрос быстрее!
Подробнее о том, как задавать вопросы по уроку
Swift Typealias
A type alias allows you to provide a new name for an existing data type into your program. After a type alias is declared, the aliased name can be used instead of the existing type throughout the program.
Type alias do not create new types. They simply provide a new name to an existing type.
The main purpose of typealias is to make our code more readable, and clearer in context for human understanding.
How to create a typealias?
It is declared using the keyword typealias as:
typealias name = existing type
In Swift, you can use typealias for most types. They can be either:
- Built-in types (for.eg: String, Int)
- User-defined types (for.e.g: class, struct, enum)
- Complex types (for e.g: closures)
Typealias for built-in types
You can use typealias for all built in data Types as String, Int, Float etc.
For example:
typealias StudentName = String
The above declaration allows StudentName to be used everywhere instead of String . So, if you want to create a constant of type string but represents more like student name. You can do as:
let name:StudentName = "Jack"
Without using typealias, you should declare constant of type string as:
let name:String = "Jack"
Above both examples creates a constant of type String . But declaring with typealias , our code becomes more readable.
Typealias for user defined types
There are many cases when you need to create your own data type. Suppose you want to create a Type that represents Student, you can create it using a class as:
class Student
Now a group of students can be represented as an array as:
var students:Array = []
The above declaration can be made more readable by creating your own type for Array using typealias as:
typealias Students = Array
Now we can make our code more readable as:
var students:Students = []
Typealias for complex types
Lets analyze one more example. Suppose we have a method that takes a closure as an input parameter.
Don’t worry if you do not know about closures. Just think of it as a special type of function. We have explained it detail in the article: Swift closures.
func someMethod(oncomp:(Int)->(String))
The above example takes a closure as an input to someMethod . The closure takes an Int value and returns String .
You can see the use of (Int)->(String) makes less sense to the reader. You can use typealias to provide a new name for it:
typealias CompletionHandler = (Int)->(String)
Now you can rewrite the method as:
func someMethod(oncomp:CompletionHandler)
We can see the same code looks more clear and programmer friendly with the use of typealias .
Table of Contents
- What is a typealias?
- How to create a typealias?
- Typealias for built-in types
- Typealias for user defined types
- Typealias for complex types