Ortak bir arayüzü zorunlu kılarken farklı SwiftUI şekillerini tek bir kümede saklamak için protokolle kısıtlanmış başlatıcısı olan somut bir Hashable sarmalayıcı kullanın. Bu eğitimde, açık yinelenen öğe kuralları ve çağrılabilir şekil işlemleri içeren küçük bir Mac şekil paleti oluşturuluyor.
Görev belirli: yapay disk ve karo bileşenlerini bir araya getirmek, ilgisiz değerleri derleme zamanında reddetmek, yinelenenleri kaldırmak ve kalan bileşenleri bir SwiftUI penceresinde görüntülemek. Koleksiyon, SwiftUI’nin Shape protokolünü genişleten ortak bir protokol aracılığıyla birden çok bileşen türünü kabul eder.
Protokol değerlerinden oluşan bir küme bu koşulu neden karşılamaz?
Swift’in Set türü, öğe türünün Hashable protokolüne uymasını gerektirir. Bir varoluşsalın içindeki her değerin Hashable protokolüne uyduğunu belirtmek, o varoluşsal türe kendi uyumunu kazandırmaz. Bu nedenle PaletteShape protokolünün Hashable protokolünü devralması, Set<any PaletteShape> türünü kendiliğinden geçerli kılmaz. Apple koleksiyonun öğe kısıtını belgeler; Swift Evolution ise varoluşsal kapsayıcı ile onun altında yatan uyumlu değer arasındaki farkı açıklar. Set, Implicitly Opened Existentials.
any PaletteShape gibi bir varoluşsal, farklı uyumlu türleri tutabilir. Sorun eşitliktir: devralınan Equatable gereksinimi, aynı Self türündeki iki değeri karşılaştırırken iki varoluşsal kapsayıcı farklı somut türler tutabilir. Swift’in varoluşsal değerler için genişletilmiş desteği, bu kapsayıcılar için evrensel bir eşitlik işlemi sağlamaz. Unlock existentials for all protocols.
Bu seçenekler sorunun farklı kısımlarını çözer:
| Gösterim | Farklı somut türler? | Ortak arayüz zorunlu mu? | Küme öğesi olmaya uygun mu? |
|---|---|---|---|
[any PaletteShape] |
Evet | Evet | Bir dizi, küme benzersizliği sağlamaz |
Set<S> ve S: PaletteShape |
Seçilen tek bir S |
Evet | Evet, bu tek somut tür için |
Set<AnyHashable> |
Evet | Şekil kısıtı yok | Evet |
Aşağıdaki Set<AnyPaletteShape> |
Evet, sarmalayıcıların içinde | Evet, oluşturulurken | Evet |
AnyHashable, tür silinmiş eşitlik ve karma işlemi sağlar; ancak başlatıcısı genel olarak hashable değerleri kabul eder. Bu nedenle yalnızca bu türü içeren bir küme, bir metni veya tamsayıyı da kabul edebilir. Aşağıdaki özel sarmalayıcı, kümeye girişi uygulamanın şekil protokolüyle sınırlar ve türü silinmiş hashable değerle birlikte şekil arayüzünü de korur. AnyHashable.
Yinelenen şekil sayılacak olan nedir?
Sarmalayıcıyı uygulamadan önce eşitliğe karar verin. Bu palet için iki bileşen, somut bileşen türleri aynıysa ve saklanan değerleri eşitse yinelenen sayılır. Sample etiketli bir disk ile Sample etiketli bir karo ayrı öğeler olarak kalır. Aynı etikete sahip iki disk tek öğeye dönüşür.
Her örnek bileşende saklanan tek özellik etiket olduğundan, sentezlenen eşitlik ve karma bu etiketi içerir. Bu, açıklayıcı bir model tercihidir: yalnızca görsel benzerlik eşitliği belirlemez. Apple, saklanan özellikleri hashable olan yapılarda Hashable uyumunun otomatik sentezlenmesini belgeler ve eşit değerlerin aynı bileşenleri karma oluşturucularına vermesini şart koşar. Hashable.
Sarmalayıcı, aynı tür kuralını açıkça belirtmek için eşitlik ve karma işlemine bir tür tanımlayıcısı da ekler; böylece türü silinmiş karşılaştırmalarla ilgili varsayımlara dayanmaz. AnyHashable, bazı değerleri kanonik gösterimler kullanarak farklı temel türler arasında karşılaştırabilir; Apple bunu koşulsuz bir somut tür ayırt edicisi olarak tanımlamaz. ObjectIdentifier, metatürleri destekler ve bu örneğin genel türü tanımlamasını sağlar. AnyHashable, ObjectIdentifier.
Mac paleti örneğinin tamamını oluşturma
Xcode’da macOS SwiftUI uygulama projesi açın ve Swift 5.7 veya daha yeni bir derleyici kullanın. Tekrarlanabilir bir başlangıç noktası için Swift 5 dil kipini ve 13.0 veya üzeri macOS dağıtım hedefini seçin. Dağıtım hedefi bu eğitim için temkinli bir tercihtir; aşağıdaki her API’nin macOS 13 gerektirdiği anlamına gelmez.
Derleyici sürümü önemlidir; çünkü örnek, devralınan protokolleri ilişkili tür ve Self gereksinimlerini içeren any PaletteShape türünü saklar. SE-0309, bu genişletilmiş varoluşsal desteğin Swift 5.7’de uygulandığını belirtir. Örnek, daha yeni bir genelleştirilmiş varoluşsal uyum özelliği gerektirmez. Unlock existentials for all protocols.
Oluşturulan uygulama bildirimini ve içerik görünümünü aşağıdaki eksiksiz kaynak dosyayla değiştirin. Uygulama hedefinde yalnızca bir @main bildirimi bulunsun. Örnek; bileşen tanımlarını, sarmalayıcıyı, örnek değerleri, kontrolleri, görünümü ve uygulama giriş noktasını içerir. Araştırma ortamında Swift bulunmadığından bu kod incelendi, ancak burada derlenmedi veya çalıştırılmadı.
import SwiftUI
protocol PaletteShape: Shape, Hashable {
var label: String { get }
}
struct Disk: PaletteShape {
let label: String
func path(in rect: CGRect) -> Path {
Path(ellipseIn: rect)
}
}
struct Tile: PaletteShape {
let label: String
func path(in rect: CGRect) -> Path {
var result = Path()
result.addRect(rect)
return result
}
}
struct AnyPaletteShape: Hashable {
private let base: any PaletteShape
private let typeID: ObjectIdentifier
private let key: AnyHashable
init<S: PaletteShape>(_ value: S) {
base = value
typeID = ObjectIdentifier(S.self)
key = AnyHashable(value)
}
var label: String { base.label }
func path(in rect: CGRect) -> Path {
base.path(in: rect)
}
static func == (
lhs: AnyPaletteShape,
rhs: AnyPaletteShape
) -> Bool {
lhs.typeID == rhs.typeID && lhs.key == rhs.key
}
func hash(into hasher: inout Hasher) {
hasher.combine(typeID)
hasher.combine(key)
}
}
func makePalette() -> Set<AnyPaletteShape> {
var result: Set<AnyPaletteShape> = [
AnyPaletteShape(Disk(label: "Disk")),
AnyPaletteShape(Tile(label: "Tile"))
]
let duplicate = result.insert(
AnyPaletteShape(Disk(label: "Disk"))
)
precondition(!duplicate.inserted)
precondition(result.count == 2)
precondition(result.contains(
AnyPaletteShape(Tile(label: "Tile"))
))
let sameLabel: Set<AnyPaletteShape> = [
AnyPaletteShape(Disk(label: "Sample")),
AnyPaletteShape(Tile(label: "Sample"))
]
precondition(sameLabel.count == 2)
return result
}
struct ContentView: View {
private let shapes = makePalette()
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Shape Palette")
.font(.title)
ForEach(
shapes.sorted { $0.label < $1.label },
id: \.self
) { item in
HStack(spacing: 16) {
GeometryReader { geometry in
item.path(in: CGRect(
origin: .zero,
size: geometry.size
))
.fill(Color.blue)
}
.frame(width: 56, height: 56)
Text(item.label)
}
}
}
.padding(24)
.frame(minWidth: 300, minHeight: 220)
}
}
@main
struct ShapePaletteApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
PaletteShape, SwiftUI’nin gerçek Shape protokolünü devralır. Disk ve karo, Apple’ın belgelediği çizim gereksinimi olan path(in:) öğesini sağlar. Uygulamaları, elips ve dikdörtgen oluşturmak için belgelenmiş Path işlemlerini kullanır. Kare görüntüleme çerçevesi, örnek elipsin daire gibi görünmesini sağlar. Shape, Path.
Kısıtlanmış başlatıcı giriş kuralıdır: init<S: PaletteShape> yalnızca ortak protokole uyan bir türü kabul eder. Saklanan özellikler özeldir; böylece çağıranlar saklanan şekli veya eşitlik anahtarını birbirinden bağımsız olarak değiştiremez. AnyPaletteShape("Disk") derleme zamanında uyum hatası vermelidir; çünkü String, PaletteShape protokolüne uymaz. Bu kasıtlı reddetme örneğini çalıştırılabilir dosyanın dışında tutun.
Kümenin tek bir somut öğe türü olsa da her sarmalayıcı farklı bir somut şekli saklayabilir. label ve path(in:) üyeleri bu paletin ihtiyaç duyduğu işlemleri iletir. Sarmalayıcının kendisi Hashable protokolünü bildirir; Shape protokolünü bildirmez.
Ekleme, üyelik ve görüntüleme sırası nasıl doğrulanır?
Uygulamayı hata ayıklama derlemesinde çalıştırın. İçerdiği kontroller üç beklenen sonucu ifade eder: eşit ikinci bir disk eklendiğinde inserted false döner, üyelik kontrolü eşit bir karoyu bulur ve etiketleri aynı olsa bile farklı bileşen türleri kümede kalır. Apple, insert(_:) yönteminin hem ekleme bayrağını hem de sonuçtaki üyeyi döndürdüğünü belgeler. Set.
Beklenen pencerede önce Disk, ardından Tile olmak üzere mavi grafiklere sahip iki satır bulunur. Kümenin tanımlı bir öğe sırası olmadığından sıralama ayrı bir görüntüleme işlemidir. Sonuçtaki dizi, ForEach’in rastgele erişimli koleksiyon gereksinimini de karşılar; id: \.self, satır tanımlayıcısı olarak sarmalayıcının hashable değerini kullanır. Set, ForEach.
Bu sıralama, etiketleri farklı olan sağlanan değerler için yeterlidir. Gelecekteki bileşenler aynı etiketleri paylaşırsa deterministik bir ikincil sıralama alanı ekleyin. Satırlar düzenlenebilir olur ve etiketler değişirken kimliklerini korumaları gerekirse, kararlı bir tanımlayıcı ekleyin ve bu tanımlayıcının küme eşitliğine katılıp katılmayacağına ayrıca karar verin.
Küme eklemesinin mevcut eşit bir bileşenin yükünü değiştirmesini beklemeyin. Değiştirme amaçlanan işlem olduğunda Apple, update(with:) yöntemini sağlar. Bu ayrım, sonraki bir model sunum özelliklerini eşitliğin dışında bırakırsa önem kazanır: eşit bir değer eklemek, görünümünü yenilemek yerine mevcut üyeyi koruyabilir. Set.
Sarmalayıcının sınırları nelerdir?
Hashable depolama, tüm SwiftUI özelliklerini korumaz
Sarmalayıcı, statik bir palet için yol oluşturmayı iletir. Temel şeklin animasyonlu verilerini, içe doğru daraltma işlemlerini veya View protokolünden devralınan tüm üyeleri iletmez. Döndürülen yolu çizmek, animasyonu koruyan bir şekil silme işlemi oluşturmaz. SwiftUI’nin Shape protokolü Animatable ve View protokollerini de devralır; tek bir çizim yöntemini iletmek bu sözleşmenin tamamını yeniden oluşturmaz. Shape.
Başka bir API somut türü silinmiş bir şekle ihtiyaç duyuyorsa SwiftUI, macOS 13 ve sonrasında kullanılabilen AnyShape türünü sağlar. Belgelenen işlevi, şeklin somut türünü silmektir ve listelenen uyumları arasında Hashable yoktur. Bu tür bir görüntüleme sınırını çözebilir; ancak burada oluşturulan eşitlik kuralını veya protokolle kısıtlanmış küme öğesini sağlamaz. AnyShape.
Yeni derleyici desteği, küme öğesi kısıtını ortadan kaldırmaz
Swift 5.7’de uygulanan SE-0352, uygun genel çağrılar için varoluşsalın temel türünün açılmasına olanak tanır. Güncel metin, Swift 6 dil kipindeki davranışı da kaydeder. Bir değeri çağrı için açmak, any PaletteShape türünü kümenin öğe parametresine uygun bir Hashable türü yapmaz. Bu tasarımı söz konusu derleyici nesilleri arasında kullanırken somut sarmalayıcıyı koruyun. Implicitly Opened Existentials.
Modeli genişletmek eşitlik konusunda karar vermeyi gerektirir
Örnekte değişmez yapılar kullanılır. Boyutlar veya başka saklanan yapılandırmalar eklerken, bunları değiştirmenin kümenin farklı bir üyesini oluşturup oluşturmayacağına karar verin. Sentezlenen karma işlemi, ek hashable saklanan özellikleri kapsar. Özel eşitlik için aynı temel bileşenleri aynı sırayla karma işlemine uygulayın; yalnızca karma değerlerinin eşleşmesi eşitliği asla kanıtlamaz. Hashable.
Uygulamanın bileşen varyantları kalıcı olarak sabitlenmişse, hashable ilişkili değerler içeren bir enum da makul bir tasarımdır: desteklenen durumları tek yerde toplar. Aynı başlatıcı üzerinden daha fazla uyumlu bileşen türünün eklenmesi isteniyorsa sarmalayıcı kullanışlıdır; böylece merkezi bir durum listesi genişletilmez. Bu tercih, uygulamanın genişletme ihtiyaçlarına bağlıdır.
Bu Mac paleti için uygulama, her gereksinimi somut bir yere bağlar: protokol kabul edilen şekilleri tanımlar, başlatıcı girişi zorunlu kılar, eşitlik yinelenenleri tanımlar ve yol iletimi görüntülemeyi sağlar. Yeni değişmez uyumlu bileşenleri ekleme sırasında sararak ekleyin ve yinelenenlerin anlamı değiştiğinde kontrolleri genişletin.
Kaynaklar
- Set — Apple
- AnyHashable — Apple
- Hashable — Apple
- ObjectIdentifier — Apple
- Shape — Apple
- Path — Apple
- ForEach — Apple
- AnyShape — Apple
- Unlock existentials for all protocols — Swift Evolution
- Implicitly Opened Existentials — Swift Evolution