
Pengiriman konten yang cepat dan berkualitas tinggi kepada pengguna adalah tugas paling penting yang terus kami kerjakan saat bekerja pada aplikasi iFunny. Tidak adanya elemen tunggu bahkan dengan koneksi yang buruk - inilah yang ingin dilakukan oleh layanan apa pun untuk melihat konten media.
Kami memiliki beberapa iterasi untuk bekerja dengan prefetch konten. Di setiap versi utama baru, kami menemukan sesuatu yang baru dan menyaksikan cara kerjanya untuk pengguna. Dalam iterasi berikutnya dari bekerja dengan prefetching, diputuskan untuk terlebih dahulu men-debug metrik yang mempengaruhi di stand lokal, dan hanya kemudian memberikan hasilnya kepada pengguna.
Pada artikel ini saya akan berbicara tentang apa yang tampak seperti prefetching di iFunny sekarang dan bagaimana proses penelitian terotomatisasi untuk lebih lanjut menyetel pengaturannya.
Pengambilan standar
Di iOS 10, Apple memberikan kemampuan untuk menjalankan prefetching out of the box. Untuk melakukan ini, kelas UICollectionView memiliki bidang:
@property (nonatomic, weak, nullable) id<UICollectionViewDataSourcePrefetching> prefetchDataSource; @property (nonatomic, getter=isPrefetchingEnabled) BOOL prefetchingEnabled;
Untuk mengaktifkan prefetching asli, tetapkan saja bidang prefetchDataSource objek yang mengimplementasikan protokol UICollectionViewDatasourcePrefetching dan setel bidang kedua dalam YES.
Untuk mengimplementasikan protokol prefetching, dua metode harus dijelaskan:
- (void)collectionView:(UICollectionView *)collectionView prefetchItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths; - (void)collectionView:(UICollectionView *)collectionView cancelPrefetchingForItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
Pada metode pertama, Anda dapat melakukan pekerjaan apa pun yang bermanfaat pada persiapan konten.
Dalam kasus iFunny, tampilannya seperti ini:
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]; }];
Metode kedua adalah opsional, tetapi dalam kasus rekaman iFunny itu tidak dipanggil oleh sistem sama sekali.
Prefetching berfungsi, tetapi kami hanya menyebut metode untuk konten mengikuti konten aktif.
Secara umum, pekerjaan prefetching standar untuk UICollectionView sangat tergantung pada bagaimana tampilan koleksi diimplementasikan. Selain itu, karena kita tidak tahu sama sekali penerapan prefetching standar, tidak mungkin menjamin operasi yang stabil. Karena itu, kami menerapkan mekanisme prefetching kami, yang selalu berfungsi sesuai kebutuhan.
Algoritma prefetching kami
Sebelum mengembangkan algoritma pengambilan awal, kami menuliskan semua fitur umpan iFunny:
- Umpan dapat terdiri dari berbagai jenis konten: gambar, video, aplikasi web, iklan asli.
- Rekaman itu berfungsi dengan pagination.
- Sebagian besar pengguna membalik umpan hanya maju.
- Di iFunny, 20% sesi pengguna terjadi melalui LTE.
Berdasarkan kondisi ini, kami telah memperoleh algoritma sederhana:
- Ada 1 elemen aktif dalam rekaman itu, sisanya tidak aktif.
- Elemen aktif selalu perlu mengunduh konten hingga akhir.
- Setiap item konten dalam umpan memiliki bobotnya sendiri.
- Pada koneksi Internet saat ini, Anda dapat memuat item dalam jumlah N.
- Setiap kali Anda menggulir rekaman, kami mengubah elemen aktif dan menghitung elemen mana yang dimuat, dan membatalkan sisa pemuatan.
Arsitektur dalam kode algoritma ini berisi beberapa kelas dasar dan protokol:
- IFPrefetchedCollectionProtocol
@protocol IFPrefetchedCollectionProtocol @property (nonatomic, readonly) NSUInteger prefetchItemsCount; - (NSObject<IFFeedItemProtocol> *)itemAtIndex:(NSInteger)index; @end
Protokol ini diperlukan untuk mendapatkan parameter koleksi dan konten di objek kelas:
@interface IFContentPrefetcher : NSObject @property (nonatomic, weak) NSObject<IFPrefetchedCollectionProtocol> *collection; @property (nonatomic, assign) NSInteger activeIndex; @end
Kelas mengimplementasikan logika algoritma untuk pengambilan konten awal:
@interface IFPrefetchOperation : NSObject @property (nonatomic, readonly) NSUInteger cost; - (void)fetchMinumumBuffer; - (void)fetchEntireBuffer; - (void)pause; - (void)cancel; - (BOOL)isEqualOperation:(IFPrefetchOperation *)object; @end
Ini adalah kelas dasar dari operasi atom, yang menggambarkan pekerjaan yang berguna untuk mengambil konten tertentu dan menunjukkan parameternya - berat.
Untuk menjalankan algoritme, kami menggambarkan dua operasi:
- Gambar. Ini memiliki berat 1. Selalu terisi penuh;
- Video Ini memiliki berat 2. Memuat sepenuhnya hanya saat aktif. Dalam keadaan tidak aktif, 200 KB pertama dimuat.
Sebagai metrik untuk mengevaluasi operasi algoritma, kami memilih jumlah klik dari elemen UI loader per 1000 elemen konten yang dilihat.
Pada prefetching standar metrik ini kami memiliki sekitar 30 tayangan / 1000 elemen. Setelah pengenalan algoritma baru, metrik ini turun menjadi 25 tayangan / 1000 elemen.
Dengan demikian, jumlah tayangan loader menurun sebesar 20% dan jumlah total konten yang dilihat oleh pengguna sedikit meningkat.
Kemudian kami melanjutkan ke pemilihan parameter optimal untuk Featured - rekaman paling populer di iFunny.
Pemilihan parameter untuk prefetching
Algoritma prefetching yang dikembangkan memiliki parameter input:
- Total biaya pengunduhan.
- Biaya pemuatan setiap item.
Kami akan terus mengukur jumlah loader.
Sebagai alat bantu untuk menyederhanakan pengumpulan data, kami akan menggunakan:
- Tes abu-abu dengan satu set kerangka kerja KIF, OHHTTPStubs.
- sh-scripts dan xcodebuild untuk menjalankan tes dengan parameter berbeda.
- Profil jaringan 3G tersedia di pengaturan Developer - Network Link Conditioner.
Mari kita lihat bagaimana masing-masing alat ini membantu kita.
Tes
Untuk meniru bagaimana pengguna melihat konten, kami memutuskan untuk menggunakan kerangka kerja KIF, yang akrab bagi pengembang iOS di Objective-C.
KIF bekerja sangat baik untuk Objective-C dan Swift, setelah beberapa manipulasi mudah dijelaskan dalam dokumentasi KIF:
https://github.com/kif-framework/KIF#use-with-swiftUntuk menguji rekaman itu, kami memilih Objective-C, termasuk untuk dapat mengganti metode yang kami butuhkan di layanan analitik.
Mari kita lihat kode tes sederhana, yang kami dapatkan:
- (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; }]; }
Dalam metode pengaturan pengujian, kita harus menghapus cache sehingga pada setiap peluncuran konten dimuat dari jaringan dan benar-benar menghapus folder cache dalam aplikasi.
Untuk memastikan kestabilan data di masing-masing tes, kami menggunakan pustaka OHHTTPStubs, yang memudahkan untuk mengganti respons terhadap permintaan jaringan dalam beberapa langkah sederhana:
- Tentukan parameter kueri. Bagi kami, ini adalah URL permintaan umpan Unggulan untuk API - http://fun.co/rp/?feed=featured&limit=30
- Catat jawaban yang diperlukan dan simpan ke file, lampirkan ke target dengan tes.
- Tentukan opsi respons. Dalam kode di atas, ini adalah header tipe konten dan kode respons.
- Lihatlah instruksi untuk OHHTTPStubs.
Anda dapat membaca lebih lanjut tentang bekerja dengan OHHTTPStubs dalam dokumentasi:
http://cocoadocs.org/docsets/OHHTTPSPSs/Tes itu sendiri terlihat seperti ini:
- (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]; }
Dengan menggunakan KIF, kami mendapat umpan lalu menggulir melalui 1.000 elemen konten dengan menunggu 1 detik.
Metode setupCustomPrefetchParams akan dibahas sedikit kemudian.
Untuk menentukan jumlah loader yang ditampilkan, kami akan menggunakan runtime Objective-C dan mengganti metode dari layanan analitik dengan metode pengujian:
+ (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]; } }
Sekarang kami memiliki tes otomatis di mana aplikasi selalu menerima konten yang sama dan menggulir jumlah elemen yang sama. Dan menurut hasilnya, ia menulis baris dengan statistik eksekusi ke log.
Karena koneksi Internet terutama mempengaruhi pengunduhan konten, pengujian dengan satu set parameter perlu diulang lebih dari satu kali.
Otomatisasi startup
Untuk mengotomatisasi dan membuat parameter pengujian, kami memutuskan untuk menggunakan peluncuran melalui xcodebuild dengan transfer parameter yang diperlukan.
Untuk meneruskan parameter ke kode, kita perlu menulis nama argumen di pengaturan target untuk tes di Prepocessor Macros:

Untuk mengakses parameter dari kode Objective-C, dua makro harus dideklarasikan:
#define STRINGIZE(x) #x #define BUILD_PARAM(x) STRINGIZE(x)
Sekarang ketika mulai dari terminal menggunakan xcodebuild:
xcodebuild test -workspace iFunny.xcworkspace -scheme iFunnyUITests -destination 'platform=iOS,id=DEVICE_ID' MAX_PREFETCH_COST="5" VIDEO_COST="2" IMAGE_COST="2"
Dalam kode Anda dapat membaca parameter yang dikirimkan:
- (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)]; }
Sekarang semuanya siap untuk menjalankan tes ini secara offline menggunakan skrip shell.
Menjalankan xcodebuild dengan serangkaian parameter 10 kali berturut-turut:
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
Kami juga membuat skrip dengan peluncuran berbagai set parameter. Semua pengujian berlangsung beberapa hari. Data yang diperoleh dirangkum dalam satu tabel, dan kami membandingkannya dengan versi kerja saat ini.
Alhasil, prefetching paling sederhana dari lima elemen ternyata menjadi yang terbaik untuk pita Featured iFunny, tanpa memperhatikan format konten (video atau gambar).
Menurut hasilnya
Artikel ini menjelaskan pendekatan yang akan memungkinkan Anda untuk menjelajahi dan memantau setiap bagian penting dari aplikasi, tanpa mengubah kode proyek utama.
Inilah yang akan membantu untuk melakukan studi tersebut:
- Penggunaan kerangka kerja uji untuk tindakan monoton.
- Otomatisasi melalui xcodebuild untuk membuat parameter startup.
- Runtime Objective-C untuk mengubah logika yang diperlukan, jika memungkinkan.
Berdasarkan pendekatan ini untuk menguji aplikasi, kami mulai menambahkan pemantauan modul penting di stand lokal dan telah menyiapkan beberapa tes yang kami jalankan secara berkala untuk memeriksa kualitas aplikasi.
PS: Menurut hasil pengujian kami, pengaturan prefetching baru relatif terhadap opsi produksi menang sekitar 8%, pada kenyataannya, mereka menerima penurunan tampilan loader sebesar 3%, yang berarti bahwa kami mulai mengirimkan senyum ke iFunny 3% lebih sering :)
PPS: Kami tidak akan berhenti di situ, kami akan terus meningkatkan prefetch konten lebih lanjut.