StoreKit 2 Mülakatı: Abonelik Yönetimi ve Makbuz Doğrulama

StoreKit 2, abonelik yönetimi, makbuz doğrulama ve uygulama içi satın alma uygulaması hakkında iOS mülakat sorularında pratik Swift kod örnekleriyle uzmanlaşın.

iOS StoreKit 2 abonelik mimarisi ve makbuz doğrulama

StoreKit 2, iOS'ta uygulama içi satın alımların ele alınma şeklinde temel bir değişimi temsil eder. iOS 15 ile tanıtılan bu modern framework, abonelik yönetimi ve işlem doğrulaması için gereken kodu önemli ölçüde basitleştirir. iOS 18 ile birlikte StoreKit 1 artık resmi olarak kullanımdan kaldırılmış durumda ve StoreKit 2 ileriye dönük tek desteklenen yol olarak kalmıştır. Teknik iOS mülakatları, geliştiricilerin uygulama monetizasyonu konusundaki uzmanlığını değerlendirmek için bu konuyu düzenli olarak ele alır.

Önemli Mülakat İçgörüsü

StoreKit 2, async/await ile yerel bir Swift API'sinden yararlanır ve karmaşık callback'ler ile istemci tarafı makbuz doğrulamaya olan ihtiyacı ortadan kaldırır. Mülakatçılar, StoreKit 1'den temel farkları ve appTransactionID ile SubscriptionOfferView gibi yeni iOS 18.4 özelliklerini ifade edebilen adayları takdir eder.

StoreKit 2 Mimarisine Genel Bakış

StoreKit 2, Swift Concurrency'den tam olarak yararlanan modern bir mimari üzerine inşa edilmiştir. StoreKit 1'in aksine, tüm işlemler asenkrondur ve yerel Swift türlerini kullanır.

Mevcut Ürünleri Getirme

İlk adım, App Store Connect'te yapılandırılmış ürünleri yüklemeyi içerir. StoreKit 2, bu işlemi bildirimsel bir API ile basitleştirir.

ProductService.swiftswift
import StoreKit

actor ProductService {

    // Product identifiers configured in App Store Connect
    private let productIdentifiers: Set<String> = [
        "com.app.subscription.monthly",
        "com.app.subscription.yearly",
        "com.app.premium.lifetime"
    ]

    // Product cache to avoid repeated network calls
    private var cachedProducts: [Product] = []

    func loadProducts() async throws -> [Product] {
        // Return cache if available
        guard cachedProducts.isEmpty else {
            return cachedProducts
        }

        // Asynchronous request to the App Store
        let products = try await Product.products(for: productIdentifiers)
        cachedProducts = products
        return products
    }

    func product(for identifier: String) async throws -> Product? {
        let products = try await loadProducts()
        return products.first { $0.id == identifier }
    }
}

Bir actor kullanmak, ürün önbelleğini yönetirken iş parçacığı güvenliğini garanti eder.

StoreKit 2 Ürün Türleri

StoreKit 2, farklı ürün türlerini açıkça ayırt eder. Bu ayrımı anlamak mülakatlar için önemlidir.

ProductType+Extensions.swiftswift
import StoreKit

extension Product.ProductType {

    var displayName: String {
        switch self {
        case .consumable:
            // Consumable products (credits, lives, etc.)
            return "Consumable"
        case .nonConsumable:
            // Permanent purchases (feature unlock)
            return "Non-Consumable"
        case .autoRenewable:
            // Subscriptions with automatic renewal
            return "Auto-Renewable Subscription"
        case .nonRenewable:
            // Subscriptions without renewal (temporary pass)
            return "Non-Renewable Subscription"
        @unknown default:
            return "Unknown"
        }
    }
}

Otomatik yenilemeli abonelikler, modern iOS uygulamaları için baskın iş modelini temsil eder.

Satın Alma İşlemlerinin Yönetimi

StoreKit 2'deki satın alma süreci, async/await sayesinde doğrusal hale gelir. StoreKit 1'in karmaşık delegate ve iç içe callback'leri artık yok.

Bir Satın Alma Başlatma

iOS 18.2'den itibaren satın almalar bir UI context parametresi gerektirir. Bu değişiklik, satın alma sayfasının uygulamanın görünüm hiyerarşisinde doğru şekilde görünmesini sağlar.

PurchaseManager.swiftswift
import StoreKit
import UIKit

actor PurchaseManager {

    enum PurchaseError: Error {
        case productNotFound
        case purchaseCancelled
        case purchasePending
        case verificationFailed
        case unknown
    }

    func purchase(_ product: Product, in scene: UIWindowScene) async throws -> Transaction {
        // Initiate purchase with UI context (iOS 18.2+)
        let result = try await product.purchase(confirmIn: scene)

        switch result {
        case .success(let verification):
            // Verify the transaction signature
            let transaction = try checkVerification(verification)

            // Mark the transaction as finished
            await transaction.finish()

            return transaction

        case .userCancelled:
            // User cancelled the purchase modal
            throw PurchaseError.purchaseCancelled

        case .pending:
            // Purchase pending (parental approval, etc.)
            throw PurchaseError.purchasePending

        @unknown default:
            throw PurchaseError.unknown
        }
    }

    private func checkVerification<T>(_ result: VerificationResult<T>) throws -> T {
        switch result {
        case .verified(let safe):
            // Transaction signed and verified by the App Store
            return safe
        case .unverified(_, let error):
            // Invalid or corrupted signature
            throw PurchaseError.verificationFailed
        }
    }
}

finish() yöntemi çok önemlidir: App Store'a içeriğin kullanıcıya teslim edildiğini bildirir.

Mülakat Uyarısı

İçeriği teslim ettikten sonra transaction.finish() çağırmayı asla unutmayın. Tamamlanmamış bir işlem bir sonraki uygulama başlatımında geri yüklenir ve beklenmedik davranışlara neden olur.

Arka Plan İşlemlerini Dinleme

StoreKit 2, uygulamanın dışında gerçekleşen işlemleri (yenilemeler, family sharing satın alımları vb.) yayınlayan asenkron bir akış olan Transaction.updates'i tanıtır.

TransactionObserver.swiftswift
import StoreKit

actor TransactionObserver {

    private var updateTask: Task<Void, Never>?

    func startObserving() {
        // Cancel any previous observation
        updateTask?.cancel()

        updateTask = Task(priority: .background) {
            // Infinite stream of transaction updates
            for await result in Transaction.updates {
                do {
                    let transaction = try self.checkVerification(result)

                    // Process the transaction based on its type
                    await self.handleTransaction(transaction)

                    // Always finish the transaction
                    await transaction.finish()
                } catch {
                    // Log the error for debugging
                    print("Transaction verification failed: (error)")
                }
            }
        }
    }

    func stopObserving() {
        updateTask?.cancel()
        updateTask = nil
    }

    private func handleTransaction(_ transaction: Transaction) async {
        switch transaction.productType {
        case .autoRenewable:
            // Update subscription status
            await updateSubscriptionStatus(transaction)
        case .nonConsumable:
            // Unlock premium feature
            await unlockFeature(transaction.productID)
        default:
            break
        }
    }

    private func checkVerification<T>(_ result: VerificationResult<T>) throws -> T {
        switch result {
        case .verified(let safe):
            return safe
        case .unverified:
            throw PurchaseError.verificationFailed
        }
    }

    private func updateSubscriptionStatus(_ transaction: Transaction) async {
        // Implementation of status update
    }

    private func unlockFeature(_ productID: String) async {
        // Implementation of feature unlock
    }

    enum PurchaseError: Error {
        case verificationFailed
    }
}

İşlem gözlemi, herhangi bir güncellemeyi kaçırmamak için uygulama başlar başlamaz başlatılmalıdır.

StoreKit 2 Abonelikleri Hakkında Yaygın Mülakat Soruları

iOS mülakatları sıklıkla abonelik yönetimi hakkında belirli sorular içerir. İşte en yaygın olanlar ve ayrıntılı cevapları. Daha fazla Swift eşzamanlılık konusu için Swift structured concurrency rehberine bakabilirsiniz.

Soru 1: Mevcut Abonelik Durumu Nasıl Kontrol Edilir?

StoreKit 2, aktif yetkilendirmelere doğrudan erişim sağlar. Transaction.currentEntitlement(for:)'ın iOS 18.4 itibarıyla kullanımdan kaldırıldığını unutmayın. Yeni currentEntitlements(for:), birden fazla yetkilendirmeyi (örneğin abonelik artı Family Sharing erişimi) işlemek için asenkron bir dizi döndürür.

SubscriptionStatusChecker.swiftswift
import StoreKit

struct SubscriptionStatusChecker {

    struct SubscriptionStatus {
        let isActive: Bool
        let expirationDate: Date?
        let willRenew: Bool
        let productID: String?
        let appTransactionID: UInt64?  // iOS 18.4+
    }

    static func checkCurrentStatus(for productID: String) async -> SubscriptionStatus {
        // Use the new currentEntitlements(for:) API (iOS 18.4+)
        var latestTransaction: Transaction?

        for await result in Transaction.currentEntitlements(for: productID) {
            if case .verified(let transaction) = result {
                if transaction.productType == .autoRenewable {
                    if latestTransaction == nil ||
                       transaction.purchaseDate > latestTransaction!.purchaseDate {
                        latestTransaction = transaction
                    }
                }
            }
        }

        guard let transaction = latestTransaction else {
            return SubscriptionStatus(
                isActive: false,
                expirationDate: nil,
                willRenew: false,
                productID: nil,
                appTransactionID: nil
            )
        }

        // Check if the subscription is still active
        let isActive = transaction.expirationDate ?? Date.distantPast > Date()

        // Retrieve renewal information
        let renewalInfo = await getRenewalInfo(for: transaction.productID)

        return SubscriptionStatus(
            isActive: isActive,
            expirationDate: transaction.expirationDate,
            willRenew: renewalInfo?.willAutoRenew ?? false,
            productID: transaction.productID,
            appTransactionID: transaction.appTransactionID
        )
    }

    private static func getRenewalInfo(for productID: String) async -> Product.SubscriptionInfo.RenewalInfo? {
        guard let product = try? await Product.products(for: [productID]).first,
              let subscription = product.subscription else {
            return nil
        }

        // Retrieve renewal status
        let status = try? await subscription.status.first
        return status?.renewalInfo
    }
}

Bu yaklaşım, sunucu çağrıları olmadan kesin abonelik durumu sağlar. iOS 18.4'te tanıtılan appTransactionID alanı, sunucudan sunucuya çağrılar olmadan müşterilerin işlemler arasında takibini sağlar.

iOS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Soru 2: Ücretsiz Denemeler ve Promosyon Teklifleri Nasıl Yönetilir?

StoreKit 2, teklif bilgilerine erişimi basitleştirir. Mülakatçılar genellikle farklı teklif türlerinin anlaşılmasını test eder. iOS 18.4 itibarıyla, promosyon teklifleri App Store Server Library'deki yeni PromotionOfferV2SignatureCreator kullanılarak JWS ile imzalanabilir.

OfferManager.swiftswift
import StoreKit

struct OfferManager {

    enum OfferType {
        case freeTrial(duration: String)
        case payAsYouGo(periods: Int, price: Decimal)
        case payUpFront(duration: String, price: Decimal)
        case oneTime(price: Decimal)  // iOS 17.2+: offer codes for all product types
        case none
    }

    static func getIntroductoryOffer(for product: Product) -> OfferType {
        guard let subscription = product.subscription,
              let offer = subscription.introductoryOffer else {
            return .none
        }

        switch offer.paymentMode {
        case .freeTrial:
            // Free trial period
            return .freeTrial(duration: formatPeriod(offer.period))

        case .payAsYouGo:
            // Reduced price over multiple periods
            return .payAsYouGo(
                periods: offer.periodCount,
                price: offer.price
            )

        case .payUpFront:
            // Single payment for a period
            return .payUpFront(
                duration: formatPeriod(offer.period),
                price: offer.price
            )

        case .oneTime:
            // One-time offer code (consumables, non-consumables, non-renewing)
            return .oneTime(price: offer.price)

        @unknown default:
            return .none
        }
    }

    static func checkEligibility(for product: Product) async -> Bool {
        // Check if user can benefit from the offer
        guard let subscription = product.subscription else {
            return false
        }

        // isEligibleForIntroOffer indicates if this is a new subscriber
        return await subscription.isEligibleForIntroOffer
    }

    private static func formatPeriod(_ period: Product.SubscriptionPeriod) -> String {
        let value = period.value
        switch period.unit {
        case .day:
            return "(value) day(value > 1 ? "s" : "")"
        case .week:
            return "(value) week(value > 1 ? "s" : "")"
        case .month:
            return "(value) month(value > 1 ? "s" : "")"
        case .year:
            return "(value) year(value > 1 ? "s" : "")"
        @unknown default:
            return ""
        }
    }
}

isEligibleForIntroOffer yöntemi, kullanılamayan teklifleri görüntülemekten kaçınmak için önemlidir. Teklif kodlarının, daha önce yalnızca otomatik yenilemeli aboneliklerle sınırlı olduğunu, ancak iOS 16.3 itibarıyla tüketilebilirler, tüketilemezler ve yenilemesiz abonelikleri de desteklediğini unutmayın.

Soru 3: Önceki Satın Alımlar Nasıl Geri Yüklenir?

Satın alma geri yükleme, App Store Review Guidelines'a göre zorunlu bir özelliktir.

RestoreManager.swiftswift
import StoreKit

actor RestoreManager {

    struct RestoreResult {
        let restoredProducts: [String]
        let activeSubscriptions: [String]
    }

    func restorePurchases() async throws -> RestoreResult {
        var restoredProducts: [String] = []
        var activeSubscriptions: [String] = []

        // Synchronize with the App Store
        try await AppStore.sync()

        // Iterate through all user transactions
        for await result in Transaction.currentEntitlements {
            guard case .verified(let transaction) = result else {
                continue
            }

            switch transaction.productType {
            case .nonConsumable:
                // Restore permanent purchases
                restoredProducts.append(transaction.productID)

            case .autoRenewable:
                // Check if subscription is active
                if let expiration = transaction.expirationDate,
                   expiration > Date() {
                    activeSubscriptions.append(transaction.productID)
                }

            default:
                // Consumables are not restorable
                break
            }
        }

        return RestoreResult(
            restoredProducts: restoredProducts,
            activeSubscriptions: activeSubscriptions
        )
    }
}

AppStore.sync(), Apple sunucularıyla senkronizasyonu zorlar ve yeniden yükleme sonrası faydalıdır.

SubscriptionOfferView: Modern Abonelik Sunumu

iOS 18.4, otomatik ürün meta veri yüklemesiyle abonelik seçeneklerini gösteren bir SwiftUI bileşeni olan SubscriptionOfferView'ı tanıtır. Bu, abonelik tabanlı uygulamalar geliştiren geliştiriciler için yaygın bir mülakat konusudur.

SubscriptionOfferViewExample.swiftswift
import SwiftUI
import StoreKit

struct SubscriptionSettingsView: View {
    let subscriptionGroupID = "com.app.subscription.group"
    
    var body: some View {
        VStack(spacing: 16) {
            // Show upgrade options for current subscribers
            SubscriptionOfferView(groupID: subscriptionGroupID)
                .visibleRelationship(.upgrade)
                .prefersPromotionalIcon(true)
                .subscriptionOfferViewDetailAction { path in
                    path.append("subscriptionDetail")
                }
            
            // Show current plan details
            SubscriptionOfferView(groupID: subscriptionGroupID)
                .visibleRelationship(.current)
        }
        .padding()
    }
}

visibleRelationship parametresi hangi abonelik katmanlarının görüneceğini kontrol eder: .upgrade bir seviye yüksek planları, .downgrade düşük seviyeleri, .crossgrade daha iyi değerli eşdeğer seviyeleri gösterir ve .all gruptaki her planı görüntüler.

Sunucu Tarafı Makbuz Doğrulama

Kritik abonelikleri olan uygulamalar için sunucu tarafı doğrulama önerilmeye devam etmektedir. StoreKit 2, modern doğrulama için JWS (JSON Web Signatures) kullanır. Kullanımdan kaldırılan verifyReceipt uç noktası, App Store Server API ile değiştirilmiştir.

Sunucu için İşlem Verilerini Çıkarma

ServerValidation.swiftswift
import StoreKit

struct TransactionPayload: Codable {
    let transactionID: String
    let originalTransactionID: String
    let productID: String
    let purchaseDate: Date
    let expirationDate: Date?
    let jwsRepresentation: String
    let appTransactionID: String?  // iOS 18.4+
}

actor ServerValidationService {

    private let apiEndpoint = "https://api.example.com/verify"

    func validateWithServer(_ transaction: Transaction) async throws -> Bool {
        // Create payload for the server
        let payload = TransactionPayload(
            transactionID: String(transaction.id),
            originalTransactionID: String(transaction.originalID),
            productID: transaction.productID,
            purchaseDate: transaction.purchaseDate,
            expirationDate: transaction.expirationDate,
            jwsRepresentation: transaction.jwsRepresentation ?? "",
            appTransactionID: transaction.appTransactionID.map { String($0) }
        )

        // Encode to JSON
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601
        let jsonData = try encoder.encode(payload)

        // Send to server
        var request = URLRequest(url: URL(string: apiEndpoint)!)
        request.httpMethod = "POST"
        request.httpBody = jsonData
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        let (data, response) = try await URLSession.shared.data(for: request)

        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            return false
        }

        // Decode server response
        let validationResponse = try JSONDecoder().decode(
            ValidationResponse.self,
            from: data
        )

        return validationResponse.isValid
    }
}

struct ValidationResponse: Codable {
    let isValid: Bool
    let subscriptionStatus: String?
}

jwsRepresentation, sunucu tarafından doğrulanabilir kriptografik imza içerir. Yeni appTransactionID alanı, ek sunucudan sunucuya gidiş-dönüşler olmadan kesin müşteri takibini sağlar.

App Store Server API

Apple'ın App Store Server API'si, JWS token'ları ile sunucu tarafı doğrulama, gerçek zamanlı abonelik olayları için Server Notifications V2 ve durumu doğrudan sorgulamak için Transaction ID'leri kabul eden (iOS 18.4+) yeni SubscriptionStatus API'sini sağlar.

Sunucu Bildirimlerini Yapılandırma

Server Notifications V2, gerçek zamanlı olay alımını sağlar.

ServerNotificationTypes.swiftswift
enum NotificationType: String, Codable {
    case subscribed = "SUBSCRIBED"
    case didRenew = "DID_RENEW"
    case didFailToRenew = "DID_FAIL_TO_RENEW"
    case didChangeRenewalStatus = "DID_CHANGE_RENEWAL_STATUS"
    case expired = "EXPIRED"
    case gracePeriodExpired = "GRACE_PERIOD_EXPIRED"
    case offerRedeemed = "OFFER_REDEEMED"
    case priceIncrease = "PRICE_INCREASE"
    case refund = "REFUND"
    case renewalExtended = "RENEWAL_EXTENDED"
    case revoke = "REVOKE"
}

struct ServerNotification: Codable {
    let notificationType: NotificationType
    let subtype: String?
    let data: NotificationData

    struct NotificationData: Codable {
        let bundleId: String
        let environment: String
        let signedTransactionInfo: String
        let signedRenewalInfo: String?
    }
}

Bu bildirimler, abonelik durumunun backend ile senkronize tutulmasını sağlar.

Hata Yönetimi ve Sınır Durumları

Mülakatlar sıklıkla hata senaryolarının ve uygun şekilde işlenmesinin anlaşılmasını test eder. iOS mimari desenleri hakkında daha fazla bilgi için iOS senior mülakat rehberindeki tasarım desenleri bölümüne bakın.

StoreKit 2'deki Yaygın Hatalar

PurchaseErrorHandler.swiftswift
import StoreKit

struct PurchaseErrorHandler {

    enum UserFacingError {
        case networkError
        case paymentFailed
        case notAuthorized
        case productNotAvailable
        case unknown

        var message: String {
            switch self {
            case .networkError:
                return "Connection failed. Please check your internet connection."
            case .paymentFailed:
                return "Payment failed. Please verify your payment information."
            case .notAuthorized:
                return "In-app purchases are disabled on this device."
            case .productNotAvailable:
                return "This product is not currently available."
            case .unknown:
                return "An unexpected error occurred."
            }
        }
    }

    static func handle(_ error: Error) -> UserFacingError {
        // Check if it's a StoreKit error
        if let storeKitError = error as? StoreKitError {
            switch storeKitError {
            case .networkError:
                return .networkError
            case .userCancelled:
                // Don't show error, user cancelled
                return .unknown
            case .notAvailableInStorefront:
                return .productNotAvailable
            case .notEntitled:
                return .notAuthorized
            default:
                return .unknown
            }
        }

        // Purchase errors
        if let purchaseError = error as? Product.PurchaseError {
            switch purchaseError {
            case .invalidQuantity:
                return .unknown
            case .productUnavailable:
                return .productNotAvailable
            case .purchaseNotAllowed:
                return .notAuthorized
            default:
                return .unknown
            }
        }

        return .unknown
    }
}

Net hata yönetimi, kullanıcı deneyimini geliştirir ve hata ayıklamayı basitleştirir.

Çevrimdışı Mod Desteği

OfflineCapabilityManager.swiftswift
import StoreKit

actor OfflineCapabilityManager {

    private let userDefaults = UserDefaults.standard
    private let entitlementsKey = "cached_entitlements"

    func cacheCurrentEntitlements() async {
        var entitlements: [String: Date] = [:]

        for await result in Transaction.currentEntitlements {
            if case .verified(let transaction) = result {
                entitlements[transaction.productID] = transaction.expirationDate
            }
        }

        // Save locally
        if let data = try? JSONEncoder().encode(entitlements) {
            userDefaults.set(data, forKey: entitlementsKey)
        }
    }

    func getCachedEntitlements() -> [String: Date] {
        guard let data = userDefaults.data(forKey: entitlementsKey),
              let entitlements = try? JSONDecoder().decode(
                  [String: Date].self,
                  from: data
              ) else {
            return [:]
        }

        // Filter expired entitlements
        let now = Date()
        return entitlements.filter { _, expiration in
            expiration > now
        }
    }

    func hasValidEntitlement(for productID: String) -> Bool {
        let cached = getCachedEntitlements()
        return cached[productID] != nil
    }
}

Yerel önbellekleme, bağlantı olmadan bile premium özelliklere erişimi korur.

Üretim için En İyi Uygulamalar

Önerilen Mimari

SubscriptionService.swiftswift
import StoreKit
import Combine

@MainActor
final class SubscriptionService: ObservableObject {

    static let shared = SubscriptionService()

    @Published private(set) var products: [Product] = []
    @Published private(set) var purchasedProductIDs: Set<String> = []
    @Published private(set) var subscriptionStatus: SubscriptionStatus = .none

    private var transactionObserver: Task<Void, Never>?

    enum SubscriptionStatus {
        case none
        case active(expirationDate: Date)
        case expired
        case inGracePeriod
    }

    private init() {
        // Start transaction observation
        startTransactionObserver()

        // Load initial state
        Task {
            await loadProducts()
            await updateSubscriptionStatus()
        }
    }

    private func startTransactionObserver() {
        transactionObserver = Task.detached(priority: .background) {
            for await result in Transaction.updates {
                if case .verified(let transaction) = result {
                    await transaction.finish()
                    await self.updateSubscriptionStatus()
                }
            }
        }
    }

    func loadProducts() async {
        do {
            let productIDs = [
                "com.app.subscription.monthly",
                "com.app.subscription.yearly"
            ]
            products = try await Product.products(for: productIDs)
        } catch {
            print("Failed to load products: (error)")
        }
    }

    func purchase(_ product: Product) async throws {
        let result = try await product.purchase()

        switch result {
        case .success(let verification):
            if case .verified(let transaction) = verification {
                await transaction.finish()
                await updateSubscriptionStatus()
            }
        case .userCancelled:
            break
        case .pending:
            break
        @unknown default:
            break
        }
    }

    func updateSubscriptionStatus() async {
        var purchasedIDs: Set<String> = []
        var latestSubscription: Transaction?

        for await result in Transaction.currentEntitlements {
            if case .verified(let transaction) = result {
                purchasedIDs.insert(transaction.productID)

                if transaction.productType == .autoRenewable {
                    if latestSubscription == nil ||
                       transaction.purchaseDate > latestSubscription!.purchaseDate {
                        latestSubscription = transaction
                    }
                }
            }
        }

        purchasedProductIDs = purchasedIDs

        if let subscription = latestSubscription,
           let expiration = subscription.expirationDate {
            if expiration > Date() {
                subscriptionStatus = .active(expirationDate: expiration)
            } else {
                subscriptionStatus = .expired
            }
        } else {
            subscriptionStatus = .none
        }
    }

    deinit {
        transactionObserver?.cancel()
    }
}

Bu merkezileştirilmiş mimari, uygulama genelinde satın alma durumu yönetimini basitleştirir.

Kaynaklar

StoreKit 2 Mülakatları için Önemli Noktalar

StoreKit 2, uygulama içi satın alma yönetimini modern ve güvenli bir geliştirme deneyimine dönüştürür. Bir mülakat için hatırlanacak önemli noktalar:

  • Yerel async/await API'si, iOS 18 itibarıyla kullanımdan kaldırılan StoreKit 1'in karmaşık delegate ve callback'lerini ortadan kaldırır
  • VerificationResult aracılığıyla otomatik doğrulama, işlemlerin kriptografik doğrulamasını yönetir
  • Transaction.updates, yenilemeler ve family sharing dahil arka plan işlemleri için asenkron bir akış sağlar
  • Transaction.currentEntitlements(for:), birden fazla yetkilendirmeyi işlemek için kullanımdan kaldırılan currentEntitlement(for:)'ın yerini alır
  • appTransactionID alanı (iOS 18.4+), sunucudan sunucuya çağrılar olmadan müşteri takibini sağlar
  • SubscriptionOfferView, upgrade, downgrade ve crossgrade seçenekleri için visibleRelationship ile abonelik sunumu için yerleşik UI sağlar
  • Promosyon teklifleri, PromotionOfferV2SignatureCreator ile JWS kullanılarak imzalanabilir
  • Teklif kodları artık tüketilebilirleri, tüketilemezleri ve yenilemesiz abonelikleri destekler (iOS 16.3+)
  • Sunucu doğrulaması, kullanımdan kaldırılan verifyReceipt uç noktasının yerine JWS token'lı App Store Server API'sini kullanır
  • Beklenmedik geri yükleme davranışını önlemek için içerik tesliminden sonra her zaman transaction.finish() çağırın
  • Bağlantı olmadan kesintisiz kullanıcı deneyimi için çevrimdışı yetkilendirme önbellekleme uygulayın

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Günün meydan okuması

iOS kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

20 Ekim 2026 tarihinde güncellendi

Etiketler

#ios
#storekit
#in-app-purchase
#swift
#interview

Paylaş

İlgili makaleler