歡迎光臨
每天分享高質量文章

iOS效能最佳化之頁面載入速率

來自:菠蘿ngmm

連結:https://juejin.im/post/5b8be6c86fb9a019d9247b2a

前言

之前搜羅了網上很多關於iOS效能最佳化方面的資料 ,本人和我的小夥伴們也用了一些時間針對自己的App進行了App的啟動速率、頁面的載入速率和 頁面的幀率方面進行了最佳化,所以結合了理論和實踐,把我們在實踐中主要踩過的坑和需要註意的東西 ,總結了一下,希望可以幫到正在準備進行App的效能最佳化的你。今天主要講一下App的頁面載入速率的最佳化。

目的

為了找到真正使我們的App緩慢的原因,我們使用Xcode或者一些第三方平臺,進行資料測試;

一、頁面載入速率的定義

頁面載入速率:關於頁面的載入速度的統計,我們是測試一個viewcontroller從viewdidload的第一行到viewdidappear的最後一行所用的時間。

二、頁面載入速率的標的值

標的:頁面載入速率最好完美的時間在0.3s左右

為了弄明白,到底是什麼原因讓我們的App,頁面載入速度相對來說比較慢,我們對頁面的UI進行最佳化,資料也進行了非同步載入,我們hook資料一看,頁面的載入速度果然有所減少,但是減少的值大概只有0.03s,很明顯這個值不足以達到我們想要的效果,後來,透過寫了一些測試demo,針對空白頁面和有UI建立的頁面進行各種對比後,似乎和我們頁面載入過程中的push動畫有很大的關係;下麵所做的實驗主要是為了驗證這個問題,針對這個問題,我選取了我們工程的一個類,對有push進入到這個頁面有過場動畫和沒有動畫進行測試,以下資料是測試結果:

透過這個實驗,我們可以看出,不加動畫的話,我們的頁面載入的速度可以說是沒有任何的卡頓,超級迅速,但是如果把過場動畫給開啟,單是動畫的時間就是在0.5s左右,而s我們是希望使用者在點選跳轉頁面的時候,標的是頁面在0.3s左右呈現,這如果加動畫,這個標的很難達到;不過透過查詢相關資料,我們證實了我們可以把如果有過場動畫的頁面,去掉動畫,而是透過我們自己去給使用者新增一個過場動畫,而這個時間是可以受到我們自己的控制,而不是傻傻的等動畫結束後再載入頁面內容。的這就是說,可以一邊動畫的時候,一邊已經開始載入頁面相關東西了,這樣可以大大的最佳化頁面載入時間。


三、最佳化前資料

四、 最佳化後的資料

到這裡 ,你一定想問 :我該如何hook資料的???

五、如何進行資料的收集

給UIViewController 建立一個分類 eg :UIViewController+Swizzle

程式碼如下


#import 
#import 

@interface UIViewController (Swizzle)
@property(nonatomic,assignCFAbsoluteTime viewLoadStartTime;

@end

#import "UIViewController+Swizzle.h"
#import 

static char *viewLoadStartTimeKey = "viewLoadStartTimeKey";
@implementation UIViewController (Swizzle)
-(void)setViewLoadStartTime:(CFAbsoluteTime)viewLoadStartTime{
objc_setAssociatedObject(self, &viewLoadStartTimeKey;, @(viewLoadStartTime), OBJC_ASSOCIATION_COPY);

}
-(CFAbsoluteTime)viewLoadStartTime{
return [objc_getAssociatedObject(self, &viewLoadStartTimeKey;) doubleValue];
}
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken;, ^{
SEL origSel = @selector(viewDidAppear:);
SEL swizSel = @selector(swiz_viewDidAppear:);
[UIViewController swizzleMethods:[self class] originalSelector:origSel swizzledSelector:swizSel];

SEL vcWillAppearSel=@selector(viewWillAppear:);
SEL swizWillAppearSel=@selector(swiz_viewWillAppear:);
[UIViewController swizzleMethods:[self class] originalSelector:vcWillAppearSel swizzledSelector:swizWillAppearSel];

SEL vcDidLoadSel=@selector(viewDidLoad);
SEL swizDidLoadSel=@selector(swiz_viewDidLoad);
[UIViewController swizzleMethods:[self class] originalSelector:vcDidLoadSel swizzledSelector:swizDidLoadSel];

SEL vcDidDisappearSel=@selector(viewDidDisappear:);
SEL swizDidDisappearSel=@selector(swiz_viewDidDisappear:);
[UIViewController swizzleMethods:[self class] originalSelector:vcDidDisappearSel swizzledSelector:swizDidDisappearSel];

SEL vcWillDisappearSel=@selector(viewWillDisappear:);
SEL swizWillDisappearSel=@selector(swiz_viewWillDisappear:);
[UIViewController swizzleMethods:[self class] originalSelector:vcWillDisappearSel swizzledSelector:swizWillDisappearSel];
});
}

+ (void)swizzleMethods:(Class)class originalSelector:(SEL)origSel swizzledSelector:(SEL)swizSel
{
Method origMethod = class_getInstanceMethod(class, origSel);
Method swizMethod = class_getInstanceMethod(class, swizSel);

//class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
if (didAddMethod) {
class_replaceMethod(class, swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else {
//origMethod and swizMethod already exist
method_exchangeImplementations(origMethod, swizMethod);
}
}

- (void)swiz_viewDidAppear:(BOOL)animated
{
[self swiz_viewDidAppear:animated];
if (self.viewLoadStartTime) {
CFAbsoluteTime linkTime = (CACurrentMediaTime() - self.viewLoadStartTime);

NGLog(@" %f s--------------------ssssss   %@:速度:         %f s",self.viewLoadStartTime, self.class,linkTime  );
self.viewLoadStartTime = 0;
}
}

-(void)swiz_viewWillAppear:(BOOL)animated
{
[self swiz_viewWillAppear:animated];
}

-(void)swiz_viewDidDisappear:(BOOL)animated
{
[self swiz_viewDidDisappear:animated];
}

-(void)swiz_viewWillDisappear:(BOOL)animated
{
[self swiz_viewWillDisappear:animated];
}
-(void)swiz_viewDidLoad
{
self.viewLoadStartTime =CACurrentMediaTime();
NSLog(@" %@swiz_viewDidLoad startTime:%f",self.class, self.viewLoadStartTime );
[self swiz_viewDidLoad];
}

@end


如何進行最佳化

方法:充分利用push 動畫的時間 ,使頁面在進入的時候,同事進行類似push 動畫,這樣可以充分減少頁面的載入速度(不包括網路請求時間,網路的請求的時間我們這邊不好控制)。

具體實現程式碼如下

重寫 push方法


- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {

if (self.viewControllers.count > 0) {
viewController.hidesBottomBarWhenPushed = YES;
if (animated) {

CATransition *animation = [CATransition animation];
animation.duration = 0.4f;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.type = kCATransitionPush;
animation.subtype = kCATransitionFromRight;
[self.navigationController.view.layer addAnimation:animation forKey:nil];
[self.view.layer addAnimation:animation forKey:nil];
[super pushViewController:viewController animated:NO];
return;
}
}
[super pushViewController:viewController animated:animated];
}


作者:菠蘿ngmm
連結:https://juejin.im/post/5b8be6c86fb9a019d9247b2a
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。


透過控制檯 ,我們就可以看到頁面的載入的速度了,主要的方法是swiz_viewDidLoad  和swiz_viewDidAppear

六、最佳化後的結果

七、結果分析

我們可以看出,我們的頁面的viewDidAppear是在過場動畫結束後被呼叫的,而過場動畫的持續時間是0.5秒左右。所以我們的頁面平均在0.8秒左右的頁面,如果要最佳化得更好,我們可以看有沒有方法解決這個問題,如果能替換掉動畫,讓動畫在進行的過程中 ,頁面的載入也在非同步的進行中,這樣 我們就可以縮短頁面的載入時間了;註:但這個載入對載入h5的頁面不適用;



編號288,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂