0Pricing
Swift Academy · درس

التأثيرات والتبعيات

عالج التأثيرات الجانبية وحقن التبعيات.

التأثيرات والتبعيات درس مجاني في Swift Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Swift Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Swift Academy 4 دروس في المجموع.

لماذا توجد التأثيرات

يجب أن تبقى الـ reducers نقية: فعند إعطائها حالة وإجراءً، لا تفعل سوى تغيير الحالة. لكن التطبيقات الفعلية تحتاج إلى تأثيرات جانبية مثل طلبات الشبكة والمؤقتات. ينمذج TCA هذه التأثيرات على أنها Effects يعيدها الـ reducer، مما يحافظ على نقاء التغيير بينما يُنفّذ العمل غير المتزامن بصورة منفصلة.

هذا الفصل هو ما يجعل الميزات قابلة للاختبار.

import ComposableArchitecture

// Reducer stays pure; side effects are values it returns.
// Returning .none means no further work.

إعادة .none

عندما يغيّر إجراءٌ الحالة فقط ولا يحتاج إلى عمل لاحق، يعيد الـ reducer القيمة .none. وهذا هو التأثير الأكثر شيوعًا، ويدل على عدم وجود عمل غير متزامن يجب تنفيذه.

لقد رأيتم ذلك من قبل؛ فهو الأساس الذي يعود إليه كل فرع.

import ComposableArchitecture

Reduce { state, action in
    switch action {
    case .incrementTapped:
        state.count += 1
        return .none
    }
}

‏Effect.run للعمل غير المتزامن

لتنفيذ عمل غير متزامن، تعيدون .run. تستقبل دالة الإغلاق الخاصة به مقبض send الذي تستخدمونه لإعادة تمرير النتائج إلى النظام على هيئة إجراءات جديدة.

يحمّل إجراء الجلب البيانات هنا، ثم يرسل إجراء استجابة عند اكتمال العملية.

import ComposableArchitecture

Reduce { state, action in
    switch action {
    case .reloadTapped:
        return .run { send in
            let value = try await loadNumber()
            await send(.responseReceived(value))
        }
    case let .responseReceived(value):
        state.count = value
        return .none
    }
}

إعادة النتائج على هيئة إجراءات

لا يمكن للتأثير تغيير الحالة مباشرةً؛ بل يرسل إجراءات عبر send. يتولى الـ reducer بعد ذلك معالجة هذه الإجراءات وتحديث الحالة. تحافظ هذه الحلقة على وجود كل تغييرات الحالة داخل الـ reducer.

وهكذا تتحول استجابة الشبكة إلى إجراء استجابة يخزّنه الـ reducer.

import ComposableArchitecture

.run { send in
    let result = try await fetch()
    await send(.dataLoaded(result))
}
// .dataLoaded is then handled by the reducer to set state.

معالجة الأخطاء في التأثيرات

قد يرمي العمل غير المتزامن أخطاءً. يمكنكم التقاط الأخطاء داخل التأثير وإرسال إجراء فشل، مما يتيح للـ reducer الانتقال إلى حالة خطأ بطريقة منظمة.

تستقبل دالة الإغلاق الاختيارية catch في .run الخطأ الذي جرى رميه.

import ComposableArchitecture

.run { send in
    let value = try await loadNumber()
    await send(.responseReceived(value))
} catch: { error, send in
    await send(.loadFailed)
}

لماذا تهم التبعيات

إذا استدعى الـ reducer عميل شبكة أو قرأ التاريخ مباشرةً، يصبح اختباره والتحكم فيه أمرًا صعبًا. يحل TCA ذلك باستخدام dependencies: وهي خدمات قابلة للحقن يصل إليها الـ reducer عبر @Dependency.

وفي الاختبارات، تستبدلونها ببدائل وهمية يمكن التنبؤ بها.

import ComposableArchitecture

// Instead of calling APIClient.shared directly,
// inject it as a dependency so tests can replace it.

غلاف الخاصية @Dependency

تعلنون عن التبعية داخل الـ reducer باستخدام @Dependency، وتسمّونها عبر مسار المفتاح الخاص بها في قيم التبعيات. توفر المكتبة تبعيات مضمّنة مثل الساعة ومولّد uuid.

يستدعي الـ reducer هنا الساعة المستمرة.

import ComposableArchitecture

@Reducer
struct TimerFeature {
    @ObservableState
    struct State: Equatable { var seconds = 0 }
    enum Action { case start, tick }

    @Dependency(\.continuousClock) var clock

    var body: some ReducerOf<Self> {
        Reduce { state, action in .none }
    }
}

استخدام تبعية في تأثير

بعد التصريح عنها، تستخدمون التبعية داخل .run. وبما أن الساعة محقونة، يمكن للاختبارات توفير ساعة اختبار تقدّم الوقت عند الطلب.

تنبض المؤقتات هنا كل ثانية باستخدام الساعة المحقونة.

import ComposableArchitecture

var body: some ReducerOf<Self> {
    Reduce { state, action in
        switch action {
        case .start:
            return .run { send in
                for await _ in self.clock.timer(interval: .seconds(1)) {
                    await send(.tick)
                }
            }
        case .tick:
            state.seconds += 1
            return .none
        }
    }
}

تعريف تبعيتكم الخاصة

تسجّلون التبعيات المخصصة بجعل قيمة ما متوافقة مع DependencyKey وتوفير قيمة liveValue. ثم تضيفون خاصية محسوبة إلى DependencyValues لكي تحصل على مسار مفتاح.

يصبح عميل API هنا تبعية من الدرجة الأولى.

import ComposableArchitecture

struct NumberClient {
    var fetch: () async throws -> Int
}

extension NumberClient: DependencyKey {
    static let liveValue = NumberClient {
        try await loadNumber()
    }
}

extension DependencyValues {
    var numberClient: NumberClient {
        get { self[NumberClient.self] }
        set { self[NumberClient.self] = newValue }
    }
}

حقن التبعية المخصصة

بعد إعداد مسار المفتاح، يعلن الـ reducer عن العميل باستخدام @Dependency(\.numberClient) ويستدعيه داخل التأثيرات. تستخدم بيئة الإنتاج القيمة الحية، بينما تستبدلها الاختبارات.

يجلب إجراء إعادة التحميل البيانات هنا من خلال العميل المحقون.

import ComposableArchitecture

@Reducer
struct NumberFeature {
    @ObservableState
    struct State: Equatable { var value = 0 }
    enum Action { case reload, response(Int) }

    @Dependency(\.numberClient) var numberClient

    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .reload:
                return .run { send in
                    let n = try await numberClient.fetch()
                    await send(.response(n))
                }
            case let .response(n):
                state.value = n
                return .none
            }
        }
    }
}

إلغاء التأثيرات

يجب أن تكون التأثيرات طويلة الأمد، مثل المؤقتات، قابلة للإلغاء. تضع للتأثير معرّفًا cancellable، ثم تلغيه لاحقًا من إجراء آخر باستخدام .cancel.

هنا يمكن إيقاف المؤقت عند الطلب.

import ComposableArchitecture

enum CancelID { case timer }

// Start a cancellable effect:
.run { send in /* ... */ }
    .cancellable(id: CancelID.timer)

// Stop it from another action:
.cancel(id: CancelID.timer)

مراجعة سريعة: التأثيرات والنتائج

تذكّر كيف يعيد التأثير نتيجته إلى الـ reducer.

مراجعة: التأثيرات والاعتماديات

تعلّمت كيف يتعامل TCA مع التأثيرات الجانبية وحقن الاعتماديات:

  • تبقى الـ reducers نقية وتعيد قيمًا من النوع Effect؛ وتعني .none عدم وجود عمل.
  • ينفّذ .run العمل غير المتزامن، ثم يعيد النتائج عبر await send(.action).
  • تُعالَج الأخطاء باستخدام إغلاق catch الخاص بالتأثير.
  • يحقن @Dependency خدمات مثل الساعة أو العملاء الذين تنشئهم، بعد تعريفها عبر DependencyKey وDependencyValues.
  • يمكن جعل التأثيرات طويلة الأمد cancellable باستخدام معرّف.

ستركّب في الخطوة التالية الميزات معًا، وستختبرها اختبارًا شاملًا.

import ComposableArchitecture

// Recap: pure reducer + Effect.run for async work
// + @Dependency for testable, injectable services.

الأسئلة الشائعة

هل درس «التأثيرات والتبعيات» مجاني؟

نعم — نص درس «التأثيرات والتبعيات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Swift Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Swift Academy 4 دروس في المجموع.

ماذا ستتعلم في «التأثيرات والتبعيات»؟

عالج التأثيرات الجانبية وحقن التبعيات. تتمرن على Swift Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Swift Academy؟

لا تُشترط خبرة سابقة. Swift Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «التأثيرات والتبعيات»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Swift Academy هذا؟

نعم. كل درس في Swift Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. الحالة والإجراء والمختزل
  2. المخزن وتكامل SwiftUI
  3. التأثيرات والتبعيات
  4. تركيب الميزات واختبارها
← العودة إلى Swift Academy