iOS性能優(yōu)化

1.為什么要把控件盡量設(shè)置成不透明的,如果是透明的會有什么影響,如何檢測這種影響?

1、自動內(nèi)存泄漏檢測工具 MLeaksFinder 或Facebook開源的[FBRetainCycleDetector]

MLeaksFinder 是騰訊開源的 iOS 平臺的自動內(nèi)存泄漏檢測工具,引進 MLeaksFinder 后,就可以在日常的開發(fā),調(diào)試業(yè)務(wù)邏輯的過程中自動地發(fā)現(xiàn)并警告內(nèi)存泄漏。具有如下特性:
●自動檢測內(nèi)存泄漏和釋放不及時的場景
●構(gòu)建泄漏對象相對于 ViewContrller 的引用鏈以幫助開發(fā)者定位問題
●不侵入業(yè)務(wù)邏輯,引入即生效,無需修改任何代碼或引入頭文件(詳情:https://github.com/Tencent/MLeaksFinder
不用寫入任何代碼就可以檢測到內(nèi)存泄漏和循環(huán)引用,控制器里的timer不能夠顯示 retain cycle,控制器外面對他的強引用不會顯示retain cycle,里面的view或者data的delegate 如果是強引用則會顯示出retain cycle 路徑

2.崩潰檢測\卡頓檢測框架 bugly

FPS 的刷新頻率非常快,并且容易發(fā)生抖動,因此直接通過比較 FPS 來偵測卡頓是比較困難的;此外,主線程卡頓監(jiān)控也會發(fā)生抖動,所以微信讀書團隊給出一種綜合方案,結(jié)合主線程監(jiān)控、FPS 監(jiān)控,以及 CPU 使用率等指標(biāo),作為判斷卡頓的標(biāo)準(zhǔn)。Bugly 的卡頓檢測也是基于這套標(biāo)準(zhǔn)。

3.性能檢測 微信Matrix-崩潰、卡頓和爆內(nèi)存

Matrix for iOS/macOS 有哪些功能
Matrix for iOS/macOS 當(dāng)前監(jiān)控范圍包括:崩潰、卡頓和爆內(nèi)存,目前包含兩款插件:
WCCrashBlockMonitorPlugin
WCMemoryStatPlugin
WCCrashBlockMonitorPlugin

一款基于 KSCrash 框架開發(fā),具有業(yè)界領(lǐng)先的卡頓堆棧捕獲能力的插件。卡頓捕捉具有如下特點:通過檢查 Runloop 運行狀態(tài)判斷應(yīng)用是否卡頓,同時支持iOS/macOS 平臺;
具備耗時堆棧提取能力,可獲取最近時間最耗時的主線程堆棧。
同時插件也具備與 KSCrash 框架一致的崩潰捕捉能力。

WCMemoryStatPlugin
一款性能優(yōu)化到極致的內(nèi)存監(jiān)控工具,能夠全面捕獲應(yīng)用出現(xiàn)爆內(nèi)存時的堆棧以及內(nèi)存分配情況。與現(xiàn)有的內(nèi)存監(jiān)控工具相比,WCMemoryStatPlugin 性能表現(xiàn)更加優(yōu)異,并且監(jiān)控的對象更加全面,它具有如下特點:

在應(yīng)用運行期間獲取對象存活以及相應(yīng)的堆棧信息,在檢測到應(yīng)用爆內(nèi)存時進行上報;
使用平衡二叉樹存儲存活對象,使用 Hash Table 存儲堆棧,性能優(yōu)化到極致。

4.避免循環(huán)引用

  1. delegate強引用
    2.block強引用
    3.timer強引用
    1.timer 不管是strong還是 weak正常寫都會有內(nèi)存泄漏
    2.timer 內(nèi)存泄漏處理方法1,利用一個中間類打破循環(huán)引用,加號方法返回timer,這樣比如controller里面創(chuàng)建這個timer,controller對timer是強引用,timer對中間類是強引用,中間類對controller是弱引用,這樣循環(huán)引用被打破,通過把controller 傳遞到中間類中,然后中間類調(diào)用 performSelector: withObject:方法還是會傳遞到controller里面去
  2. 利用NSProxy的子類,通過runtime消息轉(zhuǎn)發(fā),打破循環(huán)引用,使用NSProxy子類需要在controller的dealloc里面 [self.m_timer invalidate];結(jié)合著使用才有效果,否則崩潰
    4.通過GCD創(chuàng)建 dispatch_source_t timer 不會形成循環(huán)引用
    5.通過block改變timer循環(huán)應(yīng)用(待研究)

5.引用ASDK框架子線程異步繪制UI

6.YYKit 高性能開發(fā)框架

YYModel — 高性能的 iOS JSON 模型框架。https://github.com/ibireme/YYImage
YYCache — 高性能的 iOS 緩存框架。https://github.com/ibireme/YYCache
YYImage — 功能強大的 iOS 圖像框架。https://github.com/ibireme/YYImage
YYWebImage — 高性能的 iOS 異步圖像加載框架。https://github.com/ibireme/YYWebImage
YYText — 功能強大的 iOS 富文本框架。https://github.com/ibireme/YYText
YYKeyboardManager — iOS 鍵盤監(jiān)聽管理工具。https://github.com/ibireme/YYKeyboardManager
YYDispatchQueuePool — iOS 全局并發(fā)隊列管理工具。https://github.com/ibireme/YYDispatchQueuePool
YYAsyncLayer — iOS 異步繪制與顯示的工具。https://github.com/ibireme/YYAsyncLayer
YYCategories — 功能豐富的 Category 類型工具庫。https://github.com/ibireme/YYCategories

監(jiān)控指標(biāo)

1.CPU 使用率

1.instrument檢測
2.代碼讀取
int result;
mib[0] = CTL_HW;
mib[1] = HW_CPU_FREQ;
length = sizeof(result);
if (sysctl(mib, 2, &result, &length, NULL, 0) < 0)
{
perror("getting cpu frequency");
}
printf("CPU Frequency = %u hz\n", result);

得到獲取當(dāng)前應(yīng)用的 CPU 占用率

import #import float cpu_usage()

{
kern_return_t kr;
task_info_data_t tinfo;
mach_msg_type_number_t task_info_count;

task_info_count = TASK_INFO_MAX;
kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);
if (kr != KERN_SUCCESS) {
    return -1;
}

task_basic_info_t      basic_info;
thread_array_t         thread_list;
mach_msg_type_number_t thread_count;

thread_info_data_t     thinfo;
mach_msg_type_number_t thread_info_count;

thread_basic_info_t basic_info_th;
uint32_t stat_thread = 0; // Mach threads

basic_info = (task_basic_info_t)tinfo;

// get threads in the task
kr = task_threads(mach_task_self(), &thread_list, &thread_count);
if (kr != KERN_SUCCESS) {
    return -1;
}
if (thread_count > 0)
    stat_thread += thread_count;

long tot_sec = 0;
long tot_usec = 0;
float tot_cpu = 0;
int j;

for (j = 0; j < (int)thread_count; j++)
{
    thread_info_count = THREAD_INFO_MAX;
    kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
                     (thread_info_t)thinfo, &thread_info_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }

    basic_info_th = (thread_basic_info_t)thinfo;

    if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
        tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
        tot_usec = tot_usec + basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds;
        tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
    }

} // for each thread
kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
assert(kr == KERN_SUCCESS);

return tot_cpu;

}

下面是 GT 中獲得 App 的 CPU 占用率的方法

  • (float)getCpuUsage
    {
    kern_return_t kr;
    thread_array_t thread_list;
    mach_msg_type_number_t thread_count;
    thread_info_data_t thinfo;
    mach_msg_type_number_t thread_info_count;
    thread_basic_info_t basic_info_th;

    kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (kr != KERN_SUCCESS) {
    return -1;
    }
    cpu_usage = 0;

    for (int i = 0; i < thread_count; i++)
    {
    thread_info_count = THREAD_INFO_MAX;
    kr = thread_info(thread_list[i], THREAD_BASIC_INFO,(thread_info_t)thinfo, &thread_info_count);
    if (kr != KERN_SUCCESS) {
    return -1;
    }

      basic_info_th = (thread_basic_info_t)thinfo;
    
      if (!(basic_info_th->flags & TH_FLAGS_IDLE))
      {
          cpu_usage += basic_info_th->cpu_usage;
      }
    

    }

    cpu_usage = cpu_usage / (float)TH_USAGE_SCALE * 100.0;

    vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));

    return cpu_usage;
    }

最后得到獲取當(dāng)前 App Memory 的使用情況

  • (NSUInteger)getResidentMemory
    {
    struct mach_task_basic_info info;
    mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;

    int r = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)& info, & count);
    if (r == KERN_SUCCESS)
    {
    return info.resident_size;
    }
    else
    {
    return -1;
    }
    }

獲取當(dāng)前設(shè)備的 Memory 使用情況

int64_t getUsedMemory()
{
size_t length = 0;
int mib[6] = {0};

int pagesize = 0;
mib[0] = CTL_HW;
mib[1] = HW_PAGESIZE;
length = sizeof(pagesize);
if (sysctl(mib, 2, &pagesize, &length, NULL, 0) < 0)
{
    return 0;
}
 
mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
 
vm_statistics_data_t vmstat;
 
if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmstat, &count) != KERN_SUCCESS)
{
return 0;
}
 
int wireMem = vmstat.wire_count * pagesize;

int activeMem = vmstat.active_count * pagesize;
return wireMem + activeMem;
}

t2 = main函數(shù)執(zhí)行之后到 AppDelegate 類中的applicationDidFinishLaunching:withOptions:方法執(zhí)行結(jié)束前這段時間

CFAbsoluteTime StartTime;
int main(int argc, char * argv[]) {
@autoreleasepool {
StartTime = CFAbsoluteTimeGetCurrent();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
extern CFAbsoluteTime StartTime;
// 在 applicationDidFinishLaunching:withOptions: 方法的最后統(tǒng)計
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime);
});
上述代碼使用CFAbsoluteTimeGetCurrent()方法來計算時間,CFAbsoluteTimeGetCurrent()的概念和NSDate非常相似,只不過參考點是以 GMT 為標(biāo)準(zhǔn)的,2001年一月一日00:00:00這一刻的時間絕對值。CFAbsoluteTimeGetCurrent()也會跟著當(dāng)前設(shè)備的系統(tǒng)時間一起變化,也可能會被用戶修改。他的精確度可能是微秒(μs)

2.FPS監(jiān)控

目前主要使用CADisplayLink來監(jiān)控FPS,CADisplayLink是一個能讓我們以和屏幕刷新率相同的頻率將內(nèi)容畫到屏幕上的定時器。我們在應(yīng)用中創(chuàng)建一個新的 CADisplayLink 對象,把它添加到一個runloop中,并給它提供一個 target 和selector 在屏幕刷新的時候調(diào)用,需要注意的是添加到runloop的common mode里面,代碼如下:

  • (void)setupDisplayLink {
    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTicks:)];
    [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }

  • (void)linkTicks:(CADisplayLink *)link
    {
    //執(zhí)行次數(shù)
    _scheduleTimes ++;

    //當(dāng)前時間戳
    if(_timestamp == 0){
    _timestamp = link.timestamp;
    }
    CFTimeInterval timePassed = link.timestamp - _timestamp;

    if(timePassed >= 1.f)
    //fps
    CGFloat fps = _scheduleTimes/timePassed;
    printf("fps:%.1f, timePassed:%f\n", fps, timePassed);
    }
    }

據(jù)統(tǒng)計,有十種應(yīng)用性能問題危害最大,分別為:連接超時、閃退、卡頓、崩潰、黑白屏、網(wǎng)絡(luò)劫持、交互性能差、CPU 使用率問題、內(nèi)存泄露、不良接口。

6.其他UI技巧

1.背景顏色或子控件顏色盡可能不設(shè)置透明度,因為有透明度渲染的時候就會開啟顏色混合,影響性能
2.label.layer.shouldRasterize = true 開啟光柵化會導(dǎo)致離屏渲染,影響性能
3.UIImageView盡可能與圖片大小一致,避免不必要的縮放,影響性能
4.離屏渲染更占用資源,下列情況會觸發(fā)離屏渲染
4.1重寫drawRect方法
4.2有 mask或者陰影(layer.maskToBounds, layer.shadow),模糊效果也是一種
imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).CGPath mask
4.3 layer.shouldRasterize = true 手動開啟離屏渲染

5.避免圖層混合
確保控件的opaque屬性設(shè)置為true,確保backgroundColor和父視圖顏色一致且不透明。
如無特殊需要,不要設(shè)置低于1的alpha值。
確保UIImage沒有alpha通道。

6.避免臨時轉(zhuǎn)換
確保圖片大小和frame一致,不要在滑動時縮放圖片。
確保圖片顏色格式被GPU支持,避免勞煩CPU轉(zhuǎn)換。

7.慎用離屏渲染
絕大多數(shù)時候離屏渲染會影響性能。
重寫drawRect方法,設(shè)置圓角、陰影、模糊效果,光柵化都會導(dǎo)致離屏渲染。
設(shè)置陰影效果是加上陰影路徑。
滑動時若需要圓角效果,開啟光柵化。

8,###TableViewCell 復(fù)用
在cellForRowAtIndexPath:回調(diào)的時候只創(chuàng)建實例,快速返回cell,不綁定數(shù)據(jù)。在willDisplayCell: forRowAtIndexPath:的時候綁定數(shù)據(jù)(賦值)。

9.###高度緩存
在tableView滑動時,會不斷調(diào)用heightForRowAtIndexPath:,當(dāng)cell高度需要自適應(yīng)時,每次回調(diào)都要計算高度,會導(dǎo)致 UI 卡頓。為了避免重復(fù)無意義的計算,需要緩存高度。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容