دائرة الرقابة الداخلية. التواصل عندما لا يكون التطبيق قيد التشغيل

صورة


يتوقع المستخدمون أن تعمل الشبكة "بطريقة سحرية" دون أن يلاحظها أحد. هذا السحر يعتمد على مطوري النظام والتطبيقات. من الصعب التأثير على النظام ، وبالتالي سنقتصر على التطبيق.


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


بادئ ذي بدء ، دعونا نتعامل مع المصطلحات.


يحدث نقل البيانات في اتجاهين:


  • تنزيل (تنزيل ، تنزيل البيانات من الخادم) ،
  • تحميل (إرسال البيانات إلى الخادم).

قد يكون التطبيق نشطًا ، لكنه قد يعمل في الخلفية. رسميا ، لديه دول أخرى ، لكننا مهتمون فقط بهذه:


  • الخلفية (عندما يتم تصغير التطبيق) ،
  • نشط (عندما يكون التطبيق نشطًا ، على الشاشة).

أنماط مفيدة: رد الاتصال ، مندوب ( أنماط تصميم الكاكاو ، حول رد الاتصال على ويكيبيديا ). تحتاج أيضًا إلى معرفة URLSession (في المقالة ، يشير الرابط أيضًا إلى العمل في الخلفية مع الشبكة ، ولكن بشكل عابر).


تتم كتابة جميع الأمثلة في Swift 5 ، والعمل على iOS 11 والإصدارات الأحدث (تم اختبارها في iOS 11 و 12) وتفترض استخدام طلبات HTTP المعتادة. بالنسبة للجزء الأكبر ، كل هذا سينجح ، بدءًا من iOS 9 ، ولكن هناك "فروق دقيقة".


المخطط العام للعمل مع الشبكة. URLSession


العمل مع الشبكة ليس بالأمر الصعب:


  • إنشاء تكوين URLSessionConfiguration ؛
  • إنشاء مثيل تكوين URLSession ؛
  • إنشاء مهمة (باستخدام session.dataTask(…) وأساليب مماثلة) ؛
  • الاشتراك في تحديثات المهمة. تأتي التحديثات بشكل غير متزامن ، ويمكن أن تأتي إلى المفوض ، الذي يتم تسجيله عند إنشاء الجلسة ، أو يمكن أن يكون في رد الاتصال ، الذي يتم إنشاؤه عند إنشاء المهمة ؛
  • عندما رأينا أن المهمة مكتملة ، نعود إلى منطق التطبيق.

مثال بسيط يشبه هذا:


 let session = URLSession(configuration: .default) let url = URL(...) let dataTask = session.dataTask(with: url) { data, response, error in ... //     //  callback,    } 

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


ألاحظ على الفور أنه حتى في هذا السيناريو ، هناك الكثير من الأشياء المثيرة للاهتمام. في بعض الأحيان ، تحتاج إلى العمل مع عمليات إعادة التوجيه الصعبة ، أو في بعض الأحيان تحتاج إلى ترخيص أو تثبيت طبقة مآخذ توصيل آمنة (SSL) أو جميعها دفعة واحدة. يمكنك أن تقرأ الكثير عن هذا. لسبب ما ، وصف العمل مع الشبكة في حالة الخلفية أقل بكثير.

إنشاء جلسة للعمل في الخلفية


ما هو الفرق بين URLSession الخلفية والمعتاد؟ إنه يعمل خارج عملية التطبيق ، في مكان ما داخل النظام. لذلك ، لا "يموت" عند اكتمال عملية التطبيق. يطلق عليه جلسة خلفية (وكذلك حالة التطبيق ، وهو أمر مربك بعض الشيء) ويتطلب إعدادات محددة. على سبيل المثال ، هذا:


 let configuration = URLSessionConfiguration.background(withIdentifier: "com.my.app") configuration.sessionSendsLaunchEvents = true configuration.isDiscretionary = true configuration.allowsCellularAccess = true configuration.shouldUseExtendedBackgroundIdleMode = true configuration.waitsForConnectivity = true URLSession(configuration: configuration, delegate: self, delegateQueue: nil) 

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


  • المُعرّف (الذي تم تمريره في المُهيئ) عبارة عن سلسلة تُستخدم لمطابقة جلسات الخلفية عند إعادة تشغيل التطبيق. إذا تم إعادة تشغيل التطبيق وقمت بإنشاء جلسة خلفية باستخدام معرف مستخدم بالفعل في جلسة خلفية أخرى ، فسيتمكن المستخدم الجديد من الوصول إلى مهام الجلسة السابقة. الاستنتاج من هذا بسيط. للتشغيل الصحيح ، يجب أن يكون هذا المعرف فريدًا بالنسبة للتطبيق الخاص بك ودائمًا (يمكنك استخدام ، على سبيل المثال ، مشتقًا من تطبيقات bundleId ) ؛
  • يشير sessionSendsLaunchEvents إلى ما إذا كان يجب بدء جلسة الخلفية التطبيق عند اكتمال نقل البيانات. إذا تم تعيين هذه المعلمة على " false, فلن يحدث المشغل ، وسيستقبل التطبيق جميع الأحداث في المرة التالية التي يتم فيها تشغيل نفسه. إذا كانت المعلمة true ، فبعد اكتمال نقل البيانات ، سيقوم النظام ببدء تشغيل التطبيق واستدعاء طريقة AppDelegate: application(_:handleEventsForBackgroundURLSession:completionHandler:) المقابلة AppDelegate: application(_:handleEventsForBackgroundURLSession:completionHandler:) ؛
  • يتيح isDiscretionary للنظام جدولة المهام بشكل نادر. هذا ، من ناحية ، يحسن عمر البطارية ، ومن ناحية أخرى ، يمكن أن يبطئ المهمة. أو ربما تسريعها. على سبيل المثال ، إذا تم تنزيل وحدة تخزين كبيرة ، فسيكون النظام قادرًا على إيقاف المهمة مؤقتًا حتى يتم الاتصال بشبكة WiFi ، ثم يقوم بتنزيل كل شيء بسرعة دون إنفاق الإنترنت المحمول البطيء (إذا كان مسموحًا به على الإطلاق ، فما هو التالي). إذا تم إنشاء المهمة عندما يكون التطبيق في الخلفية بالفعل ، فسيتم تعيين هذه المعلمة تلقائيًا على true ؛
  • allowCellularAccess - معلمة توضح أنه يمكنك استخدام الاتصالات الخلوية للعمل مع الشبكة. لم ألعب معه جيدًا ، لكن وفقًا للمراجعات ، هناك (جنبًا إلى جنب مع تبديل نظام مماثل) وضعت عددًا كبيرًا من المكابس ؛
  • shouldUseExtendedBackgroundIdleMode. معلمة مفيدة توضح أنه يجب على النظام الحفاظ على اتصال بالخادم لفترة أطول عندما ينتقل التطبيق إلى الخلفية. خلاف ذلك ، سيتم قطع الاتصال.
  • waitsForConnectivity في جهاز محمول ، قد تختفي الاتصالات لفترات زمنية قصيرة. يمكن تعليق المهام التي تم إنشاؤها في هذه اللحظة حتى يظهر الاتصال ، أو إرجاع خطأ "عدم الاتصال" على الفور. تسمح لك المعلمة بالتحكم في هذا السلوك. إذا كانت false, في حالة عدم الاتصال ، ستنقطع المهمة على الفور مع وجود خطأ. إذا كان هذا true ، فانتظر حتى يظهر الرابط.
  • السطر الأخير (مهيئ الجلسة) يحتوي على معلمة مهمة ، المفوض. عنه - أكثر من ذلك بقليل.

مندوب مقابل Callbacks


كما قلت أعلاه ، هناك طريقتان للحصول على الأحداث من مهمة / من جلسة. الأول هو رد الاتصال:


 session.dataTask(with: request) { data, response, error in ...   } 

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


الخيار الثاني للعمل مع جلسة هو من خلال المفوض. في هذه الحالة ، يجب علينا إنشاء فئة تنفذ بروتوكولات URLSessionDataDelegate و (أو) أخرى قريبة (بالنسبة للأنواع المختلفة من المهام ، تكون البروتوكولات مختلفة قليلاً). تعيش الإشارة إلى مثيل من هذه الفئة في جلسة ، ويتم استدعاء أساليبها عند تمرير الأحداث إلى المفوض. يمكن تسجيل الرابط في الجلسة بواسطة المُهيئ. في المثال ، self.


 URLSession(configuration: configuration, delegate: self, delegateQueue: nil) 

للدورات العادية ، تتوفر كلتا الطريقتين. لا يمكن استخدام جلسات الخلفية إلا بواسطة المفوض.


لذلك ، قمنا بإعداد الجلسة ، وقمنا بإنشائها ، دعونا ننظر في كيفية تنزيل شيء ما.


مخطط عام لتنزيل البيانات في الخلفية


لتنزيل البيانات ، تحتاج عادةً إلى تكوين (URLRequest) ، وتسجيل المعلمات / الرؤوس / البيانات اللازمة فيه ، وإنشاء URLSessionDownloadTask وتشغيله للتنفيذ. شيء مثل هذا:


 var request = URLRequest(...) //  request,   let task = session.downloadTask(with: request) if #available(iOS 11, *) { task.countOfBytesClientExpectsToSend = [approximate size of request] task.countOfBytesClientExpectsToReceive = [approximate size of response] } task.resume() 

في هذه المرحلة ، لا يوجد شيء مختلف عن مهمة التنزيل المعتادة. صحيح ، ظهرت معلمتان countOfBytesClientExpectsToSend / countOfBytesClientExpectsToReceive ، فإنها تظهر مقدار البيانات التي نخطط لإرسالها في الطلب والعودة مرة أخرى في الاستجابة. يعد ذلك ضروريًا حتى يتمكن النظام من تخطيط العمل بالمهمة بشكل صحيح ، والتنزيل بشكل أسرع ، دون الحاجة إلى العمل الزائد. هذه القيم لا يجب أن تكون دقيقة.


بعد resume() ستنتقل المهمة إلى التنفيذ. أثناء نقل البيانات ، سيتم إحراز تقدم (حول هذا الموضوع - اقرأ أدناه ، وهناك أيضًا خيارات هناك) ، وبعد الانتهاء من تنفيذ العديد من طرق التفويض. من بينها ، هناك واحد مهم جدا:


 urlSession(_:downloadTask:didFinishDownloadingTo:) 

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


بعد هذه الطريقة المهمة ، سيتم استدعاء طريقة أخرى حيث يسقط الخطأ في حالة حدوثه. إذا لم يكن هناك error ، فسيكون error nil.


 urlSession(_:task:didCompleteWithError:) 

وماذا يحدث في النهاية ، إذا ذهب التطبيق إلى الخلفية أو اكتمل؟ كيفية استدعاء أساليب مندوب؟ انها ليست سهلة هنا.


في حالة انتهاء تنزيل شيء تم تشغيله بواسطة التطبيق وكانت علامة sessionSendsLaunchEvents في تكوين الجلسة ، سيقوم النظام بتشغيل التطبيق (في الخلفية) واستدعاء التطبيق (_: handleEventsForBackgroundURLSession: completHandler :) method في AppDelegate, .


في هذه الطريقة ، يجب على التطبيق:


  • حفظ completionHandler (سيحتاج إلى استدعاء بعد مرور بعض الوقت ، بشكل غير متزامن وفي السلسلة الرئيسية) ؛
  • إعادة إنشاء جلسة خلفية بنفس المعرف كما كان من قبل (والتي يتم تمريرها إلى هذه الطريقة في حالة وجود عدة جلسات خلفية) ؛
  • في جلسة تم إنشاؤها حديثًا ، ستصل الأحداث إلى المفوض (على وجه الخصوص ، urlSession(_:downloadTask:didFinishDownloadingTo:) المهم للغاية urlSession(_:downloadTask:didFinishDownloadingTo:) ) ، تحتاج إلى معالجتها ، وانسخ الملفات التي تريدها ؛
  • بعد أن يتم استدعاء جميع الأساليب ، يتم استدعاء طريقة تفويض أخرى ، والتي تسمى urlSessionDidFinishEvents(forBackgroundURLSession:) والتي سيتعين عليك استدعاء completionHandler. urlSessionDidFinishEvents(forBackgroundURLSession:) التي تم تخزينها مسبقًا completionHandler.

هذا مهم. من الضروري استدعاء completionHandler في مؤشر الترابط الرئيسي باستخدام DispatchQueue.main.async(...) .

في الوقت نفسه ، عليك أن تتذكر أن كل هذا يحدث في تطبيق يعمل في الخلفية. وهذا يعني أن الموارد (وقت التنفيذ) محدودة. قم بحفظ الملفات بسرعة حيث تريد ، وقم بتغيير الحالات اللازمة في التطبيق وإيقاف التشغيل - هذا هو كل ما يمكن القيام به. إذا كنت تريد عمل المزيد ، فيمكنك استخدام UIApplication.beginBackgroundTask() أو BackgroundTasks الجديد.


مخطط إرسال بيانات الخلفية العامة


تحميل الملفات إلى الخادم يعمل أيضًا مع القيود. ومع ذلك ، كل شيء يبدأ بطريقة مماثلة: نقوم بتكوين طلب ، وإنشاء مهمة (الآن سيكون URLSessionUploadTask) ، قم بتشغيل المهمة. ما هي المشكلة؟


المشكلة هي كيف نقوم بإنشاء الطلب. عادة ما نقوم بتكوين البيانات المرسلة كبيانات. URLSession, الخلفية URLSession, لا يعرف كيفية التعامل مع هذا. ومع طلب الدفق ( uploadTask(withStreamedRequest:) ) أيضًا لا يعرف كيف. من الضروري كتابة كل ما يلزم إرساله إلى ملف ، وإنشاء مهمة إرسال من الملف. اتضح بطريقة ما مثل هذا:


 var fileUrl = methodThatSavesFileAndRetursItsUrl(...) var request = URLRequest(...) let task = session.uploadTask(with: request, fromFile: fileUrl) task.resume() 

ولكن ليست هناك حاجة لتسجيل الحجم ، يمكن لـ URLSession النظر إليه بنفسه. بعد الإرسال ، يتم urlSession(_:task:didCompleteWithError:) بنفس طريقة المفوض urlSession(_:task:didCompleteWithError:) عند التنزيل. ومثل هذا ، إذا قُتل التطبيق أو دخل إلى الخلفية أثناء عملية الإرسال ، application(_:handleEventsForBackgroundURLSession:completionHandler:), الذي يجب معالجته وفقًا لنفس القواعد تمامًا كما هو الحال عند تنزيل البيانات.


ما هو تطبيق كامل؟


لاختبار تنزيلات الخلفية وعمليات الإرسال ، تحتاج إلى محاكاة إكمال التطبيق (تم تصميم خلفية العمل مع الشبكة خصيصًا للبقاء على قيد الحياة). كيف نفعل ذلك؟ في البداية - بأي حال من الأحوال. أي أنه لا توجد طريقة عادية (مرخصة ، عامة) تسمح بذلك. دعونا نرى أين هو أشعل النار.


  • أولاً ، لن يعمل مجرد إغلاق التطبيق (عن طريق الضغط على زر الصفحة الرئيسية أو عن طريق لفتة مناسبة). لن يقتل هذا التطبيق ، ولكن فقط إرساله إلى الخلفية. معنى العمل مع جلسة خلفية هو أنه يعمل حتى إذا تم قتل التطبيق "بالكامل ، تمامًا" ؛
  • ثانياً ، من المستحيل توصيل مصحح أخطاء (AppCode أو Xcode أو LLDB فقط) ، ولن يترك التطبيق يموت حتى في وقت ما بعد "إغلاقه" ؛
  • ثالثًا ، لا يمكنك قتل التطبيق من شريط المهام (مدير المهام ، الصفحة الرئيسية المزدوجة أو التمرير البطيء "لأعلى"). وبالتالي ، يعتبر التطبيق الذي تم قتله يتم قتل "نهائيًا" ، ويتوقف النظام ، إلى جانب هذا الإجراء ، عن جلسات الخلفية المرتبطة بالتطبيق ؛
  • رابعا ، تحتاج إلى اختبار هذه العملية على جهاز حقيقي. لا توجد مشاكل في التسجيل (انظر أدناه) وهي تصحيح أكثر. يقال أن محاكاة يجب أن تعمل أيضا كما يجب. لكنني لاحظت الشذوذ لا يمكن تفسيره أنني لا أستطيع أن أشرح مع أي شيء سوى مواطن الخلل محاكاة. بشكل عام ، اختبار على الجهاز ؛
  • الطريقة الوحيدة المعقولة لعمل ما تريده هي مع وظيفة exit(int) . كما يعلم الجميع ، لا يمكنك تحميله على الخادم ( هذا يتعارض بشكل مباشر مع المتطلبات ) ، لكننا في الوقت الحالي نقوم باختبار - ليس مخيفًا. أعرف خيارين معقولين لاستخدام هذه الوظيفة:
    • نسميها تلقائيًا في AppDelegate.applicationDidEnterBackground(_:) طريقة بحيث يتم قتل التطبيق مباشرة بعد الخروج إلى Springboard ؛
    • قم بعمل مكون في الواجهة (على سبيل المثال ، زر ، أو تعليق إجراء على لفتة) ، من خلال النقر على أي ، exit(...).
      في هذه الحالة ، سيتم قتل التطبيق ، ويجب أن يستمر العمل في الخلفية مع الشبكة. وبعد مرور بعض الوقت ، يجب أن نحصل على مكالمة application(_:handleEventsForBackgroundURLSession:completionHandler:).

كيفية تسجيل التطبيق إذا لم تتمكن من استخدام وحدة تحكم تصحيح Xcode؟


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


يمكنك استخدام البروتوكولات (سجلات ، سجلات) . هناك العديد من الخيارات لتنفيذها:


  • print. غالبًا ما يستخدم كـ "هيا بنا نخرج بسرعة". في حالتنا ، من المستحيل الاستخدام ، نظرًا لأنه ليس لدينا إمكانية الوصول إلى وحدة التحكم على الجهاز ، يتم قتل التطبيق.
  • NSLog. ستعمل ، لأنه يستخدم الطريقة الثالثة.
  • os_log. الطريقة الصحيحة التي تسمح لك بتكوين السجلات بشكل صحيح ، وتثبيتها بالنوع المرغوب ، وتعطيلها بعد تصحيح الأخطاء ، دون قص الشفرة نفسها ، وما إلى ذلك.

تحذير! مع os_log توجد مشكلات (على سبيل المثال ، عدم وجود سجلات تصحيح الأخطاء) يتم تشغيلها فقط في جهاز المحاكاة ، ولكن لا يتم تشغيلها على هذا الجهاز. استخدم الجهاز

كيفية استخدام os_log, اقرأ كيفية تكوينه بشكل صحيح في وثائق Apple . على وجه الخصوص ، يجب عليك تمكين سجلات debug info ، وتكون مخفية بشكل افتراضي.


تتبع التقدم المحرز في تحميل أو إرسال البيانات


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


  • للإرسال ، تحتاج إلى استخدام urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)
  • هناك طريقة urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:) مماثلة للتحميل urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)

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


الطريقة الثانية هي أكثر إثارة للاهتمام. الحقيقة هي أن كل مهمة توفر كائنًا من النوع Progress (يكمن في الحقل task.progress ) ، والذي يوفر القدرة على مراقبة عملية تعسفية ، بما في ذلك عملية نقل البيانات. كيف هو مثير للاهتمام؟ شيئين:


  • من كائنات Progress يمكنك إنشاء شجرة تنفيذ مهمة ، حيث ستظهر كل عقدة مدى تقدم كافة المهام التي تحتوي عليها. على سبيل المثال ، إذا كنت بحاجة إلى إرسال خمسة ملفات ، فيمكنك التقدم لكل ملف ، وإحراز تقدم عام ، وإضافة خمسة ملفات أخرى إليه ، ومراقبة تقدم أحد الوالدين ، وربط تحديثاته ببعض عناصر الواجهة ؛
  • يمكنك إضافة تقدمك إلى هذه الشجرة ، ويمكنك أيضًا إيقاف الإجراءات المرتبطة بالتقدم المُضاف وإيقافها مؤقتًا.

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


مهام "نقل" من جلسة عادية إلى جلسة خلفية


حسنًا ، من الأصعب العمل مع جلسة خلفية. لكن هذا مريح! لن تضيع مهمة واحدة ، هل سنحصل على جميع البيانات التي طلبناها ، لماذا لا نستخدم دائمًا جلسة الخلفية؟


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


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


إذا لم يكن هناك فهم واضح لضرورة تنفيذ المهمة في جلسة الخلفية (على سبيل المثال ، النقل غير الحرج لكمية كبيرة جدًا من البيانات ، مثل المزامنة أو النسخ الاحتياطي) ، فإن الأمر يستحق القيام بما يلي:


  • بدء المهمة في جلسة عادية. في هذه الحالة ، قم بتشغيل backgroundTask بحيث يفهم النظام أننا بحاجة إلى وقت لإكمال المهمة. يمنح هذا بعض الوقت (حتى عدة دقائق ، ولكن تم كسر شيء ما في نظام التشغيل iOS 13 وليس من الواضح ما يحدث به) حتى يمكن إكمال المهمة.
  • إذا لم يكن لديك وقت ، فعند نهاية الخلفية ، ننقل المهمة من جلسة عادية إلى جلسة خلفية ، حيث تستمر في العمل ، وتنتهي عندما تستطيع.

كيف تنقل؟ لا مفر ما عليك سوى قتل (إلغاء) المهمة المعتادة ، وإنشاء خلفية مماثلة (بنفس الطلب). لماذا يسمى هذا "نقل"؟ ولماذا في علامات الاقتباس؟


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


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


إليك نموذج التعليمات البرمجية الذي يقوم بذلك:


 let request = URLRequest(url: url) let task = foregroundSession.downloadTask(with: request) let backgroundId = UIApplication.shared.beginBackgroundTask { task.cancel() let task = backgroundSession.downloadTask(with: request) task.resume() } task.resume() 

في حالة انتهاء المهمة قبل expirationHandler ، يجب أن تتذكر استدعاء UIApplication.shared.endBackgroundTask(backgroundId) . ويرد هذا بمزيد من التفصيل في الوثائق .


لمساعدة النظام على مواصلة التنزيل (على سبيل المثال ، يمكن أن يؤدي الإلغاء إلى حذف الملف المؤقت قبل استئناف التنزيل في الخلفية) ، هناك طرق خاصة:



 let request = URLRequest(url: url) let task = foregroundSession.downloadTask(with: request) let backgroundId = UIApplication.shared.beginBackgroundTask { task.cancel { data in let task: URLSessionDownloadTask if let data = data { task = backgroundSession.downloadTask(withResumeData: data) } else { task = backgroundSession.downloadTask(with: request) } task.resume() } } 

,


السجلات


— , . — , . background , .


, , background -, , , ( UI, ). , , — . , — , , os_log. ( NSLog)


-


- , . , - . , , , ( ) . , , -, , . — — , . — , - ( ), , .



. ( ), . , , , .


قيود


:


  • , ;
  • — , ;
  • , (, …);


  • , (task.taskIdentifier) , (Dictionary). , 1, .
  • , URLSession.getAllTasks . , background . , . , . ¯\_(ツ)_/¯
  • , , , , .

, background , . , - . : https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1 . , :


If your app extension initiates a background NSURLSession task, you must also set up a shared container that both the extension and its containing app can access. Use the sharedContainerIdentifier property of the NSURLSessionConfiguration class to specify an identifier for the shared container so that you can access it later.

Source: https://habr.com/ru/post/ar478566/


All Articles