iOS性能优化

1.为什么要把控件尽量设置成不透明的,如果是透明的会有什么影响,如何检测这种影响?

1、自动内存泄漏检测工具 MLeaksFinder 或Facebook开源的[FBRetainCycleDetector]

MLeaksFinder 是腾讯开源的 iOS 平台的自动内存泄漏检测工具,引进 MLeaksFinder 后,就可以在日常的开发,调试业务逻辑的过程中自动地发现并警告内存泄漏。具有如下特性:
●自动检测内存泄漏和释放不及时的场景
●构建泄漏对象相对于 ViewContrller 的引用链以帮助开发者定位问题
●不侵入业务逻辑,引入即生效,无需修改任何代码或引入头文件(详情:https://github.com/Tencent/MLeaksFinder
不用写入任何代码就可以检测到内存泄漏和循环引用,控制器里的timer不能够显示 retain cycle,控制器外面对他的强引用不会显示retain cycle,里面的view或者data的delegate 如果是强引用则会显示出retain cycle 路径

2.崩溃检测\卡顿检测框架 bugly

FPS 的刷新频率非常快,并且容易发生抖动,因此直接通过比较 FPS 来侦测卡顿是比较困难的;此外,主线程卡顿监控也会发生抖动,所以微信读书团队给出一种综合方案,结合主线程监控、FPS 监控,以及 CPU 使用率等指标,作为判断卡顿的标准。Bugly 的卡顿检测也是基于这套标准。

3.性能检测 微信Matrix-崩溃、卡顿和爆内存

Matrix for iOS/macOS 有哪些功能
Matrix for iOS/macOS 当前监控范围包括:崩溃、卡顿和爆内存,目前包含两款插件:
WCCrashBlockMonitorPlugin
WCMemoryStatPlugin
WCCrashBlockMonitorPlugin

一款基于 KSCrash 框架开发,具有业界领先的卡顿堆栈捕获能力的插件。卡顿捕捉具有如下特点:通过检查 Runloop 运行状态判断应用是否卡顿,同时支持iOS/macOS 平台;
具备耗时堆栈提取能力,可获取最近时间最耗时的主线程堆栈。
同时插件也具备与 KSCrash 框架一致的崩溃捕捉能力。

WCMemoryStatPlugin
一款性能优化到极致的内存监控工具,能够全面捕获应用出现爆内存时的堆栈以及内存分配情况。与现有的内存监控工具相比,WCMemoryStatPlugin 性能表现更加优异,并且监控的对象更加全面,它具有如下特点:

在应用运行期间获取对象存活以及相应的堆栈信息,在检测到应用爆内存时进行上报;
使用平衡二叉树存储存活对象,使用 Hash Table 存储堆栈,性能优化到极致。

4.避免循环引用

  1. delegate强引用
    2.block强引用
    3.timer强引用
    1.timer 不管是strong还是 weak正常写都会有内存泄漏
    2.timer 内存泄漏处理方法1,利用一个中间类打破循环引用,加号方法返回timer,这样比如controller里面创建这个timer,controller对timer是强引用,timer对中间类是强引用,中间类对controller是弱引用,这样循环引用被打破,通过把controller 传递到中间类中,然后中间类调用 performSelector: withObject:方法还是会传递到controller里面去
  2. 利用NSProxy的子类,通过runtime消息转发,打破循环引用,使用NSProxy子类需要在controller的dealloc里面 [self.m_timer invalidate];结合着使用才有效果,否则崩溃
    4.通过GCD创建 dispatch_source_t timer 不会形成循环引用
    5.通过block改变timer循环应用(待研究)

5.引用ASDK框架子线程异步绘制UI

6.YYKit 高性能开发框架

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 键盘监听管理工具。https://github.com/ibireme/YYKeyboardManager
YYDispatchQueuePool — iOS 全局并发队列管理工具。https://github.com/ibireme/YYDispatchQueuePool
YYAsyncLayer — iOS 异步绘制与显示的工具。https://github.com/ibireme/YYAsyncLayer
YYCategories — 功能丰富的 Category 类型工具库。https://github.com/ibireme/YYCategories

监控指标

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);

得到获取当前应用的 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;
    }

最后得到获取当前 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;
    }
    }

获取当前设备的 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函数执行之后到 AppDelegate 类中的applicationDidFinishLaunching:withOptions:方法执行结束前这段时间

CFAbsoluteTime StartTime;
int main(int argc, char * argv[]) {
@autoreleasepool {
StartTime = CFAbsoluteTimeGetCurrent();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
extern CFAbsoluteTime StartTime;
// 在 applicationDidFinishLaunching:withOptions: 方法的最后统计
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime);
});
上述代码使用CFAbsoluteTimeGetCurrent()方法来计算时间,CFAbsoluteTimeGetCurrent()的概念和NSDate非常相似,只不过参考点是以 GMT 为标准的,2001年一月一日00:00:00这一刻的时间绝对值。CFAbsoluteTimeGetCurrent()也会跟着当前设备的系统时间一起变化,也可能会被用户修改。他的精确度可能是微秒(μs)

2.FPS监控

目前主要使用CADisplayLink来监控FPS,CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。我们在应用中创建一个新的 CADisplayLink 对象,把它添加到一个runloop中,并给它提供一个 target 和selector 在屏幕刷新的时候调用,需要注意的是添加到runloop的common mode里面,代码如下:

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

  • (void)linkTicks:(CADisplayLink *)link
    {
    //执行次数
    _scheduleTimes ++;

    //当前时间戳
    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);
    }
    }

据统计,有十种应用性能问题危害最大,分别为:连接超时、闪退、卡顿、崩溃、黑白屏、网络劫持、交互性能差、CPU 使用率问题、内存泄露、不良接口。

6.其他UI技巧

1.背景颜色或子控件颜色尽可能不设置透明度,因为有透明度渲染的时候就会开启颜色混合,影响性能
2.label.layer.shouldRasterize = true 开启光栅化会导致离屏渲染,影响性能
3.UIImageView尽可能与图片大小一致,避免不必要的缩放,影响性能
4.离屏渲染更占用资源,下列情况会触发离屏渲染
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属性设置为true,确保backgroundColor和父视图颜色一致且不透明。
如无特殊需要,不要设置低于1的alpha值。
确保UIImage没有alpha通道。

6.避免临时转换
确保图片大小和frame一致,不要在滑动时缩放图片。
确保图片颜色格式被GPU支持,避免劳烦CPU转换。

7.慎用离屏渲染
绝大多数时候离屏渲染会影响性能。
重写drawRect方法,设置圆角、阴影、模糊效果,光栅化都会导致离屏渲染。
设置阴影效果是加上阴影路径。
滑动时若需要圆角效果,开启光栅化。

8,###TableViewCell 复用
在cellForRowAtIndexPath:回调的时候只创建实例,快速返回cell,不绑定数据。在willDisplayCell: forRowAtIndexPath:的时候绑定数据(赋值)。

9.###高度缓存
在tableView滑动时,会不断调用heightForRowAtIndexPath:,当cell高度需要自适应时,每次回调都要计算高度,会导致 UI 卡顿。为了避免重复无意义的计算,需要缓存高度。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容