Если у вас есть в коде замыкание (closure), оно захватывает любые переменные из внешнего контекста для использования внутри замыкания. Это очень крутая особенность замыканий. Пользуясь этими возможностями, вы можете сделать иногда исключительно элегантный код. Иногда замыкания могут быть лучшей заменой делегированию.
Давайте сделаем такой элегантный код для нашего графического калькулятора. Возьмем в качестве основы графический калькулятор, в котором выполнены все обязательные пункты Задания 3. В посте «Задание 3. Решение -Обязательные задания» рассказано, как построить график в Графическом калькуляторе с помощью делегирования.
Я немного напомню ситуацию. У нас есть MVC — Графический калькулятор, управляемый классом GraphViewController, в который передается программа program калькулятора для построения графика ( это часть Model MVC).
Часть View этого MVC представляет собой обычный UIView, управляемый классом GraphView.
Перед нами поставлена задача создать абсолютно обобщенный класс GraphView, способный строить зависимости y = f(x). Этот класс ничего не должен знать о калькуляторе, он должен получать информацию о графике в виде общей зависимости y = f(x) и не хранить никаких данных. С другой стороны, в нашем Controller, представленным классом GraphViewController, как раз и содержится информация о графике y = f(x), но не в явном виде, а в виде программы program , которая может интерпретироваться экземпляром brain калькулятора
Имея произвольное значение x можно вычислить y c помощью калькулятора brain.
Как связать эти два класса — GraphView и GraphViewController, когда у одно из них есть информация, в которой нуждается другой? Традиционный и универсальный способ выполнения этого как в Objective-C, так и в Swift — это делегирование. Об этом способе для данного конкретного примера на Swift рассказано в посте «Задание 3. Решение -Обязательные задания». Но делегирование требует выполнения 6 шагов, состоящих из создания протокола, добавления переменной делегата, подтверждение протокола делегирования, реализации методов делегата и т.д.
В Swift функции (замыкания) являются гражданами первого сорта, то есть могут объявляться как переменные, передаваться как параметры. А также принимая во внимание, что замыкания (closures) захватывают любые переменные из внешнего контекста для использования внутри замыкания, рассмотрим как можно осуществить взаимодействие двух классов, в нашем случае GraphView и GraphViewController, без применения делегирования.
Мы убираем из кода, полученного после выполнения всех обязательных пунктов Задания 3, все, что связано с делегирование, и создаем новый дополнительный класс с именем Grapher.
Далее развиваем идею, предложенную в Лекции 9.
В классе Grapher есть просто переменная с именем yForX, тип которой — функция. Функция берет аргумент x типа Double и возвращает Double? Это Optional функция — вы не обязаны задавать эту функцию в своем экземпляре класса Grapher. Получилось нечто похожее на optional метод в протоколе старого Objective-C стиля. Такие optional методы не допускаются в протоколах нового Swift стиля.
Вернемся в класс GrapView и создадим экземпляр grapher нового класса Grapher.
Это public свойство и мы можем устанавливать его извне, то есть из GraphViewContrller.
Используя Это public свойство grapher и его Optional переменную yForX, нарисуем график в классе GrapView
Заметьте, что для задания цепочки Optionals в случае, когда сама функция является Optional, функцию нужно взять в круглые скобки, поставить знак ? вопроса, а затем написать ее аргументы.
В GraphViewController в Наблюдателе didSet { } Свойства GraphView! , которое является @IBOutlet, мы установим свойство grapher таким образом, что замыкание yForX будет захватывать экземпляр моего калькулятор self.brain, в котором уже установлена нужная программа program для построения графика.
Все. Никаких делегатов, никаких протоколов, никаких подтверждений протоколов. Единственное — добавляем [unowned self ] для исключения циклических ссылок в памяти (об этом в в Лекции 9).
Проверяем
Все работает. Мы получаем очень элегантное и ясное решение. Код на Github.
В нашем случае, когда класс Grapher содержит всего одну переменную, можно поступить еще проще и не вводить дополнительный класс Grapher, а добавить в класс GrapherView саму эту переменную как public (not private) чтобы ее можно было устанавливать в GrapherViewController
Используя Optional переменную yForX, нарисуем график в классе GrapView
В GraphViewContrller в Наблюдателе didSet { } Свойства GraphView! мы установим само замыкание yForX, которое будет захватывать мой калькулятор self.brain, и установленную в нем нужная программа program для построения графика.
Код для Swift 1.2 находится на на Github. Код для Swift 2.0 находится на на Github.
Очень полезный прием обмена информацией между различными классами, и его стоит взять на вооружение. Мы еще увидим его в будущих постах.