تصنيف nsoperation لتكون متزامنة وقابلة للإلغاء
-
27-09-2019 - |
سؤال
أنا غير قادر على العثور على وثائق جيدة حول كيفية الفئة الفرعية NSOperation
أن تكون متزامنة وأيضًا لدعم الإلغاء. قرأت مستندات Apple ، لكنني غير قادر على العثور على مثال "رسمي".
ها هو رمز المصدر الخاص بي:
@synthesize isExecuting = _isExecuting;
@synthesize isFinished = _isFinished;
@synthesize isCancelled = _isCancelled;
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
/* WHY SHOULD I PUT THIS ?
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
*/
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
else
{
NSLog(@"Operation started.");
sleep(1);
[self finish];
}
}
- (void)finish
{
NSLog(@"operationfinished.");
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
}
في المثال الذي وجدته ، لا أفهم لماذا يتم استخدام SelectionOnMainThread: يستخدم. من شأنه أن يمنع عملي من الركض في وقت واحد.
أيضًا ، عندما أعلق هذا الخط ، أقوم بتشغيل عملياتي بشكل متزامن. ومع ذلك ، فإن isCancelled
لم يتم تعديل العلم ، على الرغم من أنني اتصلت cancelAllOperations
.
المحلول
حسنًا ، كما أفهمها ، لديك سؤالان:
هل تحتاج
performSelectorOnMainThread:
الجزء الذي يظهر في التعليقات في الكود الخاص بك؟ ماذا يفعل هذا الرمز؟لماذا هو
_isCancelled
لا يتم تعديل العلم عند الاتصالcancelAllOperations
على الNSOperationQueue
التي تحتوي على هذه العملية؟
دعونا نتعامل مع هذه بالترتيب. سأفترض أن فئتك الفرعية NSOperation
يسمى MyOperation
, ، فقط لسهولة التفسير. سأشرح ما سوء الفهم ثم أعطي مثالًا مصححًا.
1. تشغيل NSOPERations بشكل متزامن
معظم الوقت ، ستستخدم NSOperation
S مع NSOperationQueue
, ، ومن التعليمات البرمجية الخاصة بك ، يبدو أن هذا ما تفعله. في هذه الحالة ، الخاص بك MyOperation
سيتم تشغيله دائمًا على موضوع خلفية ، بغض النظر عن ما -(BOOL)isConcurrent
تعود الطريقة منذ ذلك الحين NSOperationQueue
تم تصميم S بشكل صريح لتشغيل العمليات في الخلفية.
على هذا النحو ، لا تحتاج عمومًا إلى تجاوز -[NSOperation start]
الطريقة ، لأنه افتراضيًا ، فإنه يستدعي ببساطة -main
طريقة. هذه هي الطريقة التي يجب أن تكون فيها تجاوز. الافتراضي -start
الطريقة تتعامل بالفعل مع الإعداد isExecuting
و isFinished
لك في الأوقات المناسبة.
لذلك إذا كنت تريد NSOperation
لتشغيل في الخلفية ، ببساطة تجاوز -main
الطريقة ووضعها على NSOperationQueue
.
ال performSelectorOnMainThread:
في الكود الخاص بك سوف يتسبب في كل مثيل MyOperation
لأداء مهمتها دائمًا على الموضوع الرئيسي. نظرًا لأنه يمكن تشغيل قطعة واحدة فقط من الكود على مؤشر ترابط في وقت واحد ، فهذا يعني أنه لا يوجد آخر MyOperation
S يمكن أن يكون الجري. الغرض كله من NSOperation
و NSOperationQueue
هو القيام بشيء في الخلفية.
المرة الوحيدة التي ترغب في فرض الأشياء على الموضوع الرئيسي هي عندما تقوم بتحديث واجهة المستخدم. إذا كنت بحاجة إلى تحديث واجهة المستخدم عند MyOperation
التشطيبات ، الذي - التي هو متى يجب أن تستخدم performSelectorOnMainThread:
. سأوضح كيفية القيام بذلك في مثالي أدناه.
2. إلغاء nsoperation
-[NSOperationQueue cancelAllOperations]
يدعو -[NSOperation cancel]
الطريقة التي تسبب مكالمات لاحقة -[NSOperation isCancelled]
لكي ترجع YES
. لكن, ، لقد فعلت شيئين لجعل هذا غير فعال.
انت تستخدم
@synthesize isCancelled
لتجاوز nsoperation-isCancelled
طريقة. لا يوجد سبب للقيام بذلك.NSOperation
تنفذ بالفعل-isCancelled
بطريقة مقبولة تماما.أنت تتحقق
_isCancelled
متغير مثيل لتحديد ما إذا كانت العملية قد تم إلغاؤها.NSOperation
يضمن ذلك[self isCancelled]
سيعودYES
إذا تم إلغاء العملية. نعم هو كذلك ليس ضمان أنه سيتم استدعاء طريقة Setter المخصصة ، ولا أن متغير المثيل الخاص بك محدث. يجب عليك التحقق[self isCancelled]
ماذا يجب أن تفعل
رأس:
// MyOperation.h
@interface MyOperation : NSOperation {
}
@end
والتنفيذ:
// MyOperation.m
@implementation MyOperation
- (void)main {
if ([self isCancelled]) {
NSLog(@"** operation cancelled **");
}
// Do some work here
NSLog(@"Working... working....")
if ([self isCancelled]) {
NSLog(@"** operation cancelled **");
}
// Do any clean-up work here...
// If you need to update some UI when the operation is complete, do this:
[self performSelectorOnMainThread:@selector(updateButton) withObject:nil waitUntilDone:NO];
NSLog(@"Operation finished");
}
- (void)updateButton {
// Update the button here
}
@end
لاحظ أنك لا تحتاج إلى فعل أي شيء isExecuting
, isCancelled
, ، أو isFinished
. يتم التعامل معها تلقائيًا من أجلك. ببساطة تجاوز -main
طريقة. انه من السهل.
(ملاحظة: من الناحية الفنية ، هذا ليس "متزامنًا" NSOperation
, ، بمعنى أن -[MyOperation isConcurrent]
سيعود NO
كما تم تنفيذها أعلاه. ومع ذلك ، هو إرادة يتم تشغيلها على موضوع خلفية. ال isConcurrent
يجب تسمية الطريقة حقًا -willCreateOwnThread
, ، لأن هذا هو وصف أكثر دقة لقصد الطريقة.)
نصائح أخرى
الجواب الممتاز لـ @bjhomer تستحق تحديثًا.
يجب أن تتجاوز العمليات المتزامنة start
الطريقة بدلا من main
.
كما ذكر في وثائق Apple:
إذا كنت تقوم بإنشاء عملية متزامنة ، فأنت بحاجة إلى تجاوز الأساليب والخصائص التالية كحد أدنى:
start
asynchronous
executing
finished
التنفيذ المناسب أيضا يستوجب لتجاوز cancel
كذلك. صنع فئة فرعية آمن الخيط والحصول على الدلالات المطلوبة بشكل صحيح أيضا أمر صعب للغاية.
وبالتالي ، لقد وضعت فئة فرعية كاملة وعمل كملف الاقتراح المنفذ في Swift في مراجعة الكود. التعليقات والاقتراح موضع ترحيب.
يمكن استخدام هذه الفئة بسهولة كفئة أساسية لفئة التشغيل المخصصة.
أعلم أن هذا سؤال قديم ، لكنني كنت أقوم بالتحقيق في هذا مؤخرًا وواجهت نفس الأمثلة ولدي الشكوك نفسها.
إذا كان من الممكن تشغيل كل عملك بشكل متزامن داخل الطريقة الرئيسية ، فلن تحتاج إلى عملية متزامنة ، ولا بداية مبتكرة ، فقط قم بعملك والعودة من الرئيسية عند الانتهاء.
ومع ذلك ، إذا كان عبء العمل الخاص بك غير متزامن بطبيعته - أي تحميل nsurlconnection ، فيجب أن تبدأ الفئة الفرعية. عند عودة طريقة البدء الخاصة بك ، لم تنتهي العملية بعد. لن يتم اعتباره منتهيًا إلا من قبل NSOperationQueue عندما ترسل إخطارات KVO يدويًا إلى أعلام isfinishized و isexecuting (على سبيل المثال ، بمجرد الانتهاء من تحميل URL Async).
أخيرًا ، قد يرغب المرء في إرسال البداية إلى مؤشر الترابط الرئيسي عندما يتطلب عبء عمل Async الذي تريد البدء فيه استمع إلى حلقة التشغيل على الخيط الرئيسي. نظرًا لأن العمل نفسه غير متزامن ، فإنه لن يحد من تزامنك ، ولكن قد لا يكون لبدء العمل في مؤشر ترابط العامل جاهزًا.
ألق نظرة على asihtprequest. إنها فئة Wrapper HTTP مبنية على رأسها NSOperation
كطابق فرعي ويبدو أن تنفيذ هذه. لاحظ أنه اعتبارًا من منتصف عام 2011 ، يوصي المطور بعدم استخدام ASI للمشاريع الجديدة.
بخصوص تعريف "ألغيت"خاصية (أو تعريف"_ألغيت"IVAR) داخل الفئة الفرعية NSOPeration ، عادةً ما يكون ذلك ضروريًا. وذلك ببساطة لأنه عندما يقوم المستخدم بإلغاء ، يجب على الرمز المخصص دائمًا إخطار مراقبي KVO بأن عمليتك الآن تم الانتهاء من مع عملها. بعبارات أخرى، iscancelled => isfinished.
على وجه الخصوص ، عندما يعتمد كائن NSOPeration على الانتهاء من كائنات التشغيل الأخرى ، فإنه يراقب المسار الرئيسي المسموح به لتلك الكائنات. عدم توليد إشعار النهاية (في حالة الإلغاء) وبالتالي يمكن أن تمنع تنفيذ العمليات الأخرى في التطبيق الخاص بك.
راجع للشغل ، إجابة هوميروس: "يجب أن تكون طريقة isConcurrent حقًا اسمه -willcreateWrathread "منطقي كثيرًا!
لأنه إذا لم تقم بتجاوز طريقة بدء التشغيل ، فما عليك سوى الاتصال يدويًا بالتشغيل الافتراضي لـ NSOPeration-Object ، فإن الاتصال نفسه هو ، بشكل افتراضي ، متزامن ؛ لذلك ، فإن nsoperation-object ليست سوى عملية غير متطورة.
ومع ذلك ، إذا قمت بتجاوز طريقة البدء ، فإن تنفيذ Method Inside Inside ، رمز مخصص يجب أن تفرخ خيط منفصل... إلخ ، ثم تقوم بنجاح بخرق تقييد "التزامن المتزامن" ، مما يجعل الكائنات غير المتزامنة ، يمكن أن تعمل بشكل غير متزامن بعد ذلك.
منشور المدونة هذا:
http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/
يشرح لماذا قد تحتاج:
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
في الخاص بك start
طريقة.