
HTTP
يعد الاستعلام أحد أهم المهارات التي تحتاج إلى الحصول عليها عند تطوير iOS
التطبيقات. في الإصدارات السابقة Swift
(قبل الإصدار 5) ، بغض النظر عما إذا كنت قد أنشأت هذه الطلبات من الصفر أو باستخدام إطار عمل Alamofire المعروف ، فقد انتهى بك الأمر إلى رمز معقد ومربك من callback
النوع completionHandler: @escaping(Result<T, APIError>) -> Void
.إن المظهر في Swift 5
الإطار الجديد للبرمجة التفاعلية الوظيفية Combine
بالاقتران مع الموجودة URLSession
، Codable
ويزودك بكل الأدوات اللازمة لكتابة مستقلة لرمز مضغوط للغاية لجلب البيانات من الإنترنت.في هذه المقالة ، وفقًا للمفهوم ، Combine
سننشئ "ناشرين"Publisher
لتحديد البيانات من شبكة الإنترنت، والذي يمكننا بسهولة "الاشتراك في" في المستقبل واستخدامها عند تصميم UI
على حد سواء مع UIKit
وبمساعدة SwiftUI
.كما SwiftUI
يبدو أكثر إيجازا وأكثر فعالية، وذلك لأن عمل "الناشرين" Publisher
لا يقتصر على بيانات عينة فقط، ويمتد مزيد حتى التحكم بالسطح البيني ( UI
). والحقيقة هي أن SwiftUI
فصل البيانات يتم View
باستخدام ObservableObject
فئات ذات @Published
خصائص ، يتم SwiftUI
مراقبة التغييرات تلقائيًا وإعادة رسمها بالكامل View
.في هذه ObservableObject
الفئات ، يمكنك ببساطة وضع منطق عمل معين للتطبيق ، إذا كان بعضًا من هذا@Published
الخصائص هي نتيجة أخرى تحول متزامن و / أو غير متزامن @Published
الخصائص التي يمكن تغييرها مباشرة هذه العناصر "النشطة" واجهة المستخدم ( UI
) مربعات النص TextField
، Picker
، Stepper
، Toggle
الخلتوضيح ما هو على المحك ، سأعطي أمثلة محددة. تقدم الآن العديد من الخدمات مثل NewsAPI.org و Hacker News مجمعي الأخبار لتقديم المستخدمين لاختيار مجموعات مختلفة من المقالات اعتمادًا على اهتماماتهم. في حالة مجمع الأخبار NewsAPI.org ، يمكن أن يكون آخر الأخبار أو الأخبار في بعض الفئات - "الرياضة" أو "الصحة" أو "العلوم" أو "التكنولوجيا" أو "الأعمال" أو الأخبار من مصدر معلومات محدد "CNN" ، أخبار ABC ، بلومبرج ، إلخ. عادة ما "يعبر" المستخدم عن رغباته في الخدمات بالشكل Endpoint
الذي يشكل المطلوب له URL
.لذا ، باستخدام الإطار Combine
، يمكنكObservableObject
الفئات التي تستخدم رمزًا مضغوطًا للغاية (في معظم الحالات لا يزيد عن 10-12 سطرًا) مرة واحدة لتشكيل اعتماد متزامن و / أو غير متزامن لقائمة المقالات على Endpoint
شكل "اشتراك" في @Published
خصائص "سلبية" إلى خصائص "نشطة" @Published
. سيكون هذا "الاشتراك" صالحًا طوال "دورة حياة" مثيل ObservableObject
الفصل الدراسي بالكامل . وبعد ذلك SwiftUI
ستعطي المستخدم الفرصة لإدارة @Published
الخصائص "النشطة" فقط في النموذج Endpoint
، أي ما يريد رؤيته: ما إذا كانت ستكون مقالات تحتوي على آخر الأخبار أو المقالات في قسم "الصحة". سيتم توفير مظهر المقالات نفسها مع أحدث الأخبار أو المقالات في قسم "الصحة" على قسمك UI
تلقائيًا بواسطة هذه ObservableObject
الفئات وخصائصها "السلبية" @ المنشورة. في الكودSwiftUI
لن تحتاج أبدًا إلى طلب مجموعة مختارة من المقالات بشكل صريح ، لأن ObservableObject
الفصول التي تلعب دورًا هي المسؤولة عن عرضها الصحيح والمتزامن على الشاشة View Model
. سأوضحلك كيفية عمل ذلك مع NewsAPI.org و Hacker News وقاعدة بيانات أفلام TMDb في سلسلة من المقالات. في جميع الحالات الثلاث ، سيعمل نمط الاستخدام نفسه تقريبًا Combine
، لأنه في التطبيقات من هذا النوع ، يتعين عليك دائمًا إنشاء قوائم من الأفلام أو المقالات ، واختيار "الصور" (الصور) التي تصاحبها ، والبحث في قواعد البيانات للأفلام أو المقالات المطلوبة باستخدام شريط البحث.عند الوصول إلى هذه الخدمات ، قد تحدث أخطاء ، على سبيل المثال ، نظرًا لأنك حددت المفتاح الخطأ API-key
أو تجاوزت العدد المسموح به من الطلبات أو أي شيء آخر. تحتاج إلى معالجة هذا النوع من الأخطاء ، وإلا فإنك تخاطر بترك المستخدم تمامًا في حيرة مع شاشة فارغة. لذلك ، يجب أن تكون قادرًا ليس فقط على تحديد Combine
البيانات من الإنترنت باستخدام ، ولكن أيضًا الإبلاغ عن الأخطاء التي قد تحدث أثناء أخذ العينات والتحكم في مظهرها على الشاشة.سنبدأ في تطوير استراتيجيتنا من خلال تطوير تطبيق يتفاعل مع مجمع الأخبار NewsAPI.org . يجب أن أقول أنه في هذا التطبيق SwiftUI
سيتم استخدامه إلى الحد الأدنى دون أي زخرفة ، فقط من أجل إظهار كيف Combine
مع "الناشرين"Publisher
و "الاشتراك" Subscription
تتأثر UI
.يوصى بالتسجيل على موقع NewsAPI.org واستلام المفتاح API
المطلوب لإكمال أي طلبات لخدمة NewsAPI.org . يجب وضعه في ملف NewsAPI.swift في البنية APIConstants
.رمز التطبيق لهذه المقالة على جيثب .نموذج بيانات خدمة NewsAPI.org وواجهة برمجة التطبيقات
تتيح لك
خدمة NewsAPI.org تحديد معلومات حول المقالات الإخبارية الحالية [Article]
ومصادرها [Source]
. سيكون نموذج البيانات الخاص بنا بسيطًا جدًا ، فهو موجود في ملف Articles.swift :import Foundation
struct NewsResponse: Codable {
let status: String?
let totalResults: Int?
let articles: [Article]
}
struct Article: Codable, Identifiable {
let id = UUID()
let title: String
let description: String?
let author: String?
let urlToImage: String?
let publishedAt: Date?
let source: Source
}
struct SourcesResponse: Codable {
let status: String
let sources: [Source]
}
struct Source: Codable,Identifiable {
let id: String?
let name: String?
let description: String?
let country: String?
let category: String?
let url: String?
}
Article
ستحتوي المقالة على المعرف id
والعنوان title
والوصف description
والمؤلف author
وعنوان URL الخاص بالصورة urlToImage
وتاريخ النشر publishedAt
ومصدر النشر source
. فوق المقالات [Article]
عبارة عن وظيفة إضافية NewsResponse
سنكون مهتمين بها فقط في الممتلكات articles
، وهي مجموعة من المقالات. بنية الجذر NewsResponse
وهيكل Article
و Codable
، والتي سوف تسمح لنا حرفيا سطرين من التعليمات البرمجية فك رموز JSON
البيانات في نموذج. Article
يجب أن يكون الهيكل أيضًا Identifiable
، إذا أردنا أن نسهل على أنفسنا عرض مجموعة من المقالات [Article]
كقائمة List
في SwiftUI
. Identifiable
يتطلب البروتوكول وجود خاصيةid
التي سنزودها بمعرف اصطناعي فريد UUID()
. سيحتويمصدر المعلومات Source
على معرف id
واسم name
ووصف description
وبلد country
وفئة مصدر نشر category
وعنوان URL للموقع url
. فوق مصادر المعلومات ، [Source]
توجد وظيفة إضافية SourcesResponse
لن نهتم بها سوى خاصية sources
، وهي مجموعة من مصادر المعلومات. بنية الجذر SourcesResponse
وهيكل Source
و Codable
، والتي سوف تسمح لنا بسهولة جدا لفك JSON
البيانات في نموذج. Source
يجب أن يكون الهيكل أيضًا Identifiable
، إذا أردنا تسهيل عرض مجموعة من مصادر المعلومات [Source]
في شكل قائمة List
فيSwiftUI
. Identifiable
يتطلب البروتوكول وجود الممتلكات id
التي لدينا بالفعل ، لذلك لن تكون هناك حاجة إلى جهد إضافي منا.ضع في اعتبارك الآن ما نحتاجه API
لخدمة NewsAPI.org وضعه في ملف NewsAPI.swift . الجزء المركزي لدينا API
هو فئة NewsAPI
تقدم طريقتين لاختيار البيانات من مجمع الأخبار NewsAPI.org - المقالات [Article]
ومصادر المعلومات [Source]
:fetchArticles (from endpoint: Endpoint) -> AnyPublisher<[Article], Never>
- اختيار المقالات [Article]
على أساس المعلمة endpoint
،fetchSources (for country: String) -> AnyPublisher<[Source], Never>
- مجموعة مختارة من مصادر المعلومات [Source]
لبلد معين country
.
لا تعيد هذه الأساليب فقط مجموعة من المقالات [Article]
أو مجموعة من مصادر المعلومات [Source]
، ولكن "الناشرين" Publisher
المطابقين للإطار الجديد Combine
. لا يُرجع كلا الناشرين أي خطأ - Never
وإذا استمر حدوث خطأ في أخذ العينات أو التشفير ، فسيتم إرجاع مجموعة فارغة من المقالات [Article]()
أو مصادر المعلومات [Source]()
بدون أي رسالة توضح سبب إفراغ هذه المصفوفات. المقالات أو مصادر المعلومات التي نريد تحديدها من خادم NewsAPI.org ، سنشير إلى استخدام التعدادenum Endpoint:
enum Endpoint {
case topHeadLines
case articlesFromCategory(_ category: String)
case articlesFromSource(_ source: String)
case search (searchFilter: String)
case sources (country: String)
var baseURL:URL {URL(string: "https://newsapi.org/v2/")!}
func path() -> String {
switch self {
case .topHeadLines, .articlesFromCategory:
return "top-headlines"
case .search,.articlesFromSource:
return "everything"
case .sources:
return "sources"
}
}
}
:- آخر الأخبار
.topHeadLines
، - أخبار فئة معينة (الرياضة ، الصحة ، العلوم ، الأعمال ، التكنولوجيا)
.articlesFromCategory(_ category: String)
، - أخبار من مصدر محدد للمعلومات (CNN ، ABC News ، Fox News ، إلخ)
.articlesFromSource(_ source: String)
، - أي أخبار
.search (searchFilter: String)
تلبي شرطًا معينًا searchFilter
، - مصادر المعلومات
.sources (country:String)
لبلد معين country
.
لتسهيل تهيئة الخيار الذي نحتاجه ، سنضيف Endpoint
مُهيئًا init?
إلى التعداد لقوائم متنوعة من المقالات ومصادر المعلومات اعتمادًا على الفهرس index
والسطر text
، والذي له معان مختلفة لخيارات التعداد المختلفة:init? (index: Int, text: String = "sports") {
switch index {
case 0: self = .topHeadLines
case 1: self = .search(searchFilter: text)
case 2: self = .articlesFromCategory(text)
case 3: self = .articlesFromSource(text)
case 4: self = .sources (country: text)
default: return nil
}
}
دعنا نعود إلى الفصل NewsAPI
وننظر بمزيد من التفصيل في الطريقة الأولى fetchArticles (from endpoint: Endpoint)-> AnyPublisher<[Article], Never>
، التي تحدد المقالات [Article]
بناءً على المعلمة endpoint
ولا تعرض أي خطأ - Never
:func fetchArticles(from endpoint: Endpoint) -> AnyPublisher<[Article], Never> {
guard let url = endpoint.absoluteURL else {
return Just([Article]()).eraseToAnyPublisher()
}
return
URLSession.shared.dataTaskPublisher(for:url)
.map{$0.data}
.decode(type: NewsResponse.self,
decoder: APIConstants .jsonDecoder)
.map{$0.articles}
.replaceError(with: [])
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
- على أساس
endpoint
نموذج URL
الطلب ، قائمة المقالات المرغوبة endpoint.absoluteURL
، إذا تعذر ذلك ، قم بإرجاع مجموعة فارغة من المقالات[Article]()
- «»
dataTaskPublisher(for:)
, Output
(data: Data, response: URLResponse)
, Failure
- URLError
, map { }
(data: Data, response: URLResponse)
data
, JSON
data
, NewsResponse
, articles: [Atricle]
map { }
- articles
, - -
[ ]
, main
, UI
,- «» «»
eraseToAnyPublisher()
AnyPublisher
.
يتم تعيين مهمة اختيار مصادر المعلومات إلى الطريقة الثانية - fetchSources (for country: String) -> AnyPublisher<[Source], Never>
وهي نسخة دلالية دقيقة للطريقة الأولى ، باستثناء أنه في هذه المرة بدلاً من المقالات [Article]
، سنختار مصادر المعلومات [Source]
:func fetchSources() -> AnyPublisher<[Source], Never> {
guard let url = Endpoint.sources.absoluteURL else {
return Just([Source]()).eraseToAnyPublisher()
}
return
URLSession.shared.dataTaskPublisher(for:url)
.map{$0.data}
.decode(type: SourcesResponse.self,
decoder: APIConstants .jsonDecoder)
.map{$0.sources}
.replaceError(with: [])
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
ترجع لنا "الناشر" AnyPublisher <[Source], Never>
بقيمة في شكل مصفوفة من مصادر المعلومات [Source]
وغياب خطأ Never
(في حالة حدوث أخطاء ، يتم إرجاع مجموعة فارغة من المصادر [ ]
).سنفرد الجزء المشترك من هاتين الطريقتين ، ونرتبها Generic
كدالة fetch(_ url: URL) -> AnyPublisher<T, Error>
تُرجع Generic
"الناشر" AnyPublisher<T, Error>
بناءً على URL
:
func fetch<T: Decodable>(_ url: URL) -> AnyPublisher<T, Error> {
URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data}
.decode(type: T.self, decoder: APIConstants.jsonDecoder)
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
سيؤدي ذلك إلى تبسيط الطريقتين السابقتين:
func fetchArticles(from endpoint: Endpoint)
-> AnyPublisher<[Article], Never> {
guard let url = endpoint.absoluteURL else {
return Just([Article]()).eraseToAnyPublisher()
}
return fetch(url)
.map { (response: NewsResponse) -> [Article] in
return response.articles }
.replaceError(with: [Article]())
.eraseToAnyPublisher()
}
func fetchSources(for country: String)
-> AnyPublisher<[Source], Never> {
guard let url = Endpoint.sources(country: country).absoluteURL
else {
return Just([Source]()).eraseToAnyPublisher()
}
return fetch(url)
.map { (response: SourcesResponse) -> [Source] in
response.sources }
.replaceError(with: [Source]())
.eraseToAnyPublisher()
}
وبالتالي فإن "الناشرين" الذين تم الحصول عليهم لا يسلمون أي شيء حتى "يشترك" معهم. يمكننا القيام بذلك عند التصميم UI
."الناشرون" Publisher
كما View Model
في SwiftUI
. قائمة المقالات.
الآن القليل عن منطق الأداء SwiftUI
.إن SwiftUI
التجريد الوحيد لـ "التغييرات الخارجية" التي يردون عليها View
هو "الناشرين" Publisher
. يمكن فهم "التغييرات الخارجية" كمؤقت Timer
، NotificationCenter
أو إخطار مع أو كائن النموذج الخاص بك ، والذي يمكن استخدامه باستخدام البروتوكول ObservableObject
إلى "مصدر الحقيقة" خارجي واحد (مصدر الحقيقة). بالنسبة إلى "الناشرين" العاديين اكتب Timer
أو NotificationCenter
View
يتفاعل باستخدام الطريقة onReceive (_: perform:)
. مثال على استخدام "الناشر" Timer
سنقدم لاحقًا في المقالة الثالثة حول إنشاء تطبيق لـ Hacker News .في هذه المقالة ، سنركز على كيفية صنع نموذجنا SwiftUI
خارجي "مصدر الحقيقة" (مصدر الحقيقة).دعنا أولاً نلقي نظرة على كيفية عمل SwiftUI
"الناشرين" المستلمين في مثال محدد لعرض أنواع مختلفة من المقالات:.topHeadLines
- آخر الأخبار ، .articlesFromCategory(_ category: String)
- أخبار فئة معينة ، .articlesFromSource(_ source: String)
- أخبار لمصدر محدد من المعلومات ، .search (searchFilter: String)
- أخبار مختارة حسب حالة معينة.
بناءً على اختيار Endpoint
المستخدم ، نحتاج إلى تحديث قائمة المقالات articles
المحددة من NewsAPI.org . للقيام بذلك ، سننشئ فئة بسيطة جدًا ArticlesViewModel
تنفذ بروتوكولًا ObservableObject
مع ثلاث @Published
خصائص: 
@Published var indexEndpoint: Int
— Endpoint
( «», View
), @Published var searchString: String
— , ( «», View
TextField
),@Published var articles: [Article]
- ( «», NewsAPI.org, «»).
في أقرب وقت وضعناها @Published
خصائص indexEndpoint
أو searchString
، يمكننا أن نبدأ في استخدامها على حد سواء كما الخصائص البسيطة indexEndpoint
و searchString
، وبأنها "الناشرين" $indexEndpoint
و $searchString
.في الفصل الدراسي ArticlesViewModel
، لا يمكنك فقط الإعلان عن الخصائص التي تهمنا ، ولكن أيضًا وصف منطق الأعمال لتفاعلها. تحقيقا لهذه الغاية ، عند تهيئة مثيل لفئة ArticlesViewModel
في ، init
يمكننا إنشاء "اشتراك" يعمل طوال "دورة حياة" كامل لفئة الفصل ArticlesViewModel
وإعادة إنتاج اعتماد قائمة المقالات articles
على الفهرس indexEndpoint
وسلسلة البحث searchString
.للقيام بذلك، Combine
ونحن نعرب عن سلسلة "الناشرين" $indexEndpoint
و $searchString
إخراج "الناشر"AnyPublisher<[Article], Never>
قيمته قائمة بالمقالات articles
. ثم نقوم "بالاشتراك" فيه باستخدام عامل التشغيل assign (to: \.articles, on: self)
ونحصل على قائمة بالمقالات التي نحتاجها كخاصية articles
"إخراج" @Published
تحدد UI
.يجب علينا سحب سلسلة NOT ببساطة من خصائص indexEndpoint
و searchString
، وهما من "الناشرين" $indexEndpoint
و $searchString
الذين يشاركون في خلق UI
بمساعدة SwiftUI
وسوف نقوم بتغيير لهم هناك باستخدام عناصر واجهة المستخدم Picker
و TextField
.كيف سنفعل ذلك؟لدينا بالفعل وظيفة في ترسانتنا fetchArticles (from: Endpoint)
موجودة في الفصل NewsAPI
وترجع "ناشر" AnyPublisher<[Article], Never>
، اعتمادًا على القيمةEndpoint
ويمكننا استخدام فقط بطريقة أو بأخرى قيم "الناشرين" $indexEndpoint
و $searchString
تحويلها إلى حجة ل endpoint
هذه الوظيفة. اولا اجمع بين "الناشرين" $indexEndpoint
و $searchString
. لهذا ، Combine
عامل التشغيل موجود Publishers.CombineLatest
:
لإنشاء "ناشر" جديد بناءً على البيانات التي تم تلقيها من "الناشر" السابق Combine
، يتم استخدام عامل التشغيل flatMap
:
بعد ذلك ، "نشترك" في هذا "الناشر" المستلم حديثًا باستخدام "مشترك" بسيط للغاية assign (to: \.articles, on: self)
ونعين المستلم من " قيمة الناشر " @Published
للمصفوفة articles
:
لقد أنشأنا للتو init( )
" ناشر "ASYNCHRONOUS و" مشترك "فيه ، نتيجةAnyCancellable
"الاشتراك" وهذا من السهل التحقق منه إذا حافظنا على "اشتراكنا" ثابتًا let subscription
:
الخاصية الرئيسية لـ AnyCancellable
"الاشتراك" هي أنه بمجرد ترك نطاقه ، يتم تحرير الذاكرة التي يشغلها تلقائيًا. لذلك ، بمجرد init( )
اكتماله ، سيتم حذف هذا "الاشتراك" ARC
، دون وجود وقت لتخصيص المعلومات غير المتزامنة التي يتم تلقيها مع تأخير زمني للصفيف articles
. المعلومات غير المتزامنة ببساطة ليس لها مكان لـ "الأرض" ، بمعناها الحرفي ، "لقد ذهبت الأرض من تحت قدميها".لحفظ مثل هذا "الاشتراك" ، من الضروري إنشاء init()
متغير بعد المُهيئ var cancellableSet
الذي سيحفظ AnyCancellable
" اشتراكنا " في هذا المتغير طوال "دورة حياة" مثيل الفصل بأكمله ArticlesViewMode
. لذلك ، نزيل الثابت let subscription
ونتذكر AnyCancellable
" اشتراكنا " في المتغير cancellableSet
باستخدام عامل التشغيل .store ( in: &self.cancellableSet)
:
"الاشتراك" في "ناشر" ASYNCHRONOUS الذي أنشأناه init( )
سيتم الاحتفاظ به طوال "دورة حياة" مثيل الفصل الدراسي بالكامل ArticlesViewModel
.يمكننا تغيير معنى "الناشرين" $indexEndpoint
و / أو بشكل تعسفي searchString
، ودائمًا بفضل "الاشتراك" الذي تم إنشاؤه ، سيكون لدينا مجموعة من المقالات التي تتوافق مع قيم هذين الناشرين articles
دون أي جهد إضافي. ObservableObject
تسمى هذه الفئة عادة View Model
.لتقليل عدد المكالمات إلى الخادم عند كتابة سلسلة بحث searchString
، يجب ألا نستخدم "ناشر" سلسلة البحث نفسها $searchString
، ونسخته المعدلة validString
:
الآن View Model
لدينا مقالاتنا ، لنبدأ في إنشاء واجهة المستخدم ( UI
). في SwiftUI
التزامن View
مع ObservableObject
النموذج ، @ObservedObject
يتم استخدام متغير يشير إلى مثيل لفئة هذا النموذج. هذا الزوج - ObservableObject
الفئة @ObservedObject
والمتغير الذي يشير إلى مثيل هذه الفئة - هو الذي يتحكم في التغيير في واجهة المستخدم ( UI
) في SwiftUI
.نضيف إلى الهيكل ContentView
مثيلًا للفئة ArticleViewModel
في شكل متغير var articleViewModel
ونستبدلها Text ("Hello, World!")
بقائمة من المقالات ArticlesList
التي نضع فيها المقالات التي articlesViewModel.articles
تم الحصول عليها من موقعنا View Model
. ونتيجة لذلك ، نحصل على قائمة بالمقالات لفهرس ثابت وافتراضي indexEndpoint = 0
، أي .topHeadLines
لأحدث الأخبار:
أضف UI
عنصرًا إلى شاشتنا للتحكم في مجموعة المقالات التي نريد عرضها. سنستخدم Picker
تغيير الفهرس $articlesViewModel.indexEndpoint
. إن وجود الرمز $
إلزامي ، لأن هذا يعني تغييرًا في القيمة التي يوفرها @Published
"الناشر". يتم تشغيل "الاشتراك" في هذا "الناشر" على الفور ، والذي بدأناه في init ()
، سيتغير "الإخراج" @Published
"الناشر" articles
وسنرى قائمة مختلفة من المقالات على الشاشة:
بهذه الطريقة يمكننا تلقي مجموعة من المقالات لجميع الخيارات الثلاثة - "topHeadLines" ، "بحث "و" من الفئة ":
... ولكن بالنسبة لسلسلة بحث ثابتة وافتراضية searchString = "sports"
(حيث تكون مطلوبة):
ومع ذلك ، بالنسبة للخيار ، "search"
يجب عليك تزويد المستخدم بحقل نصي SearchView
لإدخال سلسلة البحث:
ونتيجة لذلك ، يمكن للمستخدم البحث عن أي أخبار عن طريق سلسلة البحث المكتوبة:
بالنسبة للخيار ، "from category"
من الضروري توفير المستخدم الفرصة لاختيار فئة ونبدأ بالفئة science
:
ونتيجة لذلك ، يمكن للمستخدم البحث عن أي أخبار عن فئة الأخبار المختارة - science
، health
، business
، technology
:
يمكننا أن نرى كيف ObservableObject
أن نموذجًا بسيطًا جدًا يحتوي على ميزتين يتحكم فيه المستخدم @Published
- indexEndpoint
وsearchString
- يسمح لك باختيار مجموعة واسعة من المعلومات من موقع NewsAPI.org .قائمة مصادر المعلومات
دعونا نلقي نظرة على كيفية عمل SwiftUI
"ناشر" مصادر المعلومات المستلمة في فصل NewsAPI fetchSources (for country: String) -> AnyPublisher<[Source], Never>
.سنحصل على قائمة بمصادر المعلومات لمختلف البلدان:
... والقدرة على البحث عنها بالاسم:
... بالإضافة إلى معلومات تفصيلية حول المصدر المحدد: اسمه وفئته وبلده ووصف قصير ورابط إلى الموقع:
إذا نقرت على الرابط ، فسننتقل إلى موقع الويب الخاص بهذا مصدر المعلومات.لكي يعمل كل هذا ، تحتاج إلى ObservableObject
نموذج بسيط للغاية يحتوي على @Published
خاصيتين يتحكم فيه المستخدم فقط - searchString
و country
:
ومرة أخرى ، نستخدم نفس المخطط: عند تهيئة مثيل لفئة من SourcesViewModel
الفئات في init
نقوم بإنشاء "اشتراك" يعمل طوال "دورة حياة" مثيل الفصل الدراسي بالكامل ونتأكد من SourcesViewModel
أن قائمة مصادر المعلومات تعتمد sources
على البلد country
وسلسلة البحث searchString
.بمساعدة Combine
نحن سحب سلسلة من "الناشرين" $searchString
و $country
إخراج "الناشر" AnyPublisher<[Source], Never>
، الذي القيمة هي قائمة من مصادر المعلومات. نقوم "بالاشتراك" فيه باستخدام عامل التشغيل assign (to: \.sources, on: self)
، نحصل على قائمة مصادر المعلومات التي نحتاجها sources
. وتذكر AnyCancellable
"الاشتراك" المستلم في متغير cancellableSet
باستخدام عامل التشغيل .store ( in: &self.cancellableSet)
.الآن View Model
لدينا مصادر معلوماتنا ، فلنبدأ في الإنشاء UI
. B SwiftUI
للمزامنة View
جObservableObject
يستخدم النموذج @ObservedObject
متغيرًا يشير إلى مثيل لفئة هذا النموذج.أضف ContentViewSources
مثيل الفئة إلى البنية SourcesViewModel
في شكل متغير var sourcesViewModel
، Text ("Hello, World!")
وقم بإزالة مكانك View
لكل خاصية من @Published
الخصائص الثلاثة sourcesViewModel
: - مربع نص
SearchView
لشريط البحث searchString
، -
Picker
للبلد country
، - قائمة
SourcesList
مصادر المعلومات
ونتيجة لذلك ، نحصل على ما نحتاج إليه View
:
في هذه الشاشة ، ندير سلسلة البحث فقط باستخدام مربع النص SearchView
و "البلد" مع Picker
، والباقي يحدث تلقائيًا.تحتوي قائمة مصادر المعلومات SourcesList
على الحد الأدنى من المعلومات حول كل مصدر - الاسم source.name
ووصف موجز source.description
:
... ولكنها تسمح لك بالحصول على معلومات أكثر تفصيلاً حول المصدر المحدد باستخدام الرابط NavigationLink
الذي destination
نشير DetailSourceView
فيه إلى بيانات المصدر التي هي مصدر المعلومات source
والنسخة المطلوبة من الفصل ArticlesViewModel
، مما يسمح احصل على قائمة بمقالاته articles
:
انظر كيف نحصل على قائمة المقالات لمصدر المعلومات المحدد في قائمة المصادر SourcesList
. يساعدنا صديقنا القديم - فئة ArticlesViewModel
يجب أن نحدد لها @Published
خصائص "الإدخال" :- الفهرس
indexEndpoint = 3
، أي خيار .articlesFromSource (_source:String)
مقابل اختيار المقالات لمصدر ثابت source
، - سلسلة
searchString
كمصدر نفسه (أو بالأحرى معرفه) source.id
:
بشكل عام ، إذا نظرت إلى تطبيق NewsApp بأكمله ، فلن ترى في أي مكان نطلب فيه صراحةً مجموعة مختارة من المقالات أو مصادر المعلومات من موقع NewsAPI.org . نحن ندير @Published
البيانات فقط ، لكننا View Model
نقوم بعملنا: يختار المقالات ومصادر المعلومات التي نحتاجها.تنزيل الصورة UIImage
للمقالة Article
.
يحتوي نموذج المقالة Article
على URL
صورة مرفقة به urlToImage
:
بناءً على ذلك ، URL
يجب علينا في المستقبل الحصول على الصور بأنفسهم UIImage
من موقع NewsAPI.org .نحن بالفعل على دراية بهذه المهمة. في الفصل ImageLoader
، باستخدام الوظيفة ، قم fetchImage(for url: URL?) -> AnyPublisher<UIImage?, Never>
بإنشاء "ناشر" AnyPublisher<UIImage?, Never>
بقيمة الصورة UIImage?
ولا يوجد خطأ Never
(في الواقع ، إذا حدثت أخطاء ، فسيتم إرجاع الصورة nil
). يمكنك "الاشتراك" في هذا "الناشر" لتلقي الصور UIImage?
عند تصميم واجهة المستخدم ( UI
). البيانات المصدر للدالة fetchImage(for url: URL?)
هي url
التي لدينا:
دعنا نفكر بالتفصيل في كيفية عمل التكوين بمساعدة Combine
"الناشر" AnyPublisher <UIImage?, Never>
، إذا علمنا url
:- إذا كانت
url
متساوية nil
، ارجع Just(nil)
، - بناء على
url
شكل "الناشر" dataTaskPublisher(for:)
، الذي قيمة الانتاج Output
هو الصفوف (tuple) (data: Data, response: URLResponse)
وخطأ Failure
- URLError
، - نأخذ فقط البيانات
map {}
من tuple (data: Data, response: URLResponse)
لمزيد من المعالجة data
، والشكل UIImage
، - إذا حدث خطأ عودة الخطوات السابقة
nil
، - نقدم النتيجة إلى
main
التدفق ، حيث نفترض المزيد من الاستخدام في التصميم UI
، - "مسح" نوع "الناشر" وإرجاع النسخة
AnyPublisher
.
ترى أن الشفرة مضغوطة تمامًا وقابلة للقراءة بشكل جيد ، لا يوجد أي منها callbacks
.لنبدأ في إنشاء View Model
الصورة UIImage?
. هذه فئة ImageLoader
تطبق البروتوكول ObservableObject
، مع @Published
خاصيتين:@Published url: URL?
هي URL
صور@Published var image: UIImage?
هي الصورة نفسها من NewsAPI.org :
ومرة أخرى ، عند تهيئة مثيل من الفصل ، ImageLoader
يجب أن نمتد السلسلة من إدخال "الناشر" $url
إلى إخراج "الناشر" AnyPublisher<UIImage?, Never>
، الذي "سنشترك فيه" لاحقًا ونحصل على الصورة التي نحتاجها image
:
نستخدم عامل التشغيل flatMap
و "مشترك" بسيط جدًا assign (to: \image, on: self)
لتعيينه إلى المستلم من "الناشر" يتم تخزين "قيم الخاصية @Published image
:
ومرة أخرى في المتغير " الاشتراك باستخدام عامل التشغيل . منطق "تنزيل الصور" هذا هو أنك تقوم بتنزيل صورة من شيء آخر بخلاف تلك التي لم يتم تحميلها مسبقًا ، أيcancellableSet
AnyCancellable
store(in: &self.cancellableSet)
nil URL
image == nil
. إذا تم اكتشاف أي خطأ أثناء عملية التنزيل ، فستكون الصورة غائبة ، أي أنها image
ستظل متساوية nil
.في SwiftUI
عرض الصورة بالمساعدة ArticleImage
التي يستخدمها مثيل imageLoader
الفئة لهذا الغرض ImageLoader
. إذا كانت صورته غير متساوية nil
، فسيتم عرضها باستخدام Image (...)
، ولكن إذا كانت متساوية nil
، فاعتمادًا على ما يساوي url
، إما أنه لا يتم عرض أي شيء EmptyView()
، أو يتم عرض مستطيل Rectangle
به نص دوار T ext("Loading...")
:
هذا المنطق يعمل بشكل جيد للحالة عندما تعلم على وجه اليقين أنه url
بخلاف nil
الحصول على صورة image
، كما هو الحال مع قاعدة بيانات الأفلام TMDb . مع NewsAPI.org ، يختلف مجمع الأخبار . تعطي مقالات بعض مصادر المعلومات صورة مختلفة عن nil URL
الصورة ، ولكن الوصول إليها مغلق ، ونحصل على مستطيل Rectangle
بنص دوار Text("Loading...")
لن يتم استبداله أبدًا:
في هذه الحالة ، إذا كانت URL
الصورة مختلفة عن nil
ذلك ، فإن المساواة في nil
الصورة image
قد تعني أن الصورة يتم تحميلها ، وحقيقة حدوث خطأ أثناء التحميل ولن نحصل أبدًا على صورة image
. للتمييز بين هاتين الحالتين ، نضيف واحدة أخرى ImageLoader
إلى خاصيتين @Published
موجودتين في الفئة : @Published var noData = false
- هذه قيمة منطقية سنشير من خلالها إلى عدم وجود بيانات الصورة بسبب خطأ أثناء التحديد:
عند إنشاء "اشتراك" ، نكتشف init
جميع الأخطاء Error
التي تحدث عند تحميل الصورة وتجميع تواجدها في @Published
الموقع self.noData = true
. إذا نجح التنزيل ، نحصل على الصورة image
. نقوم بإنشاء"الناشر" AnyPublisher<UIImage?, Error>
على أساس url
الوظيفة fetchImageErr (for url: URL?)
:
نبدأ في إنشاء طريقة من fetchImageErr
خلال تهيئة "الناشر" Future
، والتي يمكن استخدامها للحصول على قيمة TYPE واحدة بشكل غير متزامن Result
باستخدام الإغلاق. يحتوي الإغلاق على معلمة واحدة - Promise
وهي دالة من النوع (Result<Output, Failure>) → Void
: سنحول
النتيجة الناتجة Future
إلىAnyPublisher <UIImage?, Error>
بمساعدة عامل التشغيل "Erase TYPE" eraseToAnyPublisher()
.بعد ذلك، سنقوم بتنفيذ الخطوات التالية، مع الأخذ بعين الاعتبار جميع الأخطاء المحتملة (ونحن لن تحديد الأخطاء، هو ببساطة المهم بالنسبة لنا أن نعرف أن هناك خطأ):0. الاختيار url
ل nil
و noData
على true
: إذا كان الأمر كذلك، ثم العودة الخطأ، إن لم يكن، ونقل url
مزيد من السلسلة ،1. قم بإنشاء "ناشر" dataTaskPublisher(for:)
يكون مدخله - url
وقيمة المخرجات Output
هي مجموعة (data: Data, response: URLResponse)
وخطأ URLError
،2. قم بالتحليل باستخدام المجموعة tryMap { }
الناتجة (data: Data, response: URLResponse)
: إذا كان response.statusCode
في النطاق 200...299
، ثم للمعالجة الإضافية ، نأخذ البيانات فقط data
. خلاف ذلك ، فإننا "نتخلص" من الخطأ (بغض النظر عن ما) ،3. نقوم map { }
بتحويل البيانات data
إلى UIImage
،4. تسليم النتيجة إلى main
التدفق ، حيث نفترض أننا سنستخدمها لاحقًا في عملية التصميم UI
- "نشترك" في "الناشر" المستلم باستخدام sink
عمليات الإغلاق ، receiveCompletion
و - 5. إذا وجدنا خطأ في الإغلاق ، فإننا نبلغ باستخدامه ، - 6. في الختام ، نبلغ عن الاستلام الناجح لمجموعة من المقالات باستخدام ، 7. نتذكر "الاشتراك" المستلم في المتغير لضمان قابليته للتطبيق خلال "عمر" مثيل الفصل الدراسي ، 8. نحن "محو" TYPE "الناشر" وإرجاع المثيل .receiveValue
receiveCompletion
error
promise (.failure(error)))
receiveValue
promise (.success($0))
cancellableSet
ImageLoader
AnyPublisher
نعود إلى ArticleImage
حيث سنستخدم @Published
المتغير الجديد noData
. إذا لم تكن هناك بيانات صورة ، فلن نعرض أي شيء ، أي EmptyView ()
:
في النهاية ، سنقوم بتعبئة جميع إمكانياتنا لعرض البيانات من مجمع أخبار NewsAPI.org في TabView
:
عرض الأخطاء عند جلب بيانات JSON وفك تشفيرها من خادم NewsAPI.org .
عند الوصول إلى خادم NewsAPI.org ، يمكن أن تحدث أخطاء ، على سبيل المثال ، نظرًا لأنك حددت المفتاح الخطأ API-key
أو إذا كان لديك تعريفة مطور لا تكلف شيئًا ، تجاوز العدد المسموح به من الطلبات أو أي شيء آخر. في الوقت نفسه ، يوفر لك خادم NewsAPI.orgHTTP
الرمز والرسالة المقابلة: من
الضروري معالجة هذا النوع من أخطاء الخادم. خلاف ذلك ، سيقع مستخدم تطبيقك في موقف عندما يتوقف خادم NewsAPI.org فجأة ، دون سبب ، عن معالجة أي طلبات ، مما يترك المستخدم في حالة فقدان تام مع شاشة فارغة.حتى الآن ، عند اختيار المقالات [Article]
ومصادر المعلومات [Source]
من خادم NewsAPI.org تجاهلنا كل الأخطاء، وفي حال ورودها عاد صفائف فارغة [Article]()
و نتيجة لذلك [Source]()
.لنبدأ في معالجة الأخطاء ، دعنا fetchArticles (from endpoint: Endpoint) -> AnyPublisher<[Article], Never>
ننشئ NewsAPI
طريقة أخرى في الفصل استنادًا إلى طريقة اختيار المقالة الحالية fetchArticlesErr (from endpoint: Endpoint) -> AnyPublisher<[Article], NewsError>
التي ستعيد ليس فقط مجموعة من المقالات [Article]
، ولكن أيضًا خطأ محتمل NewsError
:func fetchArticlesErr(from endpoint: Endpoint) ->
AnyPublisher<[Article], NewsError> {
. . . . . . . .
}
هذه الطريقة ، بالإضافة إلى الطريقة fetchArticles
، تقبل endpoint
وتعيد "الناشر" عند الإدخال بقيمة في شكل مجموعة من المقالات [Article]
، ولكن بدلاً من عدم وجود خطأ Never
، قد يكون لدينا خطأ محدد من خلال التعداد NewsError
:
نبدأ في إنشاء طريقة جديدة من خلال تهيئة "الناشر" Future
، والتي يمكن أن تكون استخدامها للحصول بشكل غير متزامن على قيمة TYPE واحدة Result
باستخدام الإغلاق. يحتوي الإغلاق على معلمة واحدة - Promise
وهي دالة من النوع TYPE (Result<Output, Failure>) -> Void
: سنحول
المستلم Future
إلى "الناشر" الذي نحتاج إليه AnyPublisher <[Article], NewsError>
باستخدام عامل التشغيل "TYPE Erase" eraseToAnyPublisher()
.بعد ذلك ، في الطريقة الجديدة ، fetchArticlesErr
سنكرر جميع الخطوات التي اتخذناها في الطريقة fetchArticles
، لكننا سنأخذ في الاعتبار جميع الأخطاء المحتملة:
- 0. endpoint
URL endpoint.absoluteURL
, url
nil
: nil
, .urlError
, — url
, - 1. «»
dataTaskPublisher(for:)
, — url
, Output
(data: Data, response: URLResponse)
URLError
, - 2.
tryMap { }
(data: Data, response: URLResponse)
: response.statusCode
200...299
, data
. «» .responseError
, data
, String
, , - 3.
JSON
, NewsResponse
, - 4.
main
, UI
- «» «»
sink
receiveCompletion
receiveValue
,
- 5.
receiveCompletion
error
, promise (.failure(...)))
, - 6.
receiveValue
promise (.success($0.articles))
,
- 7. «»
var subscriptions
, « » NewsAPI
, - 8. «» «»
AnyPublisher
.
وتجدر الإشارة إلى أن "الناشر" dataTaskPublisher(for:)
يختلف عن نموذجه الأولي dataTask
في أنه في حالة وجود خطأ في الخادم عندما response.statusCode
لا يكون في النطاق 200...299
، فإنه لا يزال يقدم القيمة الناجحة في شكل مجموعة (data: Data, response: URLResponse)
، وليس خطأ في النموذج (Error, URLResponse?)
. في هذه الحالة ، يتم تضمين معلومات خطأ الخادم الحقيقي في data
. dataTaskPublisher(for:)
يقدم "الناشر" خطأ URLError
إذا حدث خطأ من جانب العميل (عدم القدرة على الاتصال بالخادم ، حظر نظام الأمان ATS
، إلخ).إذا أردنا عرض الأخطاء فيه SwiftUI
، فنحن بحاجة إلى الخطأ المقابل View Model
، الذي سنطلق عليه ArticlesViewModelErr
:
في الفصل ArticlesViewModelErr
الذي ينفذ البروتوكول ObservableObject
، هذه المرة لدينا أربع @Published
خصائص:@Published var indexEndpoint: Int
— Endpoint
( «», View
), @Published var searchString: String
— , Endpoint
: «» , ( «», View
), -
@Published var articles: [Article]
- ( «», NewsAPI.org ) -
@Published var articlesError: NewsError?
- , NewsAPI.org .
عند تهيئة مثيل من فئة ArticlesViewModelErr
، ويجب علينا مرة أخرى تمديد سلسلة من المدخلات "الناشرين" $indexEndpoint
و $searchString
إخراج "الناشر" AnyPublisher<[Article],NewsError>
، الذي نحن "وقعت" مع "المشترك" sink
ونحصل على الكثير من المواد articles
أو خطأ articlesError
.في صفنا، و NewsAPI
نحن قد شيدت بالفعل وظيفة fetchArticlesErr (from endpoint: Endpoint)
أن عائدات "الناشر" AnyPublisher<[Article], NewsError>
، تبعا للقيمة endpoint
، ونحن بحاجة فقط لاستخدام بطريقة أو بأخرى قيم "الناشرين" $indexEndpoint
و $searchString
تحويلها إلى حجة لهذه المهمة endpoint
. بادئ ذي بدء ، سوف نقوم بدمج "الناشرين" $indexEndpoint
و $searchString
. للقيام بذلك ، Combine
هناك عامل Publishers.CombineLatest
:
ثم يجب علينا تعيين نوع الخطأ TYPE "ناشر" يساوي المطلوب NewsError
:
بعد ذلك ، نريد استخدام الوظيفة fetchArticlesErr (from endpoint: Endpoint)
من صفنا NewsAPI
. كالعادة ، سنفعل ذلك بمساعدة عامل flatMap
إنشاء "ناشر" جديد على أساس البيانات الواردة من "الناشر" السابق:
ثم "الاشتراك" في هذا "الناشر" المستلم حديثًا بمساعدة "مشترك" sink
واستخدام عمليات الإغلاق receiveCompletion
و receiveValue
لتلقي من "الناشر" إما قيمة مجموعة من المقالات articles
أو خطأ articlesError
:
بطبيعة الحال ، من الضروري تذكر "الاشتراك" الناتج في بعض init()
المتغيرات الخارجية cancellableSet
. وإلا ، فلن نتمكن من الحصول على القيمة بشكل غير متزامنarticles
أو خطأ articlesError
بعد الانتهاء init()
:
لتقليل عدد المكالمات إلى الخادم عند كتابة سلسلة بحث searchString
، يجب ألا نستخدم "ناشر" شريط البحث نفسه $searchString
، ولكن نسخته المعدلة validString
:
"اشتراك" في "ناشر" ASYNCHRONOUS الذي أنشأناه init( )
سيكون تستمر طوال "دورة حياة" مثيل الفصل بالكامل ArticlesViewModelErr
:
ننتقل إلى تصحيح منطقتنا من UI
أجل عرض الأخطاء المحتملة لأخذ عينات البيانات عليه. في SwiftU
I ، في الهيكل الحالي ، ContentVieArticles
نستخدم آخر ، تم الحصول عليه View Model
للتو ، فقط بإضافة الأحرف "Err" في الاسم. هذه نسخة من الفصل. ArticlesViewModelErr
، الذي "يمسك" خطأ تحديد و / أو فك تشفير بيانات المقالة من خادم NewsAPI.org :
كما نضيف عرض رسالة الطوارئ Alert
في حالة حدوث خطأ.على سبيل المثال ، إذا كان مفتاح واجهة برمجة التطبيقات الخطأ هو:struct APIConstants {
static let apiKey: String = "API_KEY"
. . . . . . . . . . . . .
}
... ثم سنتلقى الرسالة التالية:
إذا تم استنفاد الحد الأقصى للطلبات ، فسوف نتلقى الرسالة التالية:
بالعودة إلى طريقة تحديد المقالات [Article]
مع وجود خطأ محتمل NewsError
، يمكننا تبسيط رمزها إذا استخدمنا Generic
"الناشر" AnyPublisher<T,NewsError>,
الذي ، بناءً على المجموعة ، url
يتلقى JSON
المعلومات بشكل غير متزامن ، ويضعها مباشرة في Codable
نموذج T
وتقارير خطأ NewsError
:
كما نعلم، هذا الرمز هو من السهل جدا استخدام للحصول على محددة "الناشر" إذا كانت البيانات المصدر ل url
هي NewsAPI.orgEndpoint
أخبار مجمع بلد أو country
مصدر المعلومات ، ويتطلب الإخراج نماذج مختلفة - على سبيل المثال ، قائمة بالمقالات أو مصادر المعلومات:

استنتاج
تعلمنا كم هو سهل لتلبية HTTP
طلبات بمساعدة Combine
بها URLSession
"الناشر" dataTaskPublisher
و Codable
. إذا لم تكن بحاجة إلى تتبع الأخطاء ، فستحصل على رمز بسيط للغاية مكون من Generic
5 أسطر لـ "الناشر" AnyPublisher<T, Never>
، والذي يتلقى JSON
المعلومات بشكل غير متزامن ويضعها مباشرةً في Codable
النموذج T
بناءً على ما url
يلي: من
السهل جدًا استخدام هذا الرمز للحصول على ناشر معين ، إذا كانت بيانات المصدر url
، على سبيل المثال Endpoint
، ويتطلب الإخراج نماذج مختلفة - على سبيل المثال ، مجموعة من المقالات أو قائمة مصادر المعلومات.إذا كنت بحاجة إلى أخذ الأخطاء في الاعتبار ، Generic
فسيكون رمز "الناشر" أكثر تعقيدًا بعض الشيء ، ولكنه سيظل رمزًا بسيطًا للغاية بدون أي عمليات رد:
باستخدام تقنية تنفيذ HTTP
الاستعلام باستخدام Combine
، هل يمكنك إنشاء "ناشر" AnyPublisher<UIImage?, Never>
يختار البيانات بشكل غير متزامن ويتلقى صورة UIImage؟ على أساس URL
. يتم ImageLoade
تنزيل برامج تنزيل الصور مؤقتًا في الذاكرة لتجنب الاسترداد المتكرر للبيانات غير المتزامنة.يمكن بسهولة بسهولة "عمل" جميع أنواع "الناشرين" التي تم الحصول عليها في فئات ObservableObject ، التي تستخدم خصائصها المنشورة @ للتحكم في واجهة المستخدم الخاصة بك المصممة باستخدام SwiftUI. تلعب هذه الفئات عادةً دور نموذج العرض ، نظرًا لأنها تحتوي على ما يسمى بخصائص "الإدخال"Published التي تتوافق مع عناصر واجهة المستخدم النشطة (TextField ، Stepper ، مربعات النص Picker ، تبديل أزرار الراديو ، إلخ) و "الإخراج" @ الخصائص المنشورة ، تتكون بشكل رئيسي من عناصر واجهة المستخدم السلبية (النصوص النصية ، صور الصور ، الأشكال الهندسية (الدائرة) ، المستطيل () ، وما إلىذلك . تتخلل هذه الفكرة تطبيق مجمّع الأخبار NewsAPI.org بأكمله المقدم في هذه المقالة. عالمي جدا واستخدم عندماتطوير تطبيق ل TMDb فيلم قاعدة بيانات و أخبار هاكر مجمع الأخبار ، والتي سيتم مناقشتها في المواد المستقبل.رمز التطبيق لهذه المقالة على جيثب .PS1. أريد أن ألفت انتباهك إلى حقيقة أنه إذا كنت تستخدم المحاكي للتطبيق المقدم في هذه المقالة ، فاعلم أن NavigationLink
المحاكي يعمل مع وجود خطأ. يمكنك استخدامNavigationLink
على جهاز المحاكاة مرة واحدة فقط. أولئك. لقد استخدمت الرابط ، وعادت ، وانقر على نفس الرابط - ولن يحدث شيء. حتى تستخدم رابطًا آخر ، لن يعمل الأول ، ولكن لن يكون الثاني ممكنًا. ولكن هذا يلاحظ فقط على المحاكي ، على جهاز حقيقي كل شيء يعمل بشكل جيد.2. بعض مصادر المعلومات لا تزال تستخدم http
بدلا من ذلك من https
أجل "صور" مقالاتهم. إذا كنت تريد بالتأكيد رؤية هذه "الصور" ، ولكن لا يمكنك التحكم في مصدر ظهورها ، فيجب عليك تكوين نظام الأمان ATS ( App Transport Security)
لتلقي هذه http
"الصور" ، ولكن هذه بالطبع ليست فكرة جيدة . يمكنك استخدام خيارات أكثر أمانًا .المراجع:
HTTP Swift 5 Combine SwiftUI. 1 .Modern Networking in Swift 5 with URLSession, Combine and Codable.URLSession.DataTaskPublisher’s failure typeCombine: Asynchronous Programming with Swift«SwiftUI & Combine: »Introducing Combine — WWDC 2019 — Videos — Apple Developer. session 722( 722 « Combine» )Combine in Practice — WWDC 2019 — Videos — Apple Developer. session 721( 721 « Combine» )