SwiftUI bileşeninizi protokole uyan tür üzerinde generic yapın, ardından depolanan görünümünüzü Provider.Content olarak tanımlayın. Böylece sağlayıcı ile ilişkili görünüm türü arasındaki ilişki bileşen boyunca korunur.

Bu eğitimde iki yapay kutucuk içeren küçük bir macOS penceresi oluşturuluyor. Amaç, ilişkili türü any View veya AnyView ile değiştirmeden bir sağlayıcıyı ve onun ürettiği görünümü depolamak. Aynı kalıp, yeniden kullanılabilir bir masaüstü bileşeni özel bir etiket, aksesuar veya içerik sağlayıcısı kabul ettiğinde de uygulanabilir.

Depolanan özellikler hangi ilişkiyi korumalı?

İlişkili tür, her protokol uyumunun seçtiği bir türü adlandırır. Bir protokol associatedtype Content: View tanımlıyorsa, protokole uyan sağlayıcının belirli bir içerik türü sunması gerekir. Uygulaması opak bir sonucun ardında gizli olsa bile generic kod bu türe Provider.Content olarak başvurabilir. Swift, ilişkili türleri ve generic kısıtlamaları Generics bölümünde belgeliyor.

Bu nedenle bileşenin birbiriyle bağlantılı iki bildirime ihtiyacı vardır: sağlayıcı protokolüyle kısıtlanmış bir generic parametre ve türü bu parametrenin ilişkili türü olan bir içerik özelliği. Yalnızca protokollere uyan iki bağımsız özellik bildirmek bu bağlantıyı ifade etmez.

SwiftUI de benzer bir sözleşme kullanır: View, associatedtype Body: View bildirir ve Swift, bu ilişkili türü özel bir görünümün body uygulamasından çıkarır. Sağlayıcınızın Content türü ile bileşenin Body türü ayrı ilişkili türlerdir; birbirlerine eşit olmaları gerekmez. Apple’ın Body belgelerine bakın.

Eksiksiz bir macOS örneği oluşturma

Bu örnek için SwiftUI kullanan ve dağıtım hedefi macOS 13 veya üzeri olan bir Xcode macOS uygulama projesi kullanın. Oluşturulan uygulama ve içerik görünümü bildirimlerini aşağıdaki tek dosyayla değiştirin; hedefte tam olarak bir @main bildirimi bulunsun. Apple, bu uygulama giriş yapısını ve WindowGroup kullanımını App belgelerinde açıklıyor.

Örnek, özellikle uzun süredir kullanılan ilişkili tür ve adlandırılmış generic söz dizimini kullanıyor. Opaque sonuçlar Swift 5.1'de uygulandı; daha sonra ele alınan isteğe bağlı birincil ilişkili tür söz dizimi ise Swift 5.7 gerektiriyor. Dosyanın tamamı güncel bir Swift 6 araç zinciri için tasarlandı. Alınan belgelere göre incelendi ancak bu ortamda Swift derleyicisi olmadığı için derlenmedi. Seçilen macOS dağıtım hedefi bir örnek önkoşuludur; temel özelliklerin tümünü destekleyen en eski işletim sistemi olduğu iddia edilmiyor. SE-0244 ve SE-0346 belgelerine bakın.

import SwiftUI

@MainActor
protocol TileProvider {
    associatedtype Content: View

    var title: String { get }
    func makeContent() -> Content
}

@MainActor
struct TextTile: TileProvider {
    let title: String
    let message: String

    func makeContent() -> Text {
        Text(message)
    }
}

@MainActor
struct CircleTile: TileProvider {
    let title: String
    let caption: String

    func makeContent() -> some View {
        HStack(spacing: 8) {
            Circle()
                .fill(Color.blue)
                .frame(width: 16, height: 16)
            Text(caption)
        }
    }
}

@MainActor
struct TileCard<Provider: TileProvider>: View {
    private let provider: Provider
    private let content: Provider.Content

    init(provider: Provider) {
        self.provider = provider
        self.content = provider.makeContent()
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(provider.title)
                .font(.headline)
            content
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(Color.gray.opacity(0.12))
    }
}

@MainActor
struct ContentView: View {
    var body: some View {
        VStack(spacing: 12) {
            TileCard(provider: TextTile(
                title: "Text tile",
                message: "Blue square"
            ))
            TileCard(provider: CircleTile(
                title: "Shape tile",
                caption: "Blue circle"
            ))
        }
        .padding()
        .frame(width: 360)
    }
}

@main
@MainActor
struct AssociatedViewDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

macOS hedefini derleyip çalıştırın. Beklenen sonuç, bir metin kutucuğu ve başlığının yanında mavi bir daire bulunan bir kutucuk içeren bir penceredir. Her ikisi de aynı generic bileşeni kullanır; sağlayıcı türü her çağrı noktasında bağımsız olarak çıkarılır.

Açık @MainActor ek açıklamaları, kullanıcı arayüzü üreten bu bildirimleri aynı aktör bağlamında tutar. Apple’ın güncel View belgeleri, protokolü ana aktör yalıtımıyla tanımlar. Buradaki ek açıklamalar, özel sağlayıcı protokolünün amaçlanan bağlamını da açıkça belirtir.

Generic bileşen türü neden korur?

İlk kart için Provider, TextTile olduğundan Provider.Content, Text olur. İkinci kart için Provider, CircleTile olur ve ilişkili içerik türü CircleTile.makeContent() ifadesinin opak sonucudur. İç içe geçmiş SwiftUI yerleşim türünü yazmadan bu türü CircleTile.Content üzerinden adlandırabilirsiniz.

Temel atama self.content = provider.makeContent() ifadesidir. Protokol, bu yöntemin tam olarak Provider.Content döndürmesini garanti eder; bu da depolanan özelliğin bildirilmiş türüyle aynıdır. Bu, çalışma zamanı tür karşılaştırması değil, generic kısıtlamadan türetilen derleme zamanı ilişkisidir. Swift’in Generics bölümü, kısıtlanmış generic parametreler üzerinden ilişkili türlere erişimi açıklar.

Uyumlu uygulamada some View kullanmak bu ilişkiyi ortadan kaldırmaz. Opaque sonuç, somut uygulamayı gizlerken tür kimliğini korur. Bu nedenle ilişkili tür tanığı olarak kullanılabilir. Swift, bu ayrımı Opaque and Boxed Protocol Types içinde açıklıyor.

Bu kartların uzmanlaşmış türleri farklıdır: TileCard<TextTile> ve TileCard<CircleTile>. Kaynak bildirimini yeniden kullanmak, her kartın evrensel ve tek bir depolanmış içerik türüne sahip olmasını gerektirmez.

Bağımsız opak veya varoluşsal özellikler neden çözüm olmaz?

Aşağıdakiler, eksiksiz programın yerine geçmeyen tanı parçalarıdır:

// Diagnostic fragment: no initializer establishes the opaque type.
let content: some View

// Diagnostic fragment: hides which conforming provider is stored.
let provider: any TileProvider

// Diagnostic fragment: stores an existential view value.
let content: any View

Opak depolanmış bir özellik, temel türünü belirleyen bir bildirim başlatıcısına ihtiyaç duyar. some View yazmak, herhangi bir bileşen başlatıcısının sonradan doldurabileceği ve çağıranın seçtiği generic bir parametre oluşturmaz. Örneğin let content: some View = Text("Sample"), o bildirim için bir tür belirler; özelliği başka bir özelliğin ilişkili türüne bağlamaz. Depolanmış özellik kuralı SE-0244: Opaque Result Types içinde belgelenmiştir.

any TileProvider gibi bir varoluşsal, farklı protokole uyan sağlayıcı türlerinin depolanmasına izin verir. Belirli uyumlu türü gizlediği için bildirim artık ilişkiyi Provider.Content olarak açığa çıkarmaz. Benzer şekilde any View, varoluşsal bir kapsayıcıdır; View protokolüne uyan adlandırılmış bir generic türle aynı sözleşme değildir. Swift’in kutulanmış protokol türleri açıklamasına bakın.

Swift, bazı generic çağrılar için varoluşsal bağımsız değişkenleri örtük olarak açabilir. Bu, her varoluşsalın her generic başlatıcıda kullanılabileceği anlamına gelmez. Özellikle SE-0352, bilinmeyen temel türün X<T> gibi generic bir sonuç içinde dışarı taşacağı durumlarda açmayı kısıtlar. Generic bir bileşen uzmanlaşması tam da bu sınıra takılabilir. Varoluşsalın her zaman kalıcı bir uzmanlaşmaya dönüştürülebileceğini varsaymak yerine, kartı oluşturmadan önce sağlayıcıyı generic tutun. SE-0352: Implicitly Opened Existentials belgesine bakın.

İçeriğin ne zaman üretilmesi gerektiğine göre depolama seçin

Örnek, her TileCard başlatmasında makeContent() çağrısını yapar ve döndürülen görünüm değerini depolar. Sonraki bileşen başlatmaları arasında kalıcı bir önbellek oluşturmaz. Her iki özelliği de tutmak istenen tür ilişkisini gösterir; ancak yalnızca ortaya çıkan görünüme ihtiyaç duyan bir bileşen, depolanan sağlayıcıyı çıkarabilir.

Bileşen, body değerlendirildiğinde içerik istemeliyse yalnızca sağlayıcıyı tutun. Aşağıdaki bileşen bildirimi, yukarıdaki protokol ve uygulamayla birlikte kullanılacak bir değişikliktir:

@MainActor
struct TileCard<Provider: TileProvider>: View {
    private let provider: Provider

    init(provider: Provider) {
        self.provider = provider
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(provider.title)
                .font(.headline)
            provider.makeContent()
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(Color.gray.opacity(0.12))
    }
}

Her iki tasarım da ilişkili türü korur. Aralarındaki fark doğrudan yöntem çağrısının nerede yer aldığına bağlıdır: ilk tasarımda başlatma sırasında, ikincide body değerlendirmesi sırasında. Bu zamanlamayı bilinçli seçin ve tam olarak bir kez çağrılmaya bağlı işler içermeyen içerik üretimi kullanın.

Örneği uyarlarken bu karar yardımcısını kullanın:

Bileşen gereksinimi Bildirim stratejisi Tür ilişkisi
Sağlayıcıyı ve ürettiği görünümü depolamak Generic sağlayıcı ve Provider.Content Hem sağlayıcı kimliğini hem içerik türünü korur
Body değerlendirmesi sırasında içerik üretmek Generic sağlayıcıyı depolamak Ayrı bir içerik değeri depolamadan ilişkili türü korur
Bilinen ortak içerik türüne sahip sağlayıcıları depolamak Kısıtlanmış bir varoluşsalı değerlendirmek Sağlayıcı kimliğini gizlerken belirtilen içerik eşitliğini korur
Farklı görünüm uygulamalarının türünü bilerek silmek Seçilen bir sınırda AnyView kullanmak Özgün görünüm türünü açığa çıkarmak yerine sarmalayıcı türü depolar

Birincil ilişkili türler generic kullanma gereğini ortadan kaldırır mı?

Birincil ilişkili türler, seçili tür eşitliği kısıtlamalarını daha kısa ifade etmenin bir yolunu sunar. Protokol sizin kontrolünüzdeyse, bu alternatif başlık Content türünü birincil yapar; protokolün geri kalan gereksinimleri aynı kalır:

// Alternative protocol header, requiring Swift 5.7 or later.
@MainActor
protocol TileProvider<Content> {
    associatedtype Content: View

    var title: String { get }
    func makeContent() -> Content
}

Bu bildirimle any TileProvider<Text>, ilişkili içerik türü Text olarak bilinen bir varoluşsalı tanımlar. Farklı sağlayıcı uygulamaları bu depolama türünü ancak uyumları tam olarak bu içerik eşitliğini sunuyorsa paylaşabilir. Bu söz dizimi bir ilişkili türü kısıtlar; protokolü generic bir yapıya dönüştürmez. SE-0346 ve SE-0353 belgelerine bakın.

Bu, keyfi ve bilinmeyen görünüm türlerini korunan tek bir somut türde birleştirmez. Özellikle CircleTile, opak bir içerik sonucu sunduğundan çağıranlar bu sonucu doğrudan Text olarak ele alamaz. Sağlayıcının ilişkili içeriği kısıtlanmadan kalmalıysa özgün generic bileşeni kullanın.

Kısıtlanmış varoluşsallar Swift 5.7'de uygulandı ve SE-0353 çalışma zamanı ile geriye dönük dağıtım kısıtlarını açıkça belirtiyor. Bu nedenle yalnızca derleyici desteği, bu isteğe bağlı tasarımın dağıtımla uyumlu olduğunu kanıtlamaya yetmez. Bu tasarımı benimsemeden önce hedef araç zincirinin kullanılabilirlik tanılamalarını kontrol edin. Ana örnek, kısıtlanmış varoluşsal depolamaya bağlı değildir. SE-0353'ün ABI tartışmasına bakın.

Derleme başarısız olduğunda neleri kontrol etmelisiniz?

Yerleşimi değiştirmeden önce türü protokol gereksiniminden depolanan özelliğe kadar izleyin:

  1. Protokolün View ile kısıtlanmış bir ilişkili tür bildirdiğini doğrulayın.
  2. Bileşenin bu protokolle kısıtlanmış, adlandırılmış bir generic sağlayıcı parametresine sahip olduğunu doğrulayın.
  3. Depolanan içeriği Provider.Content olarak tanımlayıp yöntem dönüş türüyle eşleştirin.
  4. Bir üst bileşenin sağlayıcıyı zaten any TileProvider türüne dönüştürüp dönüştürmediğini kontrol edin.
  5. Depolamadan önce bir değiştirici ekliyorsanız sonuç türünü kontrol edin: değiştirilen görünüm artık Provider.Content olmayabilir.
  6. Aktör yalıtımı tanılamalarını ilişkili tür tanılamalarından ayrı olarak inceleyin.

Apple, görünüm değiştiricilerinin bir görünümü başka bir görünümle sarmaladığını belgeliyor. Bu nedenle provider.makeContent().padding() ifadesini Provider.Content olarak bildirilmiş bir özelliğe atamak, atamanın karşılaması gereken sözleşmeyi değiştirir. Özgün içeriği depolayıp sunum değiştiricilerini body içinde uygulayın veya depolanan türü değiştirilmiş sonucu açıklayacak şekilde yeniden tasarlayın. View belgesine bakın.

Builder'ların da sürüm sınırı vardır. Apple’ın güncel belgeleri, Xcode 26 ve önceki sürümler için ViewBuilder davranışını, ardından Xcode 27'den başlayarak farklı içerik oluşturma davranışını açıklıyor. Örnek, özel builder gereksiniminden kaçınır: her sağlayıcı tek bir açık ifade döndürür. Örneği builder tabanlı sağlayıcı API'leriyle genişletirseniz, kullandığınız Xcode sürümüne ait belgeleri izleyin. ViewBuilder belgesine bakın.

Bilerek AnyView seçerseniz belgelenmiş davranışını göz önünde bulundurun: sarılmış görünüm türü değiştiğinde eski hiyerarşi yok edilir ve yenisi oluşturulur. Bu, depolamada ilişkili türü korumaktan ayrı bir tasarım kararıdır. Apple bunu AnyView içinde belgeliyor.

Bu bileşen için ilişkiyi depolama sınırında görünür tutun: generic bir Provider, Provider.Content sonucu ve aynı türde bir depolanan özellik. Böylece sağlayıcının iç yerleşimini değiştirirken derleyicinin ilişkili görünüm türünü bileşen boyunca taşımasını sağlayabilirsiniz.

Kaynaklar