Category: App

  • Generics in Swift: A Practical, End‑to‑End Guide to Type‑Safe Reuse

    Generics in Swift: A Practical, End‑to‑End Guide to Type‑Safe Reuse

    Generics are the engine behind Swift’s expressiveness and safety. They let you write a single, reusable implementation that works across many types—without sacrificing compile‑time checks. From the Standard Library’s ArrayDictionary, and Optional to your own data models and architecture, generics are everywhere.

    This guide is a hands‑on deep dive into Swift generics: how they work, patterns you’ll use daily, advanced techniques with protocols and extensions, refactoring tactics, performance notes, testing, and organizing real projects. The goal: help you build clean, composable, type‑safe systems that scale.

    Whether you’re building an iOS app with SwiftUI, a networking layer with Combine, or a server‑side Swift service, mastering generics will make your codebase more maintainable and predictable.

    What Are Generics?

    Generics allow functions, types, and protocols to operate with placeholder types instead of concrete ones. You write code once and reuse it across many types while preserving static type checking.

    A generic function:

    func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
        let temp = a
        a = b
        b = temp
    }

    A generic type:

    struct Box<T> {
        let value: T
    }

    A generic protocol:

    protocol Cache {
        associatedtype Key: Hashable
        associatedtype Value
        mutating func set(_ value: Value, for key: Key)
        func get(_ key: Key) -> Value?
    }

    With generics, you get:

    • Reuse without dynamic typing
    • Compile‑time safety and better autocomplete
    • Domain‑specific abstractions without loss of clarity
    • Powerful constraints and conditional behavior

    Why Generics Matter

    If you’ve ever duplicated functions for different types, or leaned on Any and typecasting, generics are the right tool. They:

    • Eliminate repetition while keeping APIs clean
    • Encapsulate patterns (caching, repository, transforms)
    • Enable protocol‑oriented programming with associated types
    • Work hand‑in‑glove with extensions for modular design

    The Golden Rules of Generics

    Before diving in, a few principles:

    • Start concrete, then generalize: write a specific version first, then abstract.
    • Prefer constraints that communicate intent (e.g., T: Hashable).
    • Keep generic signatures small and readable; avoid parameter explosion.
    • Use protocol requirements for contracts; use extensions for defaults.
    • Choose names that reveal semantics: ElementKeyValueResource.

    A Running Example App

    We’ll reuse a journal domain and introduce generic infrastructure for caching, networking, and utilities.

    struct Entry: Identifiable, Hashable {
        let id: UUID
        var title: String
        var body: String
        var createdAt: Date
        var tags: [Tag]
    }
    
    struct Tag: Hashable { var name: String }

    Core Generic Patterns You’ll Use Daily

    1) Generic Containers

    A simple, sharable wrapper for a value.

    struct Box<T> { let value: T }

    Add conformances conditionally:

    extension Box: Equatable where T: Equatable {}
    extension Box: Hashable where T: Hashable {}

    This keeps the API small but powerful.

    2) Generic Utilities with Constraints

    Constraints narrow applicability to meaningful contexts.

    extension Sequence where Element: Hashable {
        func unique() -> [Element] {
            var seen = Set<Element>()
            return filter { seen.insert($0).inserted }
        }
    }

    The method appears only when Element is Hashable, maintaining clarity.

    3) Generic Result Builders (Optional)

    Result builders aren’t generics per se, but they compose well with generic types to express DSLs.

    @resultBuilder
    struct ArrayBuilder<T> {
        static func buildBlock(_ components: T...) -> [T] { components }
    }
    
    func makeEntries(@ArrayBuilder<Entry> _ build: () -> [Entry]) -> [Entry] {
        build()
    }

    4) Generic Functions for Transforms

    func mapOr<T, U>(_ value: T?, default defaultValue: U, transform: (T) -> U) -> U {
        value.map(transform) ?? defaultValue
    }

    This replaces branching with a fluent pipeline.

    5) Type‑Erasure for Protocols with Associated Types

    Protocols with associatedtype don’t play well with heterogenous collections. Type‑erasure bridges that gap.

    protocol AnyCache {
        associatedtype Key: Hashable
        associatedtype Value
        mutating func set(_ value: Value, for key: Key)
        func get(_ key: Key) -> Value?
    }
    
    final class AnyCacheBox<K: Hashable, V>: AnyCache {
        typealias Key = K
        typealias Value = V
    
        private var storage: [K: V] = [:]
    
        func set(_ value: V, for key: K) { storage[key] = value }
        func get(_ key: K) -> V? { storage[key] }
    }

    You can store AnyCacheBox<URL, Data> and AnyCacheBox<String, Entry> side by side where needed.

    Protocols + Generics: The Heart of Reusable Design

    Associated Types

    Protocols with associatedtype make contracts that depend on type parameters.

    protocol Repository {
        associatedtype Model
        func list() async throws -> [Model]
        func get(id: UUID) async throws -> Model
    }
    
    struct EntryRepository: Repository {
        func list() async throws -> [Entry] { /* ... */ }
        func get(id: UUID) async throws -> Entry { /* ... */ }
    }

    Type‑Erasure for Repositories

    Expose a uniform API for varied implementations.

    struct AnyRepository<M>: Repository {
        private let _list: () async throws -> [M]
        private let _get: (UUID) async throws -> M
    
        init<R: Repository>(_ repo: R) where R.Model == M {
            _list = repo.list
            _get = repo.get
        }
    
        func list() async throws -> [M] { try await _list() }
        func get(id: UUID) async throws -> M { try await _get(id) }
    }

    This lets you swap in mocks, remote implementations, or cached variants without changing call sites.

    Generic Networking Layer

    A safe, composable HTTP client.

    struct HTTPClient {
        func get<T: Decodable>(_ url: URL) async throws -> T {
            let (data, response) = try await URLSession.shared.data(from: url)
            guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
                throw URLError(.badServerResponse)
            }
            return try JSONDecoder().decode(T.self, from: data)
        }
    }

    Usage:

    let client = HTTPClient()
    let entries: [Entry] = try await client.get(URL(string: "https://example.com/entries.json")!)

    Generic Caching Layer

    struct Cache<K: Hashable, V> {
        private var storage: [K: V] = [:]
        mutating func set(_ value: V, for key: K) { storage[key] = value }
        func get(_ key: K) -> V? { storage[key] }
    }

    With constraints, you can add extras:

    extension Cache where V: Codable {
        func toJSON() throws -> Data { try JSONEncoder().encode(storage) }
    }

    Advanced Techniques

    Conditional Conformance

    Add behavior only when parameters meet constraints.

    struct Box<T> { let value: T }
    extension Box: Codable where T: Codable {}

    This lets your generic types participate naturally in ecosystems like Codable or Hashable.

    Phantom Types for Compile‑Time Safety

    Phantom types encode invariants in the type system.

    struct UserIdTag {}
    struct OrderIdTag {}
    
    struct Id<Tag, Raw: Hashable>: Hashable {
        let raw: Raw
    }
    
    typealias UserId = Id<UserIdTag, UUID>
    typealias OrderId = Id<OrderIdTag, UUID>

    Now you can’t pass a UserId where an OrderId is expected, even though both are UUIDs.

    Generic State Machines

    Model predictable flows with generic states and events.

    enum State<S, E> {
        case idle(S)
        case running(S)
        case failed(E)
    }

    This pattern is powerful for networking, background tasks, and UI flows.

    Higher‑Order Functions + Generics

    Compose transforms with generic utilities.

    func pipe<A, B, C>(_ a2b: @escaping (A) -> B, _ b2c: @escaping (B) -> C) -> (A) -> C {
        { a in b2c(a2b(a)) }
    }

    This small abstraction eliminates glue code and clarifies intent.

    Type Inference and Overload Resolution

    Swift’s type inference is strong, but overload resolution can be tricky with generics. Prefer explicit generic parameters or local type aliases to reduce ambiguity.

    func identity<T>(_ x: T) -> T { x }
    let number = identity(42) // T inferred as Int

    If ambiguity arises, annotate:

    let asString: String = identity("42")

    Pitfalls and How to Avoid Them

    Generics can become complex quickly. Guardrails help keep them readable and maintainable.

    • Don’t over‑abstract. If a generic has more than 2–3 parameters and lots of constraints, consider concrete types.
    • Avoid mega‑protocols. Split protocols into smaller roles and compose them.
    • Beware of accidental complexity with type‑erasure; document intent.
    • Watch for type inference pitfalls and overload conflicts.
    • Keep error messages manageable by using local type aliases and helper wrappers.

    Testing Generics

    Generics are testable like any code. Focus on contracts and constraints.

    • Write unit tests for generic utilities across representative types (e.g., Int, String, custom structs).
    • Test conditional conformance behavior explicitly.
    • Validate type‑erased wrappers with mocks.
    • Benchmark performance for hot paths.

    Example:

    import XCTest
    
    final class SequenceUniqueTests: XCTestCase {
        func testUniqueForHashableElements() {
            let ints = [1, 2, 2, 3]
            XCTAssertEqual(ints.unique(), [1, 2, 3])
    
            let strings = ["a", "a", "b"]
            XCTAssertEqual(strings.unique(), ["a", "b"])
        }
    }

    Organizing Generics in a Real Codebase

    Organization prevents entropy as your generic utilities grow.

    • Group by feature: Cache.swiftHTTPClient.swiftRepository.swift.
    • Add Type+GenericHelpers.swift for narrow helpers on specific types.
    • Avoid dumping grounds like Generics.swift.
    • Document constraints and assumptions clearly in headers.

    Example layout:

    Sources/
      Journal/
        Infrastructure/
          Cache.swift
          HTTPClient.swift
          Repository.swift
        Features/
          Entries/
            EntryRepository.swift
          Utils/
            Sequence+Unique.swift

    Real‑World Generic Recipes

    Below are practical, battle‑tested patterns.

    Safe Decoding Utility

    struct SafeDecoder {
        static func decode<T: Decodable>(_ type: T.Type, from data: Data, using decoder: JSONDecoder = JSONDecoder()) throws -> T {
            try decoder.decode(type, from: data)
        }
    }

    Generic Retry with Backoff

    func retry<T>(times: Int, delay: TimeInterval, _ task: @escaping () async throws -> T) async throws -> T {
        var attempt = 0
        while true {
            do { return try await task() }
            catch {
                attempt += 1
                if attempt >= times { throw error }
                try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
            }
        }
    }

    Generic Publisher Bridge (Combine)

    import Combine
    
    extension Publisher {
        func sinkToResult(_ receiveResult: @escaping (Result<Output, Failure>) -> Void) -> AnyCancellable {
            sink(receiveCompletion: { completion in
                if case let .failure(error) = completion {
                    receiveResult(.failure(error))
                }
            }, receiveValue: { value in
                receiveResult(.success(value))
            })
        }
    }

    Generic View Modifier (SwiftUI)

    import SwiftUI
    
    extension View {
        func cardStyle() -> some View {
            self
                .padding()
                .background(RoundedRectangle(cornerRadius: 12).fill(Color(.secondarySystemBackground)))
                .shadow(radius: 2)
        }
    }

    While not a generic type, SwiftUI heavily relies on generics under the hood—your modifiers compose into generic types representing view graphs.

    Generic Sorting and Grouping

    extension Sequence {
        func grouped<Key: Hashable>(by key: (Element) -> Key) -> [Key: [Element]] {
            Dictionary(grouping: self, by: key)
        }
    
        func sortedBy<Value: Comparable>(_ keyPath: KeyPath<Element, Value>, order: (Value, Value) -> Bool = (<)) -> [Element] {
            sorted { order($0[keyPath: keyPath], $1[keyPath: keyPath]) }
        }
    }

    Usage:

    let entriesByDay = entries.grouped { Calendar.current.startOfDay(for: $0.createdAt) }
    let sortedEntries = entries.sortedBy(\Entry.createdAt, order: >)

    Generic Async Pipeline

    func asyncMap<T, U>(_ items: [T], _ transform: (T) async throws -> U) async rethrows -> [U] {
        var results: [U] = []
        results.reserveCapacity(items.count)
        for item in items {
            results.append(try await transform(item))
        }
        return results
    }

    This is a simple building block you can evolve into concurrency‑friendly batching with TaskGroups.

    Architectural Impact: Generics vs Protocols vs Concrete Types

    • Use generics when behavior is the same across types and you want compile‑time safety.
    • Use protocols with associated types when you need contracts with type relationships (e.g., KeyValue).
    • Use concrete types when domain semantics differ or you need specific functionality.

    A pragmatic approach: Start with concrete types and a protocol; introduce generics when duplication or unsafe casts appear.

    Documentation and Discoverability

    Generics benefit from clear documentation and naming.

    • Use descriptive type parameter names (ElementModelKeyValue).
    • Add // MARK: sections and examples.
    • Document constraints, assumptions, and performance characteristics.

    Refactoring Toward Generics: A Step‑By‑Step Playbook

    1. Identify duplicated functions across similar types.
    2. Extract common behavior into a generic function or type.
    3. Add constraints to express intent and prevent misuse.
    4. Replace call sites gradually.
    5. Add tests for representative types.
    6. Profile if performance matters.
    7. Document the new abstraction and limitations.

    Error Boundaries and Safety

    Generics often sit on APIs and infrastructure.

    • Validate inputs and preconditions early.
    • Prefer Result or throws for error channels.
    • Avoid leaking implementation details in generic APIs.
    • Keep generic types small; compose behaviors via extensions.

    Interoperability: Swift, Objective‑C, and Modules

    • Objective‑C has limited support for generics; use lightweight generics (NSArray<NSString *> *) where possible in bridging.
    • Keep public generic APIs stable in libraries to avoid ABI churn.
    • Consider namespacing generic helpers to avoid collisions.

    Performance Notes

    • Generics in Swift are compile‑time constructs; concrete implementations are generated, often with zero runtime cost.
    • Inline small generic functions where appropriate.
    • Watch for specialization boundaries when crossing module lines.
    • Use Instruments to confirm assumptions; prioritize clarity first.

    Case Study: Building a Reusable, Generic Data Layer

    We’ll construct a small data layer using generics and protocols:

    // Model
    struct Entry: Identifiable, Hashable {
        let id: UUID
        var title: String
        var body: String
        var createdAt: Date
    }
    
    // Repository contract
    protocol Repository {
        associatedtype Model: Identifiable
        func list() async throws -> [Model]
        func get(id: Model.ID) async throws -> Model
    }
    
    // Concrete implementation
    struct RemoteRepository<M: Decodable & Identifiable>: Repository {
        typealias Model = M
        let baseURL: URL
        let client = HTTPClient()
    
        func list() async throws -> [M] {
            try await client.get(baseURL.appendingPathComponent("list"))
        }
    
        func get(id: M.ID) async throws -> M {
            try await client.get(baseURL.appendingPathComponent("get/\(id)"))
        }
    }
    
    // Type-erased wrapper
    struct AnyRepository<M: Identifiable>: Repository {
        private let _list: () async throws -> [M]
        private let _get: (M.ID) async throws -> M
    
        init<R: Repository>(_ repo: R) where R.Model == M {
            _list = repo.list
            _get = repo.get
        }
    
        func list() async throws -> [M] { try await _list() }
        func get(id: M.ID) async throws -> M { try await _get(id) }
    }
    
    // Usage
    let repo = RemoteRepository<Entry>(baseURL: URL(string: "https://example.com/api")!)
    let anyRepo = AnyRepository(repo)
    let entries = try await anyRepo.list()

    This architecture scales: swap RemoteRepository for CachedRepository or MockRepository without changing consumers.

    Guardrails: Access Control in Generic Code

    Be explicit about visibility:

    • public for library APIs intended for external use.
    • internal for app modules.
    • private/fileprivate for implementation details.

    Keep generic helpers in the narrowest scope that serves consumers.

    Team Conventions That Keep Generics Manageable

    • Use consistent parameter names.
    • Avoid overloading that causes inference conflicts.
    • Prefer extensions for conditional behavior and small helpers.
    • Review generic APIs for readability and error message quality.

    Swift Language Notes That Matter for Generics

    • Generics support constraints (T: Protocol) and where clauses (where T: Hashable).
    • Protocols with associatedtype can’t be used as concrete types directly; use type‑erasure.
    • Conditional conformance enables behavior when parameters meet requirements.

    Understanding these rules helps avoid frustrating compiler errors.

    Putting It All Together: A Practical Walkthrough

    Let’s build a generic Store that manages loading states for any model type.

    enum LoadState<M> {
        case idle
        case loading
        case loaded([M])
        case failed(Error)
    }
    
    final class Store<M> {
        private(set) var state: LoadState<M> = .idle
        private let repository: AnyRepository<M>
    
        init(repository: AnyRepository<M>) {
            self.repository = repository
        }
    
        func reload() async {
            state = .loading
            do {
                let items = try await repository.list()
                state = .loaded(items)
            } catch {
                state = .failed(error)
            }
        }
    }
    
    // Usage
    let repo = RemoteRepository<Entry>(baseURL: URL(string: "https://example.com/api")!)
    let store = Store(repository: AnyRepository(repo))
    await store.reload()

    This design gives you a reusable store for any Model without duplicating control flow or losing type safety.

    Pragmatic Checklist for Generics

    Use this during code review:

    • Is the abstraction justified by duplication or unsafe casts?
    • Are constraints minimal and clear?
    • Is naming consistent and expressive?
    • Is there a simpler concrete or protocol‑based design?
    • Are error messages readable and predictable?

    When Not to Use Generics

    • When behavior differs significantly across types.
    • When constraints become too complex to understand.
    • When protocol inheritance or subclassing expresses the domain more clearly.
    • When performance or ABI stability requires concrete implementations.

    Prefer clarity over cleverness.

    Migration Tips: From Concrete to Generic

    • Start with the most duplicated utilities.
    • Extract to a generic function/type with constraints.
    • Keep scopes tight.
    • Replace call sites gradually.
    • Test across representative types.

    The immediate win: less duplication, more safety, clearer intent.

    Security, Localization, and Accessibility

    Generics don’t directly impact these, but your generic infrastructure should:

    • Validate boundaries for networking and decoding.
    • Provide localization helpers with clear constraints.
    • Centralize accessibility helpers in extensions, not global utilities.

    Troubleshooting and Debugging Generics

    When the compiler fights you:

    • Add explicit type annotations or local type aliases.
    • Break complex functions into smaller ones.
    • Use where clauses instead of nested constraints in signatures.
    • Reduce overload sets.
    • Check module boundaries for specialization.

    FAQ: Quick Answers for Busy Engineers

    • Are generics zero‑cost abstractions in Swift? Often yes, due to specialization.
    • Can I use protocols with associated types as parameters? Yes, with generics or type‑erasure.
    • How do I avoid inference conflicts? Use annotations and avoid ambiguous overloads.
    • Do generics work with async/await and Combine? Absolutely—both lean heavily on generics.

    Final Thoughts

    Generics let you scale code by expressing patterns once, safely. Used judiciously, they produce clean, maintainable architectures with minimal duplication and strong guarantees. Used carelessly, they can produce dense, hard‑to‑read code.

    Start concrete, abstract gradually, constrain clearly, and document intent. Combine generics with protocols and extensions to craft expressive, reusable systems that feel “Swifty” and stand the test of growth.

    Further Reading and References

    If you want me to tailor this article to your app’s architecture—naming conventions, modules, and specific patterns—share a couple of files, and I’ll propose an idiomatic generics layer aligned with your needs.

    Spread the love
  • Associated Type in Swift

    Associated Type in Swift

    Associated Type in Swift

    In Swift, an associated type is a placeholder name for a type that is used as part of the protocol. The actual type to be used for the associated type is not specified until the protocol is adopted. Associated types are often used to specify the type of values that a protocol can work with, without specifying the exact type of those values.

    Here’s an example of a protocol that uses an associated type:

    protocol Container {
        associatedtype Item
        var items: [Item] { get set }
        mutating func append(_ item: Item)
        var count: Int { get }
        subscript(i: Int) -> Item { get }
    }
    

    In this example, the Container protocol defines an associated type called Item, which represents the type of the items that the container can hold. The protocol also defines several methods and properties that operate on the items array, but the type of the items in the array is not specified. It is up to the type that adopts the Container protocol to specify the actual type to be used for the Item associated type.

    Here’s how the Container protocol might be adopted by a concrete type:

    struct IntStack: Container {
        // specify the actual type to be used for the Item associated type
        typealias Item = Int
    
        var items: [Int] = []
    
        mutating func append(_ item: Int) {
            items.append(item)
        }
    
        var count: Int {
            return items.count
        }
    
        subscript(i: Int) -> Int {
            return items[i]
        }
    }
    

    In this example, the IntStack type adopts the Container protocol and specifies that the Item associated type should be replaced with the Int type. This allows the IntStack type to use the Int type for the items in its items array, and to implement the methods and properties required by the Container protocol using the Int type.

    Spread the love
  • Decode Apple Receipt

    Decode Apple Receipt

    Decode Apple Receipt

    Verify with the server-side click here

    /**
     * ***********************************************************************
     *  SMINRANA CONFIDENTIAL
     *   __________________
     *
     * Copyright 2020  SMINRANA
     * All Rights Reserved.
     *
     * NOTICE:  All information contained herein is, and remains
     * the property of SMINRANA and its suppliers,
     * if any.  The intellectual and technical concepts contained
     * herein are proprietary to SMINRANA
     * and its suppliers and may be covered by U.S. and Foreign Patents,
     * patents in process, and are protected by trade secret or copyright law.
     * Dissemination of this information or reproduction of this material
     * is strictly forbidden unless prior written permission is obtained
     * from SMINRANA.
     * www.sminrana.com
     *
     */
    
    import SwiftUI
    import StoreKit
    import Combine
    
    class AppStorageManager: NSObject, ObservableObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
    
        @AppStorage("username") var username: String = ""
        @AppStorage("password") var password: String = ""
        
        override init() {
            super.init()
            
            SKPaymentQueue.default().add(self)
        }
        
        @Published var products = [SKProduct]()
        
        func getProdcut(indetifiers: [String]) {
            print("Start requesting products ...")
            let request = SKProductsRequest(productIdentifiers: Set(indetifiers))
            request.delegate = self
            request.start()
        }
        
    
        // SKProductsRequestDelegate
    
        func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
            print("Did receive response \(response.products)")
                    
            if !response.products.isEmpty {
                for fetchedProduct in response.products {
                    DispatchQueue.main.async {
                        self.products.append(fetchedProduct)
                    }
                }
            }
            
            for invalidIdentifier in response.invalidProductIdentifiers {
                print("Invalid identifiers found: \(invalidIdentifier)")
            }
        }
        
        func request(_ request: SKRequest, didFailWithError error: Error) {
            print("Request did fail: \(error)")
        }
        
    
        // Transaction
        
        @Published var transactionState: SKPaymentTransactionState?
        
        func purchaseProduct(product: SKProduct) {
            if SKPaymentQueue.canMakePayments() {
                let payment = SKPayment(product: product)
                SKPaymentQueue.default().add(payment)
            } else {
                print("User can't make payment.")
            }
        }
    
        func restorePurchase() {
            SKPaymentQueue.default().restoreCompletedTransactions()
        }
        
        struct PaymentReceiptResponseModel: Codable {
            var status: Int
            var email: String?
            var password: String?
            var message: String?
        }
        
        // SKPaymentTransactionObserver
    
    
        // This gets called when transaction purchased by user
        func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
            for transaction in transactions {
                switch transaction.transactionState {
                case .purchasing:
                    self.transactionState = .purchasing
                case .purchased:
                    print("===============Purchased================")
                    UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
    
                    if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
                        FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
    
                        do {
                            let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
                            let receiptString = receiptData.base64EncodedString(options: [])
                            
                           // Send receiptString to server for further verification
                        }
                        catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
                    }
    
                case .restored:
                    UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
    
                    queue.finishTransaction(transaction)
                    print("==================RESTORED State=============")
                    self.transactionState = .restored
                case .failed, .deferred:
                    print("Payment Queue Error: \(String(describing: transaction.error))")
                    queue.finishTransaction(transaction)
                    self.transactionState = .failed
                default:
                    print(">>>> something else")
                    queue.finishTransaction(transaction)
                }
            }
        }
        
        // This gets called when a transaction restored by user
        func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
            print("===============Restored================")
            if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
                FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
    
                do {
                    let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
                    let receiptString = receiptData.base64EncodedString(options: [])
                    
                    // Send receiptString to server for further verification
                }
                catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
            }
        }
        
      
    }
    
    Spread the love