تحسين تنزيل المحتوى بدون أختام



تسليم المحتوى بسرعة وعالية الجودة للمستخدمين هو أهم مهمة نعمل عليها باستمرار أثناء العمل على تطبيق iFunny. غياب عناصر الانتظار حتى مع ضعف الاتصال - هذا ما تسعى أي خدمة لعرض محتوى الوسائط إلى القيام به.

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

في هذه المقالة سوف أتحدث عن كيف يبدو الجلب المسبق في iFunny الآن وكيف تم أتمتة عملية البحث لمزيد من ضبط إعداداتها.

الجلب المسبق القياسي


في iOS 10 ، وفرت Apple القدرة على الجلب المسبق من العلبة. للقيام بذلك ، تحتوي فئة UICollectionView على حقل:

@property (nonatomic, weak, nullable) id<UICollectionViewDataSourcePrefetching> prefetchDataSource; @property (nonatomic, getter=isPrefetchingEnabled) BOOL prefetchingEnabled; 

لتمكين الجلب المسبق الأصلي ، ما عليك سوى تعيين حقل prefetchDataSource كائنًا ينفذ بروتوكول UICollectionViewDatasourcePrefetching وتعيين الحقل الثاني في YES.

لتنفيذ بروتوكول الجلب المسبق ، يجب وصف طريقتين:

 - (void)collectionView:(UICollectionView *)collectionView prefetchItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths; - (void)collectionView:(UICollectionView *)collectionView cancelPrefetchingForItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths; 

في الطريقة الأولى ، يمكنك القيام بأي عمل مفيد في إعداد المحتوى.

في حالة iFunny ، بدا الأمر كما يلي:

 NSMutableArray<NSURL *> *urls = [NSMutableArray new]; for (NSIndexPath *indexPath in indexPaths) { NSObject<IFFeedItemProtocol> *item = [self.model itemAtIndex:indexPath.row]; NSURL *downloadURL = item.downloadURL; if (downloadURL) { [urls addObject:downloadURL]; } } [self.downloadManager updateActiveURLs:urls]; [urls enumerateObjectsUsingBlock:^(NSURL *_Nonnull url, NSUInteger idx, BOOL *_Nonnull stop) { [self.downloadManager downloadContentWithURL:url.absoluteString forView:nil withOptions:0]; }]; 

الطريقة الثانية اختيارية ، ولكن في حالة شريط iFunny لم يتم استدعاؤها من قبل النظام على الإطلاق.

يعمل الجلب المسبق ، ولكننا استدعينا طريقة المحتوى الذي يتبع المحتوى النشط فقط.
بشكل عام ، يعتمد عمل الجلب المسبق القياسي لـ UICollectionView بشكل كبير على كيفية تنفيذ عرض المجموعة. بالإضافة إلى ذلك ، نظرًا لأننا لا نعرف على الإطلاق تنفيذ الجلب القياسي القياسي ، فمن المستحيل ضمان تشغيله المستقر. لذلك ، قمنا بتنفيذ آلية الجلب المسبق ، والتي عملت دائمًا كما نحتاج.

خوارزمية الجلب المسبق


قبل تطوير خوارزمية الجلب المسبق ، كتبنا جميع ميزات موجز iFunny:

  1. يمكن أن تتكون الخلاصة من أنواع مختلفة من المحتوى: الصور ومقاطع الفيديو وتطبيقات الويب والإعلانات الأصلية.
  2. يعمل الشريط مع ترقيم الصفحات.
  3. يقلب معظم المستخدمين الخلاصة للأمام فقط.
  4. في iFunny ، تحدث 20٪ من جلسات المستخدم من خلال LTE.

بناءً على هذه الشروط ، حصلنا على خوارزمية بسيطة:

  1. يوجد 1 عنصر نشط في الشريط ، وجميع العناصر الأخرى غير نشطة.
  2. يحتاج العنصر النشط دائمًا إلى تنزيل المحتوى حتى النهاية.
  3. لكل عنصر محتوى في الخلاصة وزنه الخاص.
  4. على اتصال الإنترنت الحالي ، يمكنك تحميل العناصر بمبلغ N.
  5. في كل مرة تقوم فيها بتمرير الشريط ، نقوم بتغيير العنصر النشط وحساب العناصر التي يتم تحميلها ، وإلغاء بقية التحميل.

تحتوي البنية في كود هذه الخوارزمية على عدة فئات أساسية وبروتوكول:

  • IFPrefetchedCollectionProtocol

 @protocol IFPrefetchedCollectionProtocol @property (nonatomic, readonly) NSUInteger prefetchItemsCount; - (NSObject<IFFeedItemProtocol> *)itemAtIndex:(NSInteger)index; @end 

هذا البروتوكول ضروري للحصول على معلمات المجموعة والمحتوى في كائنات الفئة:

  • IFContentPrefetcher

 @interface IFContentPrefetcher : NSObject @property (nonatomic, weak) NSObject<IFPrefetchedCollectionProtocol> *collection; @property (nonatomic, assign) NSInteger activeIndex; @end 

يطبق الفصل منطق خوارزمية الجلب المسبق للمحتوى:

  • IFPrefetchOperation

 @interface IFPrefetchOperation : NSObject @property (nonatomic, readonly) NSUInteger cost; - (void)fetchMinumumBuffer; - (void)fetchEntireBuffer; - (void)pause; - (void)cancel; - (BOOL)isEqualOperation:(IFPrefetchOperation *)object; @end 

هذه هي الفئة الأساسية للعملية الذرية ، التي تصف العمل المفيد للجلب المسبق لمحتوى معين وتشير إلى المعلمة - الوزن.

لتشغيل الخوارزمية ، قمنا بوصف عمليتين:

  1. الصورة. يبلغ وزنها 1. محملة بالكامل دائمًا ؛
  2. فيديو وزنه 2. الأحمال بشكل كامل فقط عندما تكون نشطة. في الحالة غير النشطة ، يتم تحميل أول 200 كيلوبايت.

كمقياس لتقييم تشغيل الخوارزمية ، اخترنا عدد مرات الدخول لعنصر واجهة مستخدم المُحمل لكل 1000 عنصر محتوى معروض.

في الجلب المسبق القياسي لهذا المقياس ، كان لدينا حوالي 30 مرة ظهور / 1000 عنصر. بعد إدخال الخوارزمية الجديدة ، انخفض هذا المقياس إلى 25 مرة ظهور / 1000 عنصر.

وبالتالي ، انخفض عدد مرات ظهور اللودر بنسبة 20٪ وزاد العدد الإجمالي للمحتوى الذي شاهده المستخدمون قليلاً.

ثم انتقلنا إلى اختيار المعلمات المثلى لـ Featured - الشريط الأكثر شعبية في iFunny.

اختيار معلمات الجلب المسبق


تحتوي خوارزمية الجلب المسبق المطورة على معلمات إدخال:

  1. التكلفة الإجمالية للتنزيل.
  2. تكلفة تحميل كل عنصر.

سنستمر في قياس عدد اللوادر.

كأدوات مساعدة لتبسيط جمع البيانات ، سنستخدم:

  1. اختبارات رمادية مع مجموعة من أطر KIF ، OHHTTPS.
  2. sh-scripts و xcodebuild لتشغيل الاختبارات بمعلمات مختلفة.
  3. ملف تعريف شبكة 3G متاح في إعداد Developer - Network Link Conditioner.

دعونا نرى كيف ساعدتنا كل من هذه الأدوات.

الاختبارات


لمحاكاة كيفية عرض المستخدمين للمحتوى ، قررنا استخدام إطار عمل KIF المألوف لمطوري iOS على Objective-C.

يعمل KIF بشكل رائع مع Objective-C و Swift ، بعد بعض التلاعبات السهلة الموصوفة في وثائق KIF:
https://github.com/kif-framework/KIF#use-with-swift

لاختبار الشريط ، اخترنا Objective-C ، بما في ذلك لكي نكون قادرين على استبدال الأساليب التي نحتاجها في خدمة التحليلات.

دعونا نلقي نظرة على رمز اختبار بسيط ، حصلنا عليه:

  - (void)setUp { [super setUp]; [self clearCache]; [[NSURLCache sharedURLCache] removeAllCachedResponses]; [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *_Nonnull request) { return [request.URL.absoluteString isEqualToString:@"http://fun.co/rp/?feed=featured&limit=30"]; } withStubResponse:^OHHTTPStubsResponse *_Nonnull(NSURLRequest *_Nonnull request) { NSString *path = OHPathForFile(@"featured.json", self.classForCoder); OHHTTPStubsResponse *response = [[OHHTTPStubsResponse alloc] initWithFileAtPath:path statusCode:200 headers:@{ @"Content-Type" : @"application/json" }]; return response; }]; } 

في طريقة إعداد الاختبار ، يجب علينا مسح ذاكرة التخزين المؤقت بحيث يتم تحميل المحتوى من كل عملية إطلاق من الشبكة ومسح مجلد Caches في التطبيق تمامًا.

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

  1. تحديد معلمات الاستعلام. بالنسبة لنا ، هذا هو عنوان URL لطلب الخلاصة المميزة لواجهة برمجة التطبيقات - http://fun.co/rp/؟feed=featured&limit=30
  2. سجل الجواب الضروري واحفظه في ملف ، وأرفق الهدف مع الاختبار.
  3. حدد خيارات الاستجابة. في الرمز أعلاه ، هذا هو رأس نوع المحتوى ورمز الاستجابة.
  4. تحقق من التعليمات الخاصة بأحواض OHHTTPS.

يمكنك قراءة المزيد حول العمل مع أحواض OHHTTPS في الوثائق:
http://cocoadocs.org/docsets/OHHTTPSPSs/

يبدو الاختبار نفسه كالتالي:

 - (void)testFeed { KIFUIViewTestActor *feed = [viewTester usingLabel:@"ScrolledFeed"]; [feed waitForView]; [self setupCustomPrefetchParams]; for (NSInteger i = 1; i <= 1000; i++) { [feed waitForCellInCollectionViewAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; [viewTester waitForTimeInterval:1.0f]; } [self appendStatisticLine]; } 

باستخدام KIF ، نحصل على خلاصة ثم ننتقل عبر 1000 عنصر محتوى مع الانتظار لمدة ثانية واحدة.

ستتم مناقشة طريقة setupCustomPrefetchParams بعد ذلك بقليل.

لتحديد عدد اللوادر المعروضة ، سنستخدم وقت تشغيل Objective-C واستبدال الطريقة من خدمة التحليلات بطريقة الاختبار:

 + (void)load { [self swizzleSelector:@selector(trackEventLoaderViewedVideo:) ofClass:[IFAnalyticService class]]; } + (void)swizzleSelector:(SEL)originalSelector ofClass:(Class) class { Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod([self class], originalSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, originalSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } - (void)trackEventLoaderViewedVideo : (BOOL)onVideo { if (onVideo) { [IFTestFeed trackLoaderOnVideo]; } else { [IFTestFeed trackLoaderOnImage]; } } 

لدينا الآن اختبار تلقائي حيث يتلقى التطبيق دائمًا نفس المحتوى ويمرر نفس العدد من العناصر. ووفقًا لنتائجها ، فإنها تكتب سطرًا مع إحصائيات التنفيذ إلى السجل.

نظرًا لأن الاتصال بالإنترنت يؤثر بشكل أساسي على تنزيل المحتوى ، يجب تكرار الاختبار باستخدام مجموعة واحدة من المعلمات أكثر من مرة.

أتمتة بدء التشغيل


لأتمتة وتمييز الاختبارات ، قررنا استخدام الإطلاق من خلال xcodebuild مع نقل المعلمات الضرورية.

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



للوصول إلى معلمة من رمز Objective-C ، يجب التصريح عن وحدتي ماكرو:

 #define STRINGIZE(x) #x #define BUILD_PARAM(x) STRINGIZE(x) 

الآن عند البدء من المحطة الطرفية باستخدام xcodebuild:

 xcodebuild test -workspace iFunny.xcworkspace -scheme iFunnyUITests -destination 'platform=iOS,id=DEVICE_ID' MAX_PREFETCH_COST="5" VIDEO_COST="2" IMAGE_COST="2" 

في الكود يمكنك قراءة المعلمات التي تم تمريرها:

 - (void)setupCustomPrefetchParams { NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; formatter.numberStyle = NSNumberFormatterNoStyle; [IFAppController instance].prefetchParams.goodNetMaxCost = [formatter numberFromString:@BUILD_PARAM(MAX_PREFETCH_COST)]; [IFAppController instance].prefetchParams.videoCost = [formatter numberFromString:@BUILD_PARAM(VIDEO_COST)]; [IFAppController instance].prefetchParams.imageCost = [formatter numberFromString:@BUILD_PARAM(IMAGE_COST)]; } 

الآن كل شيء جاهز لتشغيل هذه الاختبارات في وضع عدم الاتصال باستخدام البرامج النصية shell.

تشغيل xcodebuild مع مجموعة من المعلمات 10 مرات متتالية:

 max=10 for i in `seq 1 $max` do xcodebuild test -workspace iFunny.xcworkspace -scheme iFunnyUITests -destination 'platform=iOS,id=DEVICE_ID' MAX_PREFETCH_COST="$1" VIDEO_COST="$2" IMAGE_COST="$3" done 

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

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

حسب النتيجة


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

إليك ما سيساعد على إجراء مثل هذه الدراسات:

  • استخدام أطر الاختبار للأعمال الرتيبة.
  • الأتمتة عبر xcodebuild لتحديد معايير الشركات الناشئة.
  • Runtime Objective-C لتغيير المنطق الضروري ، حيثما أمكن.

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

ملاحظة: وفقًا لنتائج اختباراتنا ، فإن إعدادات الجلب المسبق الجديدة المتعلقة بخيار الإنتاج تفوز بنحو 8 ٪ ، في الواقع ، فقد حصلوا على انخفاض في عرض اللوادر بنسبة 3 ٪ ، مما يعني أننا بدأنا في توصيل الابتسامات إلى iFunny 3 ٪ أكثر من المعتاد :)

PPS: لن نتوقف عند هذا الحد ، وسنستمر في تحسين المحتوى الذي يتم جلبه مسبقًا.

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


All Articles