Açık Kaynak Pazartesi'si: AYA — Açık Yazılım Ağı iOS ve macOS UygulamalarıTurkish
Platform: iOS 18+ · macOS 15+ · Swift 6.0 · SwiftUI
Kaynak Kod: github.com/eser/aya.is — PR #89 https://github.com/eser/aya.is/pull/89
AYA (Açık Yazılım Ağı), toplumsal fayda motivasyonuyla kaliteli BT çözümleri üreten gönüllülerin bir araya gelip projeler hayata geçirmesini sağlayan bir girişim. Bu topluluğu, birbirini açık yazılım toplulukları ve çeşitli ortaklaşa yürütülen projeler aracılığıyla tanıyan, BT ve teknoloji alanında faaliyet gösteren gönüllüler oluşturuyor. Üretilen bu projeler açık kaynak lisanslarıyla, başta resmi kurumlar ve sivil toplum kuruluşları olmak üzere herkesin kullanımına ve denetimine açık BT çözümlerine dönüşüyor.
BT ve teknoloji ekosisteminde üreten insanların topluluğu yönlendirme konusunda çok kritik bir rol oynadığı düşünüyorum. AYA da tam bu noktada yazılar, etkinlikler, geliştirici profilleri, açık kaynak ürünler aracılığıyla tüm bunları tek bir çatı altında birleştiren bir topluluk.
Geçtiğimiz hafta, AYA platformu üzerinde gördüğüm iOS ve macOS istemcileri eksikliğini, tek bir SwiftUI kod tabanından hem iPhone hem iPad hem Mac uygulaması çıkartarak tamamlamaya çalıştım. Bu yazıda, iki platformu tek kod tabanından beslemek ve platform-spesifik kodu minimumda tutarak, bu uygulamaları nasıl App Store'a gönderdiğimi anlatmaya çalıştım.
Apple App Store review süreçlerinin ardından bu uygulamaların kısa sürede App Store'da erişilebilir olacaklarını ümit ediyorum.
Tek Kod Tabanı, İki Platform
Yazılım mimarisi anlamında aldığım ilk mimari karar platform başına ayrı kod tabanı yazmak yerine, paylaşılan bir SPM kütüphanesi oluşturmaktı. iOS ve macOS arasındaki gerçek farkın aslında çok küçük olduğunu düşünerek, sadece birkaç pencere boyutu ve layout kararını macOS platformuna özel olarak ayarlayıp, mükerrer kod miktarını olabildiğince az tutmaya çalıştım. *AYAKit adını verdiğim bu kütüphane; model katmanını, networking katmanını ve tüm UI bileşenlerini barındırıyor ve platform-spesifik kod bu sayede yalnızca App giriş noktalarında kalıyor:
Sources/
├── App/
│ ├── iOS/ # AYAApp.swift, RootView.swift
│ └── macOS/ # AYAApp.swift, ContentView.swift
├── Models/ # Domain modelleri
├── Networking/ # API client, hata tipleri, locale yönetimi
└── UI/
├── Tokens/ # Design system (renkler, tipografi, spacing)
├── Components/ # Tekrar kullanılabilir kartlar, arama çubuğu
└── Screens/ # Feed, StoryDetail, ActivityDetail, ProfileDetail
iOS tarafında *RootView, macOS tarafında ContentView view'leri aslında aynı FeedNavigationView'ı render ediyor. macOS'ta minimum 700pt genişlik ve NavigationSplitView, iOS'ta standart NavigationStack. 215 dosyalık bu yapıyla PR'da platform-spesifik kod 30 satırı geçmiyor. Yalnızca 30 satır. Geri kalan her şey bu iki platform arasında ortak olarak paylaşılıyor.
MVVM ve @Observable
ViewModel'ler @Observable macro'su ve @MainActor isolation ile işaretli. @Observable migration'ının sessiz tuzaklarını — init'te yan etki barındırmanın @State geçişinde nasıl patladığını — @Observable yazımda detaylıca anlatmıştım. Kaçırdıysanız aşağıdaki bağlantıdan bu konuya göz atabilirsiniz.
Alp Özcan@alpoezcan·Feb 20 ArticleMinik Swift İpuçları: @Observable ≠ ObservableObjectBu seride, Swift ile iOS ve MacOS uygulamaları geliştirirken karşılaşılan bazı problemleri kısaca paylaşmayı düşünüyorum. Bu yazı için seçtiğim konu: @Observable geçişinin (migration) sessiz ama...12
Bu yazıdaki temel çıkarımım: init fonksiyonunun saf olması, yalnızca property ataması yapmasıydı. AYA uygulamarında da bu prensibi uygulayarak ViewModel'lerin init'leri yan etki üreten işlerden arındırdım. Yani veri yükleme çağrıları sadece *.task modifier'ı üzerinden tetikleniyor.
*FeedViewModel uygulamanın kalbi. Dört farklı içerik türünü — yazılar, etkinlikler, kişiler, ürünler — yönetiyor, arama debounce'u yapıyor ve cursor-based pagination ile sonsuz kaydırmayı (infinite scrolling) destekliyor. Debounce'da 400 milisaniyelik bekleme süresi var, kullanıcı yazmayı bıraktıktan sonra API'ye istek gönderiliyor.
ViewModel içinde *Task { } ile oluşturulan unstructured task'lar, ViewModel'in yaşam döngüsünden bağımsız çalışıyor. Task cancellation yazımda da bu pattern'e değinmiştim: kullanıcı ekrandan çıktığında ViewModel deallocate olsa bile task arka planda koşmaya devam eder — orphaned task problemi. Çözüm ise deinit'te task'ı açıkça iptal etmek olduğu için burada da aynı mekanizmayı kullandım.
Alp Özcan@alpoezcan·Feb 25 ArticleMinik Swift İpuçları: Sahipsiz (Orphan) Task TuzağıBu seride, Swift ile iOS ve MacOS uygulamaları geliştirirken karşılaşılan bazı problemleri kısaca paylaşmayı düşünüyorum. Bu yazı için seçtiğim konu: Sahipsiz (Orphan Task Tuzağı). Kategori: Swift /...1
Networking: Protocol-Based API Client
ViewModel'ler oluşturduğum bir APIClientProtocol aracılığıyla API'ye ulaşıyor. Bu sayede *Sendable conformance'ı ile protocol'ü concurrent context'lerde güvenle kullanabiliyorum:
public protocol APIClientProtocol: Sendable {
func fetchStories(locale: String, cursor: String?) async throws -> APIResponse<Story>
func fetchActivities(locale: String, cursor: String?) async throws -> APIResponse<Activity>
func fetchProfiles(locale: String, filterKind: String?, cursor: String?) async throws -> APIResponse<Profile>
func search(locale: String, query: String, cursor: String?) async throws -> APIResponse<SearchResult>
// ...
}
Somut implementasyon (concrete class) olan *APIClient, URLSession üzerinden [https://api.aya.is](https://api.aya.is/)* ile haberleşiyor. JSONDecoder'da convertFromSnakeCase stratejisi tercih ettim çünkü API snake_case döndürüyor. Ancak Swift tarafında camelCase kullanıyorum. Cache limitlerini ise 50 MB bellek, 200 MB disk olarak ayarladım. Sonsuz kaydırma yapan bir uygulamada cache'in kontrolsüz büyümesi, Task cancellation yazımda anlattığım bellek baskısını sessizce arttırdığı için, fark edilmesi güç problemlere zemin hazırlar.
Bahsettiğim bu protocol-based yazılım mimarisi tasarımının en büyük avantajı test edilebilirlik. Çünkü *MockURLProtocol ile gerçek ağ isteği yapmadan tüm akışları test edebilmemize olanak sağlıyor. Ayrıca Uygulama --uitesting launch argument'ı ile başlatıldığında mock session'a geçiyor:
if ProcessInfo.processInfo.arguments.contains("--uitesting") {
let session = MockURLProtocol.mockSession()
_viewModel = State(initialValue: FeedViewModel(client: APIClient(session: session)))
}
Bu sayede ağ durumundan bağımsız olarak her seferinde aynı sonuç üreten deterministik testler oluşturabildim.
Design System: Token Tabanlı Yaklaşım
UI tutarlılığı için ise token tabanlı bir design system oluşturdum. *AYAColors, AYATypography, AYASpacing, AYACornerRadius, AYAAnimation tiplerinden oluşan bu yapı sayesinde renkler dark/light mode'a duyarlı, tipografi ise Dynamic Type ile uyumlu oldu. Bileşenlerde sabit değer yok (magic numbers) ve her zaman token referansı kullanıldığı için gelecekte oluşabilecek bir tasarım değişikliğinin tek noktadan yapılabilmesine de olanak sağlamış oldum.
Responsive layout için ekran genişliğine göre adaptif grid kullandım. 700pt'nin altında tek üzerinde iki sütun olacak şekilde iPhone'da dikey liste, iPad ve Mac'te verimli bir grid görünümü render edebildim.
13 Dil Desteği ve RTL
AYA uygulamalarını da, websitesinde desteklenen 13 dilde lokalize ettim. Bu diller şöyleydi: Türkçe, İngilizce, Arapça, Almanca, İspanyolca, Fransızca, İtalyanca, Japonca, Korece, Hollandaca, Portekizce, Rusça ve Basitleştirilmiş Çince. LocaleHelper enum'u, kullanıcının tercih ettiği dili API locale'ine dönüştürüyor ve desteklenmeyen diller için İngilizce'ye fallback yapıyor.
Arapça desteği içinse, RTL layout'ta tüm bileşenlerin doğru yansımasını sağlamak amacıyla layoutDirection environment değerini kullanıyorum. Snapshot testlerinde hem LTR hem RTL varyantları var ve bu sayede görsel regresyon testleri layout bozulmalarını testler otomatik yakalıyor.
Test Stratejisi: Üç Katman
Test altyapısı üç katmandan oluşuyor:
- Unit testler: Model decoding, API client davranışları, locale helper logiclerinden oluşan Swift Testing framework'ü ile yazılmış bir suite.
- Snapshot testler — Point-Free'nin *
swift-snapshot-testingkütüphanesi ile tüm kart bileşenlerinin görsel regresyon testlerinden oluşan bu suite'te, her bileşen için light/dark mode, LTR/RTL referans görselleri var ve CI'da piksel farkı varsa test kırılıyor. - UI testler — Feed navigasyonu, arama, detay ekranları ve erişilebilirlik altyapısını
MockURLProtocolsayesinde ağ bağımsız olarak test eden bu suite ise deterministik bir test ortamı sağlıyor.
CI/CD: GitHub Actions ve Fastlane
Her PR'da beş iş paralel koşuyor: iOS build, macOS build, unit testler, UI testler ve lint. CI tamamen xcodebuild tabanlı, yani XcodeGen ile *.xcodeproj dosyası CI'da her seferinde yeniden üretiliyor. Repo'da Xcode proje dosyası tutmamamın sebebi ise merge conflict'lerinin en yaygın kaynağını ortadan kaldırmaktı.
Fastlane ile build otomasyonu, App Store metadata yönetimi ve TestFlight dağıtımı yapılandırdım. Makefile üzerinden *make build, make test, make lint ile tüm iş akışı tek satırda agentic mühendislik ile tetikleyebiliyorum.
Erişilebilirlik
Tüm bileşenlerde VoiceOver label'ları, hint'ler ve trait'ler tanımlı. Dynamic Type desteği var ve kullanıcı sistem yazı boyutunu değiştirdiğinde uygulama buna uyum sağlıyor. Renk kontrastlarını da renk körlüğüne duyarlı olarak seçtim.
Son Söz
Tek bir SPM kütüphanesi ile hem iOS hem macOS'ta çalışan, 13 dilde lokalize, snapshot ve UI testleriyle kaplı, CI/CD pipeline'ı hazır bir native uygulama olarak geliştirdiğim AYA uygulamalarının production kalitesinde ve yüksek ölçeklenilebilirlik altyapısıyla döşenmiş olduğunu düşünüyorum.
Tüm bu süreci tamamen Claude CLI ve kendi ürettiğim iOS ve Mac OS Agent Skills dosyaları kullanarak birkaç gün gibi kısa bir sürede geliştirdim.
AYA zaten özünde açık kaynak bir girişim ve toplumsal fayda için üreten gönüllülerin bir araya geldiği bir ağ olduğu için bu uygulamaları da aynı ruhla yazmak istedim. Kodun tamamı açık, PR incelenebilir, ve her türlü katkı yazılım geliştirme süreçleri aracılığıyla (code review ve pull requests) bu projelere sağlanabilir. Küçük bir bug fix, yeni bir dil desteği, erişilebilirlik iyileştirmesi veya API kullanılarak yeni post üretme özellikleri gibi birçok alanda bu uygulamalara katkıda bulunabilirsiniz. Açık kaynak projeler birlikte büyüdüğünde anlam kazanıyor.
Katkılarınızı bekliyorum.
Ek Okuma
- AYA Açık Yazılım Ağı: https://aya.is
- PR #89 — Native iOS ve macOS Uygulamaları https://github.com/eser/aya.is/pull/89
- swift-snapshot-testing — Point-Free https://github.com/pointfreeco/swift-snapshot-testing
- XcodeGen — https://github.com/yonaskolb/XcodeGen