Published on

SwiftUI Advanced (2-qism): Property Wrapper'larning yuqori darajasi (Codable, Combine va Async/Await)

Authors

Oldingi darsda biz Property Wrapper'larning asosiy strukturasi va ishlash tamoyili bilan tanishdik. Endi ularning qanchalik kuchli bo'lishi mumkinligini amaliyotda sinab ko'ramiz.

1. Eng oddiy property wrapper (@Capitalized)

Keling, shunday property wrapper yozaylikki, u o'zgaruvchiga qanday yozuv kiritilishidan qat'iy nazar uni avtomatik bosh harfga (capitalize) o'girsit.

@propertyWrapper
struct Capitalized: DynamicProperty {
    @State private var value: String

    var wrappedValue: String {
        get { value }
        nonmutating set {
            value = newValue.capitalized // Avtomatik bosh harfga aylantiramiz
        }
    }

    init(wrappedValue: String) {
        // Boshlang'ich qiymatni ham bosh harf qilib saqlaymiz
        self._value = State(wrappedValue: wrappedValue.capitalized)
    }
}

Qanday ishlatiladi?

@Capitalized private var name: String = "ali"
// name ni o'qisangiz "Ali" chiqadi

Bu juda qulay! Bitta kichik struct orqali ilovadagi barcha matnlarni nazorat qilish mumkin.

2. Har qanday Modelni (Codable) saqlaydigan Wrapper

Oldingi darsda biz FileManager'ga faqatgina String saqlagan edik. Endi uni barcha Codable ob'ektlar (User, Product kabi struct'lar) ni qabul qiladigan darajada Generic qilamiz.

Shu bilan birga, obyekt o'zgarganida darhol boshqa joylarga ham signal yuborishi uchun uning ichiga Combine'dagi CurrentValueSubject ni (publisher) qo'shamiz!

import SwiftUI
import Combine

@propertyWrapper
struct FileManagerCodableProperty<T: Codable>: DynamicProperty {
    @State private var value: T?
    let key: String

    // O'zgarishlarni tarqatuvchi Publisher
    private let publisher: CurrentValueSubject<T?, Never>

    var wrappedValue: T? {
        get { value }
        nonmutating set {
            value = newValue
            save(newValue: newValue)
            publisher.send(newValue) // Yangi qiymatni tarqatamiz!
        }
    }

    // Dollar ($) ishlatilganda o'zimizning maxsus ob'ektni qaytaramiz
    var projectedValue: CustomProjectedValue<T> {
        CustomProjectedValue(
            binding: Binding(get: { wrappedValue }, set: { wrappedValue = $0 }),
            publisher: publisher
        )
    }

    init(key: String) {
        self.key = key

        // Boshlang'ich o'qish (JSONDecoder orqali)
        do {
            let path = FileManager.documentsPath(key: key)
            let data = try Data(contentsOf: path)
            let object = try JSONDecoder().decode(T.self, from: data)
            self._value = State(wrappedValue: object)
            self.publisher = CurrentValueSubject(object)
        } catch {
            self._value = State(wrappedValue: nil)
            self.publisher = CurrentValueSubject(nil)
        }
    }

    private func save(newValue: T?) {
        // JSONEncoder orqali saqlash kodi...
    }
}

3. Projected Value (Dollar belgisi) ni mukammallashtirish

E'tibor bergan bo'lsangiz projectedValue sifatida endi Binding emas, balki CustomProjectedValue degan ob'ektni qaytaryapmiz. U o'z ichiga nimalarni qamrab olishi kerak?

struct CustomProjectedValue<T: Codable> {
    let binding: Binding<T?>
    let publisher: CurrentValueSubject<T?, Never>

    // Async/Await orqali eshitish uchun Stream
    var stream: AsyncPublisher<CurrentValueSubject<T?, Never>> {
        publisher.values
    }
}

Natijada qanday qudratga ega bo'lamiz?

Endi SwiftUI View'ingizda bir qator kod bilan qanaqangi imkoniyatlar yaratilishini ko'ring:

struct PropertyWrapperComplexView: View {
    // 1. Modelimiz tayyor (avtomatik saqlanadi va o'qiladi)
    @FileManagerCodableProperty(key: "user_data")
    private var user: User?

    var body: some View {
        VStack {
            // ... UI kodlari ...
        }
        // 2. SwiftUI Binding sifatida ishlatish:
        .sheet(isPresented: $user.binding.mapToBool()) { ... }

        // 3. Combine orqali o'zgarishlarni tutib olish:
        .onReceive($user.publisher) { newValue in
            print("User o'zgardi: \(newValue)")
        }

        // 4. Async/Await (Swift Concurrency) orqali kuzatish:
        .task {
            for await newValue in $user.stream {
                print("Stream orqali keldi: \(newValue)")
            }
        }
    }
}

Xulosa

Ko'rib turganingizdek, @propertyWrapper bu shunchaki "o'zgaruvchining ustiga o'ralgan kod" emas. Bu butun boshli ma'lumotlar bazasi, Combine arxitekturasi va Async/Await qatlamini faqatgina Bitta Qator Kod ga yashirish imkonini beruvchi juda kuchli vositadir! Aynan shu kabi yondashuvlar dasturni eng yuksak (Senior) darajadagi ko'rinishga olib chiqadi.

Buy mea coffee