Genel bir SwiftUI bileşeni oluşturmak için başlatıcısını bir protokolde zorunlu kılın veya bileşeni döndüren bir closure enjekte edin. Bu eğitimde, derleyici ve SDK sınırları açıkça belirtilerek aynı sentetik öğe bir macOS penceresinde her iki yaklaşımla da görüntüleniyor.

Genel bir bileşen neyi taahhüt etmelidir?

Bir macOS uygulamasının küçük bir öğe kartı görüntülediğini varsayalım. Çağıranların sunumu seçmesine olanak tanırken, kartı bir öğeden oluşturacak yeniden kullanılabilir bir panel istiyorsunuz. Temel soru, oluşturma sorumluluğunun kime ait olduğudur: bileşenin tanımlanmış arayüzüne mi, yoksa bileşeni sağlayan çağırana mı?

Genel tür kısıtlaması, uygulamanın bu kısıtlamada belirtilen yeteneklere erişmesini sağlar. Component: View yazmak, bileşenin bir SwiftUI görünümü olduğunu belirtir; bileşenin init(item:) adlı bir başlatıcısı olduğunu belirtmez. Swift’in Generics bölümü protokol kısıtlamalarını açıklar; Apple’ın View belgeleri ise görünümün zorunlu body özelliğini açıklar.

Bu nedenle aşağıdaki kod parçası kasıtlı olarak geçersizdir:

// Invalid fragment: View does not require init(item:).
@MainActor
func construct<Component: View>(item: Item) -> Component {
    Component(item: item)
}

Bu başlatıcıyı tek bir somut bileşene eklemek, onu yalnızca View ile kısıtlanmış her genel tür parametresi üzerinden kullanılabilir hâle getirmez. Yeniden kullanılabilir API’nin açık bir oluşturma sözleşmesine ihtiyacı vardır. Bileşeni değiştirmeden önce şu karar tablosunu kullanın:

Oluşturma gereksinimi Uygun arayüz Gerekçe
Katılan her bileşen aynı öğeyi kabul eder init(item:) gerektiren protokol Oluşturma, bileşen ailesinin arayüzüne aittir.
Çağıranlar ek yapılandırma veya değiştiriciler seçer Genel bileşeni döndüren fabrika closure’ı Oluşturma çağrı noktasında kalır.
Çağıranın elinde tamamlanmış bir bileşen vardır Genel bileşen değerini saklayın Oluşturma zaten gerçekleşmiştir.
Sunum, sınırlı sayıda düzen arasında geçiş yapar Alternatifleri bir SwiftUI görünümünde birleştirin Çevreleyen API somut bir görünüm yapısını koruyabilir.

Bu tablo, belgelenmiş dil kurallarından türetilmiş bir tasarım yardımcısıdır. İlk iki seçenek, davranışları doğrudan karşılaştırılabilsin diye aşağıda birlikte uygulanmıştır.

Eksiksiz bir macOS örneği oluşturun

SwiftUI kullanan ve dağıtım hedefi macOS 11 veya üzeri olan bir Xcode macOS uygulama hedefi kullanın. Örnek, eşleşen Apple SDK’sıyla Swift 6 derleyicisi için yazılmıştır. Açıkça belirtilen @MainActor ek açıklamaları Swift 5.5 veya üzerini gerektirir; bu nedenle listeleme daha eski derleyiciler için tasarlanmamıştır. Apple, WindowGroup’u macOS 11’den itibaren kullanılabilir ve bir uygulamanın görünüm hiyerarşisi için kapsayıcı olarak belgeler. Bkz. WindowGroup.

Oluşturulan uygulama giriş noktası dosyasını aşağıdaki eksiksiz programla değiştirin. Hedefte yalnızca bir @main uygulama bildirimi bulunduğundan emin olun. Oluşturulan ContentView dosyası kullanılmadan kalabilir. Pakete, harici varlıklara veya indirilmiş örnek verilere gerek yoktur.

import SwiftUI

struct Item {
    let title: String
    let detail: String
}

@MainActor
protocol ItemComponent: View {
    init(item: Item)
}

@MainActor
struct ItemCard: ItemComponent {
    let item: Item

    init(item: Item) {
        self.item = item
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            Text(item.title)
                .font(.headline)
            Text(item.detail)
        }
        .padding()
        .background(Color.gray.opacity(0.12))
        .cornerRadius(8)
    }
}

@MainActor
struct ContractPanel<Component: ItemComponent>: View {
    let item: Item

    init(item: Item, component: Component.Type) {
        self.item = item
    }

    var body: some View {
        Component(item: item)
    }
}

@MainActor
struct FactoryPanel<Component: View>: View {
    private let makeComponent: @MainActor () -> Component

    init(make: @escaping @MainActor () -> Component) {
        self.makeComponent = make
    }

    var body: some View {
        makeComponent()
    }
}

@MainActor
struct DemoView: View {
    private let item = Item(
        title: "Sample tile",
        detail: "Blue circle on a neutral background"
    )

    var body: some View {
        VStack(alignment: .leading, spacing: 18) {
            Text("Initializer contract")
            ContractPanel(item: item, component: ItemCard.self)

            Text("Injected factory")
            FactoryPanel {
                ItemCard(item: item)
                    .padding(.leading, 12)
            }

            Text("Alternative factory output")
            FactoryPanel {
                Text(item.title + ": " + item.detail)
            }
        }
        .padding(24)
        .frame(width: 420)
    }
}

@main
@MainActor
struct ComponentDemoApp: App {
    var body: some Scene {
        WindowGroup {
            DemoView()
        }
    }
}

macOS uygulama hedefini çalıştırın. Beklenen sonuç, iki kart sunumu ve düz metin sunumu içeren tek bir penceredir. İkinci kartta, çağıranın sağladığı ek bir başlangıç dolgusu bulunur. Bunlar ölçülmüş sonuçlar değil, API davranışını gösteren sentetik örneklerdir. Araştırma ortamında Swift derleyicisi kurulu olmadığından bu listeleme incelendi, ancak burada derlenip çalıştırılmadı.

Ana aktör ek açıklamaları, kullanıcı arayüzü oluşturma sınırını açıkça belirtir. Apple’ın güncel View bildirimi @MainActor ve @preconcurrency ek açıklamalarıyla işaretlidir; belgelerinde ayrıca, uyumluluk bir türün özgün bildiriminde yer aldığında yalıtımın nasıl devralındığı da açıklanır. Bkz. View.

Başlatıcı sözleşmesi nasıl çalışır?

ItemComponent, View protokolünü genişletir ve yalnızca bir oluşturma gereksinimi ekler: init(item:). Swift, başlatıcı gereksinimlerini doğrudan protokollerde destekler. Uyumluluk sağlayan tür uygulamayı sunar; protokol ise başlatıcı gövdesi olmadan imzayı belirtir. Bkz. Protocols: Initializer Requirements.

Bu gereksinim, Component(item: item) ifadesini ContractPanel içinde geçerli kılar. component: Component.Type bağımsız değişkeni, genel tür çıkarımı için somut bir tür sağlar: ItemCard.self iletildiğinde bu panelin bileşen türünün ItemCard olduğu belirlenir. Panelin metatürü saklaması gerekmez; çünkü body genel tür parametresi üzerinden oluşturma yapar.

Bu sözleşme, her bileşenin aynı girdiden oluşturulabilmesi gerektiğinde kullanışlıdır. Örneğin, uygulamanın sahibi olduğu kompakt ve genişletilmiş öğe sunumları ailesi bu gereksinimi paylaşabilir. Böylece çağıran, panelin oluşturma ifadesini yeniden yazmadan seçilen bileşen türünü değiştirebilir.

Uygulamanın ek yapılandırmaya ihtiyaç duyması durumunda bu yaklaşımın ödünleşimi ortaya çıkar. init(item:) zorunlu kılındığında, tercih edilen başlatıcısı bir açıklama veya düzen ayarını da kabul etse bile uyumluluk sağlayan her bileşenin bu oluşturma yolunu sunması gerekir. Bu ek bilgi çağırana aitse, aşağıdaki fabrika yaklaşımı gereksinimi genellikle daha doğrudan ifade eder.

Sınıf tabanlı uygulamalarda, bir protokol gereksinimini karşılayan başlatıcı, sınıf final olmadığı sürece required olmalıdır. Eşleşen bir üst sınıf belirlenmiş başlatıcısını da geçersiz kılan bir alt sınıfın hem required hem de override kullanması gerekebilir. ItemCard gibi yapılar required kullanmaz. Bu ayrımlar Protocols bölümünde belgelenmiştir.

Oluşturma ne zaman bir fabrikaya taşınmalı?

FactoryPanel, @MainActor () -> Component türünde bir işlev kabul eder. Tek taahhüdü, işlev çağrıldığında seçilen bileşen türünün üretilmesidir. Closure bir başlatıcı çağırabilir, yapılandırmayı yakalayabilir veya değiştiriciler uygulanmış bir görünüm döndürebilir.

Program pratik farkı gösterir: fabrika dolgulu bir ItemCard döndürebilir veya sonuçta ortaya çıkan türlerden herhangi birinin ItemComponent protokolüne uyum sağlamasını gerektirmeden Text döndürebilir. Apple, görünüm değiştiricilerinin girdilerini başka bir görünüm içine sarabileceğini açıklar; bu nedenle burada derleyicinin fabrikanın sonuç türünü çıkarmasına izin vermek yararlıdır. Bkz. View.

Panel closure’ı daha sonra çağırmak üzere sakladığından, başlatıcı closure’ı @escaping olarak işaretler. Swift’in Closures bölümü kaçış yapan closure’ları ve bunların çevresindeki değerleri yakalayabilmesini belgeler. Bu örnekte closure, çevresindeki görünümde kullanılan sentetik öğeyi yakalar.

Fabrikanın girdilerini dikkatle seçin. Sıfır bağımsız değişkenli bir fabrika, oluşturma için gereken her şeyi çağıranın yakaladığı anlamına gelir. Panelin kendisi bir öğe seçiyorsa, parametreli bir fabrika bu sorumluluğu daha açık biçimde ifade edebilir. Aşağıdaki tanım, mevcut Item ile birlikte kullanılacak bir bileşen tanımıdır; ikinci bir eksiksiz uygulama değildir:

@MainActor
struct ItemFactoryPanel<Component: View>: View {
    let item: Item
    private let makeComponent: @MainActor (Item) -> Component

    init(
        item: Item,
        make: @escaping @MainActor (Item) -> Component
    ) {
        self.item = item
        self.makeComponent = make
    }

    var body: some View {
        makeComponent(item)
    }
}

Bu tanımla panel öğeyi sağlar, çağıran ise sunumu sağlar. Fabrika işini görünüm değerini üretmekle sınırlı tutun: örneğin oluşturma sözleşmesi tek seferlik çalıştırma, kalıcı depolama veya önbelleğe alma vaat etmez.

Fabrikaların bir sonuç oluşturucuya ihtiyacı var mı?

Eksiksiz programdaki fabrikalar sıradan closure’lardır. Her biri tek bir ifade döndürdüğünden özel bir oluşturucu ek açıklaması gerekmez. Birden fazla alt görünüm gerekiyorsa bu API için tek bir VStack döndürmek yeterlidir; yığın alt görünümleri içerir.

Fabrika parametresinin kendisinin bildirimsel ifade dizilerini veya koşullu içeriği kabul etmesi gerektiğinde sonuç oluşturucu kullanışlı hâle gelir. Swift Evolution, standartlaştırılmış sonuç oluşturucu özelliğinin Swift 5.4’te uygulandığını kaydeder. Bkz. SE-0289: Result builders.

Oluşturucu söz dizimini kullanırken SDK sınırını gözetin. Apple’ın güncel belgeleri, Xcode 26 ve önceki sürümlerde görünüm üreten parametreler için @ViewBuilder kullanımını açıklar. Xcode 27 ve sonraki sürümlerde ise türden bağımsız oluşturmayı açıklar ve bu davranışı belirtmek için ContentBuilder takma adını önerir. Bkz. ViewBuilder ve ContentBuilder. Burada kullanılan sıradan closure, bu daha yeni takma adı gerektirmez.

Örtük veya varoluşsal türler neden sözleşmenin yerini almaz?

some View, belirli bir temel sonuç türünü gizlerken onun kimliğini korur. Bu programdaki body özellikleri için uygundur. View arayüzüne init(item:) eklemez. any View olarak yazılan kutulanmış bir değer, temel türünü farklı biçimde gizler ve aynı statik tür kimliğini korumaz. Bkz. Opaque and Boxed Protocol Types.

Bunu, varoluşsal değerlerin genel işlevlere hiçbir zaman iletilemeyeceğine dair kapsamlı bir iddiaya dönüştürmeyin. SE-0352, Swift 5.7’de örtük olarak açılan varoluşsal türleri sunarak uygun çağrıların genel bir parametreyi varoluşsal değerin temel türüne bağlamasına olanak tanıdı. Ayrıca, silme işleminden sonra sonucun temsil edilemediği genel sarmalayıcı içindeki açılmış türü içeren sonuçlar da dâhil olmak üzere kısıtlamaları belgeler. Bkz. Implicitly Opened Existentials.

Bu panel görevi için genel bir parametre aracılığıyla somut bileşen türünü korumak yeterlidir. Çağıranların görünümü nasıl oluşturacaklarını seçebilmesi için varoluşsal tür depolaması eklemeye gerek yoktur.

Oluşturma hatasını nasıl teşhis edebilirsiniz?

Başarısız olan ifadeyi izleyin ve o konumda kullanılabilen sözleşmeyi inceleyin:

  1. Component(item:) başarısız olursa, genel tür kısıtlamasının tam olarak bu başlatıcı imzasını gerektiren bir protokol içerdiğini kontrol edin.
  2. Derleyici Component türünü çıkaramıyorsa ItemCard.self gibi somut bir metatür veya somut bir dönüş ifadesi olan bir fabrika sağlayın.
  3. Bir bileşenin çağırana özgü yapılandırmaya ihtiyacı varsa, protokole ilgisiz gereksinimler eklemek yerine oluşturmayı fabrikaya taşıyın.
  4. Bir closure’ı saklamak kaçış yapan closure tanılamasını tetikliyorsa başlatıcının @escaping ek açıklamasını kontrol edin.
  5. Oluşturma, aktör yalıtımı tanılamasını tetikliyorsa hem çağrı noktasının hem de fabrikanın işlev türünün amaçlanan ana aktör sınırını belirttiğini kontrol edin.
  6. Varoluşsal bir tür genel sonuç hatasına yol açıyorsa, derleyici yükseltmesinin sınırlamayı kaldıracağını varsaymadan önce SE-0352’deki sonuç türü kısıtlamalarını inceleyin.

Paylaşılan bir macOS bileşen ailesinde, türlerin tekdüze biçimde oluşturulması arayüzün bir parçasıysa başlatıcıyı zorunlu kılın. Çağıran sunumu bir araya getirip yapılandırmalıysa enjekte edilen bir fabrika kullanın. Her iki yol da panel için gereken somut bileşen türünü korurken eksik oluşturma taahhüdünü açıkça belirtir.

Kaynaklar