Swift uzantılarında geçersiz kılma yöntemleri


133

Yalnızca gereksinimleri (depolanmış özellikler, başlatıcılar) sınıf tanımlarıma koyma ve diğer her şeyi kendi başlarına taşıma eğilimindeyim extension, bir tür extensionmantıksal blok gibi gruplayacağım // MARK:.

Örneğin, bir UIView alt sınıfı için, yerleşimle ilgili şeyler için bir uzantı, bir tanesi de abone olmak ve olayları yönetmek için bir uzantı elde ederdim. Bu uzantılarda, kaçınılmaz olarak bazı UIKit yöntemlerini geçersiz kılmam gerekiyor, örn layoutSubviews. Bugüne kadar bu yaklaşımla ilgili herhangi bir sorun fark etmedim.

Örneğin bu sınıf hiyerarşisini ele alalım:

public class C: NSObject {
    public func method() { print("C") }
}

public class B: C {
}
extension B {
    override public func method() { print("B") }
}

public class A: B {
}
extension A {
    override public func method() { print("A") }
}

(A() as A).method()
(A() as B).method()
(A() as C).method()

Çıktı A B C. Bu bana biraz mantıklı geliyor. Statik olarak gönderilen Protokol Uzantılarını okudum, ancak bu bir protokol değil. Bu normal bir sınıftır ve yöntem çağrılarının çalışma zamanında dinamik olarak gönderilmesini bekliyorum. Açıkça, çağrı Cen azından dinamik olarak gönderilmeli ve üretilmeli Cmi?

Eğer mirası kaldırırsam NSObjectve Cbir kök sınıf declarations in extensions cannot override yetyaparsam , derleyici , zaten okuduğum şeyi söyleyerek şikayet eder . Fakat NSObjectbir kök sınıfa sahip olmak işleri nasıl değiştirir?

Her iki geçersiz kılmayı da kendi sınıf bildirimlerine A A Ataşımak, beklendiği gibi Büretir A B B, yalnızca A'ürettiği ürünleri C B Ctaşır, yalnızca ' ürettiği ürünleri taşır, bu sonuncusu bana kesinlikle bir anlam ifade etmez: -çıktıyı Aüretmek için statik olarak yazılan bile artık A!

dynamicAnahtar kelimeyi tanıma eklemek veya bir geçersiz kılmak bana istenen davranışı 'sınıf hiyerarşisindeki o noktadan aşağıya doğru' veriyor gibi görünüyor ...

Örneğimizi biraz daha az yapılandırılmış bir şeye değiştirelim, aslında bu soruyu göndermeme neden olan şey:

public class B: UIView {
}
extension B {
    override public func layoutSubviews() { print("B") }
}

public class A: B {
}
extension A {
    override public func layoutSubviews() { print("A") }
}


(A() as A).layoutSubviews()
(A() as B).layoutSubviews()
(A() as UIView).layoutSubviews()

Şimdi anlıyoruz A B A. Burada UIView'un layoutSubviews'larını hiçbir şekilde dinamik yapamıyorum.

Her iki geçersiz kılmayı da sınıf beyanına taşımak bizi A A Atekrar getirir , sadece A'lar veya sadece B'ler bizi hala alır A B A. dynamicyine sorunlarımı çözer.

Teoride ben eklemek olabilir dynamicherkese overrideşimdiye kadar yapmak s ama burada başka bir şey yanlış yapıyorum gibi hissediyorum.

extensionKodları gruplamak için s kullanmak benim yaptığım gibi gerçekten yanlış mı ?


Uzantıları bu şekilde kullanmak Swift için bir kuraldır. Apple bile bunu standart kitaplıkta yapıyor.
İskender - Eski Monica


1
@AMomchilov Bağlandığınız belge protokoller hakkında konuşuyor, bir şey mi kaçırıyorum?
Christian Schnorr

Her ikisi için de çalışan aynı mekanizma olduğundan şüpheleniyorum
Alexander - Reinstate Monica

3
Alt sınıf uzantılarında geçersiz kılınan yöntemlere Swift gönderimini çoğalttığı görülüyor . Matt'in cevabı, bunun bir hata olduğudur (ve bunu desteklemek için belgelerden alıntı yapıyor).
jscs

Yanıtlar:


229

Uzantılar geçersiz kılınamaz / yazılmamalıdır.

Apple'ın Swift Rehberi'nde belgelendiği gibi uzantılarda işlevselliği (özellikler veya yöntemler gibi) geçersiz kılmak mümkün değildir.

Uzantılar bir türe yeni işlevler ekleyebilir ancak mevcut işlevi geçersiz kılamazlar.

Swift Geliştirici Kılavuzu

Derleyici, Objective-C ile uyumluluk için uzantıda geçersiz kılmanıza izin veriyor. Ama aslında dil direktifini ihlal ediyor.

😊Bu bana Isaac Asimov'un " Robot Teknolojisinin Üç Yasası " nı hatırlattı 🤖

Uzantılar ( sözdizimsel şeker ), kendi argümanlarını alan bağımsız yöntemleri tanımlar. İe için çağrılan işlev layoutSubviews, derleyicinin kodun ne zaman derlendiğini bildiği bağlama bağlıdır. UIView, NSObject'ten devralan UIResponder'dan miras alır, bu nedenle uzantıdaki geçersiz kılmaya izin verilir, ancak olmamalıdır .

Yani gruplamada yanlış bir şey yok, ancak uzantıda değil sınıfta geçersiz kılmalısınız.

Yönerge Notları

Yalnızca overridebir üst sınıf yöntemi, yani load() initialize()yöntem Objective-C uyumluysa bir alt sınıfın bir uzantısında kullanabilirsiniz.

Bu nedenle, neden kullanarak derlemenize izin verdiğine bir göz atabiliriz layoutSubviews.

Tüm Swift uygulamaları, yalnızca Swift için çalışma zamanına izin veren yalnızca Swift'e özel çerçevelerin kullanılması dışında, Objective-C çalışma zamanı içinde çalışır.

Objective-C çalışma zamanının genellikle iki sınıf ana yöntemi çağırdığını load()ve initialize()uygulamanızın süreçlerindeki sınıfları başlatırken otomatik olarak çağırdığını öğrendik.

dynamicDeğiştirici ile ilgili olarak

Gönderen Elma Geliştirici Kütüphanesi (archive.org)

dynamicDeğiştiriciyi, üyelere erişimin Objective-C çalışma zamanı aracılığıyla dinamik olarak gönderilmesini zorunlu kılmak için kullanabilirsiniz .

Swift API'leri Objective-C çalışma zamanı tarafından içe aktarıldığında, özellikler, yöntemler, aboneler veya başlatıcılar için dinamik gönderme garantisi yoktur. Swift derleyicisi, Objective-C çalışma zamanını atlayarak kodunuzun performansını optimize etmek için yine de sanallaştırabilir veya satır içi üye erişimini sağlayabilir. 😳

Yani dynamicadresinden Müşteri uygulanabilir layoutSubviews-> UIView Classo Objective-C tarafından temsil edilir ve bu üye erişim her zaman Objective-C çalışma zamanı kullanılarak kullanıldığından.

Bu yüzden mi kullanmasına izin derleyici var overrideve dynamic.


6
uzantı yalnızca sınıfta tanımlanan yöntemleri geçersiz kılamaz. Üst sınıfta tanımlanan yöntemleri geçersiz kılabilir.
RJE

-Swift3-Bu garip, çünkü dahil ettiğiniz çerçevelerden yöntemleri geçersiz kılabilirsiniz (ve burada geçersiz kılarak Swizzling gibi bir şeyi kastediyorum). bu çerçeveler tamamen hızlı yazılsa bile .... belki çerçeveler de objc ile sınırlıdır ve bu yüzden 🤔
farzadshbfn

@tymac anlamıyorum. Eğer Objective-C çalışma zamanı neden Objective-C uyumluluk adına ihtiyaç şey, Swift derleyici hala uzantılarında geçersiz kılma sağlar? Swift uzantılarında geçersiz kılmayı sözdizimi hatası olarak işaretlemek Objective-C çalışma zamanına nasıl zarar verebilir?
Alexander Vasenin

1
Çok sinir bozucu, o yüzden temelde zaten bir projede bulunan bir kodla bir çerçeve yapmak istediğinizde her şeyi alt sınıflara
ayırmanız

3
@AuRis Referansınız var mı?
ricardopereira

18

Swift'in hedeflerinden biri statik dağıtım veya daha doğrusu dinamik gönderimi azaltmaktır. Obj-C ise çok dinamik bir dildir. Gördüğünüz durum, 2 dil ve birlikte çalışma biçimleri arasındaki bağlantıdan kaynaklanıyor. Gerçekten derlenmemeli.

Uzantıların ana noktalarından biri, genişletmek için değil, değiştirmek / geçersiz kılmak için olmalarıdır. Hem isimden hem de belgelerden niyetin bu olduğu açıktır. Aslında, kodunuzdan Obj-C bağlantısını çıkarırsanız ( NSObjectüst sınıf olarak kaldırın ), derlemez.

Dolayısıyla, derleyici statik olarak neyi gönderebileceğine ve dinamik olarak neyi göndermesi gerektiğine karar vermeye çalışıyor ve kodunuzdaki Obj-C bağlantısı nedeniyle bir boşluktan düşüyor. dynamic'İşe yaramasının' nedeni Obj-C'yi her şeye bağlamayı zorlaması ve böylece her zaman dinamik olmasıdır.

Dolayısıyla, gruplama için uzantıları kullanmak yanlış değil, bu harika, ancak uzantılarda geçersiz kılmak yanlış. Tüm geçersiz kılmalar ana sınıfın kendisinde olmalı ve uzantı noktalarına çağrı yapmalıdır.


Bu aynı zamanda değişkenler için de geçerli mi? Örneğin, supportedInterfaceOrientationsiçinde geçersiz kılmak istiyorsanız UINavigationController(farklı yönlerde farklı görünümler göstermek amacıyla), bir uzantı değil, özel bir sınıf kullanmalısınız? Birçok cevap, geçersiz kılmak için bir uzantı kullanılmasını önerir, supportedInterfaceOrientationsancak açıklamaya bayılır. Teşekkürler!
Crashalot

10

Alt sınıflarda geçersiz kılmalara sahip olma özelliğini korurken, sınıf imzası ile uygulamanın (uzantılarda) temiz bir şekilde ayrılmasını sağlamanın bir yolu vardır. İşin püf noktası, fonksiyonların yerine değişkenler kullanmaktır

Her bir alt sınıfı ayrı bir hızlı kaynak dosyasında tanımladığınızdan emin olursanız, ilgili uygulamayı uzantılarda temiz bir şekilde düzenlenmiş halde tutarken geçersiz kılmalar için hesaplanan değişkenleri kullanabilirsiniz. Bu, Swift'in "kurallarını" atlatacak ve sınıfınızın API'sini / imzasını tek bir yerde düzenli bir şekilde organize edecektir:

// ---------- BaseClass.swift -------------

public class BaseClass
{
    public var method1:(Int) -> String { return doMethod1 }

    public init() {}
}

// the extension could also be in a separate file  
extension BaseClass
{    
    private func doMethod1(param:Int) -> String { return "BaseClass \(param)" }
}

...

// ---------- ClassA.swift ----------

public class A:BaseClass
{
   override public var method1:(Int) -> String { return doMethod1 }
}

// this extension can be in a separate file but not in the same
// file as the BaseClass extension that defines its doMethod1 implementation
extension A
{
   private func doMethod1(param:Int) -> String 
   { 
      return "A \(param) added to \(super.method1(param))" 
   }
}

...

// ---------- ClassB.swift ----------
public class B:A
{
   override public var method1:(Int) -> String { return doMethod1 }
}

extension B
{
   private func doMethod1(param:Int) -> String 
   { 
      return "B \(param) added to \(super.method1(param))" 
   }
}

Her sınıfın uzantısı, özel olduklarından ve birbirleri tarafından görünmediklerinden (ayrı dosyalarda oldukları sürece) uygulama için aynı yöntem adlarını kullanabilir.

Gördüğünüz gibi kalıtım (değişken adını kullanarak) süper.variablename kullanılarak düzgün çalışıyor

BaseClass().method1(123)         --> "BaseClass 123"
A().method1(123)                 --> "A 123 added to BaseClass 123"
B().method1(123)                 --> "B 123 added to A 123 added to BaseClass 123"
(B() as A).method1(123)          --> "B 123 added to A 123 added to BaseClass 123"
(B() as BaseClass).method1(123)  --> "B 123 added to A 123 added to BaseClass 123"

2
Sanırım bu kendi yöntemlerim için işe yarayacak, ancak sınıflarımdaki System Framework yöntemlerini geçersiz kılarken değil.
Christian Schnorr

Bu beni bir özellik sarıcı koşullu protokol uzantısı için doğru yola yönlendirdi. Teşekkürler!
Chris Prince

1

Bu cevap OP'yi hedeflemiyor, onun ifadesinden ilham aldığım gerçeği dışında, "Sınıf tanımlarıma yalnızca gereksinimleri (depolanan özellikler, başlatıcılar) koyma ve diğer her şeyi kendi uzantılarına taşıma eğilimindeyim. .. ". Öncelikle bir C # programcısıyım ve C # 'da bu amaç için kısmi sınıflar kullanılabilir. Örneğin, Visual Studio, kullanıcı arabirimiyle ilgili öğeleri kısmi bir sınıf kullanarak ayrı bir kaynak dosyaya yerleştirir ve ana kaynak dosyanızı düzenli olarak bırakır, böylece bu dikkat dağınıklığına sahip olmazsınız.

Eğer "hızlı kısmi sınıf" ararsanız, Swift taraftarlarının Swift'in kısmi sınıflara ihtiyaç duymadığını söylediği çeşitli bağlantılar bulacaksınız çünkü uzantıları kullanabilirsiniz. İlginç bir şekilde, Google arama alanına "hızlı uzantı" yazarsanız, ilk arama önerisi "hızlı uzantı geçersiz kılma" olur ve şu anda bu Yığın Taşması sorusu ilk isabettir. Bunu, geçersiz kılma yetenekleriyle ilgili sorunların (eksikliği) Swift uzantılarıyla ilgili en çok aranan konu olduğu anlamına geliyor ve Swift uzantılarının, en azından sizin türetilmiş sınıfları kullanıyorsanız, kısmi sınıfların yerini alamayacağı gerçeğinin altını çiziyorum. programlama.

Her neyse, uzun soluklu bir girişi kısaltmak için, C #-to-Swift programımın ürettiği Swift sınıfları için ana kaynak dosyalarından bazı klişe / bagaj yöntemlerini taşımak istediğim bir durumda bu sorunla karşılaştım. Uzantılara taşıdıktan sonra bu yöntemler için geçersiz kılınmasına izin verilmemesi sorunuyla karşılaştıktan sonra, aşağıdaki basit çözüm yolunu uyguladım. Ana Swift kaynak dosyaları hala uzantı dosyalarındaki gerçek yöntemleri çağıran bazı küçük saplama yöntemlerini içerir ve bu uzantı yöntemlerine geçersiz kılma sorununu önlemek için benzersiz adlar verilir.

public protocol PCopierSerializable {

   static func getFieldTable(mCopier : MCopier) -> FieldTable
   static func createObject(initTable : [Int : Any?]) -> Any
   func doSerialization(mCopier : MCopier)
}

.

public class SimpleClass : PCopierSerializable {

   public var aMember : Int32

   public init(
               aMember : Int32
              ) {
      self.aMember = aMember
   }

   public class func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_SimpleClass(mCopier: mCopier)
   }

   public class func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_SimpleClass(initTable: initTable)
   }

   public func doSerialization(mCopier : MCopier) {
      doSerialization_SimpleClass(mCopier: mCopier)
   }
}

.

extension SimpleClass {

   class func getFieldTable_SimpleClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_SimpleClass(initTable : [Int : Any?]) -> Any {
      return SimpleClass(
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_SimpleClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367620, 1)
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

.

public class DerivedClass : SimpleClass {

   public var aNewMember : Int32

   public init(
               aNewMember : Int32,
               aMember : Int32
              ) {
      self.aNewMember = aNewMember
      super.init(
                 aMember: aMember
                )
   }

   public class override func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_DerivedClass(mCopier: mCopier)
   }

   public class override func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_DerivedClass(initTable: initTable)
   }

   public override func doSerialization(mCopier : MCopier) {
      doSerialization_DerivedClass(mCopier: mCopier)
   }
}

.

extension DerivedClass {

   class func getFieldTable_DerivedClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376443905] = { () in try mCopier.getInt32A() }  // aNewMember
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_DerivedClass(initTable : [Int : Any?]) -> Any {
      return DerivedClass(
                aNewMember: initTable[376443905] as! Int32,
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_DerivedClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367621, 2)
      mCopier.serializeProperty(376443905, .eInt32, { () in mCopier.putInt32(aNewMember) } )
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

Giriş bölümümde söylediğim gibi, bu OP'nin sorusuna gerçekten cevap vermiyor, ancak bu basit çözümün, yöntemleri ana kaynak dosyalardan uzantı dosyalarına taşımak ve hayır - sorunu geçersiz kıl.


1

Uzantılardaki işlevleri geçersiz kılmak için POP'u (Protokol Odaklı Programlama) kullanın.

protocol AProtocol {
    func aFunction()
}

extension AProtocol {
    func aFunction() {
        print("empty")
    }
}

class AClass: AProtocol {

}

extension AClass {
    func aFunction() {
        print("not empty")
    }
}

let cls = AClass()
cls.aFunction()

1
Bu, programcının AProtocol'a güvenebilecek şekilde orijinal AClass tanımını kontrol ettiğini varsayar. Birinin AClass'ta işlevselliği geçersiz kılmak istediği durumda, bu genellikle geçerli değildir (yani, AClass muhtemelen Apple tarafından sağlanan standart bir kitaplık sınıfı olacaktır).
Jonathan Leonard

Sınıfın orijinal tanımını değiştirmek istemiyorsanız veya değiştiremiyorsanız, protokolü (bazı durumlarda) bir uzantı veya alt sınıfa uygulayabileceğinizi unutmayın.
şim
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.