Swift'de hesaplanmış salt okunur özellik ve işlev


98

Swift WWDC'ye Giriş oturumunda, salt okunur bir özellik descriptiongösterilmektedir:

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

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

Bunun yerine bir yöntemi kullanmak yerine yukarıdaki yaklaşımı seçmenin herhangi bir etkisi var mı?

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

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

Bana öyle geliyor ki, salt okunur bir hesaplanmış özelliği seçmenizin en bariz nedenleri:

  • Anlambilim - bu örnekte description, gerçekleştirdiği bir eylemden ziyade sınıfın bir özelliği olması mantıklıdır .
  • Kısalık / Netlik - değeri alırken boş parantez kullanma ihtiyacını önler.

Açıkça yukarıdaki örnek fazlasıyla basit, ancak birini diğerine tercih etmek için başka iyi nedenler var mı? Örneğin, hangisini kullanacağınıza karar vermenize yol gösterecek işlevlerin veya özelliklerin bazı özellikleri var mı?


NB İlk bakışta bu oldukça yaygın bir OOP sorusu gibi görünüyor, ancak bu dili kullanırken en iyi uygulamayı yönlendirecek Swift'e özgü özellikleri bilmek istiyorum.


1
204 oturumunu izleyin - "Ne Zaman Kullanılmamalı
@property

4
bekleyin, salt okunur bir özellik yapabilir ve get {}? Bunu bilmiyordum, teşekkürler!
Dan Rosenstark

WWDC14 Session 204 burada bulunabilir (video ve slaytlar), developer.apple.com/videos/play/wwdc2014/204
user3207158

Yanıtlar:


54

Bana öyle geliyor ki bu çoğunlukla bir stil meselesi: Sadece bunun için özellikleri kullanmayı kesinlikle tercih ediyorum : özellikler; elde edebileceğiniz ve / veya ayarlayabileceğiniz basit değerler anlamına gelir. Fiili iş yapılırken işlevleri (veya yöntemleri) kullanıyorum . Belki bir şeyin hesaplanması veya diskten veya bir veritabanından okunması gerekir: Bu durumda, yalnızca basit bir değer döndürüldüğünde bile bir işlev kullanıyorum. Bu şekilde, bir aramanın ucuz (özellikler) veya muhtemelen pahalı (işlevler) olup olmadığını kolayca görebilirim.

Apple bazı Swift kodlama kurallarını yayınladığında muhtemelen daha fazla netlik elde edeceğiz.


12

Eh, Kotlin'in tavsiyelerini https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties uygulayabilirsiniz .

Bazı durumlarda, bağımsız değişkeni olmayan işlevler salt okunur özelliklerle değiştirilebilir. Anlambilim benzer olsa da, birini diğerine ne zaman tercih edeceğine dair bazı stil kuralları vardır.

Altta yatan algoritma aşağıdaki durumlarda işlev yerine bir özelliği tercih eder:

  • atmaz
  • karmaşıklığın hesaplanması ucuzdur (veya ilk çalıştırmada hesaplanır)
  • çağrılarda aynı sonucu döndürür

1
"O (1) var" önerisi artık bu tavsiyeye dahil edilmemiştir.
David Pettigrew

Kotlin'in değişikliklerini yansıtacak şekilde düzenlendi.
Carsten Hagemann

11

Genel olarak hesaplanmış özelliklere karşı yöntemler sorusu zor ve öznel olsa da, şu anda Swift'in durumunda, yöntemleri özelliklere tercih etme konusunda önemli bir argüman var. Swift'deki yöntemleri, özellikler için doğru olmayan saf işlevler olarak kullanabilirsiniz (Swift 2.0 beta'dan itibaren). Bu, fonksiyonel bileşime katılabildikleri için yöntemleri çok daha güçlü ve kullanışlı hale getirir.

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} - aynı sonucu döndürür. Array.filter () üzerindeki apple belgelerinden değiştirilmiş örnektir. Ve anlaşılması çok daha kolay.
poGUIst

7

Çalışma zamanı aynı olduğundan, bu soru aynı zamanda Objective-C için de geçerlidir. Diyorum ki, aldığınız mülklerle

  • özelliği oluşturan bir alt sınıfa bir ayarlayıcı ekleme imkanı readwrite
  • KVO / didSetdeğişiklik bildirimleri için kullanma yeteneği
  • daha genel olarak, özelliği anahtar yolları bekleyen yöntemlere aktarabilirsiniz, örneğin getirme isteği sıralaması

Swift'e özgü bir şeye gelince, sahip olduğum tek örnek @lazy, bir mülk için kullanabileceğinizdir .


7

Bir fark var: Bir özelliği kullanırsanız, sonunda onu geçersiz kılabilir ve bir alt sınıfta okumasını / yazmasını sağlayabilirsiniz.


9
Ayrıca işlevleri geçersiz kılabilirsiniz. Veya yazma yeteneği sağlamak için bir pasör ekleyin.
Johannes Fahrenkrug

Temel sınıf adı bir işlev olarak tanımladığında bir ayarlayıcı ekleyebilir veya depolanan bir özelliği tanımlayabilirsiniz. Elbette bunu bir özelliği tanımladıysa yapabilirsiniz (bu tam olarak benim amacım), ancak bir işlev tanımladıysa bunu yapabileceğinizi sanmıyorum.
Analog Dosya

Swift özel mülklere sahip olduktan sonra (buraya bakın stackoverflow.com/a/24012515/171933 ), bu özel özelliği ayarlamak için alt sınıfınıza bir ayarlayıcı işlevi ekleyebilirsiniz. Alıcı işleviniz "ad" olarak adlandırıldığında, ayarlayıcınız "setName" olarak adlandırılır, bu nedenle adlandırma çakışması olmaz.
Johannes Fahrenkrug

Bunu zaten yapabilirsiniz (fark, destek için kullandığınız depolanmış mülkün halka açık olmasıdır). Ancak OP, temelde salt okunur bir özellik veya bir işlev bildirmek arasında bir fark olup olmadığını sordu. Salt okunur bir özellik bildirirseniz, bunu türetilmiş bir sınıfta okuma-yazma yapabilirsiniz. Ekleyen bir uzantısı willSetve didSetkarşı taban gelecek türetilmiş sınıfların hiçbir şey bilmeden, sınıf, geçersiz kılınan özelliği değişiklikleri tespit edebilir. Ama bence fonksiyonlarla böyle bir şey yapamazsınız.
Analog Dosya

Bir ayarlayıcı eklemek için salt okunur bir özelliği nasıl geçersiz kılabilirsiniz? Teşekkürler. Bunu belgelerde görüyorum, "Miras alınan bir salt okunur özelliği, alt sınıf özelliğinizde hem bir alıcı hem de ayarlayıcı sağlayarak bir okuma-yazma özelliği olarak sunabilirsiniz" ama ... ayarlayıcı hangi değişkeni yazıyor?
Dan Rosenstark

5

Salt okunur durumda, hesaplanan bir özellik, aynı şekilde davransalar bile, anlamsal olarak bir yönteme eşdeğer kabul edilmemelidir , çünkü funcbildirimi kaldırmak, bir örneğin durumunu oluşturan miktarlar ile yalnızca işlevin işlevleri olan miktarlar arasındaki ayrımı bulanıklaştırır . durum. ()Çağrı sitesinde yazmaktan tasarruf edersiniz , ancak kodunuzun netliğini kaybetme riskiyle karşı karşıya kalırsınız.

Önemsiz bir örnek olarak, aşağıdaki vektör türünü düşünün:

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

Bir yöntem olarak uzunluğunu ilan ederek, bunun sadece bağlıdır devlet, bir fonksiyonu var olduğu açıktır xve y.

Öte yandan, lengthhesaplanmış bir mülk olarak ifade edecekseniz

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

Eğer örneğinde IDE'nizde sekme tamamlama dot zaman sonra VectorWithLengthAsProperty, o sanki olmazdı x, y, lengthkavramsal olarak yanlıştır eşit, üzerinde özellikleri idi.


5
Bu ilginç, ama bir bilgisayarlı salt okunur özellik geldiği bir örneği verebilir ediyorum bu ilkesine kullanılacak? Belki yanılıyorum, ama sizin argümanınız hiçbir zaman kullanılmaması gerektiğini gösteriyor gibi görünüyor , çünkü tanım gereği hesaplanmış salt okunur bir özellik asla durumu içermez.
Stuart

2

Normal işlevler yerine hesaplanmış özelliği tercih edeceğiniz durumlar vardır. Örneğin: bir kişinin tam adını döndürmek. İlk adı ve soyadını zaten biliyorsunuz. Yani gerçekten fullNameözellik bir özelliktir, işlev değil. Bu durumda, hesaplanan özelliktir (tam adı belirleyemediğiniz için, sadece adı ve soyadı kullanarak onu çıkarabilirsiniz)

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

Performans açısından bakıldığında hiçbir fark görünmüyor. Kıyaslama sonucunda görebileceğiniz gibi.

ana fikir

main.swift kod pasajı:

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)

Çıktı:

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

Tabloda:

kıyaslama


2
Date()işletim sistemi tarafından otomatik güncellemelere tabi olan bilgisayar saatini kullandığından kıyaslamalar için uygun değildir. mach_absolute_timedaha güvenilir sonuçlar alırdı.
Cristik

1

Anlamsal olarak konuşursak, hesaplanan özellikler nesnenin içsel durumu ile sıkı bir şekilde birleştirilmelidir - diğer özellikler değişmezse, o zaman hesaplanan özelliği farklı zamanlarda sorgulamak aynı çıktıyı vermelidir (== veya === ile karşılaştırılabilir) - benzer o nesnede saf bir işlev çağırmak.

Öte yandan yöntemler, her zaman aynı sonuçları alamayacağımız varsayımıyla kutudan çıkar, çünkü Swift'in işlevleri saf olarak işaretlemenin bir yolu yoktur. Ayrıca, OOP'deki yöntemler eylemler olarak kabul edilir, bu da onları yürütmenin yan etkilere neden olabileceği anlamına gelir. Yöntemin herhangi bir yan etkisi yoksa, güvenli bir şekilde hesaplanan bir özelliğe dönüştürülebilir.

Yukarıdaki ifadelerin her ikisinin de tamamen anlamsal bir perspektiften olduğuna dikkat edin, çünkü hesaplanan özelliklerin beklemediğimiz yan etkilere sahip olması ve yöntemlerin saf olması da mümkündür.


0

Tarihsel olarak açıklama NSObject üzerindeki bir özelliktir ve çoğu kişi Swift'de aynı şekilde devam etmesini bekler. Bundan sonra parantez eklemek sadece kafa karışıklığı yaratacaktır.

DÜZENLEME: Öfkeli olumsuz oylamadan sonra bir şeyi açıklığa kavuşturmam gerekiyor - eğer nokta sözdizimi ile erişiliyorsa, bir özellik olarak kabul edilebilir. Kaputun altında ne olduğu önemli değil. Nokta sözdizimi ile olağan yöntemlere erişemezsiniz.

Ayrıca, bu özelliği çağırmak, Swift örneğinde olduğu gibi fazladan parantez gerektirmedi, bu da kafa karışıklığına yol açabilir.


1
- Aslında bu yanlış descriptionbir gereklidir yöntem ile ilgili NSObjectprotokol, ve bu amaç-C kullanılarak döndürülür [myObject description]. Her neyse, özellik descriptionsadece uydurma bir örnekti - herhangi bir özel özellik / işlev için geçerli olan daha genel bir cevap arıyorum.
Stuart

1
Biraz açıklama için teşekkürler. Gerekçenizi anlasam da, bir değer döndüren herhangi bir parametresiz obj-c yönteminin bir özellik olarak kabul edilebileceğine dair ifadenize tam olarak katılmadığımdan hala emin değilim. Şimdilik olumsuz oyumu geri çekeceğim, ancak bu cevabın soruda daha önce bahsedilen 'anlambilim' nedenini açıkladığını düşünüyorum ve diller arası tutarlılık burada da gerçekten sorun değil.
Stuart
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.