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.避免循环引用
- delegate强引用
2.block强引用
3.timer强引用
1.timer 不管是strong还是 weak正常写都会有内存泄漏
2.timer 内存泄漏处理方法1,利用一个中间类打破循环引用,加号方法返回timer,这样比如controller里面创建这个timer,controller对timer是强引用,timer对中间类是强引用,中间类对controller是弱引用,这样循环引用被打破,通过把controller 传递到中间类中,然后中间类调用 performSelector: withObject:方法还是会传递到controller里面去 - 利用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 卡顿。为了避免重复无意义的计算,需要缓存高度。
网友评论