Pull to refresh

От Objective-C к Swift. Рекомендации

Reading time10 min
Views41K
Original author: Yari D'areglia
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 Замыкание


Мне нравится синтаксис блоков, это так ясно и легко запомнить!

</IronicMode>


Кстати, после нескольких лет разработок 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. Перевод не претендует на самый правильный и самый лучший перевод на хабре, если есть какие-то замечание, пишите в личку, буду править. Спасибо за понимание.
Tags:
Hubs:
Total votes 23: ↑14 and ↓9+5
Comments9

Articles