[Перевод] От 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. Перевод не претендует на самый правильный и самый лучший перевод на хабре, если есть какие-то замечание, пишите в личку, буду править. Спасибо за понимание.

© Habrahabr.ru