Properti vs fungsi properti baca-saja di Swift

98

Dalam sesi Pengenalan ke Swift WWDC, properti hanya-baca descriptionditunjukkan:

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

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

Apakah ada implikasi untuk memilih pendekatan di atas daripada menggunakan metode sebagai gantinya:

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

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

Menurut saya, alasan paling jelas mengapa Anda memilih properti hitung hanya-baca adalah:

  • Semantik - dalam contoh ini masuk akal untuk descriptionmenjadi properti kelas, daripada tindakan yang dilakukannya.
  • Singkat / Kejelasan - mencegah kebutuhan untuk menggunakan tanda kurung kosong saat mendapatkan nilainya.

Jelas sekali contoh di atas terlalu sederhana, tetapi adakah alasan bagus lainnya untuk memilih salah satu dari yang lain? Misalnya, apakah ada beberapa fitur dari fungsi atau properti yang akan memandu keputusan Anda untuk menggunakan?


NB Sekilas ini tampak seperti pertanyaan OOP yang cukup umum, tetapi saya ingin mengetahui fitur khusus Swift yang akan memandu praktik terbaik saat menggunakan bahasa ini.

Stuart
sumber
1
Tonton sesi 204 - "When Not to Use @property" Ada beberapa tip
Kostiantyn Koval
4
tunggu, Anda dapat melakukan properti hanya-baca dan melewati get {}? Saya tidak tahu itu, terima kasih!
Dan Rosenstark
WWDC14 Session 204 dapat ditemukan di sini (video dan slide), developer.apple.com/videos/play/wwdc2014/204
user3207158

Jawaban:

53

Bagi saya ini sebagian besar adalah masalah gaya: Saya sangat suka menggunakan properti hanya untuk: properti; artinya nilai-nilai sederhana yang bisa Anda dapatkan dan / atau atur. Saya menggunakan fungsi (atau metode) saat pekerjaan sebenarnya sedang dilakukan. Mungkin sesuatu harus dihitung atau dibaca dari disk atau dari database: Dalam hal ini saya menggunakan fungsi, bahkan ketika hanya nilai sederhana yang dikembalikan. Dengan cara itu saya dapat dengan mudah melihat apakah panggilan itu murah (properti) atau mungkin mahal (fungsi).

Kami mungkin akan mendapatkan lebih banyak kejelasan ketika Apple menerbitkan beberapa konvensi pengkodean Swift.

Johannes Fahrenkrug
sumber
12

Nah, Anda bisa menerapkan saran Kotlin https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties .

Dalam beberapa kasus, fungsi tanpa argumen mungkin dapat dipertukarkan dengan properti hanya-baca. Meskipun semantiknya serupa, ada beberapa ketentuan gaya tentang kapan harus memilih satu sama lain.

Lebih memilih properti daripada fungsi ketika algoritma yang mendasari:

  • tidak melempar
  • kompleksitas itu murah untuk dihitung (atau direkam saat dijalankan pertama kali)
  • mengembalikan hasil yang sama atas pemanggilan
onmyway133
sumber
1
Saran "memiliki O (1)" tidak lagi disertakan dalam nasihat itu.
David Pettigrew
Diedit untuk mencerminkan perubahan Kotlin.
Carsten Hagemann
11

Sementara pertanyaan tentang properti yang dihitung vs metode secara umum sulit dan subjektif, saat ini ada satu argumen penting dalam kasus Swift untuk lebih memilih metode daripada properti. Anda dapat menggunakan metode di Swift sebagai fungsi murni yang tidak berlaku untuk properti (mulai Swift 2.0 beta). Ini membuat metode jauh lebih kuat dan berguna karena mereka dapat berpartisipasi dalam komposisi fungsional.

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)))
Max O
sumber
1
strings.filter {! $ (0) .isEmpty} - mengembalikan hasil yang sama. Ini adalah sampel yang dimodifikasi dari dokumentasi apple di Array.filter (). Dan itu jauh lebih mudah untuk dimengerti.
poGUIst
7

Karena waktu prosesnya sama, pertanyaan ini juga berlaku untuk Objective-C. Menurut saya, dengan properti yang Anda dapatkan

  • kemungkinan menambahkan setter dalam subclass, membuat properti readwrite
  • kemampuan untuk menggunakan KVO / didSetuntuk pemberitahuan perubahan
  • secara lebih umum, Anda bisa meneruskan properti ke metode yang mengharapkan jalur kunci, misalnya penyortiran permintaan pengambilan

Mengenai sesuatu yang spesifik untuk Swift, satu-satunya contoh yang saya miliki adalah yang dapat Anda gunakan @lazyuntuk sebuah properti.

ilya n.
sumber
7

Ada perbedaan: Jika Anda menggunakan properti, Anda akhirnya dapat menimpanya dan membuatnya menjadi baca / tulis dalam subclass.

File Analog
sumber
9
Anda juga dapat mengganti fungsi. Atau tambahkan penyetel untuk memberikan kemampuan menulis.
Johannes Fahrenkrug
Anda dapat menambahkan penyetel atau mendefinisikan properti yang disimpan ketika kelas dasar mendefinisikan nama sebagai fungsi? Tentunya Anda dapat melakukannya jika itu mendefinisikan properti (itulah maksud saya), tetapi saya rasa Anda tidak dapat melakukannya jika itu mendefinisikan fungsi.
File Analog
Setelah Swift memiliki properti privat (lihat di sini stackoverflow.com/a/24012515/171933 ), Anda cukup menambahkan fungsi setter ke subkelas Anda untuk menyetel properti privat itu. Ketika fungsi pengambil Anda disebut "nama", penyetel Anda akan disebut "setName", jadi tidak ada konflik penamaan.
Johannes Fahrenkrug
Anda sudah dapat melakukannya (perbedaannya adalah properti tersimpan yang Anda gunakan untuk dukungan akan menjadi publik). Tetapi OP bertanya apakah ada perbedaan antara mendeklarasikan properti hanya baca atau fungsi di basis. Jika Anda mendeklarasikan properti hanya baca, Anda kemudian dapat membuatnya menjadi baca-tulis di kelas turunan. Sebuah ekstensi yang menambah willSetdan didSetke kelas dasar , tanpa mengetahui apa pun dari kelas turunan di masa mendatang, dapat mendeteksi perubahan dalam properti yang diganti. Tetapi Anda tidak dapat melakukan hal seperti itu dengan fungsi, saya pikir.
File Analog
Bagaimana Anda bisa mengganti properti hanya-baca untuk menambahkan penyetel? Terima kasih. Saya melihat ini di dokumen, "Anda dapat menampilkan properti read-only yang diwariskan sebagai properti baca-tulis dengan menyediakan getter dan setter di subclass property override" tetapi ... variabel apa yang ditulis oleh setter?
Dan Rosenstark
5

Dalam kasus hanya-baca, properti yang dihitung tidak boleh dianggap ekuivalen secara semantik dengan suatu metode, bahkan ketika mereka berperilaku identik, karena menghapus funcdeklarasi mengaburkan perbedaan antara kuantitas yang menyusun keadaan instance dan kuantitas yang hanya merupakan fungsi dari negara. Anda tidak perlu mengetik ()di situs panggilan, tetapi berisiko kehilangan kejelasan dalam kode Anda.

Sebagai contoh sepele, pertimbangkan jenis vektor berikut:

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

Dengan mendeklarasikan panjangnya sebagai metode, jelas bahwa itu adalah fungsi dari status, yang hanya bergantung pada xdan y.

Di sisi lain, jika Anda mengekspresikan lengthsebagai properti yang dihitung

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

maka ketika Anda dot-tab-lengkap dalam IDE Anda pada sebuah contoh dari VectorWithLengthAsProperty, itu akan terlihat seolah-olah x, y, lengthadalah sifat pada pijakan yang sama, yang secara konseptual tidak benar.

egnha.dll
sumber
5
Ini menarik, tetapi dapatkah Anda memberikan contoh di mana properti hanya-baca yang dihitung akan digunakan ketika mengikuti prinsip ini? Mungkin saya salah, tetapi argumen Anda tampaknya menyarankan bahwa mereka tidak boleh digunakan, karena menurut definisi properti hanya-baca yang dihitung tidak pernah terdiri dari status.
Stuart
2

Ada situasi di mana Anda lebih memilih properti yang dihitung daripada fungsi normal. Seperti: mengembalikan nama lengkap seseorang. Anda sudah tahu nama depan dan nama belakangnya. Jadi sebenarnya fullNameproperti adalah properti, bukan fungsi. Dalam hal ini, ini adalah properti yang dihitung (karena Anda tidak dapat mengatur nama lengkap, Anda bisa mengekstraknya menggunakan nama depan dan nama belakang)

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
William Kinaan
sumber
1

Dari segi kinerja, sepertinya tidak ada perbedaan. Seperti yang Anda lihat di hasil benchmark.

inti

main.swift potongan kode:

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)

Keluaran:

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

Dalam Bagan:

patokan

Benjamin Wen
sumber
2
Date()tidak cocok untuk benchmark karena menggunakan jam komputer, yang dapat diperbarui secara otomatis oleh sistem operasi. mach_absolute_timeakan mendapatkan hasil yang lebih dapat diandalkan.
Cristik
1

Berbicara secara semantik, properti yang dihitung harus digabungkan erat dengan keadaan intrinsik objek - jika properti lain tidak berubah, maka menanyakan properti yang dihitung pada waktu yang berbeda akan memberikan output yang sama (sebanding melalui == atau ===) - serupa untuk memanggil fungsi murni pada objek itu.

Metode di sisi lain keluar dari kotak dengan asumsi bahwa kita mungkin tidak selalu mendapatkan hasil yang sama, karena Swift tidak memiliki cara untuk menandai fungsi sebagai murni. Juga, metode dalam OOP dianggap tindakan, yang berarti bahwa menjalankannya dapat mengakibatkan efek samping. Jika metode tersebut tidak memiliki efek samping, maka metode tersebut dapat dengan aman diubah menjadi properti yang dihitung.

Perhatikan bahwa kedua pernyataan di atas murni dari perspektif semantik, karena mungkin saja properti yang dihitung memiliki efek samping yang tidak diharapkan, dan metode menjadi murni.

Cristik
sumber
0

Secara historis, deskripsi adalah properti di NSObject dan banyak yang berharap bahwa itu terus sama di Swift. Menambahkan tanda kurung setelah itu hanya akan menambah kebingungan.

EDIT: Setelah downvoting besar-besaran saya harus mengklarifikasi sesuatu - jika diakses melalui sintaks titik, itu dapat dianggap sebagai properti. Tidak peduli apa yang ada di bawah tenda. Anda tidak dapat mengakses metode biasa dengan sintaks titik.

Selain itu, memanggil properti ini tidak memerlukan orang tua tambahan, seperti dalam kasus Swift, yang dapat menyebabkan kebingungan.

Dvole
sumber
1
Sebenarnya ini tidak benar - descriptionadalah metode yang diperlukan pada NSObjectprotokol, dan dalam tujuan-C dikembalikan menggunakan [myObject description]. Bagaimanapun, properti descriptionitu hanyalah contoh yang dibuat-buat - Saya mencari jawaban yang lebih umum yang berlaku untuk properti / fungsi khusus apa pun.
Stuart
1
Terima kasih atas penjelasannya. Saya masih tidak yakin saya sepenuhnya setuju dengan pernyataan Anda bahwa metode obj-c tanpa parameter apa pun yang mengembalikan nilai dapat dianggap sebagai properti, meskipun saya memahami alasan Anda. Saya akan menarik kembali suara negatif saya untuk saat ini, tetapi saya pikir jawaban ini menjelaskan alasan 'semantik' yang telah disebutkan dalam pertanyaan, dan konsistensi lintas bahasa juga bukan masalah sebenarnya di sini.
Stuart