Swift self: Методы — SwiftBook

Содержание

Методы — SwiftBook

Методы — это функции, которые связаны с определенным типом. Классы, структуры и перечисления — все они могут определять методы экземпляра, которые включают в себя определенные задачи и функциональность для работы с экземпляром данного типа. Классы, структуры и перечисления так же могут определить методы типа, которые связаны с самим типом. Методы типа работают аналогично методам класса в Objective-C.

Дело в том, что структуры и перечисления могут определить методы в Swift, что является главным отличием от C или Objective-C. В Objective-C классы единственный тип, который может определять методы. В Swift вы можете выбирать, стоит ли вам определять класс, структуру или перечисление, и вы все равно, при любом раскладе, получаете возможность определения методов типа, который вы создадите.

Методы экземпляра являются функциями, которые принадлежат экземплярам конкретного класса, структуры или перечисления. Они обеспечивают функциональность этих экземпляров, либо давая возможность доступа и изменения свойств экземпляра, либо обеспечивая функциональность экземпляра в соответствии с его целью. Методы экземпляра имеют абсолютно одинаковый синтаксис как и функции, что описаны в разделе Функции.

Вы пишете метод экземпляра внутри фигурных скобок типа, которому он принадлежит. Метод экземпляра имеет неявный доступ ко всем остальным методам экземпляра и свойствам этого типа. Метод экземпляра может быть вызван только для конкретного экземпляра типа, которому он принадлежит. Его нельзя вызвать в изоляции, без существующего экземпляра.

Ниже пример, который определяет простой класс Counter, который может быть использован для счета количества повторений действия:

class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

Класс Counter() определяет три метода экземпляра:

  • increment увеличивает значение счетчика на 1
  • increment(by: Int) увеличивает значение счетчика на определенное значение amount.
  • reset() сбрасывает значение счетчика на 0.

Класс Counter так же определяет переменное свойство count, для отслеживания значения счетчика.

Вы можете вызвать методы экземпляра с тем же точечным синтаксисом:

let counter = Counter()
// начальное значение counter равно 0
counter.increment()
// теперь значение counter равно 1
counter.increment(by: 5)
// теперь значение counter равно 6
counter.reset()
// теперь значение counter равно 0

Параметры функций могут иметь и имя аргумента (для использования внутри функций), и ярлык аргумента (для использования при вызове функций), что описано Ярлыки аргументов и имена параметров функций. То же самое верно для имен параметров методов, потому как методы те же самые функции, но ассоциированные с определенным типом.

Свойство self

Каждый экземпляр типа имеет неявное свойство self, которое является абсолютным эквивалентом самому экземпляру. Вы используете свойство self для ссылки на текущий экземпляр, внутри методов этого экземпляра.

Метод increment может быть вызван так:

func increment() {
    self.count += 1
}

На практике вам не нужно писать self очень часто. Если вы не пишите self, то Swift полагает, что вы ссылаетесь на свойство или метод текущего экземпляра каждый раз, когда вы используете известное имя свойства или метода внутри метода. Это хорошо видно при использовании свойства count (а не self.count) внутри трех методов Counter.

Главное исключение из этого правила получается, когда имя параметра метода экземпляра совпадает с именем свойства экземпляра. В этой ситуации имя параметра имеет приоритет и появляется необходимость ссылаться на свойство в более подходящей форме. Вы используете свойство self для того, чтобы увидеть различие между именем параметра и именем свойства.

Здесь self разграничивает параметр метода x и свойство экземпляра, которое тоже x:

struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
        return self.x > x
    }
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
    print("Эта точка находится справа от линии, где x == 1.0")
}
// Выведет "Эта точка находится справа от линии, где x == 1.0"

Без префикса self, Swift будет думать, что в обоих случаях x будет как раз параметром метода, который так же называется x.

Изменение типов значений методами экземпляра

Структуры и перечисления являются типами значений. По умолчанию, свойства типов значений не могут быть изменены изнутри методов экземпляра.

Однако, если вам нужно изменить свойства вашей структуры или перечисления изнутри конкретного метода, то вы можете выбрать поведение как изменяющееся для этого метода. После этого метод может изменить свои свойства изнутри метода, и все изменения будут сохранены в исходную структуру, когда выполнение метода закончится. Метод так же может присвоить совершенно новый экземпляр для свойства self, и этот новый экземпляр заменит существующий, после того как выполнение метода закончится.

Вы можете все это осуществить, если поставите ключевое слово mutating перед словом func для определения метода:

struct Point {
   var x = 0.0, y = 0.0
   mutating func moveBy(x deltaX: Double, y deltaY: Double) {
      x += deltaX
      y += deltaY
   }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("Сейчас эта точка на (\(somePoint.x), \(somePoint.y))")
// Выведет "Сейчас эта точка на (3.0, 4.0)"

Структура Point определяет метод moveBy(x:y:), который передвигает точку типа Point на определенное количество значений. Вместо того, чтобы вернуть новую точку, этот метод фактически изменяет координаты точки, которая его вызвала. Ключевое слово mutating добавлено к определению метода, для того, чтобы изменить значения свойств.

Обратите внимание, что вы не можете вызвать изменяющийся (mutating) метод для константных типов структуры, потому как ее свойства не могут быть изменены, даже если свойства являются переменными, что описано в главе Свойства хранения постоянных экземпляров структуры.

let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// это вызовет сообщение об ошибке

Присваивание значения для self внутри изменяющегося метода

Изменяющиеся методы могут присваивать полностью новый экземпляр неявному свойству self. Пример Point, приведенный выше, мог бы быть записан в такой форме:

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

Такая версия изменяющегося метода moveBy(x:y:) создает абсолютно новую структуру, чьим значениям x, y присвоены значения конечной точки. Конечный результат вызова этой альтернативной версии метода будет абсолютно таким же как и в ранней версии.

Изменяющиеся методы для перечислений могут установить отдельный член перечисления как неявный параметр self:

enum TriStateSwitch {
    case off, low, high
    mutating func next() {
        switch self {
        case .off:
            self = .low
        case .low:
            self = .high
        case .high:
            self = .off
        }
    }
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight равен .high
ovenLight.next()
// ovenLight равен .off

В этом примере мы рассматриваем перечисление с тремя положениями переключателя. Переключатель проходит последовательно три положения (off, low, high), каждый раз меняя положение, как вызывается метод next().

Методы экземпляра, которые описаны выше, являются методами, которые вызываются экземпляром конкретного типа. Вы так же можете определить методы, которые вызываются самим типом. Такие методы зовутся

методами типа. Индикатор такого метода — ключевое слово static, которое ставится до ключевого слова метода func. Классы так же могут использовать ключевое слово class, чтобы разрешать подклассам переопределение инструкций суперкласса этого метода.

Заметка

В Objective-C определять методы типов можно только для классов. В Swift вы можете создавать методы типа не только для классов, но и для структур и перечислений. Метод каждого типа ограничен самим типом, который он поддерживает.

Такие методы так же используют точечный синтаксис, как и методы экземпляра. Однако эти методы вы вызываете самим типом, а не экземпляром этого типа. Вот как вы можете вызвать метод самим классом SomeClass:

class SomeClass {
    class func someTypeMethod(){
        //здесь идет реализация метода
    }
}
 
SomeClass.someTypeMethod()

Внутри тела метода типа неявное свойство self ссылается на сам тип, а не на экземпляр этого типа. Это значит, что вы можете использовать self для того, чтобы устранить неоднозначность между свойствами типа и параметрами метода типа, точно так же как вы делали для свойств экземпляра и параметров метода экземпляра.

Если обобщить, то любое имя метода и свойства, которое вы используете в теле метода типа, будет ссылаться на другие методы и свойства на уровне типа. Метод типа может вызвать другой метод типа с иным именем метода, без использования какого-либо префикса имени типа. Аналогично, методы типа в структурах и перечислениях могут получить доступ к свойствам типа, используя имя этого свойства, без написания префикса имени типа.

Пример ниже определяет структуру с именем LevelTracker, которая отслеживает прогресс игрока на разных уровнях игры. Это одиночная игра, но может хранить информацию для нескольких игроков на одном устройстве.

Все уровни игры (кроме первого уровня) заблокированы, когда играют в первый раз. Каждый раз, заканчивая уровень, этот уровень открывается и у остальных игроков на устройстве. Структура LevelTracker использует свойства и методы типа для отслеживания уровней, которые были разблокированы. Так же она отслеживает текущий уровень каждого игрока.

struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1
    
    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }
    
    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel 
    } 

    @discardableResult mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

Структура LevelTracker следит за самым последним уровнем, который разблокировал игрок. Это значение лежит в свойстве типа highestUnlockedLevel.

LevelTracker так же определяет две функции для работы со свойством highestUnlockedLevel. Первая функция типа unlock(_:), которая обновляет значение highestUnlockedLevel, каждый раз когда открывается новый уровень. Вторая функция типа isUnlocked(_:), которая возвращает true, если конкретный уровень уже разблокирован. (Обратите внимание, что методы типа могут получить доступ к highestUnlockedLevel без написания LevelTracker.highestUnlockedLevel.)

В дополнение к его свойствам типа и методам типа, структура LevelTracker так же отслеживает и текущий прогресс игрока в игре. Она использует свойство экземпляра currentLevel для отслеживания уровня, на котором игрок играет.

Для помощи в управлении свойством currentLevel, структура LevelTracker определяет метод экземпляра advance(to:). До того как обновить currentLevel, этот метод проверяет доступен ли запрашиваемый новый уровень. Метод advance(to:) возвращает логическое значение, указывающее, удалось ли ему поставить currentLevel. Не обязательно должно быть ошибкой игнорирование результата работы функции advance(to:), поэтому этот метод имеет маркировку @discardableResult. Об атрибутах подробнее можно прочитать в разделе Атрибуты.

Структура LevelTracker используется классом Player, который описан ниже, для отслеживания и обновления прогресса конкретного игрока:

class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

Класс Player создает новый экземпляр LevelTracker для отслеживания прогресса игрока. Так же он определяет и использует метод complete(level:), который вызывается каждый раз, как игрок заканчивает уровень. Этот метод открывает следующий уровень для всех игроков. (Логическое значение advance(to:) игнорируется, так как уровень открывается функцией LevelTracker.unlock(_:) на предыдущей строке.)

Вы можете создать экземпляр класса Player для нового игрока и увидеть, что будет, когда игрок закончит первый уровень:

var player = Player(name: "Argyrios")
player.complete(level: 1)
print("Самый последний доступный уровень сейчас равен \(LevelTracker.highestUnlockedLevel)")
//Выведет "Самый последний доступный уровень сейчас равен 2"

Если вы создадите второго игрока, и попробуете им начать прохождение уровня, который не был разблокирован ни одним игроком в игре, то вы увидите, что эта попытка будет неудачной:

player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
    print("Игрок на уровне 6")
} else {
    print("Уровень 6 еще не разблокирован")
}
// Выведет "Уровень 6 еще не разблокирован"

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

12.1. Методы | Swift World

struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    static func isUnlocked(_ level: Int) -> Bool {
        return level  Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}
Структура LevelTracker отслеживает наивысший уровень, который был разблокирован игроком. Это значение хранится в свойстве типа с названием highestUnlockedLevel.

LevelTracker также определяет две функции типа для работы со свойством highestUnlockedLevel. Первая имеет название unlock(_:) и обновляет значение highestUnlockedLevel, всякий раз при разблокировке нового уровня. Вторая — это удобная функция типа с названием isUnlocked(_:), которая возвращает true, если номер заданного уровня уже разблокирован. (Обратите внимание, что эти методы типа могут получить доступ к свойству типа highestUnlockedLevel без необходимости записывать его в виде LevelTracker.highestUnlockedLevel.)

В дополнение к свойству типа методам типа LevelTracker отслеживает прогресс отдельного игрока через игру. Он использует свойство объекта с названием currentLevel для отслеживания текущего уровня игрока.

Чтобы помочь управлять свойством currentLevel, LevelTracker определяет метод объекта с названием advance(to:). Прежде, чем обновить currentLevel, этот метод проверяет, разблокирован ли уже новый запрашиваемый уровень. Метод advance(to:) возвращает Булево значение для указания того, можно ли или нет установить currentLevel. Так как это не необходимо, ошибка для кода, что вызывает advance(to:) метод, игнорировать возвращаемое значение, то функция помечена атрибутом @discardableResult.

Пять ловушек для начинающего свифтера / Хабр

Всем привет! Я — начинающий свифтер, то есть изучаю Swift без опыта ObjC. Недавно мы с компаньонами начали проект, требующий приложение под iOS. А еще у нас есть idée fixe: с нами непременно должен работать студент из Физтеха, а приложение должно быть написано на языке Swift. И вот, пока мы ищем физтеховцев и знакомимся с ними, я решил не терять время и параллельно начать своими силами пилить проект на Swift. Так я впервые открыл XCode.

Вдруг обнаружилось много знакомых, которые точно так же не имея опыта мобильной разработки, стали осваивать ее именно посредством Swift, а не ObjC. Кто-то из них подтолкнул меня поделиться опытом на Хабре.

Итак, вот топ пять «ловушек», своевременное понимание которых точно бы сэкономило мне время.

1. Блоки (замыкания) могут порождать утечки памяти

Если вы, как и я, пришли в мобильную разработку минуя ObjC, то, наверное, одним из самых важных вводных материалов я бы назвал документацию Apple по Automatic Reference Counting. Дело в том, что при «скоростном» изучении нового языка путем погружения (то есть, начав сразу пилить реальный проект) у вас может развиться склонность пропускать «теорию», не имеющую отношения к задачам типа «показать всплывающее окно здесь и сейчас». Однако мануал по ARC содержит очень важный раздел, специально объясняющий неочевидное свойство замыканий, порождающее утечки.

Итак, пример «ловушки». Простой контроллер, который никогда не очистится из памяти:

class ViewController: UIViewController {
    var theString = "Hello World"
    var whatToDo: (()->Void)!
    override func viewDidLoad() {
        whatToDo = { println(self.theString) }
    }
    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        whatToDo()
        navigationController!.setViewControllers([], animated: true)
    }  
    deinit { println("removed from memory") }
}

Запускаем и тычем пальцем в экран. Если у нас мало опыта, то мы ошибочно ожидаем увидеть в консоли:
Hello World
removed from memory

Но на самом деле мы видим:
Hello World

То есть мы потеряли возможность обращаться к нашему контроллеру, а тот остался висеть в памяти.

Почему же? Оказывается, вызов self вот в этой невинной строчке

{ println(self.theString) }

автоматически создает строгую ссылку на контроллер из замыкания whatToDo. Так как на whatToDo уже строго ссылается сам контроллер, то в результате мы получаем два объекта в памяти, строго ссылающихся друг на друга — и они никогда не вычистятся.

Если внутри замыкания НЕ используется вызов self, то такого подвоха НЕ возникает.

В свифте, конечно, предусмотрено решение, которое Apple почему-то называет элегантным. Вот оно:

whatToDo = { [unowned self] in println(self.theString) }

Et voila! Вывод: будьте внимательны с жизненным циклом всех замыканий, содержащих вызов self.
2. Array, Dictionary и Struct по умолчанию немутабельные типы, никогда не передающиеся по ссылке

Когда стоит задача освоить новый язык очень быстро, я склонен забивать на чтение доков по таким интуитивно очевидным типам, как массивы и словари, полагаясь на то, что autocomplete научит меня всему, что надо, непосредственно в процессе кодинга. Такой торопливый подход все-таки подвел меня в ключевом месте, когда я всю дорогу воспринимал «массивы массивов» и «массивы страктов» как наборы ссылок (по аналогии с JS) — они оказался наборами копий.

После прочтения доков я все-таки прозрел: в Свифте массивы и словари являются страктами и поэтому, как любые стракты, передаются не по ссылке, а по значению (путем копирования, который компилятор оптимизирует под капотом).

Пример, иллюстрирующий мега-подвох, который вам приготовил Свифт:

struct Person : Printable {
    var name:String
    var age:Int
    var description:String { return name + " (\(age))" }
}
class ViewController: UIViewController {
    var teamLeader:Person!
    var programmers:[Person] = []
    func addJoeyTo(var persons:[Person]) {
        persons.append(Person(name: "Joey", age: 25))
    }
    override func viewDidLoad() {
        teamLeader = Person(name: "Peter", age: 30)
        programmers.append(teamLeader)
        
        // Строим ошибочные ожидания...
        teamLeader.name = "Peter the Leader"
        addJoeyTo(programmers)
     
        // ...и вот он, момент истины
        println(programmers)
    }
}

При запуске, если мы ошибочно мыслим в ключе «передача по ссылке», то ожидаем увидеть в консоли:
[Peter the Leader (30), Joey (25)] // Результат 1

Вместо этого видим:
[Peter (30)] // Результат 2

Будьте внимательны! Как же выйти из положения, если нам в действительности нужен именно первый результат? На самом деле, каждый конкретный случай требует индивидуального решения. В данном примере сработает вариант замены struct на class и замены [Person] на NSMutableArray.
3. Singleton Instance — выбираем наилучший «хак»

Ловушка заключается в том, что на текущий момент классы в Swift не могут иметь статических хранимых свойств, а только статические методы (class func) или статические вычисляемые свойства (class var x:Int {return 0}).

При этом сам Apple вообще не имеет предубеждений против глобальных инстансов в духе паттерна Singleton — в этом мы регулярно убеждаемся, используя такие перлы, как NSUserDefaults.standardUserDefaults(), NSFileManager.defaultManager(), NSNotificationCenter.defaultCenter(), UIApplication.sharedApplication(), ну и так далее. Мы действительно получим статические переменные в следующем общем обновлении — Swift 1.2.

Так как же нам создать собственные такие же инстансы в текущей версии Swift? Есть несколько возможных «хаков» под общим названием Nested Struct, но самый лаконичный из них — это следующий:

extension MyManager {
    class var instance: MyManager {
        func instantiate() -> MyManager {
            return ... // постройте свой инстанс здесь
        }
        struct Static {
            static let instance = instantiate() // lazily loaded + thread-safe!
        }
        return Static.instance
    }
}

Стракты в свифте не только поддерживают статические хранимые свойства, но также по умолчанию дают им отложенную поточно-ориентированную инициализацию. Вот это профит! Не зная об этом заранее, можно зря потратить время на написание и отладку лишнего кода.

Внимание! В следующей версии свифта (1.2) этот «хак» уже не понадобится, но дата общего релиза не известна. (Уже доступна бета-версия для тестирования, но для этого необходима также бета-версия XСode6.3, билд из которой от вас не примет Appstore. Короче — ждем глобального релиза.)

4. Методы didSet и willSet не будут вызваны в процессе выполнения конструктора

Вроде мелочь, но это способно ввести вас в тотальный ступор при отладке багов, если вы не знаете этого. Поэтому если вы запланировали какой-то набор манипуляций внутри didSet, который важен как при инициализации, так и далее в течение жизненного цикла объекта, делать это нужно таким образом:
class MyClass {
    var theProperty:OtherClass! {
        didSet {
            doLotsOfStuff()
        }
    }
    private func doLotsOfStuff () {
        // здесь реагируем на didSet theProperty
    }
    ...
    init(theProperty:OtherClass)
    {
        self.theProperty = theProperty
        doLotsOfStuff()
    }
}

5. Нельзя просто так взять и обновить UI, когда пришел ответ с сервера

Программисты с опытом ObjC могут посмеяться над этой «ловушкой», потому что она должна быть общеизвестна: методы, связанные с UI, безопасно дергать только из главного потока. Иначе — непредсказуемость и баги, толкающие в тотальный ступор. Но это наставление почему-то проходило мимо меня, пока я, наконец, не столкнулся с жуткими багами.

Пример «проблемного» кода:

func fetchFromServer() {
    let url = NSURL(string:urlString)!
    NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { data, response, error in
        if (error != nil) {
            ...
        } else {
            self.onSuccess(data)
        }
    })!.resume()
}
func onSuccess(data) {
    updateUI()
}

Обратите внимание на блок completionHandler — все это будет исполняться вне главного потока! Тем, кто еще не столкнулся с последствиями, советую не экспериментировать, а просто не забыть обставить updateUI следующим образом:
func onSuccess(data) {
    dispatch_sync(dispatch_get_main_queue(), {
        updateUI()
    })
}

Это типичное решение. Одной строчкой мы возвращаем updateUI обратно в главный поток и избегаем неожиданностей.

На сегодня все. Всем новичкам успехов!

Опытные хабровчане из mobile — ваши замечания будут очень полезны мне и всем начинающим свифтерам.

Язык программирования Swift. Русская версия / Блог компании Apphud / Хабр

Привет, Хабр! 2 июня все мы воочию могли наблюдать, как компания Apple начала творить революцию в стане Objective-C разработчиков, представив миру свой новый язык программирования – Swift. Вместе с этим, она выложила в открытый доступ небольшую документацию по языку, которую мы решили перевести, если на то будет спрос. Предлагаем вашему вниманию перевод первой главы. Если тема будет интересна, то мы продолжим публиковать перевод каждую неделю.
Оглавление

Добро пожаловать в Swift
    О Swift
    Введение в Swift

Language guide
    The Basics
    Basic Operators
    String and Characters
    Collection Types
    Control Flow
    Functions
    Closures
    Enumerations
    Classes and Structures
    Properties
    Methods
    Subscripts
    Inheritance
    Initialization
    Deinitialization
    Automatic Reference Counting
    Optional Chaining
    Type Casting
    Nested Types
    Extensions
    Protocols
    Generics
    Advanced Operators

Language Reference
    About the Language Reference
    Lexical Structure
    Types
    Expressions
    Statements
    Declarations
    Attributes
    Patterns
    Generic Parameters and Arguments
    Summary of the Grammar
    Trademarks

Добро пожаловать в Swift

О языке Swift

Swift – это новый язык программирования для разработки iOS и OS X приложений, который сочетает в себе все лучшее от C и Objective-C, но лишен ограничений, накладываемых в угоду совместимости с C. В Swift используются паттерны безопасного программирования и добавлены современные функции, превращающие создание приложения в простой, более гибкий и увлекательный процесс. Swift, созданый нами с чистого листа, – это возможность заново представить себе, как разрабатываются приложения.

Swift разрабатывался нами несколько лет. Основой нового языка программирования послужили существующие компилятор, отладчик и фреймворки. Мы упростили процесс управления памятью с помощью механизма автоматического подсчета ссылок – Automatic Reference Counting (ARC). Наши фреймворки также подверглись серьезной модернизации. Objective-C начал поддерживать блоки, литералы и модули – все это создало благоприятные условия для внедрения современных технологий. Именно эта подготовительная работа послужила фундаментом для нового языка программирования, который будет применяться для разработки будущих программных продуктов для Apple.

Разработчикам Objective-C Swift покажется знакомым. Он сочетает в себе читабельность именованных параметров и мощь динамической объектной модели Objective-C. Он открывает доступ к уже существующим фреймворкам Cocoa и совместим с кодом, написанным на Objective-C. Построенный на этой общей основе язык предлагает множество новых возможностей и унифицирует процедурные и объектно-ориентированные аспекты языка программирования.

Swift не отпугнет и начинающих программистов. Это первый мощный язык программирования, такой же понятный и увлекательный, как скриптовый язык. Он поддерживает так называемые playground-ы, которые позволяют программистам экспериментировать с кодом, видя результат в режиме реального времени без необходимости компилировать и запускать приложение.

Swift вобрал в себя все лучшее от современных языков и разработан с учетом обширного опыта компании Apple. Наш компилятор – синоним производительности, наш язык оптимизирован для разработки без оглядки на компромиссы. Он спроектирован таким образом, чтобы вы смогли легко разработать и ваше первое приложение «hello, world!», и даже целую операционную систему. Все это делает Swift важным инструментом для разработчиков и для самой компании Apple.

Swift – это новый фантастический способ создавать приложения для iOS и OS X, и мы продолжим развивать его, добавляя новый функционал и представляя новые возможности. Наша цель – амбициозна. И мы с нетерпением ждем, чтобы увидеть, что вы сумеете создать при помощи него.

Введение в Swift

По давней традиции первая программа на новом языке должна выводить на экран слова “Hello, world”. С помощью Swift это делается так:
println("Hello, world")

Если вы когда-нибудь разрабатывали на C или Objective-C этот синтаксис должен казаться вам до боли знакомым – в Swift эта строчка кода является законченной программой. Вам больше не нужно импортировать отдельные библиотеки для обеспечения базового функционала вроде ввода/вывода в консоль или работы со строками. Код, написанный в глобальной области видимости, является точкой входа в программу, таким образом функция main больше не нужна. Также обратите внимание на отсутствие точки с запятой в конце каждой строки.

Это введение содержит достаточно информации, чтобы начать писать код на Swift. Не переживайте, если вам будет что-то непонятно – мы все детально объясним в последующих главах.

Замечание
Для лучшего понимания материала мы рекомендуем использовать режим playground в Xcode. Playground позволяет вам видеть результат сразу в процессе редактирования кода без необходимости компилировать и запускать приложение.

Простые типы данных

Используйте let для создания константы и var для создания переменной. Тип константы указывать не нужно, вы можете присвоить ей значение лишь единожды.
var myVariable = 42
myVariable = 50
let myConstant = 42

Типы константы и переменной должны совпадать с типами присваиваемых им соответствующих значений. Однако это не означает, что вы должны напрямую указывать их тип. Компилятор автоматически определит тип константы и переменной при присваивании им значения. Так, в приведенном примере компилятор определит, что myVariable имеет целочисленный тип.

Если же инициализатор отсутствует или не предоставляет достаточной информации, вы можете указать тип самостоятельно после переменной, разделив название и тип двоеточием:

let implicitInteger = 70
let inplicitDouble = 70.0
let inplicitDouble: Double = 70

Давайте поэкспериментируем
Создайте константу с типом Float и проинициализируйте ее числом 4.

Значения никогда не конвертируются в другой тип неявно. Если вам необходимо конвертировать значение в другой тип, делайте это явно:
let label = "The width is "
let width = 94
let widthLabel = label + String(width)

Давайте поэкспериментируем
Попробуйте удалить явное преобразование к типу String в последней строке. Какую ошибку вы получите?

Имеется более простой способ включения значений в строки: для этого заключите выражение в скобки и поставьте перед ними обратный слэш (\). Пример:
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

Давайте поэкспериментируем
Попробуйте использовать конструкцию \() и выведите на экран строку, включающую результат суммы двух целочисленных переменных и чье-нибудь имя.

При работе с массивами и ассоциативными массивами (словарями, dictionary) используются квадратные скобки ([]):
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
 
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

Чтобы создать пустой массив или dictionary, используйте следующий синтаксис:
let emptyArray = String[]()
let emptyDictionary = Dictionary<String, Float>()

Для создания пустых массивов и словарей используйте [] и [:] соответственно, – например, когда вы присваиваете новое значение переменной или передаете аргумент в функцию.
shoppingList = []   // Went shopping and bought everything.
Условия и циклы

Для создания условий используются операторы if и switch, для создания циклов – for-in, for, while и do-while. При этом выделять круглыми скобками условия и инициализирующие выражения необязательно, тогда как фигурные скобки обязательны.
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
teamScore

Условие внутри оператора if должно быть логическим, это в частности означает, что выражение if score {…} является ошибочным, поскольку здесь нет явного сравнения (например, с нулем).

Условный оператор if можно использовать совместно с let и var для работы с константами и переменными, которые могут иметь значение nil. Такие константы и переменные называются опциональными (то есть они могут либо принимать какое-либо значение, либо быть равны nil). Чтобы создать опциональную переменную или константу добавьте знак вопроса (?) после указания типа.

 var optionalString: String? = "Hello"
optionalString == nil
 
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

Давайте поэкспериментируем
Измените optionalName на nil. Что вы видите на экране? Добавьте блок else для обработки случая, когда optionalName равен nil.

Если опциональное значение равно nil, условие будет ложным и код в фигурных скобках после if выполнен не будет. В противном случае переменной greeting будет присвоено новое значение.

Оператор множественного выбора switch поддерживает внутри себя множество других операторов сравнения и не ограничен лишь простыми сравнениями:

let vegetable = "red pepper"
switch vegetable {
case "celery":
    let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
    let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"):
    let vegetableComment = "Is it a spicy \(x)?"
default:
    let vegetableComment = "Everything tastes good in soup."
}

Давайте поэкспериментируем
Попробуйте удалить условие по умолчанию. Какую ошибку вы получите?

После выполнения подходящего блока кода, программа покидает оператор switch, не проверяя последующие условия. Таким образом вам не нужно вручную добавлять операторы прерывания (break) в конце каждого блока case.

Для перебирания элементов ассоциативного массива используйте оператор for-in совместно с указанием пары имен для каждой пары ключ-значение.

let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
largest

Давайте поэкспериментируем
Добавьте еще одну переменную, которая позволит выяснить, к какому из трех типов относится найденное максимальное число.

Оператор цикла while позволяет выполнять блок кода внутри него до тех пор, пока условие не станет ложным. Условие также может быть указано после блока, который в таком случае будет выполнен по крайней мере один раз.
var n = 2
while n < 100 {
    n = n * 2
}
n
 
var m = 2
do {
    m = m * 2
} while m < 100
m

Оператор for можно использовать для перебора последовательности чисел с помощью двух точек (..) или с помощью инициализатора, условия и инкремента. Посмотрите, эти два цикла делают одно и то же:
var firstForLoop = 0
for i in 0..3 {
    firstForLoop += i
}
firstForLoop
 
var secondForLoop = 0
for var i = 0; i < 3; ++i {
    secondForLoop += 1
}
secondForLoop

При создании цикла используйте две точки (..), если не хотите включать большее значение в диапазон, и три точки (), чтобы включить как меньшее, так и большее значения.
Функции и замыкания.

Для объявления функций используйте ключевое слово func. Вызов функции производится через указание ее имени и списка аргументов в круглых скобках. Возвращаемый тип следует отделить от перечня формальных аргументов с помощью ->.
func greet(name: String, day: String) -> String {
    return "Hello \(name), today is \(day)."
}
greet("Bob", "Tuesday")

Давайте поэкспериментируем
Удалите параметр day. Вместо него добавьте переменную, обозначающую наименование подаваемого на обед блюда.

Если функция возвращает множество значений, следует использовать кортеж:
func getGasPrices() -> (Double, Double, Double) {
    return (3.59, 3.69, 3.79)
}
getGasPrices()

Функции также могут иметь неопределенное число аргументов:
func sumOf(numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}
sumOf()
sumOf(42, 597, 12)

Давайте поэкспериментируем
Напишите функцию, позволяющую находить среднее арифметическое произвольного числа своих аргументов.

Функции могут вкладываться друг в друга. Вложенная функция может обращаться к переменным, объявленным во внешней функции. Используйте вложенные функции, чтобы привести в порядок код сложной или большой функции.
func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

Функции являются объектами первого класса (first-class type), иными словами, функция в качестве свого результата может возвращать другую функцию.
func makeIncrementer() -> (Int -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

Функция также может принимать другую функцию в качестве одного из аргументов.
func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(numbers, lessThanTen)

Функции являются частным случаем замыканий. Вы можете создать замыкание, не указывая его имени и окружив тело замыкания фигурными скобками ({}). Для отделения аргументов и типа возвращаемого значения от тела замыкания используйте оператор in.
numbers.map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
    })

Давайте поэкспериментируем
Перепишите замыкание таким образом, чтобы оно возвращало ноль для всех лишних чисел.

Существует несколько техник, позволяющих делать замыкания более лаконичными. Если тип замыкания априори известен (например, это callback делегата), можно опустить указание типа его параметров и/или типа возвращаемого значения. Замыкания, состоящие из единственного выражения, неявно возвращают результат этого выражения.
numbers.map({ number in 3 * number })

В замыкании вместо указания имени переменной, вы можете использовать ее порядковый номер – это особенно полезно при написании коротких замыканий. Замыкание, являющееся последним аргументом функции, может быть передано в нее сразу после круглых скобок с перечнем остальных параметров.
sort([1, 5, 3, 12, 2]) { $0 > $1 }
Объекты и классы

Для создания класса используется зарезервированное слово class. Члены класса объявляются точно так же, как и обычные константы и переменные. Более того, методы класса объявляются как обычные функции.
class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

Давайте поэкспериментируем
Добавьте константу-член класса и метод класса, принимающую ее в качестве своего аргумента.

Чтобы создать экземпляр (объект) класса, достаточно добавить круглые скобки после названия класса. Доступ к методам и членам класса осуществляется через точку.
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

В этом примере мы упустили одну важную деталь – конструктор класса, метод init.
class NamedShape {
    var numberOfSides: Int = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

Обратите внимание, как член класса name при помощи self отделен от аргумента конструктора name. Аргументы передаются в конструктор обычным образом, как и в любой другой метод класса. Обратите внимание на то, что каждый член класса должен быть проинициализирован – либо при объявлении (как, например, numberOfSides), либо в конструкторе (как name).

Деструктор класса – метод deinit, который можно переписать в случае необходимости.

Чтобы наследовать класс от уже существующего класса, после указания имени дочернего класса следует поставить двоеточие и указать название родительского. В Swift нет никаких ограничений по обязательному наследованию какого-либо стандартного класса.

Переопределенные дочерним классом методы должны быть помечены ключевым словом override – переопределение методов без override приведет к ошибке. Компилятор также выявляет методы, маркированные override, но не переопределяющие какие-либо методы своего родительского класса.

class Square: NamedShape {
    var sideLength: Double
    
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }
    
    func area() ->  Double {
        return sideLength * sideLength
    }
    
    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

Давайте поэкспериментируем
Создайте класс Circle и наследуйте его от класса NamedShape. Конструктор класса Circle принимает два аргумента – радиус и название. Переопределите методы area и describe этого класса.

Члены класса могут также иметь собственные getter и setter.
class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
    
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
    
    var perimeter: Double {
    get {
        return 3.0 * sideLength
    }
    set {
        sideLength = newValue / 3.0
    }
    }
    
    override func simpleDescription() -> String {
        return "An equilateral triagle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
triangle.perimeter
triangle.perimeter = 9.9
triangle.sideLength

В setter-е переменной perimeter новое присваиваемое значение неявно называется newValue. Вы можете изменить название этой переменной, указав его в скобках сразу после set.

Обратите внимание на структуру конструктора класса EquilateralTriangle. Этот метод включает в себя три последовательных шага:

  1. инициализация членов дочернего класса;
  2. вызов конструктора родительского класса;
  3. изменение значений членов родительского класса.

Если вам необходимо выполнить определенный код до или после присваивания нового значения переменной, вы можете переопределить методы willSet и didSet нужным вам образом. Например, в приведенном ниже классе гарантируется, что длина стороны треугольника всегда будет равна длине стороны квадрата.
class TriangleAndSquare {
    var triangle: EquilateralTriangle {
    willSet {
        square.sideLength = newValue.sideLength
    }
    }
    var square: Square {
    willSet {
        triangle.sideLength = newValue.sideLength
    }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
triangleAndSquare.square.sideLength
triangleAndSquare.triangle.sideLength
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
triangleAndSquare.triangle.sideLength

У методов классов имеется одно важное отличие от функций. Названия аргументов функции используются только в пределах этой функции, тогда как в методе класса параметры также используются при вызове этого метода (кроме первого параметра). По умолчанию метод класса имеет одинаковые названия параметров как при вызове, так и внутри себя. Однако вы можете указать другое название (в примере ниже – times), которое будет использовано только внутри этого метода. При этом для вызова этого метода необходимо использовать первое название (numberOfTimes).
class Counter {
    var count: Int = 0
    func incrementBy(amount: Int, numberOfTimes times: Int) {
        count += amount * times
    }
}
var counter = Counter()
counter.incrementBy(2, numberOfTimes: 7)

При работе с опциональными значениями добавьте знак вопроса (?) перед методами, членами класса и т.д. Если значение перед знаком вопроса равно nil, все, что следует после (?) игнорируется и значение всего выражения равно nil. В противном случае выражение вычисляется обычным образом. В обоих случаях результатом всего выражения будет опциональное значение.
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
Перечисления и Структуры

Для создания перечислений используется ключевое слово enum. Отметим, что перечисления также могут иметь в своем составе методы.
enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
        case .Ace:
            return "ace"
        case .Jack:
            return "jack"
        case .Queen:
            return "queen"
        case .King:
            return "king"
        default:
            return String(self.toRaw())
        }
    }
}
let ace = Rank.Ace
let aceRawValue = ace.toRaw()

Давайте поэкспериментируем
Напишите функцию, которая сравнивает 2 перечисления типа Rank по их значениям.

В вышеприведенном примере элементы перечисления первоначально имеют целочисленный тип, и вам достаточно указать значение только первого элемента – значения остальных элементов будут определены в соответствии с порядком их следования. В качестве исходного типа (raw value) значений элементов вы также можете выбрать строковый или вещественные типы.

Для преобразования исходного типа значения в тип перечисления используйте функции toRaw и fromRaw.

if let convertedRank = Rank.fromRaw(3) {
    let threeDescription = convertedRank.simpleDescription()
}

Отметим, что значения элементов перечисления являются фактическими, а не просто иной записью своих исходных значений. Вообще говоря, вы можете и не указывать их исходные значения.
enum Suit {
    case Spades, Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
        case .Spades:
            return "spades"
        case .Hearts:
            return "hearts"
        case .Diamonds:
            return "diamonds"
        case .Clubs:
            return "clubs"
        }
    }
}
let hearts = Suit.Hearts
let heartsDescription = hearts.simpleDescription()

Давайте поэкспериментируем
Добавьте метод Color, возвращающий строку “black” для Spades и Clubs и “red” для Hearts и Diamonds.

Обратите внимание на то, как осуществляется доступ к члену Hearts перечисления Suit. При присваивании значения константе hearts используется полное имя Suit.Hearts, поскольку мы явно не указываем тип этой константы. А в switch мы используем сокращенную форму .Hearts, поскольку тип значения self априори известен. Вы можете использовать сокращенную форму повсеместно, если тип переменной явно указан.

Для создания структур используется ключевое слово struct. Структуры имеют множество схожих черт с классами, включая методы и конструкторы. Одним из наиболее существенных отличий структур от классов является то, что экземпляры структур, в отличие от экземпляров классов, передаются в функции по значению (то есть предварительно создается их локальная копия), тогда как экземпляры классов передаются по ссылке.

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

Давайте поэкспериментируем
Добавьте в структуру Card метод, который создает полную колоду карт.

Экземпляр члена перечисления может иметь собственные значения и они могут быть разными. Вы присваиваете эти значения при создании экземпляра перечисления (константа success в примере). Связанные и исходные значения это разные вещи: исходное значение члена перечисления всегда постоянно для всех экземпляров перечисления и указывается при его объявлении.

Рассмотрим пример получения с сервера времени восхода и заката Солнца. Сервер отправляет в ответ либо соответствующую информацию, либо сообщение об ошибке.

enum ServerResponse {
    case Result(String, String)
    case Error(String)
}
 
let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")
 
switch success {
case let .Result(sunrise, sunset):
    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
    let serverResponse = "Failure...  \(error)"
}

Давайте поэкспериментируем
Добавьте третий вариант в оператор множественного выбора switch

Обратите внимание, каким образом из объекта ServerResponse “вытаскиваются” время восхода и заката.
Протоколы и Расширения.

Для объявления протокола используйте ключевое слово protocol.
protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

Протоколы могут поддерживаться классами, перечислениями и структурами.
class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
 
struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription

Давайте поэкспериментируем
Создайте перечисление, которое будет реализовывать этот протокол.

Обратите внимание на ключевое слово mutating в определении структуры SimpleStructure, которое информирует компилятор о том, что соответствующий метод подвергает структуру изменениям. В противовес этому методы класса SimpleClass не нужно маркировать как mutating, поскольку методы класса всегда могут беспрепятственно его изменять.

Для добавления новых методов или членов класса в уже существующий тип необходимо использовать расширения – extensions. Вы также можете использовать расширения для реализации протокола уже существующим типом, даже если он импортирован из какой-либо библиотеки или фреймворка.

extension Int: ExampleProtocol {
    var simpleDescription: String {
    return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
7.simpleDescription

Давайте поэкспериментируем
Создайте расширение типа Double с переменной-членом absoluteValue.

Вы можете использовать название протокола как и любой другой тип – например, чтобы создать массив объектов разного типа, но реализующих общий протокол. Заметьте, что при работе с объектами такого типа методы, объявленные вне протокола, будут недоступны.
let protocolValue: ExampleProtocol = a
protocolValue.simpleDescription
// protocolValue.anotherProperty  // Uncomment to see the error

Несмотря на то, что во время выполнения программы переменная protocolValue имеет тип SimpleClass, компилятор считает, что ее тип – ExampleProtocol. Это означает, что вы не сможете случайно получить доступ к методам или членам класса, которые реализуются вне протокола ExampleProtocol.
Обобщенные типы (generics)

Для создания обобщенного типа, заключите имя в угловые скобки (<>).
func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] {
    var result = ItemType[]()
    for i in 0..times {
        result += item
    }
    return result
}
repeat("knock", 4)

Создавайте обобщенные функции, классы, перечисления и структуры.
// Reimplement the Swift standard library's optional type
enum OptionalValue<T> {
    case None
    case Some(T)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)

Если вы хотите задать обобщенные типу определенные требования, такие, как, например, реализация протокола или требование быть наследованным от определенного класса, используйте where.
func anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.Element> (lhs: T, rhs: U) -> Bool {
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
anyCommonElements([1, 2, 3], [3])

Давайте поэкспериментируем
Измените функцию anyCommonElements таким образом, чтобы она возвращала массив общих элементов.

В простых случаях вы можете опустить where и написать имя протокола или класса после двоеточия. Выражение <T: Equatable> эквивалентно выражению <T where T: Equatable>.
Хотите внедрить подписки в iOS-приложение за 10 минут? Интегрируйте Apphud и:
— оформляйте покупки с помощью лишь одного метода;
— автоматически отслеживайте состояние подписки каждого пользователя;
— легко интегрируйте Subscription Offers;
— отправляйте события о подписках в Amplitude, Mixpanel, Slack и Telegram с учетом локальной валюты пользователя;
— уменьшайте Churn rate в приложениях и возвращайте отписавшихся пользователей.

Правильная передача данных — SwiftBook

Доброго времени суток, друзья!

Сегодня мы рассмотрим передачу данных между ViewControllers. На самом деле передача данных между ViewControllers может показаться тривиальной задачей, но если учесть, что любое реальное приложение для iOS будет иметь много ViewControllers, то коммуникация становится их важной частью. Неправильное понимание этого может привести к трудному исправлению ошибок.

В целом существует много способов сделать это, но лишь некоторые из них являются лучшей практикой.

В этой статье я покажу вам такие практики передачи данных:

1. Вперед

  • между ViewController с использованием segues
  • между ViewController без segues

2. Назад

  • передача через unwind segue
  • передача при помощи делегата

3. Продвинутые

  • обратная передача при помощи замыканий (closures)

4. А также расскажу о неправильных техниках

И так поехали!

Передача данных вперед

Передача данных происходит каждый раз, когда на экране появляется новый ViewController.

Это может произойти через segue или программно.

Передача данных вперед между ViewController с использованием segues

Создаем 2 контроллера в сториборде: FirstViewController и SecondViewController.

В FirstViewController добавляем кнопку, и от кнопки перетаскиваем segue на SecondViewController. И даем имя segue (это очень важно!).

В SecondViewController создаем UILabel, привязываем наш @IBOutlet и создаем переменную с именем name. И подготовка окончена.

Главный метод, с которым Вы будете работать — это prepare(for segue:).
Всегда нужно проверять соответствует ли segue.identifier названию вашего segue, (guard segue.identifier == “showSegue”) который мы задавали ранее. Если все в порядке, то мы идем дальше и устанавливаем destination, наш “пункт назначения”.
Берем наш segue вызываем destination и обязательно кастим до нужного ViewController, иначе ничего не получиться (guard let destination = segue.destination as? SecondViewController) И если Вы все правильно сделали, то написав destination, Вы получите все свойства и методы SecondViewController. Давайте передадим в переменную name имя Андрей (destination.name = “Aндрей”).
Готово! Теперь при нажатии на кнопку, мы будет передавать имя в следующий контроллер!

class FirstViewController: UIViewController {

    @IBOutlet var username: UILabel!
      
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard segue.identifier == "showSecond" else { return }
        guard let destination = segue.destination as? SecondViewController else { return }
        destination.name = "Андрей"
    }
}
class SecondViewController: UIViewController {

    var name = ""
    
    @IBOutlet var username: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        username.text = name
    }
}
Заметка

Всегда правильно задавайте идентификатор для segue. Хорошей практикой будет называть идентификатор тем, что он делает. Потому что может быть ситуации когда Вам нужно передать 2 и более segue в один контроллер. В дальнейшем это поможет свободно ориентироваться в проекте.

Передача данных вперед между ViewController без segues

Иногда вы можете подключить ViewController программно, а не использовать segue.

Первый пример:
Создаем свойство storyboard, где name — это имя storyboard, в котором находится необходимый ViewController (let storyboard = UIStoryboard(name: “Main”, bundle: nil).

Далее создаем необходимый ViewController. Для этого используем метод instantiateViewController(identifier: String) и обязательно кастим до требуемого SecondViewController (guard let secondViewController = storyboard.instatiateViewController(identifier: “SecondViewController”) as? SecondViewController else { return })

Теперь мы также можем достучаться до переменной name и задать ей нужное значение. И так же не забываем вызвать метод show(vc: UIViewController, sender: Any?), который как раз и откроет нам данный контролер.

И готово! Так как в SecondViewController не поменялся код, показываем код FirstViewController:

class FirstViewController: UIViewController {
    
    @IBAction func passData() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        guard let secondViewController = storyboard.instantiateViewController(identifier: "SecondViewController") as? SecondViewController else { return }
        secondViewController.name = "Ivan"
        
        show(secondViewController, sender: nil)
    }
}

Как видите поскольку нет никакого segue, метод prepare (for: sender 🙂 также не вызывается.

Пример второй:

Если вы работаете с xib файлами, то можно тоже довольно легко и просто передавать данные.
Создаем ThirdViewController и оставляю галочку, чтобы с контроллером создался xib файл. Перетягиваем UILabel. Подготовка окончена.
В этом примере я покажу, не простую передачу данных, как мы делали, а через инициализатор.
Создаем свойство text: String, и создаем инициализатор к нему init(text: String). Так как это xib файл, то давайте добавим еще 2 свойства nibName: String?, bundle: Bundle?, это позволит нам инициализировать ThirdViewController через тот самый xib файл.
Дальше все по стандарту присуждаем self.text = text. И вызываем super.init(nibName: String, bundle: Bundle?), так как без инициализации “дизайна” контроллера ничего не получится. И исправляем ошибку с required init?(coder: NSCoder). Наш ThirdViewController — готов для получения данных!

class ThirdViewController: UIViewController {
    let text: String
    
    init(text: String, nibName: String?, bundle: Bundle?) {
        self.text = text
        super.init(nibName: nibName, bundle: bundle)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @IBOutlet var textLabel: UILabel!
    
    override viewDidLoad() {
        super.viewDidLoad()

        textLabel.text = text
    }
}

Возвращаемся к нашему SecondViewController. Перетаскиваем кнопку и создаем @IBAction. В нем создаем экземпляр класса и инициализируем по нашему кастомному инициализатору, который мы создали ранее (let thirdVC = ThirdViewController(text: “Alexey”, nibName: “ThirdViewController”, bundle: nil). Прошу заметить, что nibName — это название xib файла, поэтому если вы создаете xib файл отдельно от контроллера, имейте это ввиду.
И после этого вызываем метод show(vc: UIViewController, sender: Any?).
Наш SecondViewController готов, для передачи данных.

class SecondViewController: UIViewController {

    var name = ""
    
    @IBOutlet var username: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        username.text = name
    }
    
    @IBAction func goToThirdVC() {
        let thirdVC = ThirdViewController(text: "Alexey", nibName: "ThirdViewController", bundle: nil)
        show(thirdVC, sender: nil)
    }
}

Передача данных в обратном направлении

Передача данных назад в приложении iOS так же важна, как и их перемещение вперед. Пользователи часто возвращаются к предыдущему экрану, который они посетили.

Когда пользователь взаимодействует с вашим приложением, вы должны обновлять эти предыдущие экраны. Это не происходит автоматически, поэтому вы можете использовать разные методы.

Передача данных в обратном направлении через unwind segue

Для этого примера создадим новые FirstViewController и SecondViewController.

Начинаем настраивать SecondViewController. Создаем UILabel и UIButton в сториборде и привызываем к контроллеру. В контроллере создаем переменную text. И добавляем метод prepare (for: sender 🙂, где меняем значение переменной text на “Data was passed”.

Теперь настраиваем FirstViewController. Так же создаем UILabel и UIButton в сториборде и привызываем к контроллеру. Перетягиваем от кнопки segue на SecondViewController. И создаем @IBAction для нашей кнопки, но с параметром (_ unwindSegue: UIStoryboardSegue), который и является главное фишкой данного примера.
Далее все по стандарту: делаем проверку на segue.identifier, и кастим наш SecondViewController. Только обратите внимание, что при получении данных вместо свойства destination, мы работаем с source. Таким образом мы сообщаем, что SecondViewController будет источником данных. Ну и присуждаем нашему UILabel переменную text из SecondViewController(textLabel.text = source.text).
И главное — это в сториборде в SecondViewController от нашего UIButton перетягиваем segue, не на FirstViewController, а на кнопку Exit, который находится рядом с кнопкой First Responder выше контролера. И не забываем про segue.identifier.
Готово! Вот примерный код, который должен у Вас получиться.

class FirstViewController: UIViewController {
    
    @IBOutlet var textLabel: UILabel!
    
    @IBAction func saveData(_ unwindSegue: UIStoryboardSegue) {
        guard unwindSegue.identifier == "passDataToFirstVC" else {
            return
        }
        guard let source = unwindSegue.source as? SecondViewController else { return }
        textLabel.text = source.text
    }
}
class SecondViewController: UIViewController {

    var text = ""
    
    @IBOutlet var textLabel: UILabel!
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        text = "Data was passed"
    }
}

Передача данных в обратном направлении при помощи делегата

Иногда техники, которые Вы видели, все еще недостаточны.

Данные, которые вы хотите передать, могут быть временными и не принадлежать общему состоянию приложения.
Когда пользователь возвращается в UINavigationController, segue не запускается. Возможно, вы захотите, чтобы передача происходила в любой момент, а не только при переходе. Правильным решением будет делегирование.
Делегирование позволяет нам создать ссылку на предыдущий контроллер представления, не зная его типа. Мы делаем это с помощью протокола, который определяет интерфейс, с которым нам нужно взаимодействовать.
Создаем протокол FirstViewControllerDelegate и подписываем его под class (что означает AnyObjects). Это позволит в будущем создать слабую (weak) ссылку на протокол.
Любое свойство delegate должно быть слабым (weak), чтобы избежать сильных (strong) ссылочных циклов. Если вы не знаете, что это такое, то я советую прочитать статью для более подробной информации о слабых и сильных ссылках.

В протоколе создаем метод update с принимающим параметром text: String (update(text: String), который будет обновлять наш UILabel. Далее подписываем FirstViewController под протокол и выполняем этот метод (textLabel.text = text)

Далее переходим в SecondViewController, создаем ту самую слабую ссылку на протокол weak var delegate: FirstViewControllerDelegate и создаем @IBAction, где указываем, что должно произойти при нажатии этой кнопки. В нашем случаем будет происходить обновление текста на предыдущем контроллере без перехода назад (delegate?.update(text: “Text was changed”).

Если сейчас запустим приложение, нажмем кнопку и вернемся на предыдущий экран, то ничего не случится, так как мы не подписались на делегат, который находится в SecondViewController. Это частая ошибка всех программистов, поэтому если у Вас что-то не получается, проверяйте подписан ли принимающий контролер на делегат контроллера-отправителя.
Так как мы осуществляем переход на следующий контроллер через segue (при нажатии на UIButton), то подпишемся именно здесь. В методе prepare (for: sender 🙂 получаем destination к SecondViewController и именно там подписываемся на делегат (destination.delegate = self). Таким образом FirstViewController получает доступ к выполнению реализации протокола в SecondViewController.
Готово! Нам удалось получить данные обратно, независимо будем ли мы возвращаться на предыдущий экран или нет.
Весь код из примера, который должен получиться:

protocol FirstViewControllerDelegate: class {
    func update(text: String)
}

class FirstViewController: UIViewController, FirstViewControllerDelegate {

    @IBOutlet weak var textLabel: UILabel!
      
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let destination = segue.destination as? SecondViewController else { return }
        destination.delegate = self
    }
    
    func update(text: String) {
        textLabel.text = text
    }
}
class SecondViewController: UIViewController {

    weak var delegate: FirstViewControllerDelegate?
    
    @IBOutlet weak var textLabel: UILabel!
    
    @IBAction func changeDataInFirstVC() {
        delegate?.update(text: "Text was changed")
    }
}
Заметка

Техника передачи данных через делегирование является мощным инструментов в работе с передачей данных между контроллерами и также работает без segue.
Главное — это подписывать принимающий контроллер на delegate контроллера-отправителя.

Для закрепления можете сами попробовать сделать это, взяв примеры из 1.2 Передача данных вперед между ViewController без segues.

Продвинутые техники

Замена делегирования на замыкания (closures) Swift

Некоторые разработчики используют замыкания (closures) Swift для передачи данных назад между ViewControllers. Этот метод похож на делегирование, но более гибкий. Это также причина, почему я обычно рекомендую не использовать его.

Используя замыкания, вы можете определить интерфейс через свойства хранения, содержащие замыкания. Поэтому создадим в SecondViewController переменную с названием closure: ((String) -> ())?.
В SecondViewController, для быстрого примера, вызовем в методе viewDidLoad то самое замыкание, и поместим в него текст (closure?(“I can pass data by closure!”)) Обратите внимание, что как и с делегатом, замыкание должно быть опциональное.

И теперь в FirstViewController также устанавливает связь с этим замыканием, когда происходит переход. Но в этом случае вместо передачи ссылки на себя, он передает замыкание.

class FirstViewController: UIViewController {

    @IBOutlet var textLabel: UILabel!
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let destination = segue.destination as? SecondViewController else { return }
        destination.closure = { [weak self] text in
            self?.textLabel.text = text
        }
    }
}
class SecondViewController: UIViewController {

    var closure: ((String) -> ())?
    
    @IBOutlet var textLabel: UILabel!
    
    @IBAction func changeDataInFirstVC() {
        closure?("I can pass data by closure")
    }
}

Также обратите внимание, что замыкание содержит ссылку на себя. Таким образом, как и делегирование, использование замыканий по-прежнему создает связь между двумя ViewController.

Этот подход немного более лаконичен, чем делегирование. Но использование замыканий также имеет ряд особенностей / недостатков, которых нет у делегирования.

Если вам нужно более одного замыкания для связи с предыдущим контроллером представления, вам нужно сохранить свойство для каждого из них. При делегировании весь интерфейс выделяется внутри протокола, и вам нужно только одно свойство делегата.

На мой взгляд, замыкания лучше работают как обратные вызовы для асинхронных задач, таких как сетевые запросы или анимации. Делегирование является лучшим решением для связи ViewController.

Неправильные техники

Мы познакомились с одними из лучших практик для передачи данных между ViewController. Но к сожалению, в просторах интернета наблюдается и много неправильных.

В этом разделе мы рассмотрим, какие из них, и почему вы не должны их использовать.

Не используйте UserDefaults iOS

В iOS UserDefaults хранят пользовательские настройки, которые должны сохраняться между запусками приложения.

Все, что хранится в UserDefaults, остается там до тех пор, пока вы не удалите приложение из телефона, поэтому это не механизм для передачи данных между объектами.

Кроме того, вы можете хранить только простые типы данных в UserDefaults в форме списков свойств. Это означает, что вам нужно преобразовать любой пользовательский тип, прежде чем вы сможете поместить его туда.

В общем, ваше приложение должно получать доступ к UserDefaults через одну точку, которая обычно является настраиваемым контроллером совместно используемой модели.

Не используйте Notifications

Notifications в iOS дают вам канал, по которому какой-то код может отправлять сообщение другим объектам, на которые он не имеет прямой ссылки.

Я видел, как многие разработчики используют Notifications для передачи данных между контроллерами представления. Это не то, для чего они нужны!

Notifications создают косвенность и усложняют выполнение вашего кода. Когда вы публикуете notifications, вы не можете быть уверены, какие объекты получат его и в каком порядке. Это часто приводит к неожиданному поведению.
Notifications могут быть полезны иногда. Но вы должны использовать их экономно, и только когда это необходимо.

Выводы

Как я и говорил, существует много способов передачи данных между ViewController. Но только некоторые из них можно назвать хорошей практикой. Поначалу другие могут показаться удобными, но потом они могут создать Вам проблемы в будущем.

Поэтому выбирайте правильный подход и двигайтесь дальше!

 

Автор статьи: Михаил Цейтлин

Автоматический подсчет ссылок (ARC) — SwiftBook

Swift использует automatic reference counting (автоматический подсчет ссылок) для отслеживания и управления памятью вашего приложения. В большинстве случаев это означает, что управление памятью «просто работает» в Swift и вам не нужно думать о самостоятельном управлении памятью. ARC автоматически освобождает память, которая использовалась экземплярами класса, когда эти экземпляры больше нам не нужны.

Однако, в некоторых случаях для управления памятью ARC нужно больше информации об отношениях между некоторыми частями вашего кода. Эта глава опишет эти случаи и покажет как включить ARC, чтобы эта система взяла на себя весь контроль памятью вашего приложения. Использование ARC в Swift очень схоже с использованием ARC в Objective-C, описание которого можно найти в Transitioning to ARC Release Notes.

ARC применима только для экземпляров класса. Структуры и перечисления являются типами значений, а не ссылочными типами, и они не хранятся и не передают свои значения по ссылке.

Каждый раз, когда вы создаете экземпляр класса, ARC выделяет фрагмент памяти для хранения информации этого экземпляра. Этот фрагмент памяти содержит информацию о типе экземпляра, о его значении и любых свойствах хранения, связанных с ним.

Дополнительно, когда экземпляр больше не нужен, ARC освобождает память, использованную под этот экземпляр, и направляет эту память туда, где она нужна. Это своего рода гарантия того, что ненужные экземпляры не будут занимать память.

Однако, если ARC освободит память используемого экземпляра, то доступ к свойствам или методам этого экземпляра будет невозможен. Если вы попробуете получить доступ к этому экземпляру, то ваше приложение скорее всего выдаст ошибку и будет остановлено.

Для того, чтобы нужный экземпляр не пропал, ARC ведет учет количества свойств, констант, переменных, которые ссылаются на каждый экземпляр класса. ARC не освободит экземпляр, если есть хотя бы одна активная ссылка.

Для того чтобы это было возможно, каждый раз как вы присваиваете экземпляр свойству, константе или переменной создается strong reference (сильная ссылка) с этим экземпляром. Такая связь называется “сильной”, так как она крепко держится за этот экземпляр и не позволяет ему освободится до тех пор, пока остаются сильные связи.

Приведем пример того, как работает ARC. Наш пример начнем с класса Person, который определяет константное свойство name:

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) инициализируется")
    }
    deinit {
        print("\(name) деинициализируется")
    }
}

Класс Person имеет инициализатор, который устанавливает name свойство экземпляра и выводит сообщение для отображения того, что идет инициализация. Так же класс Person имеет деинициализатор, который выводит сообщение, когда экземпляр класса освобождается.

Следующий фрагмент кода определяет три переменные класса Person?, который используется для установки нескольких ссылок к новому экземпляру Person в следующих кусках кода. Так как эти переменные опционального типа (Person?, а не Person), они автоматически инициализируются со значением nil, и не имеют никаких ссылок на экземпляр Person.

var reference1: Person?
var reference2: Person?
var reference3: Person?

Теперь вы можете создать экземпляр класса Person и присвоить его одной из этих трех переменных:

reference1 = Person(name: "John Appleseed")
// Выведет "John Appleseed инициализируется"

Обратите внимание, что сообщение «John Appleseed инициализируется» выводится во время того, как вы вызываете инициализатор класса Person. Это подтверждает тот факт, что происходила инициализация.

Так как новый экземпляр класса Person был присвоен переменной reference1, значит теперь существует сильная ссылка между reference1 и новым экземпляром класса Person. Теперь у этого экземпляра есть как минимум одна сильная ссылка, значит ARC держит под Person память и не освобождает ее.

Если вы присвоите другим переменным тот же экземпляр Person, то добавится две сильные ссылки к этому экземпляру:

reference2 = reference1
reference3 = reference1

Теперь экземпляр класса Person имеет три сильные ссылки.

Если вы сломаете две из этих трех ссылок (включая и первоначальную ссылку), присвоив nil двум переменным, то останется одна сильная ссылка, и экземпляр Person не будет освобожден:

reference1 = nil
reference2 = nil

ARC не освободит экземпляр класса Person до тех пор, пока остается последняя сильная ссылка, уничтожив которую мы укажем на то, что наш экземпляр больше не используется:

reference3 = nil
// Выведет "John Appleseed деинициализируется"

В примерах ранее ARC было в состоянии отслеживать количество ссылок к новому экземпляру Person, который вы создали, и освободить его, если этот экземпляр уже более не нужен.

Однако возможно написать код, в котором экземпляр класса никогда не будет иметь нулевое число сильных ссылок. Это может случиться, если экземпляры классов имеют сильные связи друг с другом, что не позволяет им освободиться. Это известно как цикл сильных ссылок.

Вы сами решаете, когда сделать вместо сильной (strong) ссылки слабую (weak) или бесхозную (unowned). Подробнее об этом можно прочитать в разделе Замена циклов сильных ссылок между экземплярами классов. Однако перед тем как узнать, в каких случаях разрешить сильный ссылочный цикл, давайте узнаем что вызывает его.

Ниже приведен пример того, как сильный ссылочный цикл может быть создан по ошибке. В этом примере мы определяем два класса Person и Apartment, которые создают модель блока квартир с их жителями:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) освобождается") }
}
 
class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
    deinit { print("Апартаменты \(unit) освобождаются") }
}

Каждый экземпляр Person имеет свойство name типа String и опциональное свойство apartment, которое изначально nil. Свойство apartment опционально, так как наша персона не обязательно всегда должна иметь апартаменты.

Аналогично, что каждый экземпляр Apartment имеет свойство unit типа String и опциональное свойство tenant, которое изначально nil. Свойство tenant опциональное, потому как не всегда в апартаментах кто-то живет.

Оба этих класса определяют деинициализатор, который отображает факт того, что экземпляр освободился. Это позволяет вам видеть освободились ли экземпляры этих классов как вы ожидали или нет.

Следующий фрагмент кода определяет две опциональные переменные с именами john и unit4A, которые будут назначены определенным экземплярам классов Apartment и Person. Оба значения переменных равны nil, в силу того, что они опциональны:

var john: Person?
var unit4A: Apartment?

Теперь вы можете создать свои экземпляры Person и Apartment и присвоить их этим переменным john, unit4A:

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

Вот как выглядят сильные связи после того создания и присваивания этих двух экземпляров. Переменная john имеет сильную связь с экземпляром класса Person, переменная unit4A имеет сильную связь с экземпляром Apartment:

Теперь вы можете соединить эти два экземпляра вместе, так что житель будет иметь апартаменты, а апартаменты будут иметь своих жителей. Обратите внимание, что восклицательный знак (!) используется для развертывания и допуска к экземплярам, хранимым в опциональных переменных john, unit4A, так что установить значения свойством можно в такой форме:

john!.apartment = unit4A
unit4A!.tenant = john

Вот как выглядят сильные связи после того, как мы соединили экземпляры:

К сожалению, соединяя таким образом, образуется цикл сильных ссылок между экземплярами. Экземпляр Person имеет сильную ссылку на экземпляр Apartment, экземпляр Apartment имеет сильную ссылку на экземпляр Person. Таким образом, когда вы разрушаете сильные ссылки, принадлежащие переменным john и unit4A, их количество все равно не падает до нуля, и экземпляры не освобождаются:

john = nil
unit4A = nil

Обратите внимание, что ни один деинициализатор не был вызван, когда вы присваивали nil. Цикл сильных ссылок предотвратил экземпляры Person и Apartment от освобождения, что вызывает утечку памяти в вашем приложении.

Вот как выглядят сильные ссылки после того, как вы присвоили nil переменным, john, unit4A:

Сильные взаимные ссылки остались между экземплярами Person и Apartment и не могут быть разрушены.

Swift предлагает два способа переопределить ссылку, чтобы она была не сильной, а слабой или бесхозной.

Слабые и безхозные ссылки позволяют одному экземпляру в цикле ссылок ссылаться на другой экземпляр без сильного прикрепления. Экземпляры могут ссылаться друг на друга без создания цикла сильных связей.

Используйте слабую ссылку, если другой экземпляр имеет более короткое время жизни, то есть когда другой экземпляр может быть освобожден первым. В приведенном выше примере использование Apartment уместно — у квартиры не было арендатора в какой-то момент своей жизни, и поэтому слабая ссылка является подходящим способом нарушить опорный цикл в этом случае. Используйте бесхозные ссылки, если другой экземпляр имеет одинаковое время жизни или более длительный срок службы.

Слабые (weak) ссылки

Слабые ссылки не удерживаются за экземпляр, на который они указывают, так что ARC не берет их во внимание, когда считает ссылки экземпляра. Такой подход позволяет избежать ситуации, когда ссылка становится частью цикла сильных ссылок. Вы указываете слабую ссылку ключевым словом weak перед объявлением имени свойства или переменной.

Так как слабая ссылка не сильно держит экземпляр, то этот экземпляр может быть освобожден, пока слабая ссылка все еще ссылается на него. Таким образом ARC автоматически присваивает слабой ссылке nil, когда экземпляр, на который она указывает, освобождается. И поскольку слабые ссылки должны позволять изменять их значение до нуля во время выполнения, они всегда объявляются как переменные, а не как константы опционального типа.

Вы можете проверить существование значения в слабой ссылке точно так же как и с любыми другими опциональными значениями, и вы никогда не будете иметь ссылку с недопустимым значением, например, указывающую на несуществующий экземпляр.

Заметка

Когда ARC устанавливает слабую ссылку на nil, наблюдатели свойств не вызываются.

Пример ниже идентичен тому, что мы разбирали с вами с классами Person, Apartment, но только теперь в нем есть одно существенное отличие. В этот раз свойство tenant экземпляра класса Apartment объявлено как слабая ссылка:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) деинициализируется") }
}
 
class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) деинициализируется") }
}

Создадим как и в предыдущем примере сильные ссылки от двух переменных (john, unit4A) и связи между двумя экземплярами:

var john: Person?
var unit4A: Apartment?
 
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
 
john!.apartment = unit4A
unit4A!.tenant = john

Вот как теперь выглядит соединение двух экземпляров между собой:

Экземпляр Person все еще имеет сильную ссылку на экземпляр Apartment, но Apartment имеет слабую (weak) ссылку на экземпляр Person. Это означает, что когда вы разрушаете сильную ссылку, которая содержится в переменной john, то больше сильных ссылок, указывающих на экземпляр Person, не остается:

john = nil
// Выведет "John Appleseed деинициализируется"

А так как больше сильных ссылок на экземпляр Person нет, то свойство tenant становится равным nil:

Остается только одна сильная ссылка на экземпляр Apartment из переменной unit4A. Если вы разрушите эту сильную ссылку, то их общее количество станет равным нулю:

unit4A = nil
// выводит "Апартаменты 4A деинициализируется"

А так как больше сильных ссылок нет, то и экземпляр Apartment тоже освобождается:

Заметка

Там, где используются сборщики «мусора», слабые указатели иногда используются для реализации простого механизма кеширования, потому что объекты без сильных связей сразу отпускаются, как только у памяти появляется необходимость избавится от «мусора». Однако со включенной ARC значения удаляются только тогда, когда уходит последняя сильная связь на них, делая слабые связи не подходящими для текущей задачи.

Бесхозные ссылки

Как и слабые ссылки, бесхозные ссылки так же не имеют сильной связи с экземпляром, на который они указывают. В отличии от слабых ссылок, бесхозные ссылки всегда имеют значение. Из-за этого бесхозные ссылки имеют неопциональный тип. Вы указываете на то, что ссылка бесхозная ключевым словом unowned, поставленным перед объявлением свойства или переменной.

Так как бесхозная ссылка не является опциональной, то вам не нужно и разворачивать ее каждый раз, когда вы собираетесь ее использовать. Вы можете обратиться к бесхозной ссылке напрямую. Однако ARC не может установить значение ссылки на nil, когда экземпляр, на который она ссылается, освобожден, так как переменные неопционального типа не могут иметь значения nil.

Заметка

Используйте бесхозные ссылки только в том случае, если вы абсолютно уверены в том, что ссылка всегда будет указывать на экземпляр.

Если вы попытаетесь получить доступ к бесхозной ссылке после того, как экземпляр, на который она ссылается освобожден, то выскочит runtime ошибка.

Следующий пример определяет два класса Customer и CreditCard, которые обыгрывают ситуацию клиента банка и кредитной карточки для этого клиента. Эти оба класса содержат экземпляры друг друга в качестве свойства. Такое взаимоотношение классов является потенциальной возможностью образования зацикливания сильных ссылок.

Взаимоотношения между Customer и CreditCard немного отличаются от предыдущего примера с Apartment и Person. В этом случае клиент может иметь или не иметь кредитной карты, но кредитная карта всегда имеет владельца. Чтобы это отобразить, класс Customer имеет опциональное свойство card, а CreditCard имеет неопциональное свойство customer.

Более того, новый экземпляр CreditCard может быть только создан путем передачи значения number и экземпляра customer в инициализатор класса CreditCard. Это гарантирует, что экземпляр CreditCard всегда будет иметь экземпляр customer, который будет связан с ним, когда экземпляр CreditCard будет создан.

Так как кредитная карта всегда будет иметь своего хозяина, вы определяете свойство customer как бесхозное, для избежания цикла сильных ссылок:

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) деинициализируется") }
}
 
class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Карта #\(number) деинициализируется") }
}
Заметка

Свойство number класса CreditCard определено как значение типа UInt64, а не Int, для того, чтобы оно было достаточно большим, чтобы хранить числа с 16 цифрами и на 32, и на 64 разрядных системах.

Следующий кусок кода определяет опциональную переменную типа Customer? с именем john, которая будет использоваться для хранения ссылки на определенного клиента. Эта переменная имеет начальное значение nil, в силу того, что это опциональный тип:

var john: Customer?

Вы можете создать экземпляр Customer и использовать его для инициализации и присваивания нового экземпляра CreditCard, как свойство клиентской card:

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234567890123456, customer: john!)

Вот как выглядят ссылки, после того как вы связали эти два экземпляра:

Экземпляр Customer имеет сильную ссылку на экземпляр CreditCard, а экземпляр CreditCard имеет бесхозную ссылку на экземпляр Customer.

Из-за того, что ссылка customer является бесхозной, то при разрушении сильной ссылки, которая находится в переменной john, больше сильных ссылок, указывающих на экземпляр Customer не остается:

Из-за того, что более сильных ссылок, ссылающихся на экземпляр Customer нет, то этот экземпляр освобождается. После того, как это происходит, у нас не остается больше сильных ссылок, указывающих на экземпляр CreditCard, так что он тоже освобождается:

john = nil
// Выведет "John Appleseed деинициализируется"
// Выведет "Карта #1234567890123456 деинициализируется"

Последний кусок кода показывает нам, что инициализаторы экземпляров Customer и CreditCard напечатали свои сообщения деинициализации, после того, как переменной john был присвоен nil.

Заметка

Примеры выше показывают как использовать safe unowned связи. Swift так же предоставляет unsafe unowned связи для случаев, где вам нужно отключить проверку безопасности во время исполнения, например в случае, когда вы хотите увеличить производительность. Как и со всеми небезопасными операциями, всю ответственность за проверку кода на безопасность вы берете на себя.

Чтобы показать, что вы будете использовать unsafe unowned связь, вам нужно написать unowned(unsafe). Если вы попытаетесь получить доступ к unsafe unowned ссылке после того, как экземпляр был освобожден, ваша программа попытается получить доступ к памяти, где этот объект хранился ранее, что само по себе является небезопасной операцией.

Бесхозные опциональные ссылки

Вы можете обозначить опциональные ссылки на класс как «unowned». С точки зрения модели ARC опциональная бесхозная ссылка и слабая ссылка могут быть использованы в одних и тех же контекстах. Разница лишь в том, что когда вы используете опциональную бесхозную ссылку, вы ответственны за то, чтобы она ссылалась на валидный объект или была бы установлена на nil.

Ниже приведен пример, который показывает курсы, которые в свою очередь может предложить департамент в школе:

class Department {
    var name: String
    var courses: [Course]
    init(name: String) {
        self.name = name
        self.courses = []
    }
}

class Course {
    var name: String
    unowned var department: Department
    unowned var nextCourse: Course?
    init(name: String, in department: Department) {
        self.name = name
        self.department = department
        self.nextCourse = nil
    }
}

Department держит сильную ссылку на каждый курс, который предлагает департамент. В модели владения ARC департамент владеет всеми курсами, что предлагает для прохождения. Сам тип Course имеет две бесхозные ссылки: одна на департамент, другая — на следующий курс, но сам курс не владеет ни одним из этих объектов. Каждый курс является частью департамента, так что свойство departament не является опциональным. Однако, некоторые курсы не имеют следующего курса, так что свойство nextCourse является опциональным.

Вот пример использования этих классов:

let department = Department(name: "Horticulture")

let intro = Course(name: "Survey of Plants", in: department)
let intermediate = Course(name: "Growing Common Herbs", in: department)
let advanced = Course(name: "Caring for Tropical Plants", in: department)

intro.nextCourse = intermediate
intermediate.nextCourse = advanced
department.courses = [intro, intermediate, advanced]

Код выше создает департамент и его три курса. intro, intermediate курсы имеют последующий курс nextCourse, что поддерживает бесхозную опциональную ссылку на курс, который должен пройти студент после окончания текущего курса.

Бесхозная опциональная ссылка не имеет сильной связи с экземпляром класса, который она удерживает, так что она не удерживает ARC от освобождения экземпляра класса. Она ведет себя точно так же как бесхозная ссылка в ARC за исключением того, что бесхозная опциональная ссылка может быть nil.

Как и в случае с неопциональными бесхозными ссылками, вы ответственны за то, чтобы nextCourse всегда ссылался на объект еще не освобожденный из памяти. В этом случае, например, когда вы удалите курс из department.courses, вам так же нужно удалить все ссылки, которые могут указывать на курс, который вы удалили.

Заметка

Лежащий в основе опционального значения тип — Optional, который является по своей сути просто перечислением в стандартной библиотеке Swift. Однако, опционалы являются исключением из правил, так как типы значений не могут быть маркированы как unowned.

Опционал, который является оберткой для класса не использует подсчет ссылок, так что вам не нужно поддерживать сильную ссылку на опционал.

Бесхозные ссылки и неявно извлеченные опциональные свойства

Примеры, приведенные выше, для слабых и бесхозных ссылок, описывают два из самых распространенных сценариев, где существует необходимость разрушения цикла сильных ссылок.

Пример с Person, Apartment показывает ситуацию, где два свойства, оба из которых могут иметь значение nil, имеют потенциальную возможность образования цикла сильных связей. Этот случай лучше всего решается с помощью слабой связи.

Пример с Customer, CreditCard демонстрирует ситуацию, где одному свойству разрешено иметь значение nil, другому — нет. Однако здесь так же существует потенциальная возможность образования цикла сильных ссылок. Такой случай лучше всего разрешается с помощью бесхозных ссылок.

Однако есть и третий вариант, в котором оба свойства должны всегда иметь значение, и ни одному из них нельзя иметь nil, после завершения инициализации. В этом случае лучше всего скомбинировать бесхозное свойство одного класса с неявно извлеченным опциональным свойством другого класса.

Это позволяет получить доступ к обоим свойствам напрямую (без опционального извлечения) после завершения инициализации, так же позволяя избегать взаимных сильных ссылок. В этой секции вы узнаете как создать такие взаимоотношения:

Пример внизу определяет два класса Country, City, каждый из которых хранит экземпляр другого класса в качестве свойства. В такой модели каждая страна должна иметь столицу, а каждый город, должен иметь страну. Для того, чтобы это отобразить, класс Country имеет свойство capitalCity, а класс City имеет свойство country:

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}
 
class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

Для создания такой внутренней зависимости между этими двумя классами, инициализатор City берет экземпляр Country и сохраняет его в свойство country.

Инициализатор City, вызывается из инициализатора Country. Однако инициализатор Country не может передавать self в инициализатор City до тех пор, пока новый экземпляр Country не будет полностью инициализирован, что описано в разделе “Двухфазная инициализация”.

Объединив все с этим требованием, вы объявляете свойство capitalCity класса Country как неявно извлеченное опциональное свойство, отображаемое восклицательным знаком в конце аннотации типа (City!). Это значит, что свойство capitalCity имеет начальное значение равное nil, как и в случае с другими опционалами, но к которому можно обратиться без предварительного развертывания значения, что описано в главе Неявно извлеченные опционалы.

Так как свойство capitalCity имеет значение по умолчанию nil, то новый экземпляр Country считается полностью инициализированным, как только экземпляр Country устанавливает свойство name с помощью своего инициализатора. Это значит, что инициализатор Country может ссылаться на неявное свойство self и раздавать его, как только свойство name получит корректное значение. Инициализатор Country может таким образом передать self в качестве одного из параметров для инициализатора City, когда инициализатор Country устанавливает свое собственное свойство capitalCity.

Из всего этого можно сделать вывод, что вы можете создать экземпляры Country и City единственным выражением, без создания цикла сильных ссылок друг на друга. Получить значение свойства capitalCity можно напрямую без использования восклицательного знака для извлечения опционального значения:

var country = Country(name: "Россия", capitalName: "Москва")
print("Столицей страны \(country.name) является \(country.capitalCity.name)")
// Выведет "Столицей страны Россия является Москва"

В примере выше использование неявно извлеченного опционала означает, что все требования двухфазного инициализатора класса выполнены. Свойство capitalCity может быть использовано как неопциональное значение, после того как инициализация закончена, все так же избегая цикла сильных ссылок.

Как вы видели ранее, циклы сильных ссылок могут быть созданы двумя экземплярами классов, когда они поддерживают друг на друга сильные ссылки. Вы так же видели как использовать слабые (weak) или бесхозные (unowned) ссылки для того, чтобы заменить ими сильные (strong).

Сильные ссылки так же могут образовываться, когда вы присваиваете замыкание свойству экземпляра класса, и тело замыкания захватывает экземпляр. Этот захват может случиться из-за того, что тело замыкания получает доступ к свойству экземпляра, например self.someProperty, или из-за того, что замыкание вызывает метод типа self.someMethod(). В обоих случаях эти доступы и вызывают тот самый “захват” self, при этом создавая цикл сильных ссылок.

Этот цикл возникает из-за того, что замыкания, как и классы, являются ссылочными типами. Когда вы присваиваете замыкание свойству, вы присваиваете ссылку на это замыкание. В общем, проблема та же, что и ранее: две сильные ссылки, которые не дают друг другу освободиться. Однако в отличии от предыдущих примеров здесь не два экземпляра классов, а замыкание и один экземпляр класса, которые поддерживают существование друг друга.

Swift предлагает элегантное решение этой проблемы, которые известно как список захвата замыкания (closure capture list). Однако до того, как вы узнаете, как разрушить такой цикл с помощью этого решения, давайте разберемся, что этот цикл может вызвать.

Пример ниже отображает, как вы можете создать цикл сильных ссылок, когда мы используем замыкание, которое ссылается на self. В этом примере определяем класс HTMLElement, который представляет модель простого элемента внутри HTML документа:

class HTMLElement {
 
    let name: String
    let text: String?
 
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
 
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
 
    deinit {
        print("\(name) деинициализируется")
    }
}

Класс HTMLElement определяет свойство name, которое отображает имя элемента, например «p» тег для отображения параграфа или “br” для тэга перехода на следующую строку. Класс HTMLElement также определяет опциональное свойство text, которому может быть присвоена строка, которая отображает текст, который может быть внутри HTML элемента.

В дополнение к этим двум простым свойствам класс HTMLElement определяет ленивое свойство asHTML. Это свойство ссылается на замыкание, которое комбинирует name, text во фрагмент HTML строки. Свойство asHTML имеет тип () -> String, или другими словами функция, которая не принимает параметров и возвращает строку.

По умолчанию свойству asHTML присвоено замыкание, которое возвращает строку, отображающую тэг HTML. Этот тэг содержит опциональный text, если таковой есть или не содержит его, если text, соответственно, отсутствует. Для элемента параграфа замыкание вернет “<p>some text</p>” или просто “<p />”, в зависимости от того, имеет ли свойство text какое либо значение или nil.

Свойство asHTML называется и используется несколько схоже с методом экземпляра. Однако из-за того что asHTML является свойством-замыканием, а не методом экземпляра, то вы можете заменить значение по умолчанию свойства asHTML на пользовательское замыкание, если вы хотите сменить отображение конкретного HTML элемента.

Например, свойству asHTML может быть присвоено замыкание, которое имеет дефолтный текст на случай если свойство text равно nil, для предотвращения отображения пустого HTML тега:

let heading = HTMLElement(name: "h2")
let defaultText = "some default text"
heading.asHTML = {
   return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// Выведет "<h2>some default text</h2>"
Заметка

Свойство asHTML объявлено как ленивое свойство, потому что оно нам нужно только тогда, когда элемент должен быть отображен в виде строкового значения для какого-либо HTML элемента выходного значения. Факт того, что свойство asHTML является ленивым, означает, что вы можете ссылаться на self внутри дефолтного замыкания, потому что обращение к ленивому свойству невозможно до тех пор, пока инициализация полностью не закончится и не будет известно, что self уже существует.

Класс HTMLElement предоставляет единственный инициализатор, который принимает аргумент name и (если хочется) аргумент text для инициализации нового элемента. Класс также определяет деинициализатор, который выводит сообщение, для отображения момента когда экземпляр HTMLElement освобождается.

Вот как вы используете класс HTMLElement для создания и вывода нового экземпляра:

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Выведет "<p>hello, world</p>"
Заметка

Переменная paragraph определена как опциональный HTMLElement, так что он может быть и nil для демонстрации цикла сильных ссылок.

К сожалению класс HTMLElement, который описан выше, создает цикл сильных ссылок между экземпляром HTMLElement и замыканием, использованным для его исходного значения asHTML. Вот как выглядит этот цикл:

Свойство asHTML экземпляра держит сильную ссылку на его замыкание. Однако из-за того, что замыкание ссылается на self внутри своего тела (self.name, self.text), оно захватывает self, что означает, что замыкание держит сильную ссылку обратно на экземпляр HTMLElement. Между ними двумя образуется цикл сильных ссылок. (Для более подробной информации по захвату значений в замыканиях читайте соответствующий раздел Захват значений.)

Заметка

Даже несмотря на то, что замыкание ссылается на self несколько раз, оно захватывает лишь одну сильную ссылку на экземпляр HTMLElement.

Если вы установите значение paragraph на nil, чем разрушите сильную ссылку на экземпляр HTMLElement, то ни экземпляр HTMLElement, ни его замыкание не будут освобождены из-за цикла сильных ссылок:

paragraph = nil

Обратите внимание, что сообщение деинициализатора HTMLElement не выводится на экран, что и есть факт того, что этот экземпляр не освобожден.

Заменить цикл сильных ссылок между замыканием и экземпляром класса можно путем определения списка захвата в качестве части определения замыкания. Список захвата определяет правила, которые нужно использовать при захвате одного или более ссылочного типа в теле замыкания. Что же касательно циклов сильных связей между двумя экземплярами классов, то вы объявляете каждую захваченную ссылку как слабую или бесхозную (weak или unowned), вместо того, чтобы оставлять ее сильной (strong). Правильный выбор между слабой или бесхозной ссылками зависит от взаимоотношений между различными частями вашего кода.

Заметка

Swift требует от вас написания self.someProperty или self.someMethod() (вместо someProperty, someMethod()), каждый раз, когда вы обращаетесь к члену свойства self внутри замыкания. Это помогает вам не забыть, что возможен случай случайного захвата self.

Определение списка захвата

Каждый элемент в списке захвата является парой ключевого слова weak или unowned и ссылки на экземпляр класса (например, self) или переменная инициализированная с помощью какого-либо значения (например, delegate = self.delegate!). Эти пары вписываются в квадратные скобки и разделяются между собой запятыми.

Размещайте список захвата перед списком параметров замыкания и его возвращаемым типом:

lazy var someClosure: (Int, String) -> String = {
      [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
   // тело замыкания
}

Если у замыкания нет списка параметров или возвращаемого типа, так как они могут быть выведены из контекста, то разместите список захвата в самом начале замыкания, перед словом in:

lazy var someClosure: () -> String = {
      [unowned self, weak delegate = self.delegate!] in
    // тело замыкания
}

Слабые (weak) или бесхозные (unowned) ссылки

Определите список захвата в замыкании как бесхозную ссылку в том случае, когда замыкание и экземпляр, который оно захватывает, всегда будут ссылаться друг на друга, тогда они всегда будут освобождаться в одно и то же время.

Наоборот, определите список захвата в качестве слабой ссылки, когда захваченная ссылка может стать nil в какой-либо момент в будущем. Слабые ссылки всегда опционального типа и автоматически становятся nil, когда экземпляр, на который они ссылаются, освобождается. Это позволяет вам проверять их существование внутри тела замыкания.

Заметка

Если захваченная ссылка никогда не будет nil, то она должна быть всегда захвачена как unowned ссылка, а не weak ссылка.

Бесхозная ссылка является подходящим методом захвата для предотвращения существования цикла сильных ссылок в нашем примере с HTMLElement. Вот как можно записать класс HTMLElement, чтобы избежать цикла:

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) освобождается")
    }
}

Эта реализация HTMLElement идентична предыдущей реализации, кроме дополнения списка захвата внутри замыкания asHTML. В этом случае список захвата [unowned self], который означает: “захватить self как unowned ссылку, вместо strong”.

Вы можете создать и вывести экземпляр HTMLElement как и раньше:

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Выведет "<p>hello, world</p>"

Вот как теперь выглядят связи:

В этот раз захват self является бесхозной ссылкой и уже не поддерживает сильной связи с экземпляром HTMLElement, которого он захватил. Если вы установите сильную ссылку от переменной paragraph на значение nil, то экземпляр HTMLElement будет освобожден, что можно определить по выводимому сообщению в примере ниже:

paragraph = nil
// Выведет "p освобождается"

Более подробную информацию можно найти в разделе «Список захвата».

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Что такое » я » используется в Swift?

Обновление: 24 Ноября 2015 Года!—24—>

Да это то же самое, что this на Java и self на С, но с Свифт, self требуется только при вызове свойства или метода из закрытия или для различения имен свойств внутри кода (например инициализаторы). Таким образом, вы можете безопасно использовать почти все компоненты класса, не используя self, если вы не звоните из закрытие.

» свойство self каждый экземпляр типа имеет неявное свойство называется self, что в точности эквивалентно самому экземпляру. Вы используйте self свойство для ссылки на текущий экземпляр в его собственные методы экземпляра.

на increment() метод в приведенном выше примере можно было бы написать вот так:

func increment() {
    self.count++
}

на практике, вам не надо писать self в коде очень часто. Если вы не пишите явно self, Swift предполагает, что вы ссылка на свойство или метод текущего экземпляра всякий раз, когда вы используйте известное свойство или имя метода в методе. Это предположение демонстрирует использование count (а не self.count) внутри трех методов экземпляра для Counter.

основное исключение из этого правила возникает, когда имя параметра для метод экземпляра имеет то же имя, что и свойство этого экземпляра. В эта ситуация, имя параметра имеет приоритет и становится необходимо обратиться к собственности более квалифицированным способом. Вы используете the self свойство для различения имени параметра и имя свойства.

здесь self различать между параметром метод, называемый x и свойство экземпляра, которое также называется x

Выдержка Из: Apple Inc. «Язык Программирования Swift (Предварительная Версия Swift 2).»


это как Рэй Вендерлих рекомендует использовать self в Swift для их руководства:

использование Self

для краткости избегайте использования self, поскольку Swift не требует доступа к свойствам объекта или вызова его методов.

используйте self при необходимости для различения имен свойств и аргументов в инициализаторах и при ссылке на свойства в выражениях закрытия (как требуется составитель):

class BoardLocation {
  let row: Int, column: Int

  init(row: Int, column: Int) {
    self.row = row
    self.column = column

    let closure = {
      println(self.row)
    }
  }
}

и GitHub рекомендации по self для их применения:

только явно ссылаться на self при необходимости

при доступе к свойствам или методам в self, оставить ссылку на self подразумевается по умолчанию:

private class History {
    var events: [Event]

    func rewrite() {
        events = []
    }
}

включить ключевое слово explicit только тогда, когда это требуется языком-например, в закрытии или когда конфликт имен параметров:

extension History {
    init(events: [Event]) {
        self.events = events
    }

    var whenVictorious: () -> () {
        return {
            self.rewrite()
        }
    }
}

обоснование: это делает семантику захвата себя более заметной в закрытиях и избегает многословия в другом месте.

ios — для чего в Swift используется «self»?

Переполнение стека
  1. Товары
  2. Клиенты
  3. Случаи использования
  1. Переполнение стека Общественные вопросы и ответы
  2. Команды Частные вопросы и ответы для вашей команды
  3. предприятие Частные вопросы и ответы для вашего предприятия
  4. работы Программирование и связанные с ним возможности технической карьеры
  5. Талант Нанять технических талантов
  6. реклама Обратитесь к разработчикам по всему миру

Загрузка…

.

Что такое .self, .Type и .Protocol? Понимание метатипов Swift

29 октября 2018 г.

Ah метатипы. Это еще один из вещей, которые я использую каждый день, но не мог объяснить в интервью, зависела ли от этого моя жизнь .

Метатипы очень полезны в Swift, и вы наверняка использовали его несколько раз. К сожалению, они выглядят довольно странно в коде, что может вызвать некоторую путаницу при попытке понять, что они из себя представляют.

Я, например, знаю, как эти странные суффиксы могут сбить вас с толку, но не волнуйтесь, они на самом деле довольно просты, если вы узнаете различия между каждым из них. Но прежде чем углубиться в это, давайте сделаем шаг назад:

Что такое метатип?

Если вы посмотрите документацию Apple, вы увидите, что метатип определяется как тип типа . Подождите, разве String не тип? Что может быть типом String , который уже является типом? SuperString ??

Теоретически это звучит странно, но это потому, что мы привыкли к синтаксису Swift, который специально скрывает от нас некоторые из этих деталей, чтобы сделать язык простым в использовании.Чтобы понять метатипы, попробуйте перестать видеть вещи как типы и вместо этого начните видеть их больше как экземпляры , и , классы (ключевое слово использования, а не объект!).

Учитывая следующий фрагмент: Как бы вы определили SwiftRocks () и : SwiftRocks ?

  struct SwiftRocks {
  static let author = "Bruno Rocha" 
  func postArticle (name: String) {} 
 } 
 
  пусть блог: SwiftRocks = SwiftRocks () 
 

Вы можете сказать, что SwiftRocks () — это объект, а SwiftRocks — его тип, но вместо этого попробуйте увидеть SwiftRocks () как экземпляр и : сам SwiftRocks как представление типа экземпляра .В конце концов, вы можете вызвать метод экземпляра postArticle () из blog , но вы не можете получить доступ к свойству класса author .

Теперь, как мы можем получить доступ к автору ? Наиболее распространенный способ — использовать SwiftRocks.author , который напрямую вернет вам String , но я попрошу вас забыть об этом на мгновение. Есть другой способ?

Я знаю, что Бруно! Можно позвонить типа (из: блог). Автор !

Ага! Это также правильно, поскольку тип (из) преобразует что-то в объект во что-то, что позволяет вам получить доступ ко всем свойствам класса.Но пробовали ли вы когда-нибудь набрать просто типа (of: blog) , чтобы посмотреть, что произойдет?

  let something = type (of: blog) // SwiftRocks.Type 
 

Один из странных суффиксов! Тип SwiftRocks — это SwiftRocks.Type , что означает, что SwiftRocks.Type — это метатип SwiftRocks .

Используя завершение кода Xcode для свойства something , вы увидите, что ссылка на метатип позволяет использовать все свойства и методы класса этого типа, включая init () :

  пусть автор: String = something.автор 
  let экземпляр: SwiftRocks = something.init () 
 

Это очень полезно, когда вам нужен метод для создания экземпляров объектов (например, как работает повторное использование ячеек UITableView и Decodable ), доступ к свойствам класса или просто выполнение действий в целом на основе типа объекта. Сделать это обычным способом легко, так как вы можете передавать метатипы в качестве аргументов:

  func createWidget  (ofType: T.Type) -> T {
  let widget = T.init () 
  myWidgets.insert (виджет) 
  виджет возврата 
 } 
 

Метатипы также могут использоваться в проверках равенства, что я лично считаю удобным при проектировании фабрик:

  func create  (blogType: T.Type) -> T {
 Блог переключателя  Тип {
 Корпус  - это TutorialBlogPost.Type: 
  вернуть blogType.init (тема: currentSubject) 
  case is ArticleBlogPost.Type: 
  вернуть blogType.init (тема: getLatestFeatures (). random ()) 
 Корпус  - это TipBlogPost. Тип: 
  вернуть blogType.init (subject: getKnowledge (). Random ()) 
  по умолчанию: 
  fatalError («Неизвестный вид блога!») 
 } 
 } 
 

Вы можете определить метатип любого типа, включая классы, структуры, перечисления и протоколы, как имя этого типа, за которым следует . Тип . Короче говоря, в то время как SwiftRocks относится к типу экземпляра (который позволяет использовать только свойства экземпляра), метатип SwiftRocks.Тип относится к типу самого класса, который позволяет использовать свойства класса SwiftRocks . «Тип типа» теперь имеет больше смысла, не так ли?

Тип

(из 🙂 Динамические метатипы против .self статических метатипов

Итак, type (of) возвращает метатип объекта, но что произойдет, если у меня нет объекта? Xcode выдает ошибку компилятора, если я пытаюсь вызвать create (blogType: TutorialBlogPost.Type) !

Короче говоря, вы не можете этого сделать по той же причине, по которой вы не можете вызвать myArray.append (String) : String — это имя типа, а не значение! Чтобы получить метатип в качестве значения, вам нужно ввести имя этого типа, за которым следует .self .

Если это звучит сбивающе с толку, вы можете увидеть это так: точно так же, как String — это тип, а «Hello World», — значение экземпляра, String.Type — это тип, а String.self — это значение метатипа.

  let intMetatype: Int.Тип = Int.self 
  // 
  let widget = createWidget (ofType: MyWidget.self) 
  tableView.register (MyTableViewCell.self, forReuseIdentifier: "myCell") 
 

.self — это то, что Apple называет статическим метатипом — причудливым словом, обозначающим тип объекта во время компиляции. Вы используете это чаще, чем ожидаете — помните, когда я сказал вам игнорировать SwiftRocks.author ? Причина заключалась в том, что это то же самое, что писать SwiftRocks.сам. автор .

Статические метатипы присутствуют в Swift повсюду, и вы неявно используете их каждый раз, когда напрямую обращаетесь к свойству класса типа. Вам может показаться интересным, что тип AnyClass , используемый регистром таблицы (cellClass 🙂 , является просто псевдонимом для AnyObject. Тип :

  публичный псевдоним AnyClass = AnyObject.Type 
 

С другой стороны, тип (из) вернет динамический метатип , который является метатипом реального времени выполнения объекта.

  let myNum: Any = 1 // Тип времени компиляции myNum - Any, но тип времени выполнения - Int. 
  type (of: myNum) // Int.type 
 

Фактическое содержимое типа (of 🙂 и его возвращаемого типа Metatype — это магия компилятора (тема для другой статьи), но вот сигнатура метода:

  func type  (of value: T) -> Metatype {} 
 

Короче говоря, если подкласс объекта имеет значение, вы должны использовать тип (из) , чтобы иметь доступ к метатипу этого подкласса.В противном случае вы можете просто получить доступ к статическому метатипу напрямую через (имя желаемого типа) .self .

Интересным свойством метатипов является то, что они рекурсивны, что означает, что у вас могут быть мета-метатипы, такие как SwiftRocks.Type.Type , но, к счастью для нашего здравомыслия, вы мало что можете сделать с ними, поскольку в настоящее время невозможно писать расширения. для метатипов.

Метатипы протокола

Хотя все сказанное выше относится к протоколам, у них есть важное отличие.Следующий код не скомпилируется:

 Протокол  MyProtocol {} 
  let metatype: MyProtocol.Type = MyProtocol.self // Невозможно преобразовать значение ... 
 

Причина в том, что в контексте протоколов MyProtocol.Type не относится к собственному метатипу протокола, но метатип любого типа — это , наследующий этого протокола. Apple называет это экзистенциальным метатипом .

 Протокол  MyProtocol {} 
  struct MyType: MyProtocol {} 
  let метатип: MyProtocol.Type = MyType.self // Теперь работает! 
 

В этом случае метатип имеет доступ только к свойствам и методам класса MyProtocol , но будут вызываться реализации MyType . Чтобы получить конкретный метатип самого типа протокола, вы можете использовать суффикс .Protocol . Это в основном то же самое, что использовать . Введите для других типов.

  пусть protMetatype: MyProtocol.Protocol = MyProtocol.self 
 

Поскольку мы говорим о самом ненаследуемом протоколе, с protMetatype вы ничего не можете сделать, кроме простых проверок равенства, таких как protMetatype is MyProtocol.Протокол . Если бы мне пришлось гадать, я бы сказал, что цель конкретного метатипа протокола больше заключается в том, чтобы заставить протоколы работать на стороне компилятора, поэтому, вероятно, мы никогда не видим этого в проектах iOS.

Заключение: больше применений для метатипов

Представление типа через метатип может помочь вам построить очень интеллектуальные и типобезопасные универсальные системы. Вот пример того, как мы используем их в обработчиках глубоких ссылок, чтобы избежать необходимости напрямую работать со строками:

 Открытый протокол  DeepLinkHandler: класс {
  var handledDeepLinks: [DeepLink.Тип] {get} 
  func canHandle (deepLink: DeepLink) -> Bool 
  Функциональная ручка (deepLink: DeepLink) 
 } 
 
  публичное расширение DeepLinkHandler {
  func canHandle (deepLink: DeepLink) -> Bool {
  пусть deepLinkType = тип (of: deepLink) 
  // К сожалению, метатипы нельзя добавлять в наборы, поскольку они не соответствуют Hashable! 
  return handledDeepLinks.contains {$ 0.identifier == deepLinkType.идентификатор} 
 } 
 } 
 
  // 
 
  класс MyClass: DeepLinkHandler {
  var handledDeepLinks: [DeepLinks.Type] {
  return [HomeDeepLink.self, PurchaseDeepLink.self] 
 } 
 
 Ручка функции  (deepLink: DeepLink) {
  переключатель deepLink {
  case let deepLink as HomeDeepLink: 
  // 
  case let deepLink as PurchaseDeepLink: 
  // 
  по умолчанию: 
  // 
 } 
 } 
 } 
 

И в качестве более свежего примера, вот как мы используем метатипы для представления и извлечения информации об A / B-тестах (так называемых «экспериментах»):

 , если ExperimentManager.получить (HomeExperiment.self) ?. showNewHomeScreen == true {
  // Показать новостройку 
 } else {
  // Показать старый дом 
 } 
 
  // Менеджер эксперимента 
 
  общедоступная статическая функция get  (_ Experiment: T.Type) -> T? {
  вернуть shared.experimentDictionary [эксперимент.идентификатор] как? Т 
 } 
 
  общедоступная статическая функция активировать (_ эксперимент: эксперимент) {
  поделился.экспериментальный словарь [тип (из: эксперимента). идентификатор] = эксперимент 
 } 
 

Следите за мной в моем Twitter — @rockbruno_ и дайте мне знать о любых предложениях и исправлениях, которыми вы хотите поделиться.

Ссылки и полезные материалы

Документы Apple: типы
Apple Docs: тип (из 🙂
,

swift4 — Swift — `self` в закрытии инициализации переменной

Переполнение стека
  1. Товары
  2. Клиенты
  3. Случаи использования
  1. Переполнение стека Общественные вопросы и ответы
  2. Команды Частные вопросы и ответы для вашей команды
  3. предприятие Частные вопросы и ответы для вашего предприятия
  4. работы Программирование и связанные с ним возможности технической карьеры
  5. Талант Нанять технических талантов
  6. реклама Обратитесь к разработчикам по всему миру
,

Захват себя с помощью Swift 4.2

Блоки

Swift становятся все более распространенным способом обеспечения поведения типа обратного вызова для асинхронных функций.

Swift 4.2 недавно представил интересное изменение, но сначала давайте сделаем шаг назад и рассмотрим, что означает «захват».

блоков Swift выглядят так:

  doSomething (затем: {
  // делаем что-нибудь еще
})
  

Из-за механизма управления памятью, если нам нужен доступ к локальной переменной в этом блоке, она должна быть захвачена .

  var dog = Собака ()
doSomething (затем: {
  dog.bark () // собака должна быть схвачена, чтобы она прожила достаточно долго
})
  

Компилятор Swift автоматически сделает это за вас. dog будет захвачен (таким образом увеличится его счетчик удержания), и вы можете гарантировать, что даже если экземпляр dog выпадает из текущей области видимости, блок все еще сохраняет его.

Иногда такое поведение нежелательно. Наиболее частая причина — это тот случай, когда вам нужно вызвать метод на self внутри блока.Блок сохранит сам , и вы только что создали двунаправленный граф зависимостей, также известный как цикл сохранения .

retain-cycle

Это плохо, потому что это означает, что self не может быть освобожден, пока живет блок. Часто блоки хранятся как свойства в экземпляре или как наблюдатели для KVO или уведомлений, и их время жизни привязано к self .

Если вы это сделали, поздравляю … у вас утечка памяти.

Избегание циклов удержания

Чтобы избежать этого, вы можете предоставить объявление привязки захвата к определению блока, чтобы повлиять на то, должна ли переменная строго удерживаться или нет:

  пусть собака = Собака ()
doSomething (затем: {[слабая собака] в
    // собака теперь Необязательная 
    собака? .bark ()
)
  

(Таким образом, если мы строго не укажем dog в свойстве или каким-либо другим способом, он выпадет из области видимости, а ссылка dog внутри блока будет равна нулю.)

Это также работает для self :

  doSomething (затем: {[слабое я] в
    самостоятельно? .doSomethingElse ()
)
  

Объявляя захват как weak , вы сообщаете блоку обнулить эту ссылку, если оригинал выпущен.

Сильный-слабый танец

Если вам нужно гарантировать выполнение элементов в блоке, вы можете создать новую сильную ссылку внутри блока . Например, переменная стала равной нулю во время выполнения блока, что предотвратило выполнение некоторых действий.

  doSomething (затем: {[слабое я] в
    охранник let strongSelf = self {else return}
    strongSelf.doSomethingElse ()
)
  

В приведенном выше примере мы гарантируем, что наша ссылка strongSelf существует для области действия блока.

Этот метод довольно распространен в проектах Swift, но всегда казался немного странным.

Примечание: многие указали, что вы тоже можете это сделать:

  охранник let `self` = self else {return}
  

, однако, это было отмечено как ошибка компилятора, и вам, вероятно, следует избегать ее использования, поскольку она может измениться в будущем выпуске.

Вернуться к Swift 4.2

Хорошо, вернемся к заголовку этого сообщения.

Swift 4.2 недавно принял предложение добавить это в язык:

  охранник let self = self else {return}
  

Это позволяет избежать некоторых странных формулировок, но это также кажется мне немного странным. Я написал об этом в Твиттере:

Эта строка полностью состоит из быстрых ключевых слов … кажется странным. pic.twitter.com/DMkskdfzMu

— Бен Шейрман 📎 (@subdigital) 13 сентября 2018 г.

Я также думаю, что строки, подобные приведенным выше, будут невероятно озадаченными для кого-то, кто новичок в этом языке.

Тем не менее, я думаю, что синтаксис намного чище, и я, вероятно, начну использовать его в своих проектах.

Что вы думаете об этом изменении?

,

Leave a comment