改善内容下载而无需加盖印章



快速,高质量地向用户交付内容是我们在处理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 feed的所有功能:

  1. Feed可以包含不同类型的内容:图片,视频,Web应用程序,本地广告。
  2. 该磁带可分页使用。
  3. 大多数用户只能将Feed向前翻转。
  4. 在iFunny中,20%的用户会话通过LTE发生。

基于这些条件,我们获得了一个简单的算法:

  1. 磁带中有1个活动元素,其余所有元素均处于非活动状态。
  2. 活动元素始终需要将内容下载到末尾。
  3. Feed中的每个内容项都有自己的权重。
  4. 在当前的Internet连接上,您可以加载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 KB。

作为评估算法运行情况的指标,我们选择了每查看1000个内容元素,加载程序UI元素的点击次数。

在标准预提取此指标时,我们大约有30次展示/ 1000个元素。 引入新算法后,该指标下降到25次展示/ 1000个元素。

因此,加载程序的印象数减少了20%,用户观看的内容总数略有增加。

然后,我们为iFunny中最流行的磁带Featured选择最佳参数。

选择预取参数


开发的预取算法具有输入参数:

  1. 下载的总费用。
  2. 加载每个项目的成本。

我们将继续测量装载机的数量。

作为简化数据收集的辅助工具,我们将使用:

  1. 使用一组KIF框架OHHTTPStubs进行灰色测试。
  2. sh-scripts和xcodebuild可以使用不同的参数运行测试。
  3. “开发人员-网络链接调节器”设置中提供了3G网络配置文件。

让我们看看这些工具如何对我们有所帮助。

测验


为了模拟用户如何查看内容,我们决定使用Objective-C上的iOS开发人员熟悉的KIF框架。

在KIF文档中介绍了一些简单的操作之后,KIF在Objective-C和Swift上非常有用:
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文件夹。

为了确保每个测试中的数据稳定性,我们使用了OHHTTPStubs库,该库使您可以通过几个简单的步骤轻松将响应替换为网络请求:

  1. 定义查询参数。 对于我们来说,这是对API的精选供稿请求的URL- http://fun.co/rp/? feed= featured&limit=30
  2. 记录必要的答案并将其保存到文件中,并随测试附加到目标。
  3. 定义响应选项。 在上面的代码中,这是Content-Type标头和响应代码。
  4. 查看OHHTTPStubs的说明。

您可以在文档中阅读有关使用OHHTTPStubs的更多信息:
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,我们将获得一个提要,然后在1秒钟的等待时间内滚动浏览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]; } } 

现在,我们有了一个自动测试,其中应用程序始终接收相同的内容并滚动相同数量的元素。 然后根据其结果,将包含执行统计信息的行写入日志。

由于Internet连接主要影响内容的下载,因此使用一组参数进行的测试需要重复多次。

启动自动化


为了使测试自动化和参数化,我们决定使用通过xcodebuild进行的启动以及所需参数的传输。

要将参数传递给代码,我们需要在目标宏中的测试的目标设置中写入参数名称:



要从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自动化以参数化启动。
  • 运行时Objective-C尽可能更改必要的逻辑。

基于这种测试应用程序的方法,我们开始在本地展台添加对重要模块的监视,并且已经准备了一些测试,我们会定期运行这些测试以检查应用程序的质量。

PS:根据我们的测试结果,相对于生产选项而言,新的预取设置赢得了大约8%,实际上,它们使装载机的显示减少了3%,这意味着我们开始向iFunny传递微笑的频率提高了3%:)

PPS:我们不会止步于此,我们将继续改善内容预取。

Source: https://habr.com/ru/post/zh-CN417059/


All Articles