Вычисляемое свойство только для чтения против функции в Swift


98

Во введении в сеанс Swift WWDC descriptionдемонстрируется свойство только для чтения :

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheels"
    }
}

let vehicle = Vehicle()
println(vehicle.description)

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

class Vehicle {
    var numberOfWheels = 0
    func description() -> String {
        return "\(numberOfWheels) wheels"
    }
}

let vehicle = Vehicle()
println(vehicle.description())

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

  • Семантика - в этом примере имеет смысл descriptionбыть свойством класса, а не действием, которое он выполняет.
  • Краткость / Четкость - предотвращает необходимость использования пустых скобок при получении значения.

Очевидно, что приведенный выше пример слишком прост, но есть ли другие веские причины выбрать один из них? Например, есть ли какие-то особенности функций или свойств, которые помогут вам решить, какие из них использовать?


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


1
Посмотрите сеанс 204 - «Когда не использовать @property» В нем есть несколько советов
Константин Коваль

4
подождите, вы можете сделать свойство только для чтения и пропустить get {}? Я этого не знал, спасибо!
Дэн Розенстарк

WWDC14 Session 204 можно найти здесь (видео и слайды), developer.apple.com/videos/play/wwdc2014/204
user3207158 02

Ответы:


53

Мне кажется , что это в основном вопрос стиля: я сильно предпочитаю использовать свойства только для этого: свойства; означает простые значения, которые вы можете получить и / или установить. Я использую функции (или методы), когда выполняется реальная работа. Возможно, что-то нужно вычислить или прочитать с диска или из базы данных: в этом случае я использую функцию, даже если возвращается только простое значение. Таким образом, я могу легко увидеть, является ли вызов дешевым (свойства) или, возможно, дорогим (функции).

Мы, вероятно, получим больше ясности, когда Apple опубликует некоторые соглашения о кодировании Swift.


12

Что ж, вы можете применить советы Котлина https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties .

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

Предпочитайте свойство функции, когда базовый алгоритм:

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

1
Предложение "имеет O (1)" больше не включается в этот совет.
Дэвид Петтигрю

Отредактировано, чтобы отразить изменения Котлина.
Carsten Hagemann

11

Хотя вопрос о вычисленных свойствах и методах в целом является сложным и субъективным, в настоящее время в случае Swift есть один важный аргумент в пользу предпочтения методов над свойствами. Вы можете использовать методы в Swift как чистые функции, что неверно для свойств (начиная с бета-версии Swift 2.0). Это делает методы намного более мощными и полезными, поскольку они могут участвовать в функциональной композиции.

func fflat<A, R>(f: (A) -> () -> (R)) -> (A) -> (R) {
    return { f($0)() }
}

func fnot<A>(f: (A) -> Bool) -> (A) -> (Bool) {
    return { !f($0) }
}

extension String {
    func isEmptyAsFunc() -> Bool {
        return isEmpty
    }
}

let strings = ["Hello", "", "world"]

strings.filter(fnot(fflat(String.isEmptyAsFunc)))

1
strings.filter {! $ (0) .isEmpty} - возвращает тот же результат. Это модифицированный образец из документации Apple на Array.filter (). И понять это намного проще.
poGUIst

7

Поскольку среда выполнения такая же, этот вопрос относится и к Objective-C. Я бы сказал, со свойствами вы получаете

  • возможность добавить сеттер в подкласс, сделав свойство readwrite
  • возможность использовать KVO / didSetдля уведомлений об изменениях
  • в более общем случае вы можете передать свойство методам, которые ожидают ключевые пути, например, сортировка запроса на выборку

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


7

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


9
Вы также можете переопределить функции. Или добавьте сеттер, чтобы обеспечить возможность письма.
Йоханнес Фаренкруг

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

Как только Swift имеет частные свойства (см. Здесь stackoverflow.com/a/24012515/171933 ), вы можете просто добавить функцию установки в свой подкласс, чтобы установить это частное свойство. Когда ваша функция получения называется "name", ваша функция установки будет называться "setName", поэтому конфликта имен не будет.
Йоханнес Фаренкруг

Вы уже можете это сделать (разница в том, что сохраненное свойство, которое вы используете для поддержки, будет общедоступным). Но OP спросил, есть ли разница между объявлением свойства только для чтения или функции в базе. Если вы объявляете свойство только для чтения, вы можете сделать его доступным для чтения и записи в производном классе. Расширение, которое добавляет willSetи didSetк базовому классу, не зная ничего о будущих производных классах, может обнаруживать изменения в переопределенном свойстве. Но с функциями, я думаю, ничего подобного нельзя.
Analog File

Как можно переопределить свойство, доступное только для чтения, для добавления установщика? Спасибо. Я вижу это в документации: «Вы можете представить унаследованное свойство, доступное только для чтения, как свойство для чтения и записи, предоставив как геттер, так и сеттер в переопределении свойств вашего подкласса», но ... в какую переменную записывает сеттер?
Дэн Розенстарк,

5

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

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

struct Vector {
    let x, y: Double
    func length() -> Double {
        return sqrt(x*x + y*y)
    }
}

Объявляя длину как метод, становится ясно, что это функция состояния, которое зависит только от xи y.

С другой стороны, если бы вы выразили lengthвычисленное свойство

struct VectorWithLengthAsProperty {
    let x, y: Double
    var length: Double {
        return sqrt(x*x + y*y)
    }
}

затем, когда вы выполняете dot-tab-complete в своей среде IDE на экземпляре VectorWithLengthAsProperty , это будет выглядеть , как будто x, y, lengthбыли свойства на равных, что концептуально неверно.


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

2

Бывают ситуации, когда вы предпочитаете вычисляемое свойство обычным функциям. Например: возврат полного имени человека. Вы уже знаете имя и фамилию. Так что на самом деле fullNameсвойство - это свойство, а не функция. В данном случае это вычисляемое свойство (поскольку вы не можете установить полное имя, вы можете просто извлечь его, используя имя и фамилию)

class Person{
    let firstName: String
    let lastName: String
    init(firstName: String, lastName: String){
        self.firstName = firstName
        self.lastName = lastName
    }
    var fullName :String{
        return firstName+" "+lastName
    }
}
let william = Person(firstName: "William", lastName: "Kinaan")
william.fullName //William Kinaan

1

С точки зрения производительности разницы нет. Как вы можете видеть в результате теста.

суть

main.swift фрагмент кода:

import Foundation

class MyClass {
    var prop: Int {
        return 88
    }

    func foo() -> Int {
        return 88
    }
}

func test(times: u_long) {
    func testProp(times: u_long) -> TimeInterval {
        let myClass = MyClass()
        let starting = Date()
        for _ in 0...times {
            _ = myClass.prop
        }
        let ending = Date()
        return ending.timeIntervalSince(starting)
    }


    func testFunc(times: u_long) -> TimeInterval {
        let myClass = MyClass()
        let starting = Date()
        for _ in 0...times {
            _ = myClass.prop
        }
        let ending = Date()
        return ending.timeIntervalSince(starting)
    }

    print("prop: \(testProp(times: times))")
    print("func: \(testFunc(times: times))")
}

test(times: 100000)
test(times: 1000000)
test(times: 10000000)
test(times: 100000000)

Вывод:

prop: 0.0380070209503174 func: 0.0350250005722046 prop: 0.371925950050354 func: 0.363085985183716 prop: 3.4023300409317 func: 3.38373708724976 prop: 33.5842199325562 func: 34.8433820009232 Program ended with exit code: 0

В диаграмме:

ориентир


2
Date()не подходит для тестов, поскольку использует часы компьютера, которые автоматически обновляются операционной системой. mach_absolute_timeполучит более надежные результаты.
Cristik

1

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

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

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


0

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

РЕДАКТИРОВАТЬ: после яростного отрицательного голосования я должен кое-что уточнить - если к нему обращаются через точечный синтаксис, это можно рассматривать как свойство. Неважно, что под капотом. Вы не можете получить доступ к обычным методам с точечным синтаксисом.

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


1
На самом деле это неверно - descriptionэто обязательный метод по NSObjectпротоколу, и поэтому в Objective-C возвращается использованием [myObject description]. В любом случае это свойство descriptionбыло просто надуманным примером - я ищу более общий ответ, применимый к любому настраиваемому свойству / функции.
Стюарт

1
Спасибо за пояснение. Я все еще не уверен, что полностью согласен с вашим утверждением, что любой метод obj-c без параметров, который возвращает значение, может считаться свойством, хотя я понимаю ваши рассуждения. Я пока откажусь от своего голоса против, но я думаю, что этот ответ описывает причину «семантики», уже упомянутую в вопросе, и кросс-языковая согласованность здесь тоже не проблема.
Стюарт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.