## 时间复杂度

//O(1)
return array[idx] == value;

//O(n)
for (int i = 0; i < count; i++) {
if (array[i] == value) {
return YES;
}
}
return NO;

//O(n2) 找重复的值
for (int i = 0; i < count; i++) {
for (int j = 0; j < count; j++) {
if ( i != j && array[i] == array[j]) {
return YES;
}
}
}
return NO;

### NSArray / NSMutableArray

• containsObject:，containsObject:，indexOfObject*，removeObject: 会遍历里面元素查看是否与之匹对，所以复杂度等于或大于 O(n)
• objectAtIndex:，firstObject:，lastObject:，addObject:，removeLastObject: 这些只针对栈顶栈底操作的时间复杂度都是 O(1)
• indexOfObject:inSortedRange:options:usingComparator: 使用的是二分查找，时间复杂度是 O(log n)

### containsObject 方法在数组和 Set 里不同的实现

- (BOOL) containsObject: (id)anObject
{
return ([self indexOfObject: anObject] != NSNotFound);
}
- (NSUInteger) indexOfObject: (id)anObject
{
unsigned  c = [self count];

if (c > 0 && anObject != nil)
{
unsigned  i;
IMP   get = [self methodForSelector: oaiSel];
BOOL  (*eq)(id, SEL, id)
= (BOOL (*)(id, SEL, id))[anObject methodForSelector: eqSel];

for (i = 0; i < c; i++)
if ((*eq)(anObject, eqSel, (*get)(self, oaiSel, i)) == YES)
return i;
}
return NSNotFound;
}

- (BOOL) containsObject: (id)anObject
{
return (([self member: anObject]) ? YES : NO);
}
//在 GSSet,m 里有对 member 的实现
- (id) member: (id)anObject
{
if (anObject != nil)
{
GSIMapNode node = GSIMapNodeForKey(&map, (GSIMapKey)anObject);
if (node != 0)
{
return node->key.obj;
}
}
return nil;
}

## 用 GCD 来做优化

### 避免线程爆炸

• 使用串行队列
• 使用 NSOperationQueues 的并发限制方法 NSOperationQueue.maxConcurrentOperationCount

for (int i = 0; i < 999; i++) {
dispatch_async(q, ^{...});
}
dispatch_barrier_sync(q, ^{});

dispatch_apply(999, q, ^(size_t i){...});

#define CONCURRENT_TASKS 4
sema = dispatch_semaphore_create(CONCURRENT_TASKS);
for (int i = 0; i < 999; i++){
dispatch_async(q, ^{
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}

### GCD 相关 Crash 日志

Thread 1:: Dispatch queue: com.apple.libdispatch-manager
0   libsystem_kernel.dylib   0x00007fff8967e08a kevent_qos + 10
1   libdispatch.dylib        0x00007fff8be05811 _dispatch_mgr_invoke + 251
2   libdispatch.dylib        0x00007fff8be05465 _dispatch_mgr_thread + 52

Thread 6:
0   libsystem_kernel.dylib       0x00007fff8967d772 __workq_kernreturn + 10
1   libsystem_pthread.dylib      0x00007fff8fd317d9 _pthread_wqthread + 1283
2   libsystem_pthread.dylib      0x00007fff8fd2ed95 start_wqthread + 13

Thread 3 Crashed:: Dispatch queue: <queue name>
<my code>
7   libdispatch.dylib        0x07fff8fcfd323 _dispatch_call_block_and_release
8   libdispatch.dylib        0x07fff8fcf8c13 _dispatch_client_callout + 8
9   libdispatch.dylib        0x07fff8fcfc365 _dispatch_queue_drain + 1100
10  libdispatch.dylib        0x07fff8fcfdecc _dispatch_queue_invoke + 202
11  libdispatch.dylib        0x07fff8fcfb6b7 _dispatch_root_queue_drain + 463
12  libdispatch.dylib        0x07fff8fd09fe4 _dispatch_worker_thread3 + 91
13  libsystem_pthread.dylib  0x07fff93c17637 _pthread_wqthread + 729
14  libsystem_pthread.dylib  0x07fff93c1540d start_wqthread + 13

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib     0x00007fff906614de mach_msg_trap + 10
1   libsystem_kernel.dylib     0x00007fff9066064f mach_msg + 55
2   com.apple.CoreFoundation   0x00007fff9a8c1eb4 __CFRunLoopServiceMachPort
3   com.apple.CoreFoundation   0x00007fff9a8c137b __CFRunLoopRun + 1371
4   com.apple.CoreFoundation   0x00007fff9a8c0bd8 CFRunLoopRunSpecific + 296
...
10  com.apple.AppKit           0x00007fff8e823c03 -[NSApplication run] + 594
11  com.apple.AppKit           0x00007fff8e7a0354 NSApplicationMain + 1832
12  com.example                0x00000001000013b4 start + 52

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
<my code>
12  com.apple.Foundation      0x00007fff931157e8 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
13  com.apple.Foundation      0x00007fff931155b5 -[NSBlockOperation main] + 9
14  com.apple.Foundation      0x00007fff93114a6c -[__NSOperationInternal _start:] + 653
15  com.apple.Foundation      0x00007fff93114543 __NSOQSchedule_f + 184
16  libdispatch.dylib         0x00007fff935d6c13 _dispatch_client_callout + 8
17  libdispatch.dylib         0x00007fff935e2cbf _dispatch_main_queue_callback_4CF + 861
18  com.apple.CoreFoundation  0x00007fff8d9223f9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
19  com.apple.CoreFoundation  0x00007fff8d8dd68f __CFRunLoopRun + 2159
20  com.apple.CoreFoundation  0x00007fff8d8dcbd8 CFRunLoopRunSpecific + 296
...
26  com.apple.AppKit          0x00007fff999a1bd3 -[NSApplication run] + 594
27  com.apple.AppKit          0x00007fff9991e324 NSApplicationMain + 1832
28  libdyld.dylib             0x00007fff9480f5c9 start + 1

## I/O 性能优化

I/O 是性能消耗大户，任何的 I/O 操作都会使低功耗状态被打破，所以减少 I/O 次数是这个性能优化的关键点，为了达成这个目下面列出一些方法。

• 将零碎的内容作为一个整体进行写入
• 使用合适的 I/O 操作 API
• 使用合适的线程
• 使用 NSCache 做缓存能够减少 I/O

### NSCache

NSCache 具有字典的所有功能，同时还有如下的特性：

• 自动清理系统占用内存
• NSCache 是线程安全
• -(void)cache:(NSCache *)cache willEvictObject:(id)obj; 缓存对象将被清理时的回调
• evictsObjectsWithDiscardedContent 可以控制是否清理

@user1 NSCache
- (id) init
{
if (nil == (self = [super init]))
{
return nil;
}
_objects = [NSMutableDictionary new];
_accesses = [NSMutableArray new];
return self;
}

@user2 _GSCachedObject : NSObject
{
@user3
id object; //cache 的值
NSString *key; //设置 cache 的 key
int accessCount; //保存访问次数，用于自动清理
NSUInteger cost; //setObject:forKey:cost:
BOOL isEvictable; //线程安全
}
@user4

- (id) objectForKey: (id)key
{
_GSCachedObject *obj = [_objects objectForKey: key];
if (nil == obj)
{
return nil;
}
if (obj->isEvictable) //保证添加删除操作线程安全
{
// 将 obj 移到 access list 末端
[_accesses removeObjectIdenticalTo: obj];
[_accesses addObject: obj];
}
obj->accessCount++;
_totalAccesses++;
return obj->object;
}

- (void) setObject: (id)obj forKey: (id)key cost: (NSUInteger)num
{
_GSCachedObject *oldObject = [_objects objectForKey: key];
_GSCachedObject *newObject;

if (nil != oldObject)
{
[self removeObjectForKey: oldObject->key];
}
[self _evictObjectsToMakeSpaceForObjectWithCost: num];
newObject = [_GSCachedObject new];
// Retained here, released when obj is dealloc'd
newObject->object = RETAIN(obj);
newObject->key = RETAIN(key);
newObject->cost = num;
if ([obj conformsToProtocol: @user5(NSDiscardableContent)])
{
newObject->isEvictable = YES;
[_accesses addObject: newObject];
}
[_objects setObject: newObject forKey: key];
RELEASE(newObject);
_totalCost += num;
}

// cost 在添加新 cache 值时指定的 cost
// _costLimit 是 totalCostLimit 属性值
if (_costLimit > 0 && _totalCost + cost > _costLimit){
spaceNeeded = _totalCost + cost - _costLimit;
}
// 只有当 cost 大于人工限制时才会清理
// 或者 cost 设置为0不进行人工干预
if (count > 0 && (spaceNeeded > 0 || count >= _countLimit))

//_totalAccesses 所有的值的访问都会 +1
NSUInteger averageAccesses = (_totalAccesses / count * 0.2) + 1;
//accessCount 每次 obj 取值时会 +1
if (obj->accessCount < averageAccesses && obj->isEvictable)

NSUInteger cost = obj->cost;
obj->cost = 0;
// 不会被再次清除
obj->isEvictable = NO;
// 添加到 remove list 里
if (_evictsObjectsWithDiscardedContent)
{
[evictedKeys addObject: obj->key];
}
_totalCost -= cost;
// 如果已经释放了足够空间就不用后面操作了
if (cost > spaceNeeded)
{
break;
}
spaceNeeded -= cost;

- (void) removeObjectForKey: (id)key
{
_GSCachedObject *obj = [_objects objectForKey: key];

if (nil != obj)
{
[_delegate cache: self willEvictObject: obj->object];
_totalAccesses -= obj->accessCount;
[_objects removeObjectForKey: key];
[_accesses removeObjectIdenticalTo: obj];
}
}

- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];
}
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
// 检查 NSCache 里是否有
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}
// 从磁盘里读
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}

## 控制 App 的 Wake 次数

[locationManager startUpdatingLocation]

[locationManager allowDeferredLocationUpdatesUntilTraveled: timeout:]

[locationManager startMonitoringSignificantLocationChanges]

[locationManager startMonitoringForRegion:(CLRegion *)]

// Start monitoring
locationManager.startMonitoringVisits()
// Stop monitoring when no longer needed
locationManager.stopMonitoringVisits()

## 如何预防这些性能问题，需要刻意预防么

• 优化计算的复杂度从而减少 CPU 的使用
• 在应用响应交互的时候停止没必要的任务处理
• 设置合适的 QoS
• 将定时器任务合并，让 CPU 更多时候处于 idle 状态

## 获取线程的信息

thread_act_array_t threads; //int 组成的数组比如 thread[1] = 5635
mach_msg_type_number_t thread_count = 0; //mach_msg_type_number_t 是 int 类型
const task_t this_task = mach_task_self(); //int
//根据当前 task 获取所有线程
kern_return_t kr = task_threads(this_task, &threads, &thread_count);

SMThreadInfoStruct threadInfoSt = {0};
thread_info_data_t threadInfo;
thread_basic_info_t threadBasicInfo;
mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;

if (thread_info((thread_act_t)thread, THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount) == KERN_SUCCESS) {
threadBasicInfo = (thread_basic_info_t)threadInfo;
if (!(threadBasicInfo->flags & TH_FLAGS_IDLE)) {
threadInfoSt.cpuUsage = threadBasicInfo->cpu_usage / 10;
threadInfoSt.userTime = threadBasicInfo->system_time.microseconds;
}
}

uintptr_t buffer[100];
int i = 0;
NSMutableString *reStr = [NSMutableString stringWithFormat:@"Stack of thread: %u:\n CPU used: %.1f percent\n user time: %d second\n", thread, threadInfoSt.cpuUsage, threadInfoSt.userTime];

## 获取线程里所有栈的信息

_STRUCT_MCONTEXT machineContext; //线程栈里所有的栈指针
//通过 thread_get_state 获取完整的 machineContext 信息，包含 thread 状态信息
mach_msg_type_number_t state_count = smThreadStateCountByCPU();
kern_return_t kr = thread_get_state(thread, smThreadStateByCPU(), (thread_state_t)&machineContext.__ss, &state_count);

//为通用回溯设计结构支持栈地址由小到大，地址里存储上个栈指针的地址
typedef struct SMStackFrame {
const struct SMStackFrame *const previous;
const uintptr_t return_address;
} SMStackFrame;

SMStackFrame stackFrame = {0};
//通过栈基址指针获取当前栈帧地址
const uintptr_t framePointer = smMachStackBasePointerByCPU(&machineContext);
if (framePointer == 0 || smMemCopySafely((void *)framePointer, &stackFrame, sizeof(stackFrame)) != KERN_SUCCESS) {
return @"Fail frame pointer";
}
for (; i < 32; i++) {
buffer[i] = stackFrame.return_address;
if (buffer[i] == 0 || stackFrame.previous == 0 || smMemCopySafely(stackFrame.previous, &stackFrame, sizeof(stackFrame)) != KERN_SUCCESS) {
break;
}
}

## 符号化

info->dli_fname = NULL;
info->dli_fbase = NULL;
info->dli_sname = NULL;
info->dli_saddr = NULL;
//根据地址获取是哪个 image
const uint32_t idx = smDyldImageIndexFromAddress(address);
if (idx == UINT_MAX) {
return false;
}
/*
Header
------------------
Load commands
Segment command 1 -------------|
Segment command 2              |
------------------             |
Data                           |
Section 1 data |segment 1 <----|
Section 2 data |          <----|
Section 3 data |          <----|
Section 4 data |segment 2
Section 5 data |
...            |
Section n data |
*/
/*----------Mach Header---------*/
//根据 image 的序号获取 mach_header
const struct mach_header* machHeader = _dyld_get_image_header(idx);
//返回 image_index 索引的 image 的虚拟内存地址 slide 的数量，如果 image_index 超出范围返回0
//动态链接器加载 image 时，image 必须映射到未占用地址的进程的虚拟地址空间。动态链接器通过添加一个值到 image 的基地址来实现，这个值是虚拟内存 slide 数量
const uintptr_t imageVMAddressSlide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx);
/*-----------ASLR 的偏移量---------*/
//https://en.wikipedia.org/wiki/Address_space_layout_randomization
const uintptr_t addressWithSlide = address - imageVMAddressSlide;
//根据 Image 的 Index 来获取 segment 的基地址
//段定义Mach-O文件中的字节范围以及动态链接器加载应用程序时这些字节映射到虚拟内存中的地址和内存保护属性。 因此，段总是虚拟内存页对齐。 片段包含零个或多个节。
const uintptr_t segmentBase = smSegmentBaseOfImageIndex(idx) + imageVMAddressSlide;
if (segmentBase == 0) {
return false;
}
//
info->dli_fname = _dyld_get_image_name(idx);
info->dli_fbase = (void*)machHeader;

/*--------------Mach Segment-------------*/
//地址最匹配的symbol
const nlistByCPU* bestMatch = NULL;
uintptr_t bestDistance = ULONG_MAX;
uintptr_t cmdPointer = smCmdFirstPointerFromMachHeader(machHeader);
if (cmdPointer == 0) {
return false;
}
//遍历每个 segment 判断目标地址是否落在该 segment 包含的范围里
for (uint32_t iCmd = 0; iCmd < machHeader->ncmds; iCmd++) {
const struct load_command* loadCmd = (struct load_command*)cmdPointer;
/*----------目标 Image 的符号表----------*/
//Segment 除了 __TEXT 和 __DATA 外还有 __LINKEDIT segment，它里面包含动态链接器的使用的原始数据，比如符号，字符串和重定位表项。
//LC_SYMTAB 描述了 __LINKEDIT segment 内查找字符串和符号表的位置
if (loadCmd->cmd == LC_SYMTAB) {
//获取字符串和符号表的虚拟内存偏移量。
const struct symtab_command* symtabCmd = (struct symtab_command*)cmdPointer;
const nlistByCPU* symbolTable = (nlistByCPU*)(segmentBase + symtabCmd->symoff);
const uintptr_t stringTable = segmentBase + symtabCmd->stroff;

for (uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++) {
//如果 n_value 是0，symbol 指向外部对象
if (symbolTable[iSym].n_value != 0) {
//给定的偏移量是文件偏移量，减去 __LINKEDIT segment 的文件偏移量获得字符串和符号表的虚拟内存偏移量
uintptr_t symbolBase = symbolTable[iSym].n_value;
uintptr_t currentDistance = addressWithSlide - symbolBase;
//寻找最小的距离 bestDistance，因为 addressWithSlide 是某个方法的指令地址，要大于这个方法的入口。
//离 addressWithSlide 越近的函数入口越匹配
if ((addressWithSlide >= symbolBase) && (currentDistance <= bestDistance)) {
bestMatch = symbolTable + iSym;
bestDistance = currentDistance;
}
}
}
if (bestMatch != NULL) {
//将虚拟内存偏移量添加到 __LINKEDIT segment 的虚拟内存地址可以提供字符串和符号表的内存 address。
info->dli_saddr = (void*)(bestMatch->n_value + imageVMAddressSlide);
info->dli_sname = (char*)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx);
if (*info->dli_sname == '_') {
info->dli_sname++;
}
//所有的 symbols 的已经被处理好了
if (info->dli_saddr == info->dli_fbase && bestMatch->n_type == 3) {
info->dli_sname = NULL;
}
break;
}
}
cmdPointer += loadCmd->cmdsize;
}

## 能够获取更多信息的方法

• 响应能力
• 按钮点击
• 手势操作
• Tab 切换
• vc 的切换和转场

hook c 函数可以使用 facebook 的 fishhook， 获取方法调用树状结构可以使用 InspectiveC，下面对于他们的实现详细介绍一下：

## 获取方法调用树结构

// Shared structures.
typedef struct CallRecord_ {
id obj;   //通过 object_getClass 能够得到 Class 再通过 NSStringFromClass 能够得到类名
SEL _cmd; //通过 NSStringFromSelector 方法能够得到方法名
uintptr_t lr;
int prevHitIndex;
char isWatchHit;
} CallRecord;

typedef struct ThreadCallStack_ {
FILE *file;
char *spacesStr;
CallRecord *stack;
int allocatedLength;
int index; //index 记录当前调用方法树的深度
int numWatchHits;
int lastPrintedIndex;
int lastHitIndex;
char isLoggingEnabled;
char isCompleteLoggingEnabled;
} ThreadCallStack;

## 存储读取 ThreadCallStack

pthread_setspecific() 可以将私有数据设置在指定线程上，pthread_getspecific() 用来读取这个私有数据，利用这个特性可以就可以将 ThreadCallStack 的数据和该线程绑定在一起，随时进行数据的存取。代码如下：

static inline ThreadCallStack * getThreadCallStack() {
ThreadCallStack *cs = (ThreadCallStack *)pthread_getspecific(threadKey); //读取
if (cs == NULL) {
cs = (ThreadCallStack *)malloc(sizeof(ThreadCallStack));
#ifdef MAIN_THREAD_ONLY
cs->file = (pthread_main_np()) ? newFileForThread() : NULL;
#else
cs->file = newFileForThread();
#endif
cs->isLoggingEnabled = (cs->file != NULL);
cs->isCompleteLoggingEnabled = 0;
cs->spacesStr = (char *)malloc(DEFAULT_CALLSTACK_DEPTH + 1);
memset(cs->spacesStr, ' ', DEFAULT_CALLSTACK_DEPTH);
cs->spacesStr[DEFAULT_CALLSTACK_DEPTH] = '\0';
cs->stack = (CallRecord *)calloc(DEFAULT_CALLSTACK_DEPTH, sizeof(CallRecord)); //分配 CallRecord 默认空间
cs->allocatedLength = DEFAULT_CALLSTACK_DEPTH;
cs->index = cs->lastPrintedIndex = cs->lastHitIndex = -1;
cs->numWatchHits = 0;
pthread_setspecific(threadKey, cs); //保存数据
}
return cs;
}

## 记录方法调用深度

//开始时
static inline void pushCallRecord(id obj, uintptr_t lr, SEL _cmd, ThreadCallStack *cs) {
int nextIndex = (++cs->index); //增加深度
if (nextIndex >= cs->allocatedLength) {
cs->allocatedLength += CALLSTACK_DEPTH_INCREMENT;
cs->stack = (CallRecord *)realloc(cs->stack, cs->allocatedLength * sizeof(CallRecord));
cs->spacesStr = (char *)realloc(cs->spacesStr, cs->allocatedLength + 1);
memset(cs->spacesStr, ' ', cs->allocatedLength);
cs->spacesStr[cs->allocatedLength] = '\0';
}
CallRecord *newRecord = &cs->stack[nextIndex];
newRecord->obj = obj;
newRecord->_cmd = _cmd;
newRecord->lr = lr;
newRecord->isWatchHit = 0;
}
//结束时
static inline CallRecord * popCallRecord(ThreadCallStack *cs) {
return &cs->stack[cs->index--]; //减少深度
}

## 在 objc_msgSend 前后插入执行方法

static void replacementObjc_msgSend() {
__asm__ volatile (
// sp 是堆栈寄存器，存放栈的偏移地址，每次都指向栈顶。
// 保存 {q0-q7} 偏移地址到 sp 寄存器
"stp q6, q7, [sp, #-32]!\n"
"stp q4, q5, [sp, #-32]!\n"
"stp q2, q3, [sp, #-32]!\n"
"stp q0, q1, [sp, #-32]!\n"
// 保存 {x0-x8, lr}
"stp x8, lr, [sp, #-16]!\n"
"stp x6, x7, [sp, #-16]!\n"
"stp x4, x5, [sp, #-16]!\n"
"stp x2, x3, [sp, #-16]!\n"
"stp x0, x1, [sp, #-16]!\n"
// 交换参数.
"mov x2, x1\n"
"mov x1, lr\n"
"mov x3, sp\n"
// 调用 preObjc_msgSend，使用 bl label 语法。bl 执行一个分支链接操作，label 是无条件分支的，是和本指令的地址偏移，范围是 -128MB 到 +128MB
"bl __Z15preObjc_msgSendP11objc_objectmP13objc_selectorP9RegState_\n"
"mov x9, x0\n"
"mov x10, x1\n"
"tst x10, x10\n"
// 读取 {x0-x8, lr} 从保存到 sp 栈顶的偏移地址读起
"ldp x0, x1, [sp], #16\n"
"ldp x2, x3, [sp], #16\n"
"ldp x4, x5, [sp], #16\n"
"ldp x6, x7, [sp], #16\n"
"ldp x8, lr, [sp], #16\n"
// 读取 {q0-q7}
"ldp q0, q1, [sp], #32\n"
"ldp q2, q3, [sp], #32\n"
"ldp q4, q5, [sp], #32\n"
"ldp q6, q7, [sp], #32\n"
"b.eq Lpassthrough\n"
// 调用原始 objc_msgSend。使用 blr xn 语法。blr 除了从指定寄存器读取新的 PC 值外效果和 bl 一样。xn 是通用寄存器的64位名称分支地址，范围0到31
"blr x9\n"
// 保存 {x0-x9}
"stp x0, x1, [sp, #-16]!\n"
"stp x2, x3, [sp, #-16]!\n"
"stp x4, x5, [sp, #-16]!\n"
"stp x6, x7, [sp, #-16]!\n"
"stp x8, x9, [sp, #-16]!\n"
// 保存 {q0-q7}
"stp q0, q1, [sp, #-32]!\n"
"stp q2, q3, [sp, #-32]!\n"
"stp q4, q5, [sp, #-32]!\n"
"stp q6, q7, [sp, #-32]!\n"
// 调用 postObjc_msgSend hook.
"bl __Z16postObjc_msgSendv\n"
"mov lr, x0\n"
// 读取 {q0-q7}
"ldp q6, q7, [sp], #32\n"
"ldp q4, q5, [sp], #32\n"
"ldp q2, q3, [sp], #32\n"
"ldp q0, q1, [sp], #32\n"
// 读取 {x0-x9}
"ldp x8, x9, [sp], #16\n"
"ldp x6, x7, [sp], #16\n"
"ldp x4, x5, [sp], #16\n"
"ldp x2, x3, [sp], #16\n"
"ldp x0, x1, [sp], #16\n"
"ret\n"
"Lpassthrough:\n"
// br 无条件分支到寄存器中的地址
"br x9"
);
}

## 记录时间的方法

NSDate* tmpStartData = [NSDate date];
//some code need caculate
double deltaTime = [[NSDate date] timeIntervalSinceDate:tmpStartData];
NSLog(@"cost time: %f s", deltaTime);

clock_t start = clock();
//some code need caculate
clock_t end = clock();
NSLog(@"cost time: %f s", (double)(end - start)/CLOCKS_PER_SEC);

CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
//some code need caculate
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"cost time = %f s", end - start); //s


CFTimeInterval start = CACurrentMediaTime();
//some code need caculate
CFTimeInterval end = CACurrentMediaTime();
NSLog(@"cost time: %f s", end - start);

uint64_t start = mach_absolute_time ();
//some code need caculate
uint64_t end = mach_absolute_time ();
uint64_t elapsed = 1e-9 *(end - start);

NSDate 或 CFAbsoluteTimeGetCurrent() 返回的时钟时间将会会网络时间同步，从时钟 偏移量的角度。mach_absolute_time() 和 CACurrentMediaTime() 是基于内建时钟的。选择一种，加到 pushCallRecord 和 popCallRecord 里，相减就能够获得耗时。

## 遍历 dyld

if (!_rebindings_head->next) {
_dyld_register_func_for_add_image(_rebind_symbols_for_image);
} else {
uint32_t c = _dyld_image_count();
for (uint32_t i = 0; i < c; i++) {
_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
}
}

## 找出符号表相关 Command

segment_command_t *cur_seg_cmd;
segment_command_t *linkedit_segment = NULL;
struct symtab_command* symtab_cmd = NULL;
struct dysymtab_command* dysymtab_cmd = NULL;

uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
linkedit_segment = cur_seg_cmd;
}
} else if (cur_seg_cmd->cmd == LC_SYMTAB) {
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
}
}

## 获得 base 和 indirect 符号表

// Find base symbol/string table addresses
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);

// Get indirect symbol table (array of uint32_t indices into symbol table)
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);

## 进行方法替换

uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
for (uint i = 0; i < section->size / sizeof(void *); i++) {
uint32_t symtab_index = indirect_symbol_indices[i];
if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
symtab_index == (INDIRECT_SYMBOL_LOCAL   | INDIRECT_SYMBOL_ABS)) {
continue;
}
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
char *symbol_name = strtab + strtab_offset;
if (strnlen(symbol_name, 2) < 2) {
continue;
}
struct rebindings_entry *cur = rebindings;
while (cur) {
for (uint j = 0; j < cur->rebindings_nel; j++) {
if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
if (cur->rebindings[j].replaced != NULL &&
indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
}
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
goto symbol_loop;
}
}
cur = cur->next;
}
symbol_loop:;

## 设计方法调用频次记录的结构

 (nonatomic, strong) NSString *className;       //类名
(nonatomic, strong) NSString *methodName;      //方法名
(nonatomic, assign) BOOL isClassMethod;        //是否是类方法
(nonatomic, assign) NSTimeInterval timeCost;   //时间消耗
(nonatomic, assign) NSUInteger callDepth;      //Call 层级
(nonatomic, copy) NSString *path;              //路径
(nonatomic, assign) BOOL lastCall;             //是否是最后一个 Call
(nonatomic, assign) NSUInteger frequency;      //访问频次
(nonatomic, strong) NSArray <SMCallTraceTimeCostModel *> *subCosts;

## 拼装方法路径

for (SMCallTraceTimeCostModel *model in arr) {
//记录方法路径
model.path = [NSString stringWithFormat:@"[%@ %@]",model.className,model.methodName];
[self appendRecord:model to:mStr];
}

+ (void)appendRecord:(SMCallTraceTimeCostModel *)cost to:(NSMutableString *)mStr {
[mStr appendFormat:@"%@\n path%@\n",[cost des],cost.path];
if (cost.subCosts.count < 1) {
cost.lastCall = YES;
}
//记录到数据库中
[[[SMLagDB shareInstance] increaseWithClsCallModel:cost] subscribeNext:^(id x) {}];
for (SMCallTraceTimeCostModel *model in cost.subCosts) {
//记录方法的子方法的路径
model.path = [NSString stringWithFormat:@"%@ - [%@ %@]",cost.path,model.className,model.methodName];
[self appendRecord:model to:mStr];
}
}

## 记录方法调用频次数据库

### 创建数据库

_clsCallDBPath = [PATH_OF_DOCUMENT stringByAppendingPathComponent:@"clsCall.sqlite"];
if ([[NSFileManager defaultManager] fileExistsAtPath:_clsCallDBPath] == NO) {
FMDatabase *db = [FMDatabase databaseWithPath:_clsCallDBPath];
if ([db open]) {
/*
cid: 主id
fid: 父id 暂时不用
cls: 类名
mtd: 方法名
path: 完整路径标识
timecost: 方法消耗时长
calldepth: 层级
frequency: 调用次数
lastcall: 是否是最后一个 call
*/
NSString *createSql = @"create table clscall (cid INTEGER PRIMARY KEY AUTOINCREMENT  NOT NULL, fid integer, cls text, mtd text, path text, timecost integer, calldepth integer, frequency integer, lastcall integer)";
[db executeUpdate:createSql];
}
}

### 添加记录

FMResultSet *rsl = [db executeQuery:@"select cid,frequency from clscall where path = ?", model.path];
if ([rsl next]) {
//有相同路径就更新路径访问频率
int fq = [rsl intForColumn:@"frequency"] + 1;
int cid = [rsl intForColumn:@"cid"];
[db executeUpdate:@"update clscall set frequency = ? where cid = ?", @(fq), @(cid)];
} else {
//没有就添加一条记录
NSNumber *lastCall =
if (model.lastCall) {
lastCall =
}
[db executeUpdate:@"insert into clscall (cls, mtd, path, timecost, calldepth, frequency, lastcall) values (?, ?, ?, ?, ?, ?, ?)", model.className, model.methodName, model.path, @(model.timeCost), @(model.callDepth), , lastCall];
}
[db close];
[subscriber sendCompleted];

### 检索记录

FMResultSet *rs = [db executeQuery:@"select * from clscall where lastcall=? order by frequency desc limit ?, 50",, @(page * 50)];
NSUInteger count = 0;
NSMutableArray *arr = [NSMutableArray array];
while ([rs next]) {
SMCallTraceTimeCostModel *model = [self clsCallModelFromResultSet:rs];
[arr addObject:model];
count ++;
}
if (count > 0) {
[subscriber sendNext:arr];
} else {
[subscriber sendError:nil];
}
[subscriber sendCompleted];
[db close];

## 找出 CPU 使用大的线程堆栈

//轮询检查多个线程 cpu 情况
+ (void)updateCPU {
thread_act_array_t threads;
mach_msg_type_number_t threadCount = 0;
const task_t thisTask = mach_task_self();
kern_return_t kr = task_threads(thisTask, &threads, &threadCount);
if (kr != KERN_SUCCESS) {
return;
}
for (int i = 0; i < threadCount; i++) {
thread_info_data_t threadInfo;
thread_basic_info_t threadBaseInfo;
mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;
if (thread_info((thread_act_t)threads[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount) == KERN_SUCCESS) {
threadBaseInfo = (thread_basic_info_t)threadInfo;
if (!(threadBaseInfo->flags & TH_FLAGS_IDLE)) {
integer_t cpuUsage = threadBaseInfo->cpu_usage / 10;
if (cpuUsage > 70) {
//cup 消耗大于 70 时打印和记录堆栈
NSString *reStr = smStackOfThread(threads[i]);
//记录数据库中
[[[SMLagDB shareInstance] increaseWithStackString:reStr] subscribeNext:^(id x) {}];
NSLog(@"CPU useage overload thread stack：\n%@",reStr);
}
}
}
}
}

## Demo

• 子线程检测主线程卡顿使用的话在需要开始检测的地方添加 [[SMLagMonitor shareInstance] beginMonitor]; 即可。
• 需要检测所有方法调用的用法就是在需要检测的地方调用 [SMCallTrace start]; 就可以了，不检测打印出结果的话调用 stop 和 save 就好了。这里还可以设置最大深度和最小耗时检测来过滤不需要看到的信息。
• 方法调用频次使用可以在需要开始统计的地方加上 [SMCallTrace startWithMaxDepth:3]; 记录时使用 [SMCallTrace stopSaveAndClean]; 记录到到数据库中同时清理内存的占用。可以 hook VC 的 viewWillAppear 和 viewWillDisappear，在 appear 时开始记录，在 disappear 时记录到数据库同时清理一次。结果展示的 view controller 是 SMClsCallViewController，push 出来就能够看到列表结果。

## WWDC

• WWDC 2013 224 Designing Code for Performance
• WWDC 2013 408 Optimizing Your Code Using LLVM
• WWDC 2013 712 Energy Best Practices
• WWDC 2014 710 writing energy efficient code part 1
• WWDC 2014 710 writing energy efficient code part 2
• WWDC 2015 230 performance on ios and watchos
• WWDC 2015 707 achieving allday battery life
• WWDC 2015 708 debugging energy issues
• WWDC 2015 718 building responsive and efficient apps with gcd
• WWDC 2016 406 optimizing app startup time
• WWDC 2016 719 optimizing io for performance and battery life
• WWDC 2017 238 writing energy efficient apps
• WWDC 2017 706 modernizing grand central dispatch usage
© 著作权归作者所有

iOS开发，个人工作和兴趣的记录。 作者介绍： 前滴滴出行技术专家。极客时间《iOS开发高手课》和纸书《跟戴铭学...
4条评论
#1

Jersey
#2

containObject那几个接口的时间复杂度是不会大于O(n)的吧，应该是小于等于吧，当然一般说法是等于O(n)