Как правильно объявить переменную в Swift?

Я нашел довольно интересными эти разные способы объявления переменной в Swift:

// METHOD 1
var dogName: String = "Charlie"

// METHOD 2
var dogName: String {
    return "Charlie"
}

// METHOD 3
let dogName = {
    return "Charlie"
}

// METHOD 4
var dogName: String = {
    return "Charlie"
}()

Очевидно, что метод 3 объявляет let, и мы знаем разницу; но почему Swift разрешает метод 4?

В чем разница между этими четырьмя методами?

В частности, я сильно запутался между методом 2 и 4. Кроме того, почему метод 3 теряет последние скобки по сравнению с методом 4?


person Massimo Polimeni    schedule 12.07.2017    source источник


Ответы (4)


Метод 1 — это стандартное объявление переменной для строки. У него есть сеттер и геттер

var dogName: String = "Charlie"

print(dogName) -> "Charlie"
dogName = "Rex" // Valid

Метод 2 является вычисляемым свойством типа String и доступен только для чтения.

var dogName: String {
    return "Charlie"
}

print(dogName) -> "Charlie"
dogName = "Rex" // Invalid as property is read-only

Метод 3 — это свойство типа () -> String, доступное только для чтения, поэтому в основном это лямбда-функция.

let dogName = {
    return "Charlie"
}

print(dogName) -> "(Function)"
print(dogName()) -> "Charlie"
dogName = "Rex" // Invalid as property is read-only

Метод 4 — это замыкание, которое будет выполняться при инициализации содержащего его объекта. Поскольку это var, вы можете заменить его другим значением

var dogName: String = {
    return "Charlie"
}()

print(dogName) -> "Charlie"
dogName = "Rex" // Valid

При этом, поскольку метод 4 является закрытием, вы можете выполнять в нем другие команды. Вот пример, где вы можете использовать эту конструкцию для инициализации UILabel:

var dogNameLabel: UILabel = {
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
    label.text = "Charlie"
    return label
}()
person Thomas    schedule 12.07.2017
comment
Ну, вы не совсем правы насчет метода 4. Закрытие будет выполнено при инициализации объекта, где свойство объявлено, а не только при доступе к нему. Таким образом, это похоже на значение свойства по умолчанию, назначенное из результата этого закрытия. Но вы правы, вы можете позже заменить его на что-нибудь другое. - person Oleg Danu; 12.07.2017
comment
@OlegDanu Вы совершенно правы, я соответственно обновил ответ. Спасибо! - person Thomas; 12.07.2017
comment
Кстати, вычисляемые свойства не всегда доступны только для чтения. Вы можете определить get и set для вычисляемых свойств, чтобы сделать их доступными для чтения и записи. Однако при объявлении, как показано выше, они неявно имеют только геттер, что фактически делает их доступными только для чтения. См. developer.apple.com/library/content/documentation/Swift/ - person Itai Ferber; 12.07.2017
comment
@ItaiFerber Да, вы правы, вычисляемое свойство не всегда доступно только для чтения, спасибо, что указали на это. Я удалил ошибочную часть описания Method2. - person Thomas; 12.07.2017
comment
@Султан Это неправда. Метод 3 — это лямбда, которую вам нужно вызвать, чтобы получить значение. Вы можете подтвердить это с помощью let x = { return 5 }; print(type(of: x)) - person Itai Ferber; 12.07.2017

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

Это класс Foo

class Foo {

    var className = "Foo"

    var dogName1 : String { return "Charlie " + className }

    let dogName2 = {
        return "My name is Charlie"
    }

    var dogName3 : String = {
        var string = "My"
        string += " name"
        string += " is"
        string += " Charlie"
        print(string)
        return string
    }()

}

Теперь давайте создадим экземпляр

let foo = Foo()
  • Метод 1 — это сохраненное свойство className с установщиком и получателем.

    let name = foo.className
    foo.className = "Bar"
    print(foo.className) // "Bar"
    
  • Метод 2 – это вычисляемое свойство dogName1 только с геттером. Его можно использовать для динамического вычисления значений.

    print(foo.dogName1) // "Charlie Bar"
    
  • Способ 3 — это замыкание dogName2 типа () -> String. Его можно присвоить переменной, а затем выполнить

    let dogName = foo.dogName2 // assigns the closure but does not return the string.
    print(dogName()) // "My name is Charlie"
    
  • Метод 4 — это переменная dogName3, которая немедленно выполняет свое закрытие один раз при инициализации экземпляра Foo() (что подтверждается строкой print)

    print(foo.dogName3) // "My name is Charlie" but does not execute the closure and print `string` again 
    
  • Существует даже метод 5: если вы объявите dogName3 как lazy

    lazy var dogName3 : String = {
    

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

person vadian    schedule 12.07.2017

Я провел быстрый тест, переименовав все как dogName1, dogName2, dogName3 и dogName4. Затем я добавил код, чтобы попытаться изменить каждое имя на «Снупи».

№ 2 и № 3 не собирались, так как компилятор знал, что они оба доступны только для чтения. (# 2, несмотря на то, что он объявлен как var, настроен на то, чтобы всегда возвращать «Чарли».

Закомментировав эти две строки, я установил две точки останова: после инициализации и одну после попытки обновления.

Наконец, я попытался сделать print каждого из них.

Точка останова №1: #1 и #4 имеют значение "Чарли", #2 нет (поскольку он не инициализирован), а #3 отображается как инициализированный, но без значения (поскольку он еще не вызывался И да, () в конце инициализирует что-то в памяти.

Точка останова № 2: № 1 и № 4 были обновлены до «Snoopy».

Результаты print: № 1 и № 4 — «Снупи», № 2 — «Чарли» и № 3 — «(Функция)».

Вывод. Нет никакой разницы между №1 и №4. Каждый из них объявлен var и по умолчанию имеет значение «Чарли». # 2, доступен только для чтения из-за let и всегда будет возвращать «Чарли». №3? Он создает экземпляр и не строится, если вы пытаетесь его изменить, но я не знаю, как его использовать.

Я обновлю этот ответ, если у кого-то есть что добавить о № 3.

person dfd    schedule 12.07.2017

Способ 1 вполне очевиден.

В методе 2 вы определили геттер для данной переменной.

Тип dogName в методе 3 — это () -> String, а не String. То, что вы инициализировали, является именованной лямбдой.

В методе 4 вы инициализируете переменную анонимной (безымянной) лямбда-функцией, которая возвращает строку.

Разница между 3 и 4 заключается в том, что в 4-м вы вызываете эту функцию с помощью (), поэтому вы получаете String, а в предыдущем вы этого не делаете, поэтому это функция.

person Maroš Beťko    schedule 12.07.2017