Combine عبارة عن إطار عمل
Swift
تفاعلي تم تنفيذه مؤخرًا على جميع منصات
Apple
، بما في ذلك
Xcode 11
. باستخدام
Combine ، من السهل جدًا معالجة تسلسل القيم التي تظهر بشكل غير متزامن مع مرور الوقت. كما أنه يبسط التعليمات البرمجية غير المتزامنة عن طريق التخلي عن عمليات الاستعادة المتداخلة المعقدة المعقدة.
لكن الدراسة "
دمج" في البداية قد لا تبدو بهذه البساطة. الحقيقة هي أن "اللاعبين" الأساسيين في
Combine هم مفاهيم مجردة مثل "الناشرون"
الناشرون ، "المشتركون"
المشتركون والمشغلون ، والتي بدونها لن يكون من الممكن التقدم في فهم منطق
الدمج بين الأداء. ومع ذلك ، نظرًا لحقيقة أن
Apple
توفر للمطورين "ناشرين" جاهزين و "مشتركين" ومشغلين ، فإن الشفرة المكتوبة مع
Combine مضغوطة جدًا ويمكن قراءتها جيدًا.
سترى هذا في مثال تطبيق مرتبط
باسترداد معلومات
الأفلام بشكل غير متزامن من قاعدة بيانات
TMDb الشائعة جدًا
الآن .
سننشئ تطبيقين مختلفين:
UIKit و
SwiftUI ، ونبين كيف يعمل
Combine معهم.

آمل أن يجعل هذا المقال أسهل بالنسبة لك لتعلم
الجمع . يمكن العثور على رمز جميع التطبيقات التي تم تطويرها في هذه المقالة على
جيثب .
الجمع بين عدة عناصر رئيسية:
الناشر الناشر .

الناشرون
الناشرون هم أنواع تقدم
القيم لكل من يهتم. يتم تطبيق مفهوم "الناشر" للناشر على "
دمج "
كبروتوكول ، وليس كنوع محدد. ارتبط بروتوكول
Publisher أنواع عام لقيمة الإخراج وخطأ
الفشل .
يستخدم "الناشر" الذي لا ينشر أبداً أي خطأ في TYPE
Never لخطأ
الفشل .


توفر
Apple
للمطورين تطبيقات محددة لـ "الناشرين" الجاهزين:
فقط ،
المستقبل ،
فارغ ،
مؤجل ،
تسلسل ،
@ منشور ، إلخ. تتم إضافة "الناشرين" أيضًا إلى فصول
التأسيس :
URLSession ، <
NotificationCenter ،
Timer .
"المشترك" المشترك .

إنه أيضًا بروتوكول
بروتوكول يوفر واجهة "الاشتراك"
لقيم من "ناشر". لقد ربط أنواع عام لأخطاء
الإدخال والفشل . من الواضح ، يجب أن تتطابق أنواع Publisher
Publisher ومشترك المشترك .

لأي ناشر Publisher ، يوجد مشتركان
مدمجان :
sink and
assign :

يعتمد مصدر "المشترك" على
غلقين: يتم تنفيذ إغلاق واحد ،
receValue ، عندما تحصل على
القيم ، ويتم تنفيذ الإغلاق الثاني ،
receCompletion ، عند اكتمال "المنشور" (عادة أو مع وجود خطأ).
تعيين "المشترك" ، تقدم كل قيمة مستلمة ، نحو
key Path
المحدد.
اشتراك " اشتراك ".

أولاً ، يقوم الناشر "
الناشر " بإنشاء اشتراك "
المشترك "
وتوصيله إلى المشترك "
المشترك " من خلال طريقة
الاستلام (الاشتراك :) :

بعد ذلك ، يمكن
للاشتراك إرسال
قيمه إلى "المشتركين" من
المشتركين باستخدام طريقتين:

إذا كنت قد انتهيت من العمل مع
اشتراك الاشتراك ، فيمكنك استدعاء طريقة
الإلغاء () :

"الموضوع" الموضوع .

هذا بروتوكول
بروتوكول يوفر واجهة لكل من العملاء ، لكل من "الناشر" و "المشترك". في الأساس ، الموضوع "
الموضوع " هو الناشر "
الناشر " ، والذي يمكنه قبول إدخال القيمة
المدخلة والتي يمكنك استخدامها "لضخ"
القيم في الدفق عن طريق استدعاء أسلوب
send () . قد يكون ذلك مفيدًا عند تكييف التعليمات البرمجية الضرورية الحالية في "
دمج النماذج".
مشغل المشغل .

باستخدام المشغل ، يمكنك إنشاء "ناشر" ناشر جديد من
ناشر آخر "ناشر" عن طريق تحويل
القيم وتصفيةها وحتى دمجها من قبل العديد من
الناشرين السابقين.

ترى هنا الكثير من أسماء المشغلين المألوفة:
صورة مضغوطة وخريطة ومرشح وإسقاط أولاً وإلحاق .
الناشرين مؤسسة الناشرين في صلب المؤسسة .
توفر
Apple
أيضًا للمطورين العديد من وظائف
Combine المضمنة بالفعل في
Foundation <framework ، أي ناشرو الناشرين ، للقيام بمهام مثل جلب البيانات باستخدام
URLSession ، والعمل مع الإشعارات باستخدام خصائص
Notification و
Timer ومراقبة تستند إلى
KVO . سيساعدنا هذا التوافق المدمج في
دمج إطار
Combine في مشروعنا الحالي.
لمعرفة المزيد حول هذا الموضوع ، راجع مقالة
"البرنامج التعليمي الشامل للاندماج في Swift" .
ماذا نتعلم كيف نفعل مع الجمع ؟
في هذه المقالة ، سوف نتعلم كيفية استخدام إطار
Combine لجلب بيانات الأفلام من موقع
TMDb . إليك ما سوف ندرسه معًا:
- استخدام "الناشر" Future لإنشاء إغلاق مع Promise لقيمة واحدة: إما قيمة أو خطأ.
- استخدام "الناشر" URLSession.datataskPublisher "للاشتراك" في بيانات البيانات المنشورة بواسطة
UR
L. - استخدام عامل التشغيل tryMap لتحويل بيانات البيانات باستخدام ناشر Publisher آخر.
- استخدام مشغل فك التشفير لتحويل بيانات البيانات إلى كائن فك تشفير ونشرها لنقلها إلى عناصر لاحقة من السلسلة.
- استخدام عامل التشغيل "للاشتراك" في ناشر "ناشر" باستخدام عمليات الإغلاق.
- استخدم جملة التعيين "للاشتراك" في "الناشر" للناشر وتعيين القيمة التي
key Path
المحدد.
المشروع الأولي
قبل أن نبدأ ، يجب علينا التسجيل لاستلام مفتاح
API
على موقع
TMDb . تحتاج أيضًا إلى تنزيل المشروع الأولي من مستودع
جيثب .
تأكد من وضع مفتاح
API
الخاص بك في
فئة MovieStore الصفية لتدع apiKey ثابتًا.

فيما يلي العناصر الأساسية التي سننشئ منها مشروعنا:
- 1. يوجد داخل الملف
Movie.swift
نماذج سوف نستخدمها في مشروعنا. ينفّذ هيكل الجذر لـ MoviesResponse بروتوكول فك التشفير ، وسوف نستخدمه عند فك تشفير بيانات JSON
في نموذج. تحتوي بنية MoviesResponse على خاصية نتائج ، والتي تنفذ أيضًا بروتوكول فك الترميز وهي عبارة عن مجموعة من الأفلام [فيلم] . هي التي تهمنا:

- 2. التعداد MovieStoreAPIError تنفذ بروتوكول خطأ . ستستخدم
API
بنا هذا التعداد لتمثيل أنواع مختلفة من الأخطاء: أخطاء استرجاع URL
urlError ، وأخطاء فك تشفير أخطاء فك التشفير ، واستجابة أخطاء جلب بيانات الأخطاء.

- 3. لدينا
API
لديها بروتوكول MovieService مع طريقة واحدة ، fetchMovies (من نقطة النهاية: Endpoint) ، والذي يختار [Movie] الأفلام على أساس معلمة نقطة النهاية . Endpoint نفسها عبارة عن تعداد يمثل نقطة نهاية للوصول إلى قاعدة بيانات TMDb لجلب أفلام مثل nowPlaying (الأحدث) والشعبية (الشائعة) و topRated (أعلى) والقادمة (قريبًا على الشاشة).

- 4. فئة MovieStore هي فئة معينة تنفذ بروتوكول MovieService لجلب البيانات من موقع TMDb . داخل هذه الفئة ، نطبق طريقة fetchMovies (...) باستخدام الجمع .

- 5. فئة MovieListViewController هي فئة ViewController الرئيسية التي نستخدم فيها طريقة sink "للاشتراك" في طريقة جلب فيلم fetchMovies (...) ، والتي تُرجع المستقبل "ناشر" ، ثم نقوم بتحديث جدول TableView مع بيانات فيلم جديدة الأفلام باستخدام
API
DiffableDataSourceSnapshot الجديد.
قبل البدء ، دعونا نلقي نظرة على بعض المكونات
المدمجة التي سنستخدمها في
API
لاسترجاع البيانات عن بُعد.
"اشترك" في "الناشر" باستخدام المصارف وإغلاقاتها.
إن أسهل طريقة "للاشتراك" في "الناشر" للناشر هي استخدام
sink مع الإغلاقات الخاصة به ، وسيتم تنفيذ أحدهما كلما حصلنا على
قيمة جديدة ، والآخر عندما ينتهي "الناشر" من تسليم
القيم .

تذكر أنه في
Combine ، يُرجع كل "اشتراك"
إلغاء ، وسيتم حذفه بمجرد مغادرة سياقنا. من أجل الحفاظ على "اشتراك" لفترة أطول ، على سبيل المثال ، للحصول على قيم بشكل غير متزامن ، نحتاج إلى حفظ "الاشتراك" في خاصية
subscription1 . هذا سمح لنا باستمرار الحصول على جميع
القيم (7،8،3،4) .
المستقبل بشكل غير متزامن "ينشر" قيمة واحدة: إما قيمة أو خطأ فشل .
في إطار
Combine ، يمكن استخدام "الناشر"
Future للحصول بشكل غير متزامن على TYPE من
النتيجة باستخدام الإغلاق. إغلاق يحتوي على معلمة واحدة -
وعد ، والتي هي وظيفة من TYPE
(النتيجة <الإخراج ، الفشل>) -> الفراغ .
دعونا نلقي نظرة على مثال بسيط لفهم كيفية عمل ناشر
Future :

نخلق
المستقبل بنتيجة ناجحة من TYPE
Int وخطأ في TYPE
Never . داخل إغلاق
Future ، نستخدم
DispatchQueue.main.asyncAfter (...) لتأخير تنفيذ التعليمات البرمجية بمقدار ثانيتين ، وبالتالي محاكاة السلوك غير المتزامن. داخل الإغلاق ، نعود إلى
الوعد مع نتيجة ناجحة
للوعد (.success (...)) كقيمة عدد صحيح عشوائي في النطاق بين
0 و
100 . بعد ذلك ، نستخدم اشتراكين
مستقبليين -
قابلان للإلغاء وإلغاء - ونحصل كلاهما على نفس النتيجة ، على الرغم من إنشاء رقم عشوائي من الداخل.
1.
تجدر الإشارة إلى أن "ناشر" Future in Combine لديه بعض الميزات السلوكية مقارنة بـ "الناشرين" الآخرين:
- ينشر "الناشر" المستقبل دائمًا "قيمة واحدة ( قيمة أو خطأ) ، وهذا يكمل عمله.
- المستقبل "ناشر" هو فئة (
reference type
) فئة ، على عكس "الناشرين" الآخرين ، والتي هي أساسًا هياكل ( value type
) ، ويتم تمريرها كمعلمة إغلاق التعهد ، الذي يتم إنشاؤه فور تهيئة مثيل "الناشر" في المستقبل . أي ، يتم إرسال إغلاق الوعد قبل أن يشترك أي مشترك " مشترك " في مثيل "ناشر" المستقبل على الإطلاق. لا يتطلب "ناشر" Future "أي مشترك" على الإطلاق لعمله ، كما يتطلب جميع الناشرين العاديين الآخرين. هذا هو السبب في أن النص "مرحبًا من داخل المستقبل!" يتم طباعته مرة واحدة فقط في الرمز أعلاه.
- "الناشر" المستقبلي هو "ناشر"
eager
(غير صبور) ، على عكس معظم "الناشرين" البطيئين الآخرين ("النشر" فقط إذا كان هناك "اشتراك"). بمجرد إغلاق ناشر Future للوعد ، يتم تذكر النتيجة ثم تسليمها إلى "المشتركين" الحاليين والمستقبليين. من التعليمة البرمجية أعلاه ، نرى أنه عندما "تشترك" بشكل متكرر في بالوعة للناشر المستقبلي ، فإنك تحصل دائمًا على نفس القيمة "العشوائية" (في هذه الحالة 6 ، ولكن يمكن أن تكون مختلفة ، ولكنها دائمًا متشابهة) ، على الرغم من استخدامها في الإغلاق قيمة كثافة العمليات العشوائية.
يسمح لك منطق "الناشر"
Future هذا باستخدامه بنجاح في حفظ نتيجة محتسبة غير متزامنة تستهلك الموارد ولا تزعج "الخادم" بسبب "اشتراكات" متعددة لاحقة.
إذا كان هذا المنطق لـ "ناشر"
Future لا يناسبك وترغب في
تسمية مستقبلك كسولًا وفي كل مرة تحصل فيها على قيم
Int عشوائية جديدة ، فيجب عليك "التفاف"
Future in
Deferred :

سنستخدم
Future بطريقة كلاسيكية ، كما هو مقترح في
Combine ، أي "ناشر" محسوب غير متزامن محسوب "مشترك".
ملاحظة 2. نحتاج إلى تقديم ملاحظة أخرى فيما يتعلق بـ "الاشتراك" باستخدام مصدر إلى "الناشر" غير المتزامن. تقوم طريقة الحوض بإرجاع AnyCancellable ، والتي لا نتذكرها باستمرار. هذا يعني أن Swift
ستدمر AnyCancellable في الوقت الذي تغادر فيه السياق المحدد ، وهو ما يحدث في السلسلة main thread
. وبالتالي ، اتضح أن AnyCancellable يتم إتلافه قبل أن يبدأ الإغلاق مع Promise في main thread
. عندما يتم تدمير AnyCancellable ، يتم استدعاء طريقة الإلغاء الخاصة بها ، والتي في هذه الحالة تلغي "الاشتراك". هذا هو السبب في أننا نتذكر "اشتراكاتنا" الخاصة بالمستقبل في المستقبل في المتغيرات cancellable <و cancellable1 أو في Set <AnyCancellable> () .
استخدام Combine لجلب الأفلام من موقع TMDb .
بادئ ذي بدء ، افتح مشروع بدء التشغيل
MovieStore.swift
ملف
MovieStore.swift وأسلوب
fetchMovies بتطبيق فارغ:

باستخدام طريقة
fetchMovies ، يمكننا اختيار أفلام متنوعة عن طريق تعيين قيم محددة لمعلمة إدخال
نقطة النهاية الخاصة بـ TYPE
Endpoint . TYPE
Endpoint هو تعداد يأخذ القيم
الآنالتشغيل (الحالي) ،
والقادم (
يطرح إلى الشاشة قريبًا) ،
والشعبية (الشائعة) ،
topRated (أعلى):

لنبدأ بتهيئة
Future بإغلاق
callback
. تلقى
المستقبل فإننا سنرد بعد ذلك.

داخل إغلاق
callback
، نقوم بإنشاء
URL
للقيمة المطابقة لمعلمة إدخال
نقطة النهاية باستخدام دالة
generURL (مع نقطة النهاية: نقطة النهاية) :

إذا تعذر إنشاء
URL
الصحيح ،
فسنرجع خطأً باستخدام
الوعد (.failure (.urlError (...)) ، وإلا فإننا نمضي قدمًا
وننفذ URLSession.dataTaskPublisher "الناشر".
"للاشتراك" في بيانات من
URL
معين
URL
يمكننا استخدام طريقة
datataskPublisher المضمنة في فئة
URLSession ، والتي تأخذ
URL
كمعلمة وتُرجع الناشر "الناشر" مع بيانات مخرجات المجموعة
(البيانات: البيانات ، الاستجابة: URLResponse) وخطأ URLError .

لتحويل ناشر Publisher إلى ناشر Publisher آخر ، استخدم عامل التشغيل
tryMap . مقارنةً
بالخريطة ، يمكن لمشغل
tryMap أن
يلقي خطأ " خطأ" داخل
الرميات ، مما يعيدنا لناشر الناشر الجديد.
في الخطوة التالية ، سوف نستخدم مشغل
tryMap للتحقق من كود
http
statusCode لاستجابة
الاستجابة للتأكد من أن قيمتها تتراوح بين
200 و
300 . إذا لم يكن كذلك ، فإننا نلقي
رمية قيمة خطأ تعداد
MovieStoreAPIError تعداد. بخلاف ذلك (عند عدم وجود أخطاء) ، فإننا ببساطة نعيد بيانات
البيانات المستلمة
إلى الناشر التالي في السلسلة "الناشر".

في الخطوة التالية ، سوف نستخدم مشغل
فك التشفير ، الذي يقوم
بترميز بيانات إخراج
JSON
الخاصة بـ "الناشر"
tryMap السابق في
MovieResponse <Model باستخدام
JSONDecoder .

...
تم تكوين
jsonDecoder لتنسيق تاريخ محدد:

للمعالجة المراد تنفيذها في
main thread
الرسائل main thread
، سنستخدم عامل
التلقي (على :) ونمرر RunLoop.main كمعلمة إدخال. سيسمح هذا "للمشترك" بالحصول على قيمة
القيمة في الخيط
main
.

أخيرًا ، وصلنا إلى نهاية سلسلة التحويل الخاصة بنا ، وهناك نستخدم
sink للحصول على اشتراك "
اشتراك " في "سلسلة" تشكيل الناشرين "الناشرين". لتهيئة مثيل لفئة
Sink ، نحتاج إلى شيئين ، رغم أن أحدهما اختياري:
- تلقى الإغلاقالقيمة:. سيتم استدعاؤها عندما يحصل اشتراك "اشتراك" على قيمة جديدة من الناشر " الناشر ".
- receCompletion إغلاق : (اختياري). سيتم استدعاؤها بعد أن ينهي الناشر "الناشر" نشر القيمة ، وسيُعطى تعداد الإكمال ، والذي يمكننا استخدامه للتحقق مما إذا كان "نشر" القيم قد اكتمل حقًا أم أن الإكمال كان بسبب خطأ.
داخل إغلاق
receValue ، ندعو ببساطة
إلى الوعد بخيار
.success وقيمة
$ 0.results ، وهو في حالتنا عبارة عن مجموعة من أفلام
الأفلام . داخل إغلاق
receCompletion ، نتحقق مما إذا كان
للإكمال خطأ في
الخطأ ، ثم نمرر خطأ
الوعد المقابل مع خيار
.failure .

لاحظ أننا نجمع هنا جميع الأخطاء "التي تم طرحها" في المراحل السابقة من "سلسلة الناشر".
بعد ذلك ، نحفظ اشتراك "الاشتراك" في
الخاصية Set <AnyCancellable> () .
والحقيقة هي أن اشتراك "الاشتراك" قابل
للإلغاء ، فهو بروتوكول يدمر ويمسح كل شيء بعد الانتهاء من وظيفة
fetchMovies . لضمان الحفاظ على اشتراك "الاشتراك" حتى بعد الانتهاء من هذه الوظيفة ، نحتاج إلى تذكر اشتراك "
الاشتراك " في المتغير الخارجي لوظيفة
fetchMovies . في حالتنا ، نستخدم خاصية
الاشتراكات ، التي لديها نوع
Set <AnyCancellable> () ونستخدم طريقة
.store (في: & self.subscriptions) ، والتي تضمن
سهولة استخدام "الاشتراك" بعد
انتهاء وظيفة
fetchMovies من عملها.

هذا يخلص إلى تكوين طريقة
fetchMovies لاختيار الأفلام من قاعدة بيانات
TMDb باستخدام
Combine framework.
تأخذ طريقة
fetchMovies ، كمعلمة الإدخال
من ، قيمة
التعداد Endpoint ، أي الأفلام المحددة التي تهمنا:
.nowPlaying - الأفلام التي تظهر حاليًا على الشاشة ،
.upcoming - أفلام ستأتي قريبًا ،
.أفلام شعبية -
.topRated - أعلى الأفلام ، وهذا هو ، مع تصنيف عالية جدا.
دعونا نحاول تطبيق
API
هذه على تصميم التطبيق مع واجهات مستخدم
UIKit المعتادة في شكل
Table View Controller
:

وإلى تطبيق تم تصميم واجهة المستخدم الخاصة به باستخدام إطار عمل
SwiftUI التعريفي الجديد:

"اشترك" في أفلام من أفلام View Controller
المعتادة.
ننتقل إلى ملف
MovieListViewController.swift وفي طريقة
viewDidLoad استدعاء الأسلوب
fetchMovies .

داخل طريقة
fetchMovies الخاصة بنا
، نستخدم
movieAPI التي تم تطويرها
مسبقًا وطريقة
fetchMovies الخاصة
بها مع المعلمة
.nowPlaying كنقطة
النهاية لمعلمة
from from . أي أننا سنختار الأفلام الموجودة حاليًا على شاشات دور السينما.

تقوم طريقة
movieAPI.fetchMovies (من: .nowPlaying) بإرجاع "ناشر"
Future ، نشترك فيه باستخدام
sink ،
ونزوده بإغلاقين . في إغلاق
receCompletion ،
نتحقق مما إذا كان هناك خطأ في
الخطأ ونعرض تحذيرًا طارئًا
لتنبيه المستخدم مع ظهور رسالة الخطأ.

في عملية الإغلاق
ReceValue ، ندعو طريقة
createSnapshot ونمرر الأفلام المختارة
إليها .

تقوم دالة
generSnapshot بإنشاء NSDiffableDataSourceSnapshot جديدة باستخدام
أفلامنا ، وتطبق
اللقطة الناتجة على
diffableDataSource من طاولتنا.
نطلق التطبيق ونشاهد كيف تعمل
UIKit مع "الناشرين" و "المشتركين" من إطار
الجمع . هذا تطبيق بسيط للغاية لا يسمح لك بمشاهدة مجموعات مختلفة من الأفلام - تظهر الآن على الشاشة ، شعبية ، ذات تصنيف عالي أو تلك التي ستظهر على الشاشة في المستقبل القريب. نرى فقط تلك الأفلام التي ستظهر على الشاشة (
.القادمة ). بالطبع ، يمكنك القيام بذلك عن طريق إضافة أي عنصر
UI
لتعيين قيم تعداد
نقطة النهاية ، على سبيل المثال ، باستخدام
Stepper
أو
Segmented Control
، ثم تحديث واجهة المستخدم. هذا معروف جيدًا ، لكننا لن نقوم بذلك في
تطبيق يستند إلى
UIKit ، ولكن نترك هذا
لإطار عمل
SwiftUI التعريفي الجديد.
يمكن العثور على رمز
تطبيق يستند إلى
UIKit على
Github في
CombineFetchAPICompleted-UIKit
.
استخدم SwiftUI لعرض أفلام الأفلام
.
CombineFetchAPI-MY
SwiftUI File
->
New
->
Project
Single View App
iOS
:

UI
—
SwiftUI :

Movie.swift
Model
,
TMDb MovieStore.swift
,
MovieStoreAPIError.swift
MovieService.swift
,
MovieService
Protocol
:
SwiftUI ,
Codable ,
JSON
,
Identifiable ,
[Movie] List .
SwiftUI Equatable Hashable ,
UIKit API
UITableViewDiffableDataSource UIKit . <
struct Movie ,
Equatable Hashable :
........................... .
Identifiable ,
Swift
Identifiable ,
Hashable Equatable .
,
SwiftUI , ,
endpoint , :

,
UIKit ,
movieAPI.fetchMovies (from endpoint: Endpoint) ,
endpoint «»
Future<[Movie, MovieStoreAPIError]> .
Endpoint , ,
case Endpoint index :

,
movies ,
indexEndpoint Endpoint .
View Model
,
MoviesViewModel ,
ObservableObject .
MoviesViewModel.swift View Model
:
@Published :
@Published var indexEndpoint: Int — ,
@Published var movies: [Movie] - .
@Published indexEndpoint ,
indexEndpoint ,
$indexEndpoint .
MoviesViewModel «»
$indexEndpoint «»
AnyPublisher<[Movie], Never> ,
movieAPI.fetchMovies (from: Endpoint (index: indexPoint)) flatMap .

«» «» «»
assing (to: \.movies, on: self) «»
movies . «»
assing (to: \.movies, on: self) , «» ,
Never . ?
replaceError(with: [ ]) ,
movies .
SwiftUI .
,
View Model
,
UI
.
ContentView.swift View Model
@EnvironmentObject var moviesViewModel Text(«Hello, World!»)Text("\(moviesViewModel.indexEndpoint)") ,
indexEndpoint .

View Model
indexEndpoint = 2 , , (
Upcoming ):

UI
, .
Stepper :

…
Picker :

«»
$moviesViewModel.indexEndpoint View Model
, ( ) :
List ForEach movie :
moviesViewModel.movies View Model
:

«»
$moviesViewModel.movies $ , .
moviesViewModel.movies .
, ,
URL
Movie :
Thomas Ricouard MovieSwiftUI .
movies ,
UIImage ImageService ,
Combine fetchImage , «»
AnyPublisher<UIImage?, Never> :

…
final class ImageLoader: ObservableObject ,
ObservableObject @Published image: UIImage? :

,
ObservableObject -
objectWillChange .
SwiftUI , - , ,
Views , .
objectWillChange ,
@Published . -
objectWillChange . .
ImageLoader @Published var image:UIImage? . ,
ImageLoader , «»
$image «»
loadImage() ,
poster size @Published var image:UIImage? .
objectWillChange .
, ,
imageLoader ImageLoader :
View MoviePosterImage :

…
ContentView :

SwiftUI Github CombineFetchAPI-NOError
.
.
,
TMDb .
movieAPI.fetchMovies (from endpoint: Endpoint) , , «»
Future<[Movie, MovieStoreAPIError]> .
, ,
View Model
@Published moviesError: MovieStoreAPIError? , .
Optional ,
nil , :

,
moviesError ,
MoviesViewModel «»
sink :
moviesError UI
,
nil …
AlertView :

,
API
:
SwiftUI Github CombineFetchAPI-Error .
,
Future<[Movie],MovieStoreAPIError> ,
AnyPublisher<[Movie], Never> fetchMoviesLight :

(
Never ) «»
assign(to: \.movies, on: self) :

:

استنتاج
Combine values . ,
Combine , — .
Combine upstream «»
Publishers , «»
Subscribers .
Combine ,
Foundation ,
Foundation .
SwiftUI Combine <
@ObservableObject ,
@Binding @EnvironmentObject .
iOS
Apple
.
:
Fetching Remote Async API with Apple Combine Frameworktry! Swift NYC 2019 — Getting Started with Combine«The ultimate Combine framework tutorial in Swift».Combine: Asynchronous Programming with SwiftIntroducing Combine — WWDC 2019 — Videos — Apple Developer. session 722( 722 « Combine» )Combine in Practice — WWDC 2019 — Videos — Apple Developer. session 721( 721 « Combine» )SwiftUI & Combine: . SwiftUI Combine .MovieSwiftUI .
Visualize Combine Magic with SwiftUI Part 1 ()Visualize Combine Magic with SwiftUI – Part 2 (Operators, subscribing, and canceling in Combine)Visualize Combine Magic with SwiftUI Part 3 (See Combine Merge and Append in Action)
Visualize Combine Magic with SwiftUI — Part 4Visualize Combine Magic with SwiftUI — Part 5Getting Started With the Combine Framework in SwiftTransforming Operators in Swift Combine Framework: Map vs FlatMap vs SwitchToLatestCombine's FutureUsing CombineURLSession and the Combine framework