إن البنية القابلة للتركيب (اختصارًا TCA) عبارة عن مكتبة لبناء التطبيقات بطريقة متسقة ومفهومة، مع أخذ التركيب والاختبار وبيئة العمل في الاعتبار. ويمكن استخدامه في SwiftUI وUIKit والمزيد، وعلى أي نظام أساسي من أنظمة Apple (iOS وmacOS وvisionOS وtvOS وwatchOS).
ما هي العمارة المركبة؟
يتعلم أكثر
أمثلة
الاستخدام الأساسي
التوثيق
مجتمع
تثبيت
ترجمات
توفر هذه المكتبة بعض الأدوات الأساسية التي يمكن استخدامها لإنشاء تطبيقات ذات أغراض وتعقيدات مختلفة. فهو يوفر قصصًا مقنعة يمكنك متابعتها لحل العديد من المشكلات التي تواجهها يوميًا عند إنشاء التطبيقات، مثل:
إدارة الدولة
كيفية إدارة حالة التطبيق الخاص بك باستخدام أنواع قيمة بسيطة، ومشاركة الحالة عبر العديد من الشاشات بحيث يمكن ملاحظة الطفرات في شاشة واحدة على الفور في شاشة أخرى.
تعبير
كيفية تقسيم الميزات الكبيرة إلى مكونات أصغر يمكن استخلاصها إلى وحدات معزولة خاصة بها ويمكن لصقها معًا بسهولة مرة أخرى لتكوين الميزة.
تأثيرات جانبية
كيفية السماح لأجزاء معينة من التطبيق بالتحدث إلى العالم الخارجي بأكثر الطرق الممكنة قابلية للاختبار والفهم.
اختبار
كيفية ليس فقط اختبار الميزة المضمنة في البنية، ولكن أيضًا كتابة اختبارات التكامل للميزات التي تتكون من أجزاء عديدة، وكتابة اختبارات شاملة لفهم كيفية تأثير الآثار الجانبية على تطبيقك. يتيح لك ذلك تقديم ضمانات قوية بأن منطق عملك يعمل بالطريقة التي تتوقعها.
بيئة العمل
كيفية إنجاز كل ما سبق في واجهة برمجة تطبيقات بسيطة مع أقل عدد ممكن من المفاهيم والأجزاء المتحركة.
تم تصميم الهندسة المعمارية المركبة على مدار العديد من الحلقات على Point-Free، وهي سلسلة فيديو تستكشف البرمجة الوظيفية ولغة Swift، يستضيفها براندون ويليامز وستيفن سيليس.
يمكنك مشاهدة جميع الحلقات هنا، بالإضافة إلى جولة مخصصة ومتعددة الأجزاء للهندسة المعمارية من الصفر.
يأتي هذا الريبو مع الكثير من الأمثلة لتوضيح كيفية حل المشكلات الشائعة والمعقدة باستخدام البنية القابلة للتركيب. تحقق من هذا الدليل لرؤيتهم جميعا، بما في ذلك:
دراسات الحالة
ابدء
التأثيرات
ملاحة
مخفضات الترتيب العالي
مكونات قابلة لإعادة الاستخدام
مدير الموقع
مدير الحركة
يبحث
التعرف على الكلام
تطبيق SyncUps
تيك تاك تو
كل شيء
المذكرات الصوتية
هل تبحث عن شيء أكثر جوهرية؟ تحقق من الكود المصدري للعبة isowords، وهي لعبة بحث عن الكلمات على نظام التشغيل iOS مدمجة في SwiftUI والبنية القابلة للتركيب.
ملحوظة
للحصول على برنامج تعليمي تفاعلي خطوة بخطوة، تأكد من مراجعة تعرف على البنية القابلة للتركيب.
لإنشاء ميزة باستخدام البنية القابلة للتركيب، عليك تحديد بعض الأنواع والقيم التي تمثل نطاقك:
الحالة : نوع يصف البيانات التي تحتاجها الميزة لتنفيذ منطقها وعرض واجهة المستخدم الخاصة بها.
الإجراء : نوع يمثل جميع الإجراءات التي يمكن أن تحدث في الميزة الخاصة بك، مثل إجراءات المستخدم والإشعارات ومصادر الأحداث والمزيد.
المخفض : وظيفة تصف كيفية تطوير الحالة الحالية للتطبيق إلى الحالة التالية في ضوء الإجراء. المخفض مسؤول أيضًا عن إرجاع أي تأثيرات يجب تشغيلها، مثل طلبات واجهة برمجة التطبيقات (API)، والتي يمكن إجراؤها عن طريق إرجاع قيمة Effect
.
المتجر : وقت التشغيل الذي يحرك ميزتك فعليًا. يمكنك إرسال جميع إجراءات المستخدم إلى المتجر حتى يتمكن المتجر من تشغيل المخفض والتأثيرات، ويمكنك ملاحظة تغييرات الحالة في المتجر حتى تتمكن من تحديث واجهة المستخدم.
تتمثل فوائد القيام بذلك في أنك ستفتح على الفور قابلية اختبار الميزة الخاصة بك، وستكون قادرًا على تقسيم الميزات الكبيرة والمعقدة إلى مجالات أصغر يمكن لصقها معًا.
كمثال أساسي، فكر في واجهة مستخدم تعرض رقمًا مع الأزرار "+" و"-" التي تزيد الرقم وتنقصه. ولجعل الأمور مثيرة للاهتمام، لنفترض أن هناك أيضًا زرًا يقوم عند النقر عليه بتقديم طلب واجهة برمجة التطبيقات (API) لجلب حقيقة عشوائية حول هذا الرقم وعرضها في العرض.
لتنفيذ هذه الميزة، نقوم بإنشاء نوع جديد يضم مجال الميزة وسلوكها، وسيتم شرحه باستخدام الماكرو @Reducer
:
استيراد ميزة ComposableArchitecture@Reducerstruct {}
نحن هنا بحاجة إلى تحديد نوع لحالة الميزة، والتي تتكون من عدد صحيح للعدد الحالي، بالإضافة إلى سلسلة اختيارية تمثل الحقيقة التي يتم تقديمها:
@Reducerstruct Features { @ObservableState حالة البنية: Equatable { var count = 0 var numberFact: String؟ }}
ملحوظة
لقد قمنا بتطبيق الماكرو @ObservableState
على State
للاستفادة من أدوات المراقبة الموجودة في المكتبة.
نحتاج أيضًا إلى تحديد نوع لإجراءات الميزة. هناك الإجراءات الواضحة، مثل النقر على زر التناقص، أو زر الزيادة، أو زر الحقيقة. ولكن هناك أيضًا بعض الإجراءات غير الواضحة بعض الشيء، مثل الإجراء الذي يحدث عندما نتلقى استجابة من طلب واجهة برمجة التطبيقات الواقعية:
@Reducerstruct Features { @ObservableState struct State: Equatable { /* ... */ } enum Action { case decrementButtonTapped case incrementButtonTapped case numberFactButtonTapped case numberFactResponse(String) }}
ثم نقوم بتنفيذ خاصية body
، وهي المسؤولة عن تكوين المنطق والسلوك الفعليين للميزة. يمكننا فيه استخدام مخفض Reduce
لوصف كيفية تغيير الحالة الحالية إلى الحالة التالية، وما هي التأثيرات التي يجب تنفيذها. لا تحتاج بعض الإجراءات إلى تنفيذ تأثيرات، ويمكنها إرجاع .none
لتمثيل ذلك:
@Reducerstruct Features { @ObservableState struct State: Equatable { /* ... */ } enum Action { /* ... */ } var body: بعض المخفض{ تقليل { الحالة، الإجراء في تبديل الإجراء {الحالة .decrementButtonTapped: State.count -= 1 return .none case .incrementButtonTapped: state.count += 1 return .none case .numberFactButtonTapped: return .run { [count =state.count] send in Let (data, _) = حاول الانتظار URLSession.shared.data( من: URL(سلسلة: "http://numbersapi.com/(count)/trivia")! ) في انتظار الإرسال( .numberFactResponse(String(decoding: data, as: UTF8.self)) ) } الحالة Let .numberFactResponse(fact): State.numberFact = إرجاع الحقيقة .none } } }}
ثم أخيرًا نحدد العرض الذي يعرض الميزة. إنه يحتفظ بـ StoreOf
حتى يتمكن من ملاحظة جميع التغييرات في الحالة وإعادة العرض، ويمكننا إرسال جميع إجراءات المستخدم إلى المتجر حتى تتغير الحالة:
هيكل عرض الميزة: عرض { السماح بالتخزين: StoreOfvar body: some View { Form { section { Text("(store.count)") Button("Decrement") { store.send(.decrementButtonTapped) } Button( "زيادة") { store.send(.incrementButtonTapped) } } القسم { Button("رقم الحقيقة") { store.send(.numberFactButtonTapped) } } إذا تركت حقيقة = store.numberFact { نص(حقيقة) } } }}
من السهل أيضًا إخراج وحدة تحكم UIKit من هذا المتجر. يمكنك ملاحظة تغييرات الحالة في المتجر في viewDidLoad
، ثم تعبئة مكونات واجهة المستخدم بالبيانات من المتجر. الكود أطول قليلًا من إصدار SwiftUI، لذا قمنا بتصغيره هنا:
فئة الميزة ViewController: UIViewController { السماح بالتخزين: StoreOfinit(store: StoreOf ) { self.store = store super.init(nibName: nil, Bundle: nil) } مطلوب init?(coder: NSCoder) { FatError("init(coder:) لم يتم تنفيذه") } override func viewDidLoad() { super.viewDidLoad() دع countLabel = UILabel() دع decrementButton = UIButton() دع incrementButton = UIButton() دع FactLabel = UILabel() // تم الحذف: إضافة عروض فرعية وإعداد القيود... لاحظ {[النفس الضعيفة] في حارس ترك نفسه آخر {عودة} countLabel.text = "(self.store.text)" actLabel.text = self.store.numberFact } } @objc خاص func incrementButtonTapped() { self.store.send(.incrementButtonTapped) } @objc خاص func decrementButtonTapped() { self.store.send(.decrementButtonTapped) } @objc خاص فونك فاكتبوتونتابد () { self.store.send(.numberFactButtonTapped) }}
بمجرد أن نكون مستعدين لعرض هذا العرض، على سبيل المثال في نقطة دخول التطبيق، يمكننا إنشاء متجر. يمكن القيام بذلك عن طريق تحديد الحالة الأولية لبدء التطبيق، بالإضافة إلى المخفض الذي سيعمل على تشغيل التطبيق:
import ComposableArchitecture@mainstruct MyApp: App { var body: some Scene { WindowGroup { featureView( المتجر: Store(initialState: الميزة.State()) { الميزة() } ) } }}
وهذا يكفي للحصول على شيء ما على الشاشة للعب به. إنها بالتأكيد بضع خطوات أكثر مما لو كنت تريد القيام بذلك بطريقة الفانيليا SwiftUI، ولكن هناك بعض الفوائد. إنه يمنحنا طريقة متسقة لتطبيق طفرات الحالة، بدلاً من تشتيت المنطق في بعض الكائنات التي يمكن ملاحظتها وفي عمليات إغلاق الإجراءات المختلفة لمكونات واجهة المستخدم. كما أنه يوفر لنا طريقة موجزة للتعبير عن الآثار الجانبية. ويمكننا اختبار هذا المنطق على الفور، بما في ذلك التأثيرات، دون القيام بالكثير من العمل الإضافي.
ملحوظة
لمزيد من المعلومات المتعمقة حول الاختبار، راجع مقالة الاختبار المخصصة.
للاختبار، استخدم TestStore
، والذي يمكن إنشاؤه بنفس المعلومات الموجودة في Store
، ولكنه يقوم بعمل إضافي للسماح لك بتأكيد كيفية تطور الميزة الخاصة بك عند إرسال الإجراءات:
@Testfunc basics() async { Let store = TestStore(initialState: الميزة.State()) { الميزة() }}
بمجرد إنشاء مخزن الاختبار، يمكننا استخدامه لتأكيد تدفق خطوات المستخدم بالكامل. كل خطوة على الطريق نحتاج إلى إثبات أن تلك الحالة قد غيرت الطريقة التي نتوقعها. على سبيل المثال، يمكننا محاكاة تدفق المستخدم للنقر على أزرار الزيادة والنقصان:
// اختبر أن النقر على أزرار الزيادة/النقصان يغير العد، انتظر store.send(.incrementButtonTapped) { $0.count = 1}في انتظار المتجر.send(.decrementButtonTapped) { 0 دولار.العدد = 0}
علاوة على ذلك، إذا تسببت إحدى الخطوات في تنفيذ تأثير، والذي يغذي البيانات مرة أخرى إلى المتجر، فيجب علينا التأكيد على ذلك. على سبيل المثال، إذا قمنا بمحاكاة المستخدم الذي ينقر على زر الحقيقة، فإننا نتوقع أن نتلقى ردًا بالحقيقة، مما يؤدي بعد ذلك إلى ملء حالة numberFact
:
في انتظار store.send(.numberFactButtonTapped) في انتظار store.receive(.numberFactResponse) { $0.numberFact = ???}
ومع ذلك، كيف نعرف ما هي الحقيقة التي سيتم إرسالها إلينا؟
يستخدم المخفض حاليًا تأثيرًا يصل إلى العالم الحقيقي للوصول إلى خادم API، وهذا يعني أنه ليس لدينا طريقة للتحكم في سلوكه. نحن نعتمد على اتصالنا بالإنترنت وتوافر خادم API لكتابة هذا الاختبار.
سيكون من الأفضل أن يتم تمرير هذه التبعية إلى المخفض حتى نتمكن من استخدام التبعية المباشرة عند تشغيل التطبيق على الجهاز، ولكن استخدم التبعية الساخرة للاختبارات. يمكننا القيام بذلك عن طريق إضافة خاصية إلى مخفض Feature
:
@Reducerstruct Features { Let numberFact: (Int) رميات غير متزامنة -> String // ...}
ثم يمكننا استخدامه في reduce
التنفيذ:
الحالة .numberFactButtonTapped: إرجاع .تشغيل {[العدد = الحالة. العد] إرسال دع الحقيقة = حاول الانتظار self.numberFact(count) انتظار الإرسال(.numberFactResponse(fact)) }
وفي نقطة دخول التطبيق يمكننا توفير نسخة من التبعية التي تتفاعل فعليًا مع خادم واجهة برمجة التطبيقات (API) في العالم الحقيقي:
@mainstruct MyApp: التطبيق { var body: some Scene { WindowGroup { featureView( المتجر: المتجر (initialState: الميزة. الحالة ()) { الميزة ( numberFact: { الرقم الموجود في Let (data, _) = حاول الانتظار URLSession.shared.data( من: URL(سلسلة: "http://numbersapi.com/(number)")! ) إرجاع سلسلة (فك التشفير: البيانات، مثل: UTF8.self) } ) } ) } }}
لكن في الاختبارات يمكننا استخدام تبعية وهمية تُرجع على الفور حقيقة حتمية يمكن التنبؤ بها:
@Testfunc basics() async { Let store = TestStore(initialState: feature.State()) { الميزة(numberFact: { "($0) هو رقم جيد برنت" }) }}
مع هذا القليل من العمل المسبق، يمكننا إنهاء الاختبار عن طريق محاكاة ضغط المستخدم على زر الحقيقة، ثم تلقي الاستجابة من التبعية لتقديم الحقيقة:
في انتظار store.send(.numberFactButtonTapped) في انتظار store.receive(.numberFactResponse) { $0.numberFact = "0 هو رقم جيد لخام برنت"}
يمكننا أيضًا تحسين بيئة العمل لاستخدام تبعية numberFact
في تطبيقنا. بمرور الوقت، قد يتطور التطبيق إلى العديد من الميزات، وقد تحتاج بعض هذه الميزات أيضًا إلى الوصول إلى numberFact
، وقد يكون تمريرها بشكل صريح عبر جميع الطبقات أمرًا مزعجًا. هناك عملية يمكنك اتباعها "لتسجيل" التبعيات في المكتبة، مما يجعلها متاحة على الفور لأي طبقة في التطبيق.
ملحوظة
لمزيد من المعلومات المتعمقة حول إدارة التبعيات، راجع مقالة التبعيات المخصصة.
يمكننا أن نبدأ بتغليف وظيفة حقيقة الأرقام في نوع جديد:
بناء NumberFactClient { var fetch: (Int) رميات غير متزامنة -> String}
ومن ثم تسجيل ذلك النوع في نظام إدارة التبعية عن طريق مطابقة العميل لبروتوكول DependencyKey
، والذي يتطلب منك تحديد القيمة المباشرة لاستخدامها عند تشغيل التطبيق في المحاكيات أو الأجهزة:
ملحق NumberFactClient: DependencyKey { static Let LiveValue = Self( جلب: { الرقم في Let (data, _) = حاول انتظار URLSession.shared .data(from: URL(string: "http://numbersapi.com/(number)")!) return String(decoding: data, مثل: UTF8.self) } )}extension DependencyValues { var numberFact: NumberFactClient { get { self[NumberFactClient.self] } set { الذات[NumberFactClient.self] = newValue } }}
مع هذا القليل من العمل المسبق الذي تم إنجازه، يمكنك البدء على الفور في الاستفادة من التبعية في أي ميزة باستخدام مجمّع الخاصية @Dependency
:
@المخفض ميزة البنية {- دع numberFact: (Int) رميات غير متزامنة -> String+ @Dependency(.numberFact) var numberFact …- حاول انتظار self.numberFact(count)+ حاول انتظار self.numberFact.fetch(count) }
يعمل هذا الكود تمامًا كما كان يعمل من قبل، ولكن لم يعد عليك تمرير التبعية بشكل صريح عند إنشاء مخفض الميزة. عند تشغيل التطبيق في المعاينات أو جهاز المحاكاة أو على جهاز، سيتم توفير التبعية المباشرة للمخفض، وفي الاختبارات سيتم توفير تبعية الاختبار.
وهذا يعني أن نقطة الدخول إلى التطبيق لم تعد بحاجة إلى إنشاء تبعيات:
@mainstruct MyApp: التطبيق { var body: some Scene { WindowGroup { featureView( المتجر: Store(initialState: الميزة.State()) { الميزة() } ) } }}
ويمكن إنشاء مخزن الاختبار دون تحديد أي تبعيات، ولكن لا يزال بإمكانك تجاوز أي تبعية تحتاج إليها لغرض الاختبار:
Let store = TestStore(initialState: feature.State()) { الميزة()} مع التبعيات: { $0.numberFact.fetch = { "($0) هو رقم جيد برنت" }}// ...
هذه هي أساسيات بناء واختبار ميزة في البنية القابلة للتركيب. هناك الكثير من الأشياء التي يجب استكشافها، مثل التركيب والنمطية والقدرة على التكيف والتأثيرات المعقدة. يحتوي دليل الأمثلة على مجموعة من المشاريع التي يمكنك استكشافها لمعرفة المزيد من الاستخدامات المتقدمة.
وثائق الإصدارات main
متاحة هنا:
main
1.17.0 (دليل الترحيل)
1.16.0 (دليل الترحيل)
1.15.0 (دليل الترحيل)
1.14.0 (دليل الترحيل)
1.13.0 (دليل الترحيل)
1.12.0 (دليل الترحيل)
1.11.0 (دليل الترحيل)
1.10.0 (دليل الترحيل)
1.9.0 (دليل الترحيل)
1.8.0 (دليل الترحيل)
1.7.0 (دليل الترحيل)
1.6.0 (دليل الترحيل)
1.5.0 (دليل الترحيل)
1.4.0 (دليل الترحيل)
1.3.0
1.2.0
1.1.0
1.0.0
0.59.0
0.58.0
0.57.0
يوجد عدد من المقالات في الوثائق التي قد تجدها مفيدة عندما تصبح أكثر ارتياحًا مع المكتبة:
ابدء
التبعيات
اختبار
ملاحة
حالة المشاركة
أداء
التزامن
الارتباطات
إذا كنت ترغب في مناقشة البنية القابلة للتركيب أو كان لديك سؤال حول كيفية استخدامها لحل مشكلة معينة، فهناك عدد من الأماكن التي يمكنك مناقشتها مع زملائك المتحمسين للنقاط الخالية:
بالنسبة للمناقشات الطويلة، نوصي بعلامة تبويب المناقشات في هذا الريبو.
بالنسبة للدردشة غير الرسمية، نوصي بـ Point-Free Community Slack.
يمكنك إضافة ComposableArchitecture إلى مشروع Xcode عن طريق إضافته كتبعية للحزمة.
من القائمة ملف ، حدد إضافة تبعيات الحزمة...
أدخل "https://github.com/pointfreeco/swift-composable-architecture" في حقل نص عنوان URL لمستودع الحزمة
اعتمادًا على كيفية تنظيم مشروعك:
إذا كان لديك هدف تطبيق واحد يحتاج إلى الوصول إلى المكتبة، فقم بإضافة ComposableArchitecture مباشرةً إلى تطبيقك.
إذا كنت تريد استخدام هذه المكتبة من أهداف Xcode متعددة، أو مزج أهداف Xcode وأهداف SPM، فيجب عليك إنشاء إطار عمل مشترك يعتمد على ComposableArchitecture ثم الاعتماد على إطار العمل هذا في جميع أهدافك. للحصول على مثال على ذلك، راجع التطبيق التجريبي Tic-Tac-Toe، الذي يقسم الكثير من الميزات إلى وحدات ويستهلك المكتبة الثابتة بهذه الطريقة باستخدام حزمة tic-tac-toe Swift.
تم تصميم البنية القابلة للتركيب مع أخذ القابلية للتوسعة في الاعتبار، ويوجد عدد من المكتبات المدعومة من المجتمع المتاحة لتحسين تطبيقاتك:
إضافات الهندسة المعمارية القابلة للتركيب: مكتبة مصاحبة للهندسة المعمارية القابلة للتركيب.
TCAComposer: إطار ماكرو لإنشاء التعليمات البرمجية النمطية في البنية القابلة للتركيب.
TCACoordinators: نمط المنسق في البنية القابلة للتركيب.
إذا كنت ترغب في المساهمة بمكتبة، يرجى فتح PR مع رابط إليها!
تمت المساهمة بالترجمات التالية لهذا الملف التمهيدي من قبل أعضاء المجتمع:
عربي
فرنسي
الهندية
الاندونيسية
ايطالي
اليابانية
كوري
بولندي
البرتغالية
الروسية
الصينية المبسطة
الأسبانية
الأوكرانية
إذا كنت ترغب في المساهمة في الترجمة، يرجى فتح PR مع رابط إلى Gist!
لدينا مقالة مخصصة لجميع الأسئلة والتعليقات الأكثر شيوعًا التي يطرحها الأشخاص بخصوص المكتبة.
قدم الأشخاص التاليون تعليقاتهم على المكتبة في مراحلها الأولى وساعدوا في جعل المكتبة على ما هي عليه اليوم:
بول كولتون، كان ديديوغلو، مات ديبهاوس، جوزيف دوليجال، إيمانتاس، ماثيو جونسون، جورج كايماكاس، نيكيتا ليونوف، كريستوفر ليسيو، جيفري ماكو، أليخاندرو مارتينيز، شاي مشالي، ويليس بلامر، سيمون بيير روي، جاستن برايس، سفين أ. شميدت ، كايل شيرمان، بيتر شيما، جاسديف سينغ، مكسيم سميرنوف، ورايان ستون، ودانييل هوليس تافاريس، وجميع المشتركين في برنامج Point-Free؟.
شكر خاص لكريس ليسيو الذي ساعدنا في التعامل مع العديد من ميزات SwiftUI الغريبة وساعدنا في تحسين واجهة برمجة التطبيقات النهائية.
وبفضل Shai Mishali ومشروع CombineCommunity، الذي أخذنا منه تطبيق Publishers.Create
، والذي نستخدمه في Effect
للمساعدة في ربط واجهات برمجة التطبيقات المستندة إلى المفوضين ورد الاتصال، مما يجعل التفاعل مع أطر عمل الطرف الثالث أسهل بكثير.
تم بناء البنية القابلة للتركيب على أساس الأفكار التي بدأتها مكتبات أخرى، على وجه الخصوص Elm وRedux.
هناك أيضًا العديد من مكتبات الهندسة المعمارية في مجتمع Swift وiOS. كل واحد من هؤلاء لديه مجموعة خاصة به من الأولويات والمقايضات التي تختلف عن البنية القابلة للتركيب.
الضلوع
حلقة
ريسويفت
سير العمل
ReactorKit
ردود الفعل
موبيوس. سويفت
فلوكسور
PromisedArchitectureKit
تم إصدار هذه المكتبة بموجب ترخيص MIT. راجع الترخيص للحصول على التفاصيل.