[Перевод] От Objective-C к Swift. Рекомендации
Swift это новый язык программирования от компаний Apple, который она презентовала в этом году на WWDC. Вместе с языком программирования, Apple выпустила отличный справочник по языку Swift, который я рекомендую прочитать или ознакомиться с ним. Тем не менее, читать книгу это очень долго! Так что если у Вас нет много времени и Вы просто хотите узнать о новом языке Swift, то эта статья для Вас.В данной статье я бы хотел поделиться некоторыми размышлениями по поводу перехода от Objective-C к Swift. Я постараюсь дать Вам несколько советов и указать на недостатки при разном подходе к обеим языкам. Поэтому без лишних отступлений, перейдем к самой статье.
Одиночный файл против файла реализации интерфейса
Первое, наиболее существенное изменение, которое стоит упомянуть, это отказ от структуры interface.h/implementation.m.
Должен признать, что я сторонник этой модели. Получение и обмен информацией о классе просто с помощью файла интерфейса является безопасным и быстрым.
В Swift интерфейса и реализации не разделены на два файла. Мы просто реализовываем наш класс (и в момент написания даже не представляется возможным добавить модификаторы видимости).
Если действительно сложно справляться с данным изменением, то можно использовать следующее: здравый смысл.
Мы можем легко увеличить читаемость наших классов хорошим документированием. Например, можно перемещать элементы, которые хотим сделать «публичными», в верхней части нашего файла используя расширения для разграничения данных, доступных для всех и личную информацию.Еще один очень распространенный приём — ставить для приватных методов и переменных подчеркивание »_»
Вот небольшой пример их смешевания:
// Public extension DataReader { var data { } func readData (){ var data = _webserviceInteraction () } } // Private implementation class DataReader: NSObject { let _wsURL = NSURL (string: «http://theurl.com») func _webserviceInteraction ()→String{ // … } } Хотя мы не можем изменить видимость элементов наших классов, можно попытаться сделать доступ к некоторым «более трудным» из них.Нестандартным решением является использование вложенного класса, которое частично скрывает личные данные (по крайней мере, в автозаполнение)
Пример:
import UIKit class DataReader: NSObject { // Public *********************** var data: String?{ get{return private.internalData} } init (){ private = DataReaderPrivate () } func publicFunction (){ private.privateFunc () } // Private ********************** var private: DataReaderPrivate class DataReaderPrivate { var internalData: String? init (){ internalData = «Private data!» } func privateFunc (){} } } Мы помещаем приватную реализацию в приватный постоянный случай и используем «обычную» реализацию в класс в качестве общественного интерфейса. Приватные элементы, на самом деле, не скрыты, но для доступа к ним мы должны пройти через «приватные» константы.
let reader = DataReader () reader.private.privateFunc () Возникает вопрос: стоит ли это конечной цели, частичное скрытие личных элементов? Мое предложение — ждать модификаторов видимости (Apple работает над этим), а пока использовать хорошее документирование с или без расширений.
Константы и переменные
В Objective-C я редко использовал константные ключевые слова, даже когда знал, что некоторые данные никогда не изменятся. В Swift, разработчики Apple предлагают использовать константу (let) вместо переменной (var). Так что просто следите за ней и попытайтесь выяснить роль ваших переменных. В конечном счете, Вы будете использовать больше констант, чем ожидаете.
Пишите только то, что необходимо написать
Посмотрите на 2 строчки кодов и найдите разницу:
let wsURL: NSURL = NSURL (string: «http://wsurl.com»); vs let wsURL = NSURL (string: «http://wsurl.com») В течение первых двух недель работы с Swift, я заставлял себя убирать точку с запятой с каждого ряда кода. Сейчас уже легче (и я уже забыл, каково это в Objective-C).
Вывод типа дает возможность присвоить тип переменной, выводя ее непосредственно из её определения. Это еще одно преимущество, которое немного трудно освоить, когда оно исходит с использованием подробного языка Objective-C.
Мы должны попытаться использовать методы последовательного присваивания имен, в противном случае было бы трудно для другого разработчика (и для вас), определить тип, выведенный неудачным выбором присваивания имен:
let a = something () Более подходящее имя облегчает работу:
let a = anInt () Следующее существенное изменение — использование круглых скобок, которые больше не нужно ставить:
if (a > b){} vs if a > b {} Помните, то, что Вы пишите в скобках оценивается как выражение и не всегда разрешено делать запись таким способом. При связке переменных, например, мы не можем использовать скобки:
if (let x = data){} // Error! if let x = data {} // OK! Не требуется выбирать интерфейс или удалять точку с запятой и скобки, но можно рассмотреть эти варианты, как один из способов написания кода в Swift. В конечном итоге мы повышаем читабельность и экономим время на наборе текста и символов.
Опциональные
Работая с функциями, которые возвращают «value» (значение) или «nothing» (ничего), не задумывались ли Вы, какой лучший способ для определения «nothing»? Я использовал NSNotFound, -1, 0, пользовательские возвращаемые значения.
Благодаря Optionals (Дополнительное) имеем «nothing-value», которое полностью определяется, нам просто нужно добавить знак вопроса после типа данных.
Мы можем написать:
class Person{ let name: String let car: Car? // Optional value init (name: String){ self.name = name } } // ACCESSING THE OPTIONAL VALUE *********** var Mark = Person (name: «mark») // use optional binding if let car = Mark.car { car.accelerate () } // unwrap the value Mark.car?.accelerate () В этом примере отношение «Человек владеет автомобилем» определяется как Optional (Дополнительное). Это значит, что свойство «автомобиль» может быть ноль, а человек не мог иметь автомобиль.
Тогда мы обращаемся к этому значению, используя дополнительную привязку (если позволить (let) машину =) или с помощью развернутой фразы (автомобиль?). Если мы не определяем свойство как дополнительное, мы обязаны установить значение для этого свойства, иначе функция инициализации вынесет ошибку.Последняя возможность определить не дополнительное значение свойства — в пределах функции инициализации.
Таким образом, мы должны определить, как свойства класса будут взаимодействовать с остальным классом, и как они будут вести себя во время существования экземпляров класса.
Эти усовершенствования полностью изменяют способ, которым мы представляем наши классы.
Дополнительная распаковка
Если вам трудно работать с опциями, потому что Вы не можете понять, почему компилятор попросит вас, дать развернутое значение перед его использованием …
Mark.car? … я предлагаю подумать об опции дополнительно как о структуре (это структура, поэтому не должна быть слишком трудной), которая не содержит величины напрямую, но добавляет слой вокруг неё (окутывает, обрамляет-wrap). Если внутренняя величина определяется, вы удаляете слой (разворачиваете-unwrap), и получаете требуемое значение, в противном случае вы получаете ноль.
Принудительное разворачивание через знак »!» — это просто способ, чтобы удалить слой, не заботясь о внутренней величине. Вы рискуете, пытаясь получить доступ к значению за слоем. Если это значение равно нулю, приложение просто дает сбой.
Шаблон делегирования
После нескольких лет программирования на Objective-C и Cocoa мы зависимы от шаблона делегирования. Однако, мы по-прежнему используем эту схему. Вот супер простой пример использования делегата:
@objc protocol DataReaderDelegate{ @optional func DataWillRead () func DataDidRead () } class DataReader: NSObject { var delegate: DataReaderDelegate? var data: NSData? func buildData (){ delegate?.DataWillRead?() // Optional method check data = _createData () delegate?.DataDidRead () // Required method check } } Мы заменяем проверку существования делегата и используем respondToSelector с дополнительной цепочкой.
delegate?.DataWillRead?() Обратите внимание, что мы должны приставить протокол с помощью ключевого слова @obj, потому что мы использовали @optional. Кстати, составитель предупреждает нас сообщением в случае, если мы забыли его.
Для реализации этого делегата мы реализовываем протокол в другом класс, и назначаем его, также как и в Objective-C:
class ViewController: UIViewController, DataReaderDelegate { override func viewDidLoad () { super.viewDidLoad () let reader = DataReader () reader.delegate = self } func DataWillRead () {…} func DataDidRead () {…} } Шаблон программировани — Target-action
Другим общепринятым методом, который мы пользуемся в Swift, является интерактивный элемент (target-action), и в этом случае применяем его также как и в Objective-C.
class ViewController: UIViewController { @IBOutlet var button: UIButton override func viewDidLoad () { super.viewDidLoad () button.addTarget (self, action: «buttonPressed:», forControlEvents: UIControlEvents.TouchUpInside) } func buttonPressed (sender: UIButton){…} } Небольшая разница заключается в том, как мы определяем адрес сегмента (selector). Мы просто пишем метод прототипа, используя строку, которая автоматически будет видоизменятся в:
Selector («buttonPressed:») Шаблон программировани Singleton
Любите его или ненавидьте его, Синглтон по прежнему является одной из самых принятых моделей программирования.
Нравится вам это или нет, паттерн Singleton — один из самых применимых образцов. Мы можем реализовать его, используя GDC и dispatch_once или положиться на поточнобезопасную природу ключевого слова let.
class DataReader: NSObject { class var sharedReader: DataReader { struct Static{ static let _instance = DataReader () } return Static._instance } … } Давайте посмотрим на данный код:1. SharedReader — это статическая составляющая (мы можем заменить ее функцией).2. Статические (не составляющие) свойства еще не разрешены в реализации класса. Так, благодаря вложенным типам, мы добавляем вложенную структуру в класс. Структура поддерживает статические свойства, так что мы просто добавляем здесь статическое свойство.3. Свойство _instance является константой. Оно не может быть заменено другим значением, и является поточнобезопасным.
Мы можем обратиться к единственному экземпляру DataReader с помощью:
DataReader.sharedReader
Структуры и вычисления
В Swift, структура и вычисления имеют много характеристик, которые Вы едва ли примените в других языках.
Они поддерживают:
struct User{ // Struct properties let name: String let ID: Int // Method!!! func sayHello (){ println («I’m » + self.name + » my ID is: \(self.ID)») } } let pamela = User (name: «Pamela», ID: 123456) pamela.sayHello () Как видите, структура использует функцию инициализации, которая в данном случае автоматически создана Swift (мы можем добавить другие параметры ввода для клиентов).
Синтаксис enum немного отличается от того, который мы использовали. Он определяется ключевым словом case:
enum Fruit { case orange case apple } Еnum не ограничивается своими свойствами:
enum Fruit: String { case .orange = «Orange» case .apple = «Apple» } Мы также можем построить enum с более сложными характеристиками:
enum Fruit{ // Available Fruits case orange case apple // Nested type struct Vitamin{ var name: String } // Compound property var mainVitamin: Vitamin { switch self{ case .orange: return Vitamin (name: «C») case .apple: return Vitamin (name: «B») } } } let Apple = Fruit.apple var Vitamin = Apple.mainVitamin В предыдущем коде мы добавили вложенный тип (vitamin) и дополнительное свойство (mainVitamin), который присваивает начальные значения элементов для данной структуры в зависимости от значения enum.
Изменяемые и неизменяемые
С помощью Objective-C, мы привыкли к неизменным и изменяемым версиям любого класса. Некоторые примеры: NSArray и NSDictionary.
С Swift нам не нужны различные типы данных, мы просто по-новому используем постоянное или переменное значение.
Переменная массив является изменяемой, в то время как с константой массива мы не можем изменить свои сохраненные значения. Так что просто имейте в виду, правило «let = неизменное, var = переменная» (Исправленная ошибка: перед Beta 3 можно изменить неизменный массив).
Блоки vs Замыкание
Мне нравится синтаксис блоков, это так ясно и легко запомнить!
Кстати, после нескольких лет разработок Cocoa, мы привыкли к данному синтаксису, и иногда я предпочитаю заменить легкие задачи делегирования блоками. Они наполнены смыслом, быстрые и хорошо применимы.
В Swift аналогичными блоками являются элементы замыкания. Они обладают многими свойствами, и Apple проделала большую работу, пытаясь упростить путь их написания. Пример на официальной документации Swift лишает меня дара речи. Он начинается таким определением:
reversed = sort (names, { (s1: String, s2: String) → Bool in return s1 > s2 }) и перепроектируется в:
reversed = sort (names, >) Таким образом, есть различные пути реализации замыкания благодаря выводу типа, сокращениям ($ 0, $ 1) и прямым функциям (>).
В этой статье я не описываю синтаксис замкнутого выражения, но хочется сказать несколько слов о значениях сбора данных в пределах замкнутого выражения.
В Objective-C мы определяем переменную как __block, когда мы намерены изменить его значение через Block. Использование замыканий, в данном случае, становится ненужным.
Мы можем получить доступ и изменять любое значение окружающей области. На самом деле замкнутые выражения достаточно разумны, чтобы захватить внешние элементы. Элемент вводится в качестве копии или ссылки. Если закрытие изменяет значение элемента, оно создает ссылку, если нет — создает копию.
Если закрытие относится к вхождению, которое содержит или использует его, мы можем натолкнуться на цикл обращения.
Давайте посмотрим пример:
class Person{ var age: Int = 0 @lazy var agePotion: (Int) → Void = { (agex: Int)→Void in self.age += agex } func modifyAge (agex: Int, modifier:(Int)→Void){ modifier (agex) } } var Mark: Person? = Person () Mark!.modifyAge (50, Mark!.agePotion) Mark = nil // Memory Leak Замкнутое выражения agePotion используется, сохраняя ссылку на текущий экземпляр. В то же время этот экземпляр содержит ссылку на закрытие — и здесь возникает цикл обращения.
Чтобы избежать этой проблемы мы используем список Capture. Этот список связывает слабую ссылку с экземпляром, который мы хотим использовать в Закрытии. Синтаксис очень прост — добавьте слабую ссылку перед определением Закрытие и экземпляр получит слабую ссылку вместо сильной.
@lazy var agePotion: (Int) → Void = { [unowned self](agex: Int)→Void in self.age += agex } Unowned и Слабые ссылки
Мы уже знаем, как слабая ссылка работает в Objective-C. Точно также она работает в Swift, никаких изменений нет.
Я действительно оценил введение этого ключевого слова, потому что это хорошая подсказка для определения соотношения между классами.
Опишем простое соотношение между человеком и его банковским счетом:
1. Человек может иметь счет в банке (не обязательно)2. Счет в банке должен принадлежать человеку (обязательно)
We can describe this relation with code: class Person{ let name: String let account: BankAccount! init (name: String){ self.name = name self.account = BankAccount (owner: self) } } class BankAccount{ let owner: Person init (owner: Person){ self.owner = owner } } Эти отношения собираются создать цикл. Первое решение будет добавлением слабой ссылки на свойство «Банк Account.owner». Однако с помощью слабой ссылки, мы определяем еще одно полезное ограничение: свойство должно всегда иметь значение, оно не может быть равным нулю (таким образом, мы удовлетворяем пункт 2 из предыдущего списка).
На самом деле, больше нечего сказать о слабой ссылке. Она работает так же, как слабая ссылка без увеличения регистра, на который она указывает и обеспечивает значение, отличное от нуля.
Заключение.
Должен признать: иногда я по-прежнему работаю над ошибками составителя. Чем больше я использую Swift, тем яснее становится, что я не зря трачу время экспериментируя и изучая. Есть много интересных изменений и вещей по сравнению с Objective-C, которые не существовали раньше, и которые мотивируют меня практиковаться больше.
Это долгожданная новинка в развитии IOS/OSX и я уверен, что Вы полюбите его!
p.s. Перевод не претендует на самый правильный и самый лучший перевод на хабре, если есть какие-то замечание, пишите в личку, буду править. Спасибо за понимание.