• 16.08.2026 04:21:01
  • Admin Admin

SwiftUI uygulamalar?nda deep link, NavigationStack durumu ve sahne bazl? state restoration tasar?m?n? ele al?r. iphone uygulama geli?tirme s?recinde g?venli URL ayr??t?rma, ?l??m ve yay?n ?ncesi do?rulama ad?mlar?n? g?sterir.

SwiftUI Deep Link ve State Restoration ile iPhone Uygulama Geliştirme

SwiftUI ve iOS MVVM Mimarisi İçin Route Modeli Tasarlamak

Deep link yönetimini View katmanında `if url.path...` bloklarına dağıtmak yerine, yönlendirilebilir ekranları `Codable` bir route modeli olarak tanımlayın. Bu yaklaşımda ios mvvm mimarisi içinde View yalnızca `Router.path` binding'ini tüketir; URL ayrıştırma, doğrulama ve navigation kararları test edilebilir bir `@Observable` nesnede kalır. `NavigationPath` type-erased olduğu için sürümlenebilir kalıcı depolama ve migration senaryolarında `[Route]` dizisi daha denetlenebilirdir.

import Observation
import SwiftUI

enum Route: Hashable, Codable {
    case product(id: UUID)
    case search(query: String)
    case settings
}

@MainActor
@Observable
final class Router {
    var path: [Route] = []

    func open(_ route: Route) {
        // Aynı ürün zaten en üstteyse gereksiz push üretme.
        guard path.last != route else { return }
        path.append(route)
    }

    func reset(to route: Route? = nil) {
        path = route.map { [$0] } ?? []
    }
}

struct RootView: View {
    @State private var router = Router()

    var body: some View {
        @Bindable var router = router

        NavigationStack(path: $router.path) {
            CatalogView()
                .navigationDestination(for: Route.self) { route in
                    switch route {
                    case .product(let id): ProductView(productID: id)
                    case .search(let query): SearchView(initialQuery: query)
                    case .settings: SettingsView()
                    }
                }
        }
    }
}

Bir swiftui eğitimi veya xcode eğitimi sırasında sık atlanan ayrıntı şudur: route içine doğrudan API'den gelen büyük `Product` nesnesini koymayın. `NavigationStack` her path değişiminde `Hashable` değerlerle çalışır; 300 KB'lık model, hash hesaplama ve state encode maliyetini büyütür, ayrıca eski cache verisini geri yükleme riski doğurur. Route'ta yalnızca kimlik ve küçük, sürümlenebilir parametreler taşıyın; `ProductView(productID:)` veriyi repository üzerinden yeniden yüklesin.

Swift Eğitimi Perspektifiyle Güvenli Universal Link Ayrıştırma

Universal link'i yalnızca `url.pathComponents` ile parçalamak yeterli değildir: uygulama beklemediği host, şema, percent-encoded yol veya çok uzun query alabilir. Aşağıdaki ayrıştırıcı, HTTPS host allow-list uygular, UUID doğrular ve arama metnini sınırlar. Böylece `https://example.com/product/not-a-uuid` gibi bir bağlantı yarım oluşmuş bir ekrana değil, kontrollü bir hataya gider. Bu sınırlar özellikle dış kampanya linkleri ve kullanıcı tarafından paylaşılan URL'ler için gereklidir.

enum DeepLinkError: Error {
    case untrustedHost, unsupportedRoute, invalidIdentifier
}

struct DeepLinkParser {
    private let allowedHosts: Set<String> = ["example.com", "www.example.com"]

    func parse(_ url: URL) throws -> Route {
        guard url.scheme == "https", let host = url.host?.lowercased(),
              allowedHosts.contains(host) else {
            throw DeepLinkError.untrustedHost
        }

        let parts = url.pathComponents.filter { $0 != "/" }
        switch parts {
        case ["product", let rawID]:
            guard let id = UUID(uuidString: rawID) else {
                throw DeepLinkError.invalidIdentifier
            }
            return .product(id: id)

        case ["search"]:
            let query = URLComponents(url: url, resolvingAgainstBaseURL: false)?
                .queryItems?.first(where: { $0.name == "q" })?.value ?? ""
            guard !query.isEmpty, query.count <= 128 else {
                throw DeepLinkError.unsupportedRoute
            }
            return .search(query: query)

        case ["settings"]:
            return .settings
        default:
            throw DeepLinkError.unsupportedRoute
        }
    }
}

// Scene seviyesinde alınan URL tek bir giriş noktasına yönlendirilir.
.onOpenURL { url in
    do { router.open(try DeepLinkParser().parse(url)) }
    catch { logger.error("Rejected deep link: \(url.absoluteString, privacy: .private)") }
}

Buradaki `URLComponents` seçimi bilinçlidir: query değerini elle `split(separator: "=")` ile ayırmak, `q=kalem%20seti` ve birden çok query item içeren URL'lerde yanlış sonuç üretir. Ayrıca `onOpenURL` bir uygulama aktifken de çalışır; kullanıcı zaten başka bir ürün detayındaysa iş kuralınıza göre `open` yerine `reset(to:)` çağırın. Örneğin ödeme tamamlanma linkinde eski checkout ekranını stack'te bırakmak, geri tuşunun kullanıcıyı geçersiz ödeme akışına taşımasına neden olur.

iPhone Uygulama Geliştirme Sürecinde Sahne Bazlı State Restoration

`@AppStorage` ile navigation state saklamak çoklu pencere destekleyen iPad ve masaüstü hedeflerinde yanlış kapsamdır; tüm sahneler aynı anahtarı paylaşır. Her pencerenin kendi geri yükleme verisine ihtiyacı varsa `@SceneStorage` kullanın. Kalıcı veri için sürüm alanı eklemek kritiktir: yeni bir `Route` case'i veya alanı eklendiğinde eski JSON'u sessizce decode etmeye güvenmek yerine, hangi migration'ın uygulanacağını belirleyebilirsiniz.

struct StoredRouteState: Codable {
    let schema: Int
    let routes: [Route]
}

struct RestorableRootView: View {
    @State private var router = Router()
    @SceneStorage("navigation-state-v1") private var storedData: Data?

    var body: some View {
        RootView()
            .environment(router)
            .task {
                guard let storedData,
                      let state = try? JSONDecoder().decode(StoredRouteState.self, from: storedData),
                      state.schema == 1 else { return }
                router.path = state.routes
            }
            .onChange(of: router.path) { _, routes in
                let state = StoredRouteState(schema: 1, routes: routes)
                storedData = try? JSONEncoder().encode(state)
            }
    }
}

Bu kodda dikkat edilmesi gereken edge case, restore edilmiş route'un artık erişilebilir olmamasıdır: kullanıcı oturumu kapandıysa veya ürün silindiyse state'i körlemesine gösteremezsiniz. `ProductView` veriyi yükledikten sonra HTTP 404 alırsa `router.path.removeLast()` yerine route kimliğine göre filtreleme yapın; kullanıcı aynı anda iki deep link açtıysa en üst elemanı kaldırmak yanlış ekranı kapatabilir. Bu davranışı `XCTest` içinde `router.path = [.settings, .product(id: missingID)]` kurulumu ile test edip, yalnızca `missingID` route'unun çıkarıldığını doğrulayın.

SwiftUI Navigation Yazma Maliyetini Instruments ile Ölçmek

Navigation state'i her karakter girişinde encode etmek, küçük demolar dışında ölçülmesi gereken bir I/O davranışıdır. Xcode Instruments'ta Points of Interest şablonunu açın, route persist işlemini `OSSignposter` ile işaretleyin ve önce arama alanına 100 karakter girerek baseline alın. Ardından 250 ms debounce uygulayın; karşılaştırmada signpost sayısı yaklaşık 100'den tek haneli sayıya inmeli, asıl kabul kriteri ise ana iş parçacığındaki encode aralıklarının frame budget'ını aşmamasıdır.

import OSLog

private let signposter = OSSignposter(
    subsystem: "com.example.catalog",
    category: "navigation-state"
)

@State private var saveTask: Task<Void, Never>?

.onChange(of: router.path) { _, routes in
    saveTask?.cancel()
    saveTask = Task { @MainActor in
        try? await Task.sleep(for: .milliseconds(250))
        guard !Task.isCancelled else { return }

        let interval = signposter.beginInterval("persist-route")
        defer { signposter.endInterval("persist-route", interval) }
        storedData = try? JSONEncoder().encode(
            StoredRouteState(schema: 1, routes: routes)
        )
    }
}

Profiling sırasında Release yapılandırmasına yakın bir derleme kullanın; Debug'daki SwiftUI diagnostics ve debugger attach maliyeti zaman çizelgesini çarpıtabilir. Instruments'ta yalnızca toplam süreye bakmayın: `persist-route` aralıklarının `onOpenURL` veya scroll gesture anlarıyla çakışıp çakışmadığını inceleyin. Eğer route sayısı büyüyorsa debounce tek başına çözüm değildir; örneğin yalnızca yeniden kurulabilir `product(id:)` route'larını saklayıp geçici `search(query:)` state'ini hariç tutarak serialize edilen bayt miktarını da ölçün.

App Store Yayınlama Öncesi Universal Link Doğrulama Listesi

app store yayınlama aşamasında Universal Links için uygulama binary'si, Associated Domains entitlement'ı ve sunucudaki `apple-app-site-association` dosyası birlikte doğru olmalıdır. Xcode'da Signing & Capabilities altında `applinks:example.com` ekleyin; sonra yayın ortamından yönlendirme olmadan dosyanın sunulduğunu doğrulayın. `curl -I` çıktısında 200 durum kodu, TLS sertifikası ve `Content-Type: application/json` kontrol edilmelidir; 301 ile başka bir domaine yönlenen AASA uç noktası güvenilir bir test değildir.

# Dosya uzantısı olmadan bu iki konumdan biri 200 dönmelidir.
curl --http2 -I https://example.com/.well-known/apple-app-site-association
curl --http2 https://example.com/.well-known/apple-app-site-association

# JSON söz dizimini CI içinde doğrulayın.
curl -fsSL https://example.com/.well-known/apple-app-site-association | jq .

AASA içindeki `appID`, Apple Developer Team ID ile bundle identifier'ın `TEAMID.com.company.app` birleşimidir; yanlış ortam bundle ID'si, URL'nin Safari'de açılmasına yol açar ve uygulama tarafındaki `onOpenURL` hiç çağrılmaz. CI'a yukarıdaki `curl -fsSL` ve `jq` adımını eklemek, CDN deploy'unda yanlış MIME type veya bozuk JSON yayınlanmasını yakalar. Bu kontrol, ios eğitimi, ios kursu, swift eğitimi ya da swift kursu projelerinde de gerçek cihaz testiyle tamamlanmalıdır: linki Notlar uygulamasına yapıştırıp uzun basarak hedefin uygulama olarak göründüğünü doğrulayın.

Sık Sorulan Sorular

SwiftUI eğitiminde NavigationPath mi yoksa [Route] mu state restoration için kullanılmalı?

Ekran tipleriniz kapalı bir küme ise `[Route]` kullanın. `Route: Codable, Hashable` ile JSON şemasını, route başına taşınan veriyi ve migration'ı denetlersiniz. `NavigationPath` heterojen değerler için kullanışlıdır; ancak type-erased yapısı nedeniyle kalıcı state'i sürümlendirmek ve bozuk bir elemanı izole etmek daha zordur.

iOS MVVM mimarisi içinde deep link hangi katmanda parse edilmeli?

URL doğrulama ve `Route` üretimi ViewModel'den bağımsız bir `DeepLinkParser` veya Router servisine konmalıdır. View yalnızca `.onOpenURL` ile parser'ı çağırıp route'u router'a verir. Parser için `https://example.com/product/{uuid}`, yanlış host, bozuk UUID, 129 karakterlik query ve percent-encoded query içeren unit test vakaları yazın.

iPhone uygulama geliştirme projesinde Universal Link neden Safari'de açılıyor?

Önce uygulamanın Associated Domains entitlement'ında tam domainin bulunduğunu kontrol edin. Ardından `https://domain/.well-known/apple-app-site-association` isteğinin redirect olmadan 200 ve geçerli JSON döndürdüğünü `curl -I` ile doğrulayın. Son olarak AASA'daki `appID` değerindeki Team ID ve bundle identifier'ın imzalanmış uygulamayla birebir eşleştiğini kontrol edin.

Xcode eğitiminde SwiftUI state persistence performansı nasıl ölçülür?

Persist fonksiyonunun başına `OSSignposter.beginInterval`, sonuna `endInterval` koyun ve Instruments Points of Interest ile önce debounce'suz, sonra 250 ms debounce'lu iki kayıt alın. Route değişimi başına encode sayısını ve en uzun signpost süresini karşılaştırın; yalnızca ortalama süreyi değil, gesture ve URL açma anlarındaki ana iş parçacığı çakışmalarını inceleyin.

AI / LLM Discovery

Bu makale Opendart Akademi iOS / Swift eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt