Just My Life & My Work

我家產品HappyStyle開發到尾聲,需求上的功能已全數完成,接下來的時間可以來優化各個功能。若想要讓使用者體驗 (User Experience)變得更優,那麼勢必要提升App運作的流暢度。

像是在滑內容清單,若能無「卡頓」現象會是優良的體驗,那麼如何來減少卡頓狀況?我們可以利用Xcode內建的工具Time Profiler (時間分析器),顧名思義就是利用它來分析各個方法 (Method)函式 (Function),所執行的時間狀況會在界面上清楚呈現,除了可以觀察自訂的也能探索內建的。

操作步驟:

  1. 選擇要調適的App,在這裡最好使用真機運行應用。
  2. 點擊按鈕打開並監聽App。
  3. 設置監聽結果的顯示方式,通常只需要勾選Separate by ThreadHide System Libraries
    Separate by Thread:監聽結果按照線程劃分。
    Hide System Libraries:勾選此項你會顯示你App的代碼,這是非常有用的。因為通常你只想關心CPU花在自己代碼上的時間不是系統上的。
    Invert Call Tree:從上倒下跟踪堆棧,這意味著你看到的表中的方法,將已從第0幀開始取樣,這通常你是想要的,只有這樣你才能看到CPU中話費時間最深的方法。也就是說FuncA{FunB{FunC}}勾選此項後堆棧以C->BA把調用層級最深的C顯示在最外面。
    Top Functions:一個函數花費的時間直接在該函數中的總和,以及在函數調用該函數所花費的時間的總時間。因此,如果函數A調用B,那麼A的時間報告在A花費的時間加上B花費的時間,這非常真有用,因為它可以讓你每次下到調用堆棧時挑最大的時間數字,歸零在你最耗時的方法。

測試程式碼可以這麼寫:

/**
 Theme: Test Time Profiler
 IDE: Xcode 10
 Language: Objective C
 Date: 108/01/27
 Author: HappyMan
 Blog: https://cg2010studio.com/
 */

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self printLogUsingOC];
    [self printLogUsingC];
    [self printMainQueue];
    [self printGlobalQueue];
}

-(void)printLogUsingOC
{
    double dateStart = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i<10000; i++) { NSLog(@"---->NSLog:%d", i);
    }
    double dateEnd = CFAbsoluteTimeGetCurrent()-dateStart;
    NSLog(@"NSLog timeConsuming = %f", dateEnd);
}

-(void)printLogUsingC
{
    double dateStart = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i<10000; i++) { printf("====>printf:%d", i);
    }
    double dateEnd = CFAbsoluteTimeGetCurrent()-dateStart;
    NSLog(@"printf timeConsuming = %f", dateEnd);
}

-(void)printGlobalQueue
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        double dateStart = CFAbsoluteTimeGetCurrent();
        for (int i = 0; i<10000; i++) { NSLog(@"~~~~>%d", i);
        }
        double dateEnd = CFAbsoluteTimeGetCurrent()-dateStart;
        NSLog(@"forLoopGlobalQueue NSLog timeConsuming = %f", dateEnd);
    });
}

-(void)printMainQueue
{
    dispatch_async(dispatch_get_main_queue(), ^{
        double dateStart = CFAbsoluteTimeGetCurrent();
        for (int i = 0; i<10000; i++) { NSLog(@"****>%d", i);
        }
        double dateEnd = CFAbsoluteTimeGetCurrent()-dateStart;
        NSLog(@"forLoopMainQueue NSLog timeConsuming = %f", dateEnd);
    });
}

Time Profiler iPhone XS Max

Time Profiler iPhone 6

可以看出同樣的條件下,NSLog (Objective C)比printf (C)慢很多。

剛好我手邊有四支不同規格的iPhone,我以出產先後來陳列測試數據:

iPhone 6

NSLog timeConsuming = 0.806157
printf timeConsuming = 0.072821
forLoopMainQueue NSLog timeConsuming = 1.221460
forLoopGlobalQueue NSLog timeConsuming = 1.913152

NSLog timeConsuming = 0.605738
printf timeConsuming = 0.017561
forLoopMainQueue NSLog timeConsuming = 0.742060
forLoopGlobalQueue NSLog timeConsuming = 1.066033

00:00.000.000 Initializing application’s address space and dynamic linking required frameworks took 349.16 ms.
00:00.349.159 Application took 79.01 ms between when UIApplicationMain() or NSApplicationMain() started and when the did-finish-launching notifications finished.
00:02.024.851 Currently running in the foreground…

iPhone SE

NSLog timeConsuming = 0.135863
printf timeConsuming = 0.002952
forLoopMainQueue NSLog timeConsuming = 0.245584
forLoopGlobalQueue NSLog timeConsuming = 0.268479

00:00.000.000 Initializing application’s address space and dynamic linking required frameworks took 116.41 ms.
00:00.116.414 Application took 19.15 ms between when UIApplicationMain() or NSApplicationMain() started and when the did-finish-launching notifications finished.
00:00.544.820 Currently running in the foreground…

iPhone 8+

NSLog timeConsuming = 0.090414
printf timeConsuming = 0.003238
forLoopMainQueue NSLog timeConsuming = 0.108809
forLoopGlobalQueue NSLog timeConsuming = 0.144815

00:00.000.000 Initializing application’s address space and dynamic linking required frameworks took 194.80 ms.
00:00.194.797 Application took 19.73 ms between when UIApplicationMain() or NSApplicationMain() started and when the did-finish-launching notifications finished.
00:00.474.407 Currently running in the foreground…

iPhone XS Max

NSLog timeConsuming = 0.091390
printf timeConsuming = 0.004488
forLoopMainQueue NSLog timeConsuming = 0.107889
forLoopGlobalQueue NSLog timeConsuming = 0.144465

00:00.000.000 Initializing application’s address space and dynamic linking required frameworks took 319.71 ms.
00:00.319.708 Application took 25.37 ms between when UIApplicationMain() or NSApplicationMain() started and when the did-finish-launching notifications finished.
00:00.599.288 Currently running in the foreground…

可以看得出來,執行時間跟硬體規格有很大的關係呢!我親愛的iPhone 6已邁入第五年,使用流暢度早已越來越差><~

同樣是把訊息列印在Console上,NSLog (Objective C)printf (C)差異頗大,畢竟Objective C是以C為基底的物件導向程式語言,實作上肯定會是C的超集合 (Super Set)。

  • Red: the set of all programs valid in C, C++, and Objective-C (relatively small)
  • Green: the set of all programs valid in C and Objective-C, but invalid in C++ (even smaller)
  • Gray: the set of all programs valid in Objective C and C++, but invalid in C (empty, as far as I know)
  • Blue: the set of all programs valid only in Objective C (relatively large)
  • Yellow: the set of all programs valid only in C++ (largest)
  • The set of valid C programs (in red and green) is an strict subset of the set of valid Objective C programs (blue)

實際應用

接下來應用到我們家專案上,發現樂高系統底層可以優化,畢竟至少一半的視覺元件要指定值時會執行該方法,若能在此方法上「動手腳」,肯定會有突破性的進展。

看到Time Profiler有時間長度佔比:

  • 53.85%
  • 25.00%

我陸續修掉耗時久的寫法⋯⋯

已使用四年歷史我的裝置iPhone 6,優化後效能提升至少10倍呢!因為發明樂高系統的資深丹哥使用高階手機,所以難以察覺效能上的問題。

同事Adam在裝新版本後,發現卡頓現象漸低很多超有感!很高興我幫公司產品提升性能,促進使用者體驗更加完美~

參考:Instruments — Time Profiler使用Time Profiler 使用iOS性能優化- 工具Instruments之Time ProfilerWhat does “Objective-C is a superset of C more strictly than C++” mean exactly?ios Instruments之Time ProfilerInstruments Tutorial with Swift: Getting StartedWWDC 2016 – Using Time Profiler in Instruments

廣告

隨意留個言吧:)~

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

標籤雲

%d 位部落客按了讚: