Skip to content

Instantly share code, notes, and snippets.

@NorhanBoghdadi
Last active January 14, 2022 23:15
Show Gist options
  • Save NorhanBoghdadi/1b98d55c02b683ddef7e05c2ebcccd47 to your computer and use it in GitHub Desktop.
Save NorhanBoghdadi/1b98d55c02b683ddef7e05c2ebcccd47 to your computer and use it in GitHub Desktop.

مكتبة الـ Composable Architecture

ـCI

الـ Composable Architecture (سيتم الإشارة لها بـ TCA للإختصار): هي مكتبة لبناء البرامج بطريقة متسقة وشاملة، وهي تدعم الـ composition (التكوين) والاختبارات وسهولة الإستخدام. يمكن استخدامها مع (SwiftUI, UIKit) كما انها تدعم جميع منصات أبل (iOS, macOS, tvOS, watchOS).

ما هي مكتبة ال Composable Architecture؟

تقوم هذه المكتبة بتوفير العديد من الأدوات الأساسية التي يمكن استخدامها في بناء البرامج مختلفة الاهداف والتعقيد. انها تقوم بتوفير قصص مقنعة بإمكانك تتبعها لحل العديد من المشاكل التي تواجهك يوميًا في بناء البرامج، مثل:

  • ـ State management
    كيف يمكنك التحكم في حالة البرنامج بإستخدام "نوع القيمة" بسيط، وايضا يمكنك مشاركة هذه الحالة بين العديد من الشاشات، ليمكنك من متابعة التغيير الذي يحدث في شاشة من شاشة اخرى.
  • Composition
    كيف تقوم بتفكيك خاصية كبيرة الي اجزاء صغيرة، بحيث ان تلك الوحدات الصغيرة يمكن تجميعها مرة اخري لتكوين الخاصية.
  • Side effects
    كيف يمكنك ان تجعل اجزاء معينة من البرنامج تتواصل مع العالم الخارجي بأكثر طريقة مفهومة وقابلة للاختبار.
  • Testing
    كيف تقوم باختبار الخاصية في طريقة البناء "Architecture"
    وايضًا ان تكتب اختبارات للخواص الناتجة من تجميع العديد من الاجزاء، وايضًا يمكن كتابة اختبارات end-to-end تمكنك من فهم كيف تأثر الاثار الجانبية "side effects" في البرنامج الخاص بك. وهذا يسمح لك بامتلاك ضمانات قوية بان المنطق الذي تستخدمه يعمل بالطريقة المتوقعة.
  • Ergonomics
    كيف يمكنك تحقيق جميع النقط السابقة باستخدام API بسيط مع بعض من المصطلحات والمبادى.

تعرف اكثر على ال Composable Architecture

تم تصميم ال Composable Architecture على العديد من الحلقات والتي يمكن إيجادها في Point-Free، سلسلة من الفيديوهات التي تقوم باكتشاف البرمجة الوظيفية مع سويفت، يقوم باستضافاتها Brandon Williams و Stephen Celis.

يمكنك مشاعدة جميع الحلقات هنا, وايضا يمكنك مشاهدة جولة في TCA من البداية:
الجزء الاول,الجزء الثاني, الجزء الثالث و الجزء الرابع.

video poster image

الأمثلة

Screen shots of example applications

يحتوي هذا المخزن على العديد من الأمثلة لتوضيح كيف يمكنك حل المشاكل الشائعة والمعقدة باستخدام The Composable Architecture. قم بفحص هذا الدليل لتراهم جميعًا، وهذا يشمل:

هل تبحث عن شئ اكثر اهمية؟ قم بفحص هذا الكودisowords, وهي عبارة عن لعبة بحث عن الكلام، مبنية باستخدام SwiftUI و ال Composable Architecture.

نموذج بسيط للإستخدام

لبناء خاصية جديدة بإستخدام هذه المكتبة، تقوم بتعريف بعض الأنواع والقيم التي تحاكي الكيانات ذات الصلة بالبرنامج المراد تطويره.

  • ـState: وهو نوع يمثل البيانات التي تحتاجها الخاصية الجديدة لتنفيذ عملها ورسم واجهة المستخدم التي تمثلها.
  • Action: وهو نوع يمثل جميع الأحداث الي قد تتم في الخاصية الجديدة، مثل اجراءات المستخدم، الإشعارات، مصادر الحدث، واشياء اخرى.
  • Environment: النوع الذي يحتوي على الكيانات التابعة للخاصية الجديدة مثل واجهة برمجة التطبيق (API) او البيانات التحليلة الخاصة بالتطبيق، إلى ما غير ذلك.
  • Reducer: وهي عبارة عن function تصف كيفية الإنتقال من الوضع الحالي للتطبيق للوضع الذي يليه في ضوء حدث معين. وهي مسئولة أيضًا عن إعطائنا أية نتائج يتوجب تنفيذها مثل طلبات الـAPI، ويتم هذا من خلال إرجاع قيمة من نوع Effect.
  • Store: يمثل الـ runtime المسئول عن تنفيذ الخاصية الجديدة، حيث تقوم بإرسال جميع المهام التي يرغب المستخدم بالقيام بها له، ويقوم بدوره بتشغيل الreducer والحصول على النتائج؛ حيث تتمكن حينها من متابعة التغييرات التي تطرأ على الـstore فتقوم بتحديث واجهة المستخدم عند الحاجة.

تتمثل الفائدة من القيام بذلك في أنك سوف تتمكن بشكل فوري وتلقائي من اختبار الكود الخاص بالخاصية الجديدة، وسوف تتمكن أيضًا من تقسيم أية خاصية مهما كانت كبيرة أو معقدة إلى نطاقات عمل أصغر يسهل العمل عليها والربط بينهم.

فمثلًا، تخيل واجهة مستخدم تعرض رقمًا بالإضافة إلى زر الـ "+" والـ"-" لإضافة أو طرح رقم. ثم، لجعل الأمور مثيرة للإهتمام، تخيل أن هناك زرا إضافيًا، عند النقر عليه، يقوم بعمل طلب من خلال الـ API لتحميل حقيقة عشوائية عن الرقم الظاهر على الشاشة ثم يقوم بعرض هذه الحقيقة على شاشة عرض الرسائل التحذيرية.

يمكن اعتبار أن حالة state هذه الخاصية تتكون من رقم صحيح للعدد الحالي؛ بالإضافة إلى optional string الذي يمثل عنوان الرسالة التحذيرية التي نريد عرضها (وهو optional string لأن nil في هذه الحالة تعني عدم عرض الرسالة):

struct AppState: Equatable {
  var count = 0
  var numberFactAlert: String?
}

ثم نقوم بتحديد الأحداث المتعلقة بهذه الخاصية، حيث يوجد الأحداث الصريحة مثل النقر على زر الطرح، أو زر الإضافة أو زر عرض الحقيقة العشوائية؛ ويوجد أيضًا بعض الأحداث الضمنية مثل غلق الرسالة التحذيرية أو عند الحصول على رد من الخادم الخاص بـAPI الحقائق العشوائية:

enum AppAction: Equatable {
  case factAlertDismissed
  case decrementButtonTapped
  case incrementButtonTapped
  case numberFactButtonTapped
  case numberFactResponse(Result<String, ApiError>)
}

struct ApiError: Error, Equatable {}

بعد ذلك، نقوم بمحاكاة بيئة الكيانات التي تعتمد عليها هذه الخاصية للقيام بعملها. وبشكل أكثر دقة، نحتاج، من أجل تحميل حقيقة عن الرقم المعروض ان نقوم بإنشاء قيمة من نوع Effect تشتمل على طلب الحصول علي بيانات من شبكة الإنترنت. وتكون الكيانات التابعة، في هذه الحالة، عبارة عن function من Int إلى Effect<String, ApiError>، حيث يمثل الـ string الرد القادم من الشبكة. علاوة على ذلك، سوف تقوم قيمة الـ Effect التي انشأناها بالقيام بعملها من خلال مسار تعليمات غير رئيسي (thread) (تمامًا مثل URLSession). لذلك، سنحتاج إلى طريقة تمكننا من استقبال القيم المرتجعة على مسار التعليمات الرئيسي. ونقوم بذلك عن طريق استخدام منسق سلسسلة التعليمات الرئيسية؛ الذي يتوجب علينا وبشدة التحكم فيه لنتمكن من كتابة كود الإختبارات. يجب علينا استخدام AnyScheduler لنستطيع استخدام DispatchQueue في النسخة التي ستكون في يد المستخدم، واستخدام منسق اختبارات في النسخة تحت الإختبار.

struct AppEnvironment {
  var mainQueue: AnySchedulerOf<DispatchQueue>
  var numberFact: (Int) -> Effect<String, ApiError>
}

وتكون الخطوة التالية بإنشاء قيمة من نوع Reducer التي من شأنها تطبيق الخوارزميات الخاصة بمجال عمل التطبيق؛ وهي تصف كيفية الإنتقال من الحالة الحالية للحالة التالية، كما تصف النتائج التي يتوجب علينا تنفيذها. ولكن لا تحتاج جميع الأحداث أن تنفذ نتائج معينة ويمكنها ان تعود بقيمة من نوع .none في هذه الحالة.

let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, environment in
  switch action {
  case .factAlertDismissed:
    state.numberFactAlert = nil
    return .none

  case .decrementButtonTapped:
    state.count -= 1
    return .none

  case .incrementButtonTapped:
    state.count += 1
    return .none

  case .numberFactButtonTapped:
    return environment.numberFact(state.count)
      .receive(on: environment.mainQueue)
      .catchToEffect(AppAction.numberFactResponse)

  case let .numberFactResponse(.success(fact)):
    state.numberFactAlert = fact
    return .none

  case .numberFactResponse(.failure):
    state.numberFactAlert = "Could not load a number fact :("
    return .none
  }
}

وأخيرًا، نحدد مكان عرض الخاصية الجديدة، والتي تشتمل على قيمة من نوع Store<AppState, AppAction> لتتمكن من مراقبة جميع التغييرات التي تطرأ على الحالة وإعادة رسم واجهة المستخدم وفقًا لذلك؛ كما سنتمكن من إرسال جميع الإجراءات التي يقوم بها المستخدم إلى الـstore المسئول عن التنقل بين الحالات المختلفة. ويتوجب علينا كذلك إنشاء struct يشتمل على الحقيقية العشوائية لنتمكن من جعله Identifiable، حيث يعد هذا مطلبًا اساسيًا لـ .alert:

struct AppView: View {
  let store: Store<AppState, AppAction>

  var body: some View {
    WithViewStore(self.store) { viewStore in
      VStack {
        HStack {
          Button("") { viewStore.send(.decrementButtonTapped) }
          Text("\(viewStore.count)")
          Button("+") { viewStore.send(.incrementButtonTapped) }
        }

        Button("Number fact") { viewStore.send(.numberFactButtonTapped) }
      }
      .alert(
        item: viewStore.binding(
          get: { $0.numberFactAlert.map(FactAlert.init(title:)) },
          send: .factAlertDismissed
        ),
        content: { Alert(title: Text($0.title)) }
      )
    }
  }
}

struct FactAlert: Identifiable {
  var title: String
  var id: String { self.title }
}

من المهم جدًا ملاحظة أننا قمنا بتنفيذ هذه الخاصية بدون وجود قيمة حقيقية وفعلية من نوع Effect؛ ويعد هذا امرا مهما لأنه يعني أننا نستطيع بناء الخواص الجديدة التي نريدها بمعزل عن الكيانات التابعة لها مما قد يساهم بشكل كبير في تقليل الوقت الذي يحتاجه الجهاز في تنفيذ الكود.

كما أنه من المناسب، في حالة الـ UIKit أن يكون لدينا UIViewController تعتمد على هذا الـstore؛ حيث تقوم بالإشتراك في هدا الـstore في viewDidLoad وذلك لتتمكن من تحديث واجهة المستخدم وإظهار الرسائل التحذيرية. إن الكود الخاص بالـ UIKit أطول قليلًا من SwiftUI، لذلك قمنا بإخفاءه هنا:

انقر هنا لإظهار الكود
class AppViewController: UIViewController {
  let viewStore: ViewStore<AppState, AppAction>
  var cancellables: Set<AnyCancellable> = []

  init(store: Store<AppState, AppAction>) {
    self.viewStore = ViewStore(store)
    super.init(nibName: nil, bundle: nil)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    let countLabel = UILabel()
    let incrementButton = UIButton()
    let decrementButton = UIButton()
    let factButton = UIButton()

    // Omitted: Add subviews and set up constraints...

    self.viewStore.publisher
      .map { "\($0.count)" }
      .assign(to: \.text, on: countLabel)
      .store(in: &self.cancellables)

    self.viewStore.publisher.numberFactAlert
      .sink { [weak self] numberFactAlert in
        let alertController = UIAlertController(
          title: numberFactAlert, message: nil, preferredStyle: .alert
        )
        alertController.addAction(
          UIAlertAction(
            title: "Ok",
            style: .default,
            handler: { _ in self?.viewStore.send(.factAlertDismissed) }
          )
        )
        self?.present(alertController, animated: true, completion: nil)
      }
      .store(in: &self.cancellables)
  }

  @objc private func incrementButtonTapped() {
    self.viewStore.send(.incrementButtonTapped)
  }
  @objc private func decrementButtonTapped() {
    self.viewStore.send(.decrementButtonTapped)
  }
  @objc private func factButtonTapped() {
    self.viewStore.send(.numberFactButtonTapped)
  }
}

عندما نكون جاهزين لعرض واجهة المستخدم، نقوم بإنشاء الـ store في الـ scene delegate على سبيل المثال؛ حينها يتعين علينا توفير أي كيانات تابعة. في المثال الحالي، بإمكاننا إستخدام الـ effect الذي يقوم بإرجاع string وهمي:

let appView = AppView(
  store: Store(
    initialState: AppState(),
    reducer: appReducer,
    environment: AppEnvironment(
      mainQueue: .main,
      numberFact: { number in Effect(value: "\(number) is a good number Brent") }
    )
  )
)

ويعد هذا كافيا لإظهار شيئا على الشاشة لتجربته. بالتأكيد، تتطلب هذه الطريقة خطوات أكثر عن استخدام SwiftUI فقط، ولكن لهذا بعض الفوائد. حيث يصبح لدينا طريقة ثابتة لتطبيق أية تغييرات في حالة الخاصية، بدلا من توزيع الكود بين بعض الكيانات الـobservable وبين بعض الـ closures الخاصة بعناصر واجهة المستخدم. علاوة على ذلك، أصبح لدينا طريقة مختصرة لتوضيح النتائج غير المباشرة. كما يمكننا اختبار الكود مباشرة، بما في ذلك، القيم المرتجعة، دون أن نكون بحاجة للقيام بعمل إضافي.

الاختبارات

لكي تختبر، عليك اولًا ان تنشئ TestStore بنفس المعلومات التي قد تنشئها مع Store عادي، مع الاختلاف هذه المرة ان بامكانك إمداد تبعيات صالحة للاختبار. بالأخص، يمكنك استخدام test scheduler بدل من DispatchQueue.main لأنه يسمح لنا بالتحكم في وقت التنفيذ، وايضًا لسنا مضطرين بانتظار قوائم الانتظار.

let scheduler = DispatchQueue.test

let store = TestStore(
  initialState: AppState(),
  reducer: appReducer,
  environment: AppEnvironment(
    mainQueue: scheduler.eraseToAnyScheduler(),
    numberFact: { number in Effect(value: "\(number) is a good number Brent") }
  )
)

وبمجرد انشاء test store بامكاننا استخدامه لعمل تأكيد كامل لجريان الخطوات للمستخدم. ومع كل خطوة علينا اثبات ان الحالة state غيرت ما نتوقع. علاوة على ذلك، اذا سببت خطوة تنفيذ تأثير effect، والذي بدوره يغذي المتجر ببيانات، علينا تأكيد بأن تلك الافعال action تم تسلمها بصورة صحيحة.

في هذا الاختبار يمكن المستخدم ان يقوم بزيادة ونقص العد، ثم يسأل عن الرقم السليم، والرد على السؤال يحفز انذار بالظهور، ورفض الانذار يجعله يختفي.

// Test that tapping on the increment/decrement buttons changes the count
store.send(.incrementButtonTapped) {
  $0.count = 1
}
store.send(.decrementButtonTapped) {
  $0.count = 0
}

// Test that tapping the fact button causes us to receive a response from the effect. Note
// that we have to advance the scheduler because we used `.receive(on:)` in the reducer.
store.send(.numberFactButtonTapped)

scheduler.advance()
store.receive(.numberFactResponse(.success("0 is a good number Brent"))) {
  $0.numberFactAlert = "0 is a good number Brent"
}

// And finally dismiss the alert
store.send(.factAlertDismissed) {
  $0.numberFactAlert = nil
}

وهذه هي مبادى بناء واختبار خاصية في ال Composable Architecture. ويوجد العديد من الاشياء التي يمكن استكشافها، مثل التركيب، النمطية، القدرة على التكيف و التأثيرات المعقدة. و ال الأمثلة تملك بعض من المشاريع لاستكشاف ورؤية العديد من الاستخدامات المتقدمة.

المعالج

  • تأتي مكتبة الـ Composable Architecture مع العديد من الأدوات لتساعد في المعالجة.
  • يقوم ال reducer.debug() بتزويد reducer مع معالج يقوم بوصف كل فعل يستلمه ال reducer وكل متغير يقوم به.
received action:
  AppAction.todoCheckboxTapped(
    index: 0
  )
  AppState(
    todos: [
      Todo(
-       isComplete: false,
+       isComplete: true,
        description: "Milk",
        id: 5834811A-83B4-4E5E-BCD3-8A38F6BDCA90
      ),
      Todo(
        isComplete: false,
        description: "Eggs",
        id: AB3C7921-8262-4412-AA93-9DC5575C1107
      ),
      Todo(
        isComplete: true,
        description: "Hand Soap",
        id: 06E94D88-D726-42EF-BA8B-7B4478179D19
      ),
    ]
  )
  • يقوم reducer.signpost() بتزويد reduce بعلامات لتكتسب افكار عن الوقت اللازم لكي يتم تنفيذ الافعال، ومتى تعمل الeffects.

مكتبات إضافية

واحد من اهم مبادئ ال Composable Architecture هي انه لا يتم تطبيق ال "Side effects" بطريقة مباشرة، ولكن يتم استخدامها من خلال wrapper من نوع Effect ، ويتم ارجاعها من ال"reducers"، ثم يقوم الStore لاحقًا بعمل التأثير effect. انه من المهم تبسيط كيف تسري البيانات في البرنامج، ولزيادة قابلية الاختبار في الدورة الكاملة لنشاط المستخدم للتأثير على التنفيذ.

لكن، هذا يعني ايضًا ان العديد من المكاتب والSDKs التي تتعامل معاها يوميًا تحتاج إلى تعديل لتكون صالحة لل Composable Architecture. لهذا السبب نحتاج إلى تخفيف الالم من استخدام بعض من الانظمة المشهورة لابل عن طريق توفير غلاف لمكتبات اخرى تقوم بعرض نفس الوظائف بطريقة تتناسب مع مكتبتنا، ندعم:

  • ـComposableCoreLocation:
    غلاف ل CLLocationManager والذي يسهل الاستخدام من "reducer" ، ويسهل كتابة الاختبارات للتأكد من منطق وظائفCLLocationManager.
  • ComposableCoreMotion: غلاف ل CMMotionManager والذي يسهل استخدام ال"reducer"ويسهل كتابة الاختبارات للتأكد من منطق وظائف CMMotionManager.
  • هناك العديد قادم قريبًا. 😉

واذا كنت مهتمًا للمشاركة بwrapper لمكتبة لو نقم بعد بتغطيتها، من فضلك ابدأ بissue، تعرض فيها اهتمامك حتى يمكننا مناقشة الطريق اللازم.

أسئلة متكررة

  • كيف يمكن مقارنة ال Compoasble Architecture مع Elm, Redux، والاخرين؟

    اضغط للمزيد تم بناء ال Composable Architecture على اساس من الافكار الشائعه مع Elm و Redux. ولكن تم اخراجه ليكون مألوف مع Swift و على منصات ابل. في بعض الاحوال TCA يعتبر اكثر عندًا من بعض المكتبات الأخرى. مثلًا Redux ليس ملزم كيفية عمل ال "side effects"، ولكن TCA يلزم ان جميع ال "side effects" ان تكون مصاغة على شكل `Effect` وان تعود من ال reducer.

    في بعض الطرق الاخرى يعتبر TCA اكثر لينًا من المكتبات الاخري، مثلًا Elm يتحكم اي نوع من التأثير effect يمكن تكوينه من الCmd، ولكن TCA يسمح بخطة هروب لأي نوع من التأثير effect لانه تابع لبروتوكول Publisher.

    وايضًا يوجد بعض الاشياء التي يقوم TCA بوضعها اولوية ولكن لا يلتفت اليها Redux و Elm او معظم المكتبات الاخرى. مثلًا، يعتبر التركيب Compostion جزء مهم من TCA، وهو عبارة عن عملية تقسيم الوظائف لاجزاء صغيرة ثم تجميعها مرة اخرى. ويتم عمل ذلك عن طريق معاملات pullback و combine، وتساعد في التعامل مع الوظائف المعقدة وايضًا عمل نماذج لخلق كود منفصل جيد و وقت معالجة Compile time افضل.

  • لماذا لا يعتبرStore "ثريد" آمن؟
    لماذا لا تكون send في قائمة الانتظار؟
    لماذا لا تعمل send في ال"ثريد" الاساسي؟

اضغط لترى الإجابة

كل التعاملات مع Store (شاملة كل مجالاتها والViewStore المشتقة منها) يجب ان تبنى في نفس الثريد. واذا كان ال Store "يزود" swiftUI او UIKit، فان جميع التعاملات يجب ان تكون في main ثريد.

عندما يُرسل فعل إلي الStore،فانه يدار "reducer" في الحالة الحالية، وهذه العمليه لا يمكن ات تتم من خلال threads متعددة. لحل هذه المشكلة يمكن استخدام الصف queue في تنفيذ send، ولكن هذا يولد بعض الصعوبات الجديدة:

  1. اذا تم عملها بسهولة ب DispatchQueue.main.async ستقوم بحمل قفزة من الثريد حتى اذا كنت بالفعل في الثريد الاساسي. مما قد ينتج بعض الافعال التي لا يمكن التنبؤ بها في UIKit و SwiftUI، لذلك يجب عليك ان تعمل بشكل متزامن كما في كتل الرسوم المتحركة.

  2. من المحتمل ان تنشئ scheduler والذي يقوم بعمله في الثريد الاساسي فورا او يستخدم DispatchQueue.main.async. (e.g. see CombineScheduler's UIScheduler) وهذا يدخل العديد من الصعوبات، ولا يحبذ استخدامه بلا اسباب جيدة.

لهذا نلزم جميع الافعال ان ترسل من نفس الثريد. هذا المطلب يملك نفس تصميم URLSession وبعض Apple Apis الاخرى. تلك ال APIs تميل إلى توصيل الناتج منها في الثريد الملائم لهم، ثم تكون مسئوليتك اعادة ارسالهم الى الثريد الاساسي اذا كنت تريد ذلك. يجعلك ال Composable Architecture مسئول من التأكيد عن ارسال الافعال actions في الثريد الاساسي. واذا كنت تستخدم تأثير effect والذي يرسل على ثريد اخر، يجب ان تجبرها بالعودة الى الثريد الاساسي عن طريق استخدام .receive(on:)

يفترض هذا النهج اقل الافتراضات عن كيفية عمل وتحول الeffects، ويمنع قفذات الثريد واعادة الارسال الغير ضرورية. وايضا يوفر بعض من الاختبارات. اذا كانت تأثيراتك غير مسئولة عن جدولتها، فان جميع الاختبارات عن التأثير تعمل بشكل متزامن وفوري. لن يكون بإمكانك اختبار التأثيرات effects المتنوعه وتشابكها مع بعضها وكيف تؤثر على حالة البرنامج الخاص بك. ولكن بترك المنظم خارج ال Store بامكاننا اختبار هذه النواحي من ال"تأثير" اذا رغبنا في ذلك، او بامكاننا تجاهلها.

ولكن، اذا كنت لا تزال غير معجب باختيارنا، لا تقلق. يعتبر TCA مرن كفاية ليسمح لك بتقديم الوظائف التي ترغب بها. من المسموح به ايضا انه يمكنك ان تقوم بانشاء reducer ذات ترتيب عالي والذي بامكانه اجبار "تأثيراتك" ان تقوم بايصال الناتج في "الثريد" الاساسي، بغض النظر عن مكان عملها:

extension Reducer {     
    func receive<S: Scheduler>(on scheduler: S) -> Self {
        Self { state, action, environment in
            self(&state, action, environment)
                .receive(on: scheduler)
                .eraseToEffect()
        }
    }
}          

من المرجح ان تحتاج ايضًا إلى UIScheduler حتى لا تضطر ان تنشئ thread hops.

متطلبات الإستخدام

تعتمد ال Composable Architecture على إطار عمل Combine، لذلك تتطلب دعم نظام تشغيل لا يقل عن iOS 13، macOS 10.15, Mac Catalyst 13, tvOS 13, watch OS 6. واذا كان برنامجك يجب ان يدعم نظم تشغيل اقدم يمكنك استخدام: ReactiveSwift و RxSwift والتي يمكنك اختيارها

تثبيت المكتبة

يمكنك اضافه الـ(ComposableArchitecture) الي مشروع Xcode كحزمه اعتماديه (Package dependency) ، باتباع الخطوات التاليه :

  1. من قائمه ملف (File) , اختر إضافه حزم (...Add Packages).
  2. ادخل اللينك "https://github.com/pointfreeco/swift-composable-architecture" في مربع البحت عن عنوان الحزم.
  3. اعتمادا علي كيفيه تنظميك للمشروع :
  • اذا كان لديك هدف تطبيق واحد (target) يحتاج للوصل الي الحزمه ، فقم بإضافة ComposableArchitecture مباشره الي تطبيقك.
  • اذا كنت تريد استحدام الحزمه من خلال اكثر من هدف (target) ، او تريد الخلط واستخدام اهداف xcode واهداف SPM ، فيجب عليك إنشاء إطار عمل مشترك (Framework) يعتمد علي ال ComposableArchitecture ومن ثم الاعتماد علي هذا الاطار في جميع اهدافك. كمثال علي ذلك ، تحقق من التطبيق التجربيي (demo) المسمي Tic-Tac-Toe ، والذي يقسم العديد من الميزات (features) الي وحدات (modules) ويستهلك المكتبه الثابته (static library) بهذه الطريقه عن طريق حزمه tic-tac-toe.

مرجع الإستخدام

يمكنك الوصول إلي الاصدار الاخير من مرجع الاستخدام (Documentaion) للـ (Composable Architecture APIs) من هنا .

الحصول علي المساعدة

اذا كنت ترغب في مناقشه ال(Composable Architecture) أو لديك سؤال حول كيفيه استخدامه لحل مشكله معينه يمكنك بدء موضوع مناقشه في خانه المناقشات هنا في هذا المستودع ، او يمكنك السؤال في منتدى سويفت الخاص به.

الترجمات

ساهم أعضاء المجتمع بالترجمات التالية لهذه الوئيقه (README) :

إذا كنت ترغب في المساهمة بترجمة ، يرجى فتح طلب مساهمه PR ملحق به رابط Gist!.

الشكر والتقدير

قدم الأشخاص التالية أسماؤهم تعليقات على المكتبة في مراحلها الأولى وساعدوا في جعل المكتبة على ما هي عليه اليوم:

بول كولتون ، كان ديديوغلو ، مات ديبهاوس ، جوزيف دوليال ، إيمانتاس ، ماثيو جونسون ، جورج كيماكاس ، نيكيتا ليونوف ، كريستوفر ليسيو ، جيفري ماكو ، أليخاندرو مارتينيز ، شاي مشالي ، ويليس بلامر ، سيمون بيير روي ، جوستين برايس ، سفين إيه شميدت ، كايل شيرمان ، بيتر شيما ، جاسديف سينغ ، مكسيم سميرنوف ، رايان ستون ، دانيال هوليس تافاريس ، وجميع مشتركي Point-Free 😁.

شكر خاص لـ كريس ليسيو الذي ساعدنا في العمل من خلال العديد من تقليعات SwiftUI الغريبة وساعد في تحسين واجهة برمجة التطبيقات النهائيه API .

وبفضل شاي مشالي ومشروع CombineCommunity ، الذي أخذنا منه طريقة تنفيذه لـ "Publishers.Create" "، والتي بدورنا نستخدمها في Effect للمساعده في الربط بين ال delegate وال callback-based APIs ، مما يجعل التعامل مع اطر العمل الخارجيه ( 3rd Party frameworks ) اسهل.

مكتبات أخرى

تم بناء Composable Architecture على أساس الأفكار التي بدأتها مكتبات أخرى ، ولا سيما Elm و Redux.

هناك أيضًا العديد من المكتبات في مجتمع Swift و iOS. لكل منها مجموعة أولوياتها الخاصة والتفضيلات التي تختلف عن ال Composable Architecture .

الرخصه

تم إصدار هذه المكتبة بموجب ترخيص معهد ماساتشوستس للتكنولوجيا MIT , راجع LICENSE للحصول على التفاصيل.

@NorhanBoghdadi
Copy link
Author

Contributed to Arabic translation: @bashmoanas @Abdeltwab .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment