
Несмотря на то, что APM становится все более популярным, профессиональные производители APM всех размеров появлялись как грибы после дождя, и есть также много технических статей на APM на рынке, большинство из них являются просто простым вкусом и не копаются глубоко в деталях реализации. Эта статья направлена на то, чтобы выявить внутренний принцип работы IOS SDK известных производителей APM путем анализа конкретных деталей реализации SDK. Я полагаю, что перед прочтением этой статьи читателям также было любопытно о деталях реализации APM SDK, как и автор. К счастью, статья, которую вы читаете, понадобится вам, чтобы раскрыть истинный контекст APM шаг за шагом. APM SDK, проанализированные в этой статье, включают в себя мониторинг производительности Tingyun , OneApm и Firebase и т. Д. Автор немного талантлив и знает. Если есть какие -либо ошибки, он не исправит их, чтобы перепечатать их и сделать их более совершенными.
Версия Tingyun SDK, проанализированная в этой статье, составляет 2.3.5, что немного отличается от последней версии. Тем не менее, я грубо читаю код новой версии, но разница не большая и не влияет на анализ.
Мониторинг рендеринга страницы кажется очень простым, но в реальном процессе разработки все еще будет много проблем. О том, что легче придумать,-это несколько ключевых методов жизненного цикла, таких как viewDidLoad , viewDidAppear: и т. Д., Для расчета времени рендеринга страницы и, наконец, найти медленную страницу. Однако, если вы действительно начнете достигать этого с помощью вышеуказанных идей, вы столкнетесь с трудностями. Как я могу применить жизненный цикл всех страниц в APM SDK? Что если я попытаюсь зацепить UIViewController ? Метод Hook UIViewController очевидно, невозможно, потому что он работает только на методе UIViewController , и большинство контроллеров представления в приложении наследуют от UIViewController , поэтому этот метод невозможным. Но Tingyun SDK может быть реализован. Логика крючка страницы в основном реализована в классе _priv_NBSUIAgent . Ниже приведено определение класса _priv_NBSUIAgent , среди которых hook_viewDidLoad и другие методы являются подсказками.
@class _priv_NBSUIAgent : NSObject {
+hookUIImage
+hookNSManagedObjectContext
+hookNSJSONSerialization
+hookNSData
+hookNSArray
+hookNSDictionary
+hook_viewDidLoad:
+hook_viewWillAppear:
+hook_viewDidAppear:
+hook_viewWillLayoutSubviews:
+hook_viewDidLayoutSubviews:
+nbs_jump_initialize:
+hookSubOfController
+hookFMDB
+start
}
Давайте сначала обратим наше внимание на еще один более подозрительный метод: hookSubOfController , конкретная реализация заключается в следующем:
void +[_priv_NBSUIAgent hookSubOfController](void * self, void * _cmd) {
r14 = self;
r12 = [_subMetaClassNamesInMainBundle_c("UIViewController") retain];
var_C0 = r12;
if ((r12 != 0x0) && ([r12 count] != 0x0)) {
var_C8 = object_getClass(r14);
if ([r12 count] != 0x0) {
r15 = @selector(nbs_jump_initialize:);
rdx = 0x0;
do {
var_98 = rdx;
r12 = [[r12 objectAtIndexedSubscript:rdx, rcx, r8] retain];
[r12 release];
if ([r12 respondsToSelector:r15, rcx, r8] == 0x0) {
_hookClass_CopyAMetaMethod();
}
r13 = class_getName(r12);
rax = [NSString stringWithFormat:@"nbs_%s_initialize", r13];
rax = [rax retain];
var_A0 = rax;
rax = NSSelectorFromString(rax);
var_B0 = rax;
rax = objc_retainBlock(__NSConcreteStackBlock);
var_A8 = rax;
r15 = objc_retainBlock(rax);
var_B8 = imp_implementationWithBlock(r15);
[r15 release];
rax = class_getSuperclass(r12);
r15 = objc_retainBlock(__NSConcreteStackBlock);
rbx = objc_retainBlock(r15);
r13 = imp_implementationWithBlock(rbx);
[rbx release];
rcx = r13;
r8 = var_B8;
_nbs_Swizzle_orReplaceWithIMPs(r12, @selector(initialize), var_B0, rcx, r8);
rdi = r15;
r15 = @selector(nbs_jump_initialize:);
[rdi release];
[var_A8 release];
[var_A0 release];
rax = [var_C0 count];
r12 = var_C0;
rdx = var_98 + 0x1;
} while (var_98 + 0x1 < rax);
}
}
[r12 release];
return;
}
Из именования _subMetaClassNamesInMainBundle_c и входящего параметра «uiviewcontroller» можно сделать вывод, что эта функция C является подклассом всех UIViewController в Mainbundle. На самом деле, если вы остановите линию сборочной линии после того, как функциональный вызов будет завершен через LLDB, вы обнаружите, что возвращенный массив действительно является подклассом UIViewController . Следующий оператор if определяет, что регистр r12 не является nil , а count регистра r12 не равна 0 до выполнения логики, if будет выполнена. Регистр r12 сохраняет возвращаемое значение функции _subMetaClassNamesInMainBundle_c , которая является массивом подкласса UIViewController .
Код функции _subMetaClassNamesInMainBundle_c выглядит следующим образом:
void _subMetaClassNamesInMainBundle_c(int arg0) {
rbx = objc_getClass(arg0);
rdi = 0x0;
if (rbx == 0x0) goto loc_10001dbde;
loc_10001db4d:
r15 = _classNamesInMainBundle_c(var_2C);
var_38 = [NSMutableArray new];
if (var_2C == 0x0) goto loc_10001dbd2;
loc_10001db77:
r14 = 0x0;
goto loc_10001db7a;
loc_10001db7a:
r13 = objc_getClass(*(r15 + r14 * 0x8));
r12 = r13;
if (r13 == 0x0) goto loc_10001dbc9;
loc_10001db8e:
rax = class_getSuperclass(r12);
if (rax == rbx) goto loc_10001dba5;
loc_10001db9b:
COND = rax != r12;
r12 = rax;
if (COND) goto loc_10001db8e;
loc_10001dbc9:
r14 = r14 + 0x1;
if (r14 < var_2C) goto loc_10001db7a;
loc_10001dbd2:
free(r15);
rdi = var_38;
goto loc_10001dbde;
loc_10001dbde:
[rdi autorelease];
return;
loc_10001dba5:
rax = class_getName(r13);
rax = objc_getMetaClass(rax);
[var_38 addObject:rax];
goto loc_10001dbc9;
}
Подпрограмма loc_10001db4d в функции _subMetaClassNamesInMainBundle_c вызывает функцию _classNamesInMainBundle_c , а код функции выглядит следующим образом:
int _classNamesInMainBundle_c(int arg0) {
rbx = [[NSBundle mainBundle] retain];
r15 = [[rbx executablePath] retain];
[rbx release];
rbx = objc_retainAutorelease(r15);
r14 = objc_copyClassNamesForImage([rbx UTF8String], arg0);
[rbx release];
rax = r14;
return rax;
}
Реализация функции _classNamesInMainBundle_c очевидна, это не что иное, как называть objc_copyClassNamesForImage , чтобы получить имена всех классов исполняемого пути mainBundle . Количество наборов присваивается переменной outCount , и вызывающий абонент может использовать outCount для ее прохождения.
static inline char ** WDTClassNamesInMainBundle ( unsigned int *outCount) {
NSString *executablePath = [[ NSBundle mainBundle ] executablePath ];
char **classNames = objc_copyClassNamesForImage ([executablePath UTF8String ], outCount);
return classNames;
} Если вас не заботятся о деталях, то реализация функции _subMetaClassNamesInMainBundle_c также очень ясна, которая заключается в том, чтобы пройти возвратное значение функции objc_copyClassNamesForImage . Если элемент является подклассом UIViewController , то metaClass класса получается и добавляется в массив переменных var_38 .
Далее, давайте сосредоточимся на заявлении do-while LOOP внутри. Заявление для суждения цикла составляет var_98 + 0x1 < rax . var_98 назначает регистр rdx в начале цикла, а регистр rdx инициализируется до 0 за пределами цикла, поэтому var_98 является счетчиком, а реестр rax - это метод count назначенный реестру r12 . Основываясь на этом do-while фактически пересекает массив подкласса UIViewController . Поведение прохождения заключается в обмене initialize и nbs_jump_initialize: Методы через _nbs_Swizzle_orReplaceWithIMPs .
Код для nbs_jump_initialize выглядит следующим образом:
void +[_priv_NBSUIAgent nbs_jump_initialize:](void * self, void * _cmd, void * arg2) {
rbx = arg2;
r15 = self;
r14 = [NSStringFromSelector(rbx) retain];
if ((r14 != 0x0) && ([r14 isEqualToString:@""] == 0x0)) {
[r15 class];
rax = _nbs_getClassImpOf();
(rax)(r15, @selector(initialize));
}
rax = class_getName(r15);
r13 = [[NSString stringWithUTF8String:rax] retain];
rdx = @"_Aspects_";
if ([r13 hasSuffix:rdx] == 0x0) goto loc_100050137;
loc_10005011e:
if (*(int8_t *)_is_tiaoshi_kai == 0x0) goto loc_100050218;
loc_10005012e:
rsi = cfstring__V__A;
goto loc_100050195;
loc_100050195:
__NBSDebugLog(0x3, rsi, rdx, rcx, r8, r9, stack[2048]);
goto loc_100050218;
loc_100050218:
[r13 release];
rdi = r14;
[rdi release];
return;
loc_100050137:
rdx = @"RACSelectorSignal";
if ([r13 hasSuffix:rdx] == 0x0) goto loc_10005016b;
loc_100050152:
if (*(int8_t *)_is_tiaoshi_kai == 0x0) goto loc_100050218;
loc_100050162:
rsi = cfstring__V__R;
goto loc_100050195;
loc_10005016b:
if (_classSelf_isImpOf(r15, "nbs_vc_flag") == 0x0) goto loc_1000501a3;
loc_10005017e:
if (*(int8_t *)_is_tiaoshi_kai == 0x0) goto loc_100050218;
loc_10005018e:
rsi = cfstring____Yh;
goto loc_100050195;
loc_1000501a3:
rbx = objc_retainBlock(void ^(void * _block, void * arg1) {
return;
});
rax = imp_implementationWithBlock(rbx);
class_addMethod(r15, @selector(nbs_vc_flag), rax, "v@:");
[rbx release];
[_priv_NBSUIAgent hook_viewDidLoad:r15];
[_priv_NBSUIAgent hook_viewWillAppear:r15];
[_priv_NBSUIAgent hook_viewDidAppear:r15];
goto loc_100050218;
}
Код nbs_jump_initialize немного длинный, но из подпрограммы loc_1000501a3 можно заметить, что основная логика выполнит три метода: hook_viewDidLoad , hook_viewWillAppear и hook_viewDidAppear , тем самым подключая эти три метода подкласса UIViewController .
Во -первых, используйте hook_viewDidLoad: метод в качестве примера для объяснения. Следующий код может быть немного неясным и требует тщательного анализа.
void +[_priv_NBSUIAgent hook_viewDidLoad:](void * self, void * _cmd, void * arg2) {
rax = [_priv_NBSUIHookMatrix class];
var_D8 = _nbs_getInstanceImpOf();
var_D0 = _nbs_getInstanceImpOf();
rbx = class_getName(arg2);
r14 = class_getSuperclass(arg2);
rax = [NSString stringWithFormat:@"nbs_%s_viewDidLoad", rbx];
rax = [rax retain];
var_B8 = rax;
var_C0 = NSSelectorFromString(rax);
r12 = objc_retainBlock(__NSConcreteStackBlock);
var_D0 = imp_implementationWithBlock(r12);
[r12 release];
rbx = objc_retainBlock(__NSConcreteStackBlock);
r14 = imp_implementationWithBlock(rbx);
[rbx release];
_nbs_Swizzle_orReplaceWithIMPs(arg2, @selector(viewDidLoad), var_C0, r14, var_D0);
[var_B8 release];
return;
}
hook_viewDidLoad: параметр arg2 в методе - это класс ViewController to Hook, получить имя класса arg2 и назначить его регистру rbx , а затем используйте rbx для построения строки nbs_%s_viewDidLoad , например, nbs_XXViewController_viewDidLoad , получить селектор строки и присвоения IT для var_C0 . __NSConcreteStackBlock в следующих предложениях является объектом блока созданного стека хранения. Этот блок затем получит указатель функции IMP через метод imp_implementationWithBlock блоком. _nbs_Swizzle_orReplaceWithIMPs - это функция, которая реализует обмен методом, и параметры: arg2 - это класс ViewController ; @selector(viewDidLoad) - селектор viewDidLoad ; var_C0 - это селектор nbs_%s_viewDidLoad , r14 - это IMP второго __NSConcreteStackBlock ; var_D0 - это IMP первого __NSConcreteStackBlock .
Вся логика hook_viewDidLoad: примерно ясна, но здесь есть вопрос, почему бы не прямо обмениваться двумя беспами, а сначала построить два блока, а затем обменивать им беседы двух блоков? Причина в том, что результат родительского класса ViewController , то есть class_getSuperclass , должен быть передан в качестве параметров обмену метода. Таким образом, количество параметров, подписанных двумя обмененными селекторами, противоречиво, и эту проблему необходимо решить умно, построив блок. Фактически, первый __NSConcreteStackBlock выполняет nbs_jump_viewDidLoad:superClass: метод _priv_NBSUIHookMatrix . Как упоминалось ранее, в параметрах этого метода есть superClass . Что касается того, почему этот параметр необходим, я представлю его позже.
Почему второй __NSConcreteStackBlock выполняет nbs_jump_viewDidLoad:superClass: Метод? Снимите опцию Remove potentially dead code бункера, код заключается в следующем:
void +[_priv_NBSUIAgent hook_viewDidLoad:](void * self, void * _cmd, void * arg2) {
rsi = _cmd;
rdi = self;
r12 = _objc_msgSend;
rax = [_priv_NBSUIHookMatrix class];
rsi = @selector(nbs_jump_viewDidLoad:superClass:);
rdi = rax;
var_D8 = _nbs_getInstanceImpOf();
rdi = arg2;
rsi = @selector(viewDidLoad);
var_D0 = _nbs_getInstanceImpOf();
rbx = class_getName(arg2);
r14 = class_getSuperclass(arg2);
LODWORD(rax) = 0x0;
rax = [NSString stringWithFormat:@"nbs_%s_viewDidLoad", rbx];
rax = [rax retain];
var_B8 = rax;
var_C0 = NSSelectorFromString(rax);
var_60 = 0xc0000000;
var_5C = 0x0;
var_58 = ___37+[_priv_NBSUIAgent hook_viewDidLoad:]_block_invoke;
var_50 = ___block_descriptor_tmp;
var_48 = var_D8;
var_40 = @selector(viewDidLoad);
var_38 = var_D0;
var_30 = r14;
r12 = objc_retainBlock(__NSConcreteStackBlock);
var_D0 = imp_implementationWithBlock(r12);
r13 = _objc_release;
rax = [r12 release];
var_A8 = 0xc0000000;
var_A4 = 0x0;
var_A0 = ___37+[_priv_NBSUIAgent hook_viewDidLoad:]_block_invoke_2;
var_98 = ___block_descriptor_tmp47;
var_90 = rbx;
var_88 = var_D8;
var_80 = @selector(viewDidLoad);
var_78 = r14;
var_70 = arg2;
rbx = objc_retainBlock(__NSConcreteStackBlock);
r14 = imp_implementationWithBlock(rbx);
rax = [rbx release];
rax = _nbs_Swizzle_orReplaceWithIMPs(arg2, @selector(viewDidLoad), var_C0, r14, var_D0);
rax = [var_B8 release];
rsp = rsp + 0xb8;
rbx = stack[2047];
r12 = stack[2046];
r13 = stack[2045];
r14 = stack[2044];
r15 = stack[2043];
rbp = stack[2042];
return;
}
Давайте посмотрим на код _nbs_getInstanceImpOf :
void _nbs_getInstanceImpOf() {
rax = class_getInstanceMethod(rdi, rsi);
method_getImplementation(rax);
return;
}
Функция функции _nbs_getInstanceImpOf очень очевидна. Чтобы получить IMP селектора rsi в классе rdi , читатели обнаружит, что _nbs_getInstanceImpOf был дважды вызов в методе hook_viewDidLoad: :. Первым rdi является класс _priv_NBSUIHookMatrix , rdx - это @selector(nbs_jump_viewDidLoad:superClass:) , второй rdi - это класс ViewController , rdx - @selector(viewDidLoad) .
Далее, давайте посмотрим на первый __NSConcreteStackBlock , что означает блок, который будет вызывать nbs_jump_viewDidLoad:superClass: Код заключается в следующем:
int ___37+[_priv_NBSUIAgent hook_viewDidLoad:]_block_invoke(int arg0, int arg1) {
r8 = *(arg0 + 0x20);
rax = *(arg0 + 0x28);
rdx = *(arg0 + 0x30);
rcx = *(arg0 + 0x38);
rax = (r8)(arg1, rax, rdx, rcx, r8);
return rax;
}
Регистр r8 - это IMP of nbs_jump_viewDidLoad:superClass: и этот код просто вызывает этот IMP. Параметры функции IMP совпадают с nbs_jump_viewDidLoad:superClass: :.
void -[_priv_NBSUIHookMatrix nbs_jump_viewDidLoad:superClass:](void * self, void * _cmd, void * * arg2, void * arg3) {
rbx = arg3;
var_70 = arg2;
var_68 = _cmd;
r14 = self;
rax = [self class];
rax = class_getSuperclass(rax);
if ((rbx != 0x0) && (rax != rbx)) {
rax = var_70;
if (rax != 0x0) {
rdi = r14;
(rax)(rdi, @selector(viewDidLoad));
}
else {
NSLog(@"");
[[r14 super] viewDidLoad];
}
}
else {
var_B8 = rbx;
objc_storeWeak(_currentViewController, 0x0);
r14 = 0x0;
[[NSString stringWithFormat:@"%d#loading", 0x0] retain];
r12 = 0x0;
if (0x0 != 0x0) {
rcx = class_getName([r12 class]);
r14 = [[NSString stringWithFormat:@"MobileView/Controller/%s#%@", rcx, @"loading"] retain];
}
var_A0 = r14;
r14 = [[_priv_NBSUILogCenter_assistant alloc] initWithControllerName:r14];
var_80 = r14;
var_60 = _objc_release;
[r14 setTheVC:_objc_release];
[r14 setVC_Address:_objc_release];
[r14 setIsOther:0x0];
[*_controllerStack push:r14];
rbx = [_glb_all_activing_VCS() retain];
var_98 = _objc_msgSend;
[rbx setObject:r14 forKey:_objc_msgSend];
[rbx release];
r12 = [[NSDate date] retain];
[r12 timeIntervalSince1970];
xmm0 = intrinsic_mulsd(xmm0, *0x100066938);
rbx = intrinsic_cvttsd2si(rbx, xmm0);
[r12 release];
[r14 setStartTime:rbx];
rcx = class_getName([var_60 class]);
r13 = [[NSString stringWithFormat:@"%s", rcx] retain];
r14 = [NSStringFromSelector(var_68) retain];
var_88 = [_nbs_embedIn_start() retain];
[r14 release];
[r13 release];
rbx = [[NBSLensInterfaceEventLogger shareObject] retain];
var_78 = rbx;
rax = [NBSLensUITraceSegment new];
var_58 = rax;
rbx = [[rbx theStack] retain];
[rbx push:rax];
[rbx release];
rcx = class_getName([var_60 class]);
r13 = [[NSString stringWithFormat:@"%s", rcx] retain];
r12 = [NSStringFromSelector(var_68) retain];
r14 = [[NSString stringWithFormat:@"%@#%@", r13, r12] retain];
var_A8 = r14;
[r12 release];
rdi = r13;
[rdi release];
[var_58 setSegmentName:r14];
rax = [NSDictionary dictionary];
rax = [rax retain];
var_B0 = rax;
[var_58 setSegmentParam:rax];
rbx = [[NSThread currentThread] retain];
rdx = rbx;
[var_58 setThreadInfomation:rdx];
[rbx release];
rbx = [[NSDate date] retain];
[rbx timeIntervalSince1970];
xmm0 = intrinsic_mulsd(xmm0, *0x100066938);
var_68 = intrinsic_movsd(var_68, xmm0);
[rbx release];
xmm0 = intrinsic_movsd(xmm0, var_68);
[var_58 setStartTime:rdx];
[var_58 setEntryTime:0x0];
r14 = [NBSLensUITraceSegment new];
var_90 = r14;
xmm0 = intrinsic_movsd(xmm0, var_68);
[r14 setStartTime:0x0];
rcx = class_getName([var_60 class]);
r15 = [[NSString stringWithFormat:@"%s", rcx] retain];
rbx = [[NSString stringWithFormat:@"%@#viewLoading", r15] retain];
[r14 setSegmentName:rbx];
[rbx release];
[r15 release];
rcx = var_30;
rax = [NSDictionary dictionaryWithObjects:rbx forKeys:rcx count:0x0];
[r14 setSegmentParam:rax];
rbx = [[NSThread currentThread] retain];
[r14 setThreadInfomation:rbx];
[rbx release];
[r14 setEntryTime:0x0];
rax = var_70;
if (rax != 0x0) {
(rax)(var_60, @selector(viewDidLoad), 0x0, rcx, 0x0);
}
else {
NSLog(@"");
[[var_60 super] viewDidLoad];
}
_nbs_embedIn_finish();
rdx = [var_88 mach_tm2];
[var_80 setFinishTime:rdx];
rbx = [[NSDate date] retain];
[rbx timeIntervalSince1970];
xmm0 = intrinsic_mulsd(xmm0, *0x100066938);
var_70 = intrinsic_movsd(var_70, xmm0);
[rbx release];
xmm0 = intrinsic_movsd(xmm0, var_70);
xmm0 = intrinsic_subsd(xmm0, var_68);
rdx = intrinsic_cvttsd2si(rdx, xmm0);
[var_58 setExitTime:rdx];
rbx = [[var_78 theStack] retain];
rax = [rbx pop];
rax = [rax retain];
[rax release];
[rbx release];
rbx = [[var_78 theStack] retain];
r15 = [rbx isEmpty];
[rbx release];
if (r15 == 0x0) {
rbx = [[var_78 theStack] retain];
r14 = [[rbx peer] retain];
[rbx release];
[r14 startTime];
xmm1 = intrinsic_movsd(xmm1, var_68);
xmm1 = intrinsic_subsd(xmm1, xmm0);
rdx = intrinsic_cvttsd2si(rdx, xmm1);
[var_58 setEntryTime:rdx];
[r14 startTime];
rdx = intrinsic_cvttsd2si(rdx, intrinsic_subsd(intrinsic_movsd(xmm1, var_70), xmm0));
[var_58 setExitTime:rdx];
rbx = [[r14 childSegments] retain];
rdx = var_58;
[rbx addObject:rdx];
[rbx release];
[r14 release];
}
rbx = [[var_90 childSegments] retain];
[rbx addObject:var_58];
[rbx release];
objc_setAssociatedObject(var_60, @"viewLoading", var_90, 0x1);
rax = [*_controllerStack pop];
rax = [rax retain];
[rax release];
rbx = [[_priv_NBSLENS_VCSBuffer sharedObj] retain];
[rbx addObj:var_80];
[rbx release];
rbx = [_glb_all_activing_VCS() retain];
[rbx removeObjectForKey:var_98];
[rbx release];
[var_90 release];
[var_B0 release];
[var_A8 release];
[var_58 release];
[var_78 release];
[var_88 release];
[var_80 release];
[var_A0 release];
[var_98 release];
}
return;
}
Время запуска объясняется примером мониторинга производительности Firebase SDK. FPM SDK используется в качестве аббревиатуры для его описания. FPM SDK реализует статистику о холодном времени запуска, а основная логика реализована в классе FPRAppActivityTracker .
Сначала посмотрите на метод +load класса, и код декомпиляции выглядит следующим образом:
void +[FPRAppActivityTracker load](void * self, void * _cmd) {
rax = [NSDate date];
rax = [rax retain];
rdi = *_appStartTime;
*_appStartTime = rax;
[rdi release];
rbx = [[NSNotificationCenter defaultCenter] retain];
[rbx addObserver:self selector:@selector(windowDidBecomeVisible:) name:*_UIWindowDidBecomeVisibleNotification object:0x0];
rdi = rbx;
[rdi release];
return;
}
Очевидно, что _appStartTime - это статический экземпляр NSDate , используемый для сохранения времени запуска всего запуска приложения, поэтому FPM SDK отмечает время запуска запуска приложения в +load FPRAppActivityTracker . Читатели, которые понимают метод +load должны знать, что метод представляет собой метод крюка до того, как будет вызвана main функция. Точное время, когда изображение загружается в время выполнения, и метод +load готов, а затем метод +load будет вызвана. Кроме того, различные типы методов +load также связаны с порядок файла Build Phases->Compile Sources . Мы считаем, что они не оказывают существенного влияния на статистику времени запуска.
После этого зарегистрировано уведомление о UIWindowDidBecomeVisibleNotification . Это уведомление запускается, когда объект UIWindow активируется и отображается на интерфейсе. Читатели могут зарегистрировать это уведомление, а затем распечатать объект уведомления с помощью LLDB. Пример заключается в следующем:
NSConcreteNotification 0x7fc94a716f50 {name = UIWindowDidBecomeVisibleNotification; object = <UIStatusBarWindow: 0x7fc94a5092a0; frame = (0 0; 320 568); opaque = NO; gestureRecognizers = <NSArray: 0x7fc94a619f30>; layer = <UIWindowLayer: 0x7fc94a513f50>>}
В первый раз, когда я получил уведомление UIWindowDidBecomeVisibleNotification , было раньше, чем - application:didFinishLaunchingWithOptions: обратный вызов, это уведомление было запускается, когда было создано window строки состояния. Эта реализация кажется немного сложной и не может гарантировать, что Apple скорректирует время вызова в будущем.
Ниже приведено официальное описание UIWindowDidBecomeVisibleNotification .
Опубликовано, когда становится видимым объект UIWindow. Объект уведомления - это окно -объект, который стал видимым. Это уведомление не содержит словаря пользователя. Переключение между приложениями не генерирует уведомления, связанные с видимостью для Windows. Изменения видимости окна отражают изменения в скрытом свойстве окна и отражают только видимость окна в приложении.
Ниже приведен метод обработки уведомлений. Я восстановил метод псевдокода Objective-C, который может сравнить декомпилированный псевдокод.
void +[FPRAppActivityTracker windowDidBecomeVisible:](void * self, void * _cmd, void * arg2) {
var_30 = self;
r13 = _objc_msgSend;
r12 = [[self sharedInstance] retain];
[r12 startAppActivityTracking];
rbx = [[FIRTrace alloc] initInternalTraceWithName:@"_as"];
[r12 setAppStartTrace:rbx];
[rbx release];
r15 = @selector(appStartTrace);
rbx = [_objc_msgSend(r12, r15) retain];
[rbx startWithStartTime:*_appStartTime];
[rbx release];
rbx = [_objc_msgSend(r12, r15) retain];
rcx = *_appStartTime;
rdx = @"_astui";
[rbx startStageNamed:rdx startTime:rcx];
[rbx release];
rax = *(int8_t *)_windowDidBecomeVisible:.FDDStageStarted;
rax = rax & 0x1;
COND = rax != 0x0;
if (!COND) {
r13 = _objc_msgSend;
rcx = *_appStartTime;
rbx = [_objc_msgSend(r12, r15, rdx, rcx) retain];
rdx = @"_astfd";
[rbx startStageNamed:rdx, rcx];
[rbx release];
*(int8_t *)_windowDidBecomeVisible:.FDDStageStarted = 0x1;
}
rbx = [(r13)(@class(NSNotificationCenter), @selector(defaultCenter), rdx, *_UIWindowDidBecomeVisibleNotification) retain];
(r13)(rbx, @selector(removeObserver:name:object:), var_30, *_UIWindowDidBecomeVisibleNotification, 0x0);
[rbx release];
rdi = r12;
[rdi release];
return;
}
+ (void)windowDidBecomeVisible:(NSNotification *)notification {
FPRAppActivityTracker *tracker = [self sharedInstance];
[tracker startAppActivityTracking];
FIRTrace *trace = [[FIRTrace alloc] initInternalTraceWithName:@"_as"];
[tracker setAppStartTrace: trace];
[[tracker appStartTrace] startWithStartTime:_appStartTime];
[[tracker appStartTrace] startStageNamed:@"_astui" startTime:_appStartTime];
if (_windowDidBecomeVisible:.FDDStageStarted) {
[[tracker appStartTrace] startStageNamed:@"_astfd" startTime:_appStartTime];
_windowDidBecomeVisible:.FDDStageStarted = 1;
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIWindowDidBecomeVisibleNotification object:nil];
}
Метод будет выходить из уведомления UIWindowDidBecomeVisibleNotification в конце, поскольку уведомление будет называется несколько раз, и нам нужно только выполнить его только один раз. Во -первых, вызовите метод -startAppActivityTracking , чтобы начать отслеживать деятельность приложения. Этот метод будет подробно обсуждаться позже.
Прежде всего, мы ясно, что сетевые запросы, которые мы здесь обсуждаем, не имеют никаких специальных инструкций для ссылки на HTTP -запросы. Tingyun SDK в основном использует два метода для реализации сетевого мониторинга: первым является подключение API, используемого сетевым программированием iOS, которое в основном нацелена на собственные сетевые запросы; Второе состоит в том, чтобы унаследовать NSURLProtocol для реализации сетевых запросов, которые в основном предназначены для сетевых запросов в UIWEBVIEW.
SDK зацепит API для построения NSURLSessionDataTask , NSURLSessionUploadTask и NSURLSessionDownloadTask во всех сетевых запросах. Логика крюка находится в функции C _nbs_hook_NSURLSession , а псевдокод выглядит следующим образом:
int _nbs_hook_NSURLSession() {
_nbs_hook_NSURLSessionTask();
r13 = [[_priv_NSURLSession_NBS class] retain];
r14 = [objc_getClass("NSURLSession") retain];
r15 = [objc_getMetaClass(class_getName(r13)) retain];
r12 = [objc_getMetaClass("NSURLSession") retain];
if ((((((_nbs_hookClass_CopyAMethod() != 0x0) && (_nbs_hookClass_CopyAMethod() != 0x0)) && (_nbs_hookClass_CopyAMethod() != 0x0)) && (_nbs_hookClass_CopyAMethod() != 0x0)) && (_nbs_hookClass_CopyAMethod() != 0x0)) && (_nbs_hookClass_CopyAMethod() != 0x0)) {
if (_nbs_hookClass_CopyAMethod() != 0x0) {
if (_nbs_hookClass_CopyAMethod() != 0x0) {
if (_nbs_hookClass_CopyAMethod() != 0x0) {
if (_nbs_hookClass_CopyAMethod() != 0x0) {
_nbs_Swizzle(r14, @selector(dataTaskWithRequest:completionHandler:), @selector(nbs_dataTaskWithRequest:completionHandler:));
_nbs_Swizzle(r14, @selector(downloadTaskWithRequest:completionHandler:), @selector(nbs_downloadTaskWithRequest:completionHandler:));
_nbs_Swizzle(r14, @selector(downloadTaskWithResumeData:completionHandler:), @selector(nbs_downloadTaskWithResumeData:completionHandler:));
_nbs_Swizzle(r14, @selector(uploadTaskWithRequest:fromData:completionHandler:), @selector(nbs_uploadTaskWithRequest:fromData:completionHandler:));
_nbs_Swizzle(r14, @selector(uploadTaskWithRequest:fromFile:completionHandler:), @selector(nbs_uploadTaskWithRequest:fromFile:completionHandler:));
_nbs_Swizzle(r14, @selector(downloadTaskWithRequest:), @selector(nbs_downloadTaskWithRequest:));
_nbs_Swizzle(r14, @selector(uploadTaskWithRequest:fromFile:), @selector(nbs_uploadTaskWithRequest:fromFile:));
_nbs_Swizzle(r14, @selector(uploadTaskWithRequest:fromData:), @selector(nbs_uploadTaskWithRequest:fromData:));
_nbs_Swizzle(r12, @selector(sessionWithConfiguration:delegate:delegateQueue:), @selector(nbs_sessionWithConfiguration:delegate:delegateQueue:));
_nbs_Swizzle(r14, @selector(uploadTaskWithStreamedRequest:), @selector(nbs_uploadTaskWithStreamedRequest:));
}
}
}
}
}
[r12 release];
[r15 release];
[r14 release];
rdi = r13;
rax = [rdi release];
return rax;
}
_NBS_SWIZZLE - это функция C, которая слушает облако для реализации метода Swizzling.
Из кода мы видим, что в дополнение к использованию _nbs_Swizzle для API API NSURLSessionDataTask , NSURLSessionUploadTask и NSURLSessionDownloadTask , упомянутых выше, также заменяет реализацию sessionWithConfiguration:delegate:delegateQueue: Метод. Я объясню, почему этот метод подключен позже.
Все реализации метода Hook определены в классе _priv_NSURLSession_NBS .
Основной код nbs_dataTaskWithRequest:completionHandler: выглядит следующим образом:
typedef void (^nbs_URLSessionDataTaskCompletionHandler)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error);
- (NSURLSessionDataTask *)nbs_dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler {
_priv_NBSHTTPTransaction *httpTransaction = [_priv_NBSHTTPTransaction new];
nbs_URLSessionDataTaskCompletionHandler wrappedCompletionHandler;
__block NSURLSessionDataTask *dataTask;
if (completionHandler) {
wrappedCompletionHandler = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970];
[dataTask.httpTransaction finishAt:timeInterval];
completionHandler(data, response, error);
};
}
dataTask = [self nbs_dataTaskWithRequest:request
completionHandler:wrappedCompletionHandler];
if (dataTask) {
dataTask.httpTransaction = httpTransaction;
}
return dataTask;
}
_priv_NBSHTTPTransaction - это модель параметров производительности, связанных с HTTP -запросами в SDK. Эта структура класса выглядит следующим образом:
@class _priv_NBSHTTPTransaction : NSObject {
@property isFileURL
@property tm_dur_dns
@property tm_dur_cnnct
@property tm_pnt_send
@property tm_dur_firstP
@property tm_dur_end
@property tm_dur_ssl
@property sendSize
@property receiveSize
@property headerSize
@property dataSize
@property statusCode
@property errCode
@property contentLength
@property errText
@property url
@property ip
@property contentType
@property anyObj
@property useContentLength
@property netType
@property appData
@property request
@property response
@property responseData
@property urlParams
@property dataBody
@property httpMethodNumber
@property libClassId
@property socketItem
@property threadId
@property cdn_associate
@property connectType
@property cdnVendorName
@property cdn_flg
ivar tm_dur_cnnct
ivar tm_dur_dns
ivar tm_dur_firstP
ivar tm_dur_end
ivar tm_dur_ssl
ivar tm_pnt_send
ivar sendSize
ivar receiveSize
ivar headerSize
ivar dataSize
ivar statusCode
ivar errCode
ivar errText
ivar url
ivar ip
ivar contentType
ivar contentLength
ivar anyObj
ivar useContentLength
ivar netType
ivar appData
ivar response
ivar responseData
ivar urlParams
ivar dataBody
ivar httpMethodNumber
ivar libClassId
ivar socketItem
ivar threadId
ivar cdn_associate
ivar cdn_flg
ivar isFileURL
ivar connectType
ivar cdnVendorName
ivar _request
-clear
-init
-getText
-addIntoArray:
-startWithIP:DNSTime:atTimePoint:withObject:
-updateWithResponse:timePoint:
-updateWithReceiveData:
-updateWithTotalReceiveData:
-updateWithTotalReceiveSize:
-updateSendSize:
-updateWithError:
-finishAt:
-.cxx_destruct
-tm_dur_dns
-setTm_dur_dns:
-tm_pnt_send
-setTm_pnt_send:
-tm_dur_firstP
-setTm_dur_firstP:
-tm_dur_end
-setTm_dur_end:
-tm_dur_cnnct
-setTm_dur_cnnct:
-tm_dur_ssl
-setTm_dur_ssl:
-sendSize
-setSendSize:
-receiveSize
-setReceiveSize:
-errCode
-setErrCode:
-contentLength
-setContentLength:
-statusCode
-setStatusCode:
-headerSize
-setHeaderSize:
-dataSize
-setDataSize:
-url
-setUrl:
-ip
-setIp:
-errText
-setErrText:
-contentType
-setContentType:
-useContentLength
-setUseContentLength:
-netType
-setNetType:
-appData
-setAppData:
-response
-setResponse:
-responseData
-setResponseData:
-anyObj
-setAnyObj:
-urlParams
-setUrlParams:
-dataBody
-setDataBody:
-httpMethodNumber
-setHttpMethodNumber:
-libClassId
-setLibClassId:
-isFileURL
-setIsFileURL:
-socketItem
-setSocketItem:
-threadId
-setThreadId:
-connectType
-setConnectType:
-cdnVendorName
-setCdnVendorName:
-cdn_associate
-setCdn_associate:
-cdn_flg
-setCdn_flg:
-request
-setRequest:
}
В следующей таблице перечислены значения некоторых ключевых атрибутов:
| свойство | значение |
|---|---|
| tm_pnt_send | Запросить время начала |
| tm_dur_dns | Время разрешения DNS |
| tm_dur_cnnct | Время установления соединения TCP |
| tm_dur_firstp | Первое время упаковки |
| tm_dur_ssl | SSL Time Shake |
| StatusCode | Http code |
Время отклика является хорошим количественным индикатором, который может использоваться для измерения времени ожидания пользователей для запроса услуг. Обычно он определяется как период, когда пользователь отправляет запрос, а содержимое ответа на сервере достигает клиента.
Следующий рисунок представляет собой подробное объяснение HTTP -запросов

Из приведенного выше рисунка мы можем заметить, что время отклика включает в себя время разрешения имен доменов DNS, время подключения, установленное с сервером, время обработки сервера и время, когда ответ прибывает в клиент.
Если вы используете Charles для перехвата HTTP -запросов, вы можете просмотреть данные времени ответа в столбце TIME OF Opreview. Duration на рисунке ниже представляет общее время отклика запроса, которое также включает в себя DNS (время разрешения имени домена DNS), Connect (время установления соединения) и SSL Handshake (время рукопожатия SSL), упомянутое выше. Поскольку этот запрос является HTTP -запросом, поле SSL Handshake остается пустым.

Фактически, после разработки функции времени отклика в SDK мы также можем проверить правильность результатов таким образом. Конечно, время, полученное в SDK, не совсем равно Charles, потому что два метода реализации совершенно разные, но разница между ними должна быть в пределах разумного диапазона. Этот аспект будет подробно обсуждаться ниже.
Благодаря вышеупомянутому введению мы можем легко придумать идею: через функцию, когда запрос крюка выдается, запишите время запроса, затем зацепите ответ вызовов в SDK iOS, запишите время окончания и рассчитайте разницу, чтобы получить время ответа на этот запрос. То же самое относится и к общей идее Тингюна, но есть много деталей, на которые нужно обратить внимание. Давайте подробно обсудим его конкретный план реализации.
Облако прослушивания - это метод resume NSURLSessionTask в функции _nbs_hook_NSURLSessionTask для достижения цели записи начала запроса.
void _nbs_hook_NSURLSessionTask() {
r14 = _objc_msgSend;
rax = [NSURLSessionConfiguration ephemeralSessionConfiguration];
rax = [rax retain];
var_40 = rax;
rax = [NSURLSession sessionWithConfiguration:rax];
rax = [rax retain];
rdx = 0x0;
var_38 = rax;
rax = [rax dataTaskWithURL:rdx];
rax = [rax retain];
var_30 = rax;
rbx = [rax class];
r12 = @selector(resume);
if (class_getInstanceMethod(rbx, r12) != 0x0) {
r15 = @selector(superclass);
r13 = @selector(resume);
var_48 = r15;
do {
if (_nbs_slow_isClassItSelfHasMethod(rbx, r12) != 0x0) {
r15 = class_getInstanceMethod(rbx, r12);
rax = method_getImplementation(r15);
rax = objc_retainBlock(__NSConcreteStackBlock);
var_50 = imp_implementationWithBlock(rax);
r13 = r13;
[rax release];
rdi = r15;
r15 = var_48;
rax = method_getTypeEncoding(rdi);
rdx = var_50;
rcx = rax;
class_replaceMethod(rbx, r12, rdx, rcx);
}
r14 = _objc_msgSend;
rbx = _objc_msgSend(rbx, r15, rdx, rcx);
rax = class_getInstanceMethod(rbx, r13);
r12 = r13;
} while (rax != 0x0);
}
(r14)(var_30, @selector(cancel), rdx);
(r14)(var_38, @selector(finishTasksAndInvalidate), rdx);
[var_30 release];
[var_38 release];
[var_40 release];
return;
}
Восстановите вышеуказанный псевдокод в код Objective-C следующим образом:
void _nbs_hook_NSURLSessionTask() {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *task = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
Class cls = task.class;
if (class_getInstanceMethod(cls, @selector(resume))) {
Method method;
do {
if (_nbs_slow_isClassItSelfHasMethod(cls, @selector(resume))) {
Method resumeMethod = class_getInstanceMethod(cls, @selector(resume));
IMP imp = imp_implementationWithBlock(^(id self) {
});
class_replaceMethod(cls, @selector(resume), imp, method_getTypeEncoding(resumeMethod));
}
cls = [cls superclass];
method = class_getInstanceMethod(cls, @selector(resume));
} while (method);
}
[task cancel];
[session finishTasksAndInvalidate];
}
Мы знаем, что в рамках Foundation некоторые классы на самом деле являются кластерами классов, таких как NSDictionary и NSArray . NSURLSessionTask также является классовой семьей, и цепочки наследования различны в разных системных версиях, поэтому, очевидно, вы не можете напрямую подключить класс NSURLSessionTask . Здесь принимается умный метод для создания эфемерного сеанса с помощью метода ephemeralSessionConfiguration . Он похож на сеанс по умолчанию, но данные не хранятся на диске, а все кэши, файлы cookie, учетные данные и т. Д. Сохраняются в оперативной памяти и связаны с сеансом. Таким образом, они будут автоматически очищены, когда сеанс будет недействительным. Затем, благодаря этому краткому сеансу, создается объект сеанса, и объект задачи наконец -то создан, и реальный класс получается через этот объект задачи.
Вышеупомянутый умный подход изначально не создается Tingyun. Это на самом деле относится к подходу Afnetworking. Чтобы добавить уведомления в афневообразовании, resume и suspend методов Hook NSURLSessionTask также реализованы в AFURLSessionManager .
if (NSClassFromString(@"NSURLSessionTask")) {
/**
iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
Many Unit Tests have been built to validate as much of this behavior has possible.
Here is what we know:
- NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
- Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
- On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
- On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
- On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
- On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
- Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.
Some Assumptions:
- No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
- No background task classes override `resume` or `suspend`
The current solution:
1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
2) Grab a pointer to the original implementation of `af_resume`
3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
4) Grab the super class of the current class.
5) Grab a pointer for the current class to the current implementation of `resume`.
6) Grab a pointer for the super class to the current implementation of `resume`.
7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
8) Set the current class to the super class, and repeat steps 3-8
*/
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
Метод
class_copyMethodListбудет вызван в методе_nbs_slow_isClassItSelfHasMethodдля получения списка методов этого класса. Обратите внимание, что список методов, полученный этим методом, не содержит методов родительского класса, поэтому метод_nbs_slow_isClassItSelfHasMethodфактически заключается в том, чтобы судить, содержит ли сам классcls@selector(resume).
Фактически, приведенная выше логика также реализована в гибке библиотеки с открытым исходным кодом, но в реализации существуют небольшие различия. Flex будет отличать его от Hook __NSCFLocalSessionTask , nsurlsessionTask и __nscfurlsessionTask в соответствии с версией системы. Я лично чувствую, что реализация облака прослушивания более элегантна, чем жесткое кодирование гибкого . Поскольку __NSCFLocalSessionTask и __NSCFURLSessionTask являются частными классами, разделится и сплайсинг принимаются, чтобы избежать отказа аудита.
+ (void)injectIntoNSURLSessionTaskResume
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// In iOS 7 resume lives in __NSCFLocalSessionTask
// In iOS 8 resume lives in NSURLSessionTask
// In iOS 9 resume lives in __NSCFURLSessionTask
Class class = Nil;
if (![[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) {
class = NSClassFromString([@[@"__", @"NSC", @"FLocalS", @"ession", @"Task"] componentsJoinedByString:@""]);
} else if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 9) {
class = [NSURLSessionTask class];
} else {
class = NSClassFromString([@[@"__", @"NSC", @"FURLS", @"ession", @"Task"] componentsJoinedByString:@""]);
}
SEL selector = @selector(resume);
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
Method originalResume = class_getInstanceMethod(class, selector);
void (^swizzleBlock)(NSURLSessionTask *) = ^(NSURLSessionTask *slf) {
[[FLEXNetworkObserver sharedObserver] URLSessionTaskWillResume:slf];
((void(*)(id, SEL))objc_msgSend)(slf, swizzledSelector);
};
IMP implementation = imp_implementationWithBlock(swizzleBlock);
class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalResume));
Method newResume = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalResume, newResume);
});
}
Реализация замены исходного resume выше, реализована через imp_implementationWithBlock , а замененный блок выглядит следующим образом:
void ___nbs_hook_NSURLSessionTask_block_invoke(int arg0, int arg1, int arg2) {
rbx = [[NSDate date] retain];
[rbx timeIntervalSince1970];
var_40 = intrinsic_movsd(var_40, xmm0);
[rbx release];
r15 = _is_tiaoshi_kai;
COND = *(int8_t *)r15 == 0x0;
var_50 = r13;
if (!COND) {
rax = [var_30 URL];
rax = [rax retain];
r14 = r15;
r15 = rax;
rbx = [[r15 absoluteString] retain];
rdx = rbx;
__NBSDebugLog(0x3, @"NSURLSession:start:url:%@", rdx, rcx, r8, r9, stack[2048]);
[rbx release];
rdi = r15;
r15 = r14;
[rdi release];
}
rbx = [objc_getAssociatedObject(r12, @"m_SessAssociatedKey") retain];
if (rbx != 0x0) {
xmm1 = intrinsic_movsd(xmm1, var_40);
xmm1 = intrinsic_mulsd(xmm1, *0x1000b9990);
xmm0 = intrinsic_movsd(xmm0, *0x1000b9da8);
[rbx startWithIP:0x0 DNSTime:var_30 atTimePoint:r8 withObject:r9];
[rbx setRequest:var_30];
[rbx setLibClassId:0x1];
}
else {
if (*(int8_t *)r15 != 0x0) {
__NBSDebugLog(0x3, cfstring_r, rdx, rcx, r8, r9, stack[2048]);
}
}
}
В приведенном выше псевдокоде нерелевантной логики в ___nbs_hook_NSURLSessionTask_block_invoke игнорируется. Вы можете видеть, что сгенерирована временная метка, и временная метка используется в качестве параметра входа метода [rbx startWithIP:0x0 DNSTime:var_30 atTimePoint:r8 withObject:r9] . rbx является экземпляром _priv_NBSHTTPTransaction , и этот экземпляр получается через связанный объект NSURLSessionDataTask . Логика создания экземпляра _priv_NBSHTTPTransaction и настройки связанного объекта находится в -[_priv_NSURLSession_NBS nbs_dataTaskWithRequest:completionHandler:] .
r12 = [[var_30 nbs_dataTaskWithRequest:r13 completionHandler:0x0] retain];
r15 = [_priv_NBSHTTPTransaction new];
if (r12 != 0x0) {
objc_setAssociatedObject(r12, @"m_SessAssociatedKey", r15, 0x301);
}
[r15 release];
-[_priv_NBSHTTPTransaction startWithIP:DNSTime:atTimePoint:withObject:] метод назначит время параметра своему свойству tm_pnt_send .
-[_priv_NBSHTTPTransaction startWithIP:DNSTime:atTimePoint:withObject:] {
var_30 = intrinsic_movsd(var_30, arg4, rdx, arg5);
r12->tm_pnt_send = intrinsic_movsd(r12->tm_pnt_send, intrinsic_movsd(xmm0, var_30));
}
Конечно, в дополнение к -[_priv_NSURLSession_NBS nbs_dataTaskWithRequest:completionHandler:] Следующий метод также содержит эту логику:
nbs_downloadTaskWithRequest:nbs_downloadTaskWithRequest:completionHandler:nbs_downloadTaskWithResumeData:completionHandler:nbs_uploadTaskWithRequest:fromData:completionHandler:nbs_uploadTaskWithRequest:fromFile:completionHandler:nbs_uploadTaskWithRequest:fromFile:nbs_uploadTaskWithRequest:fromData:nbs_uploadTaskWithStreamedRequest: Окончательное время ответа рассчитывается в методе finishAt и присваивается его атрибуту tm_dur_end .
void -[_priv_NBSHTTPTransaction finishAt:](void * self, void * _cmd, double arg2) {
r14 = self;
rbx = [r14 retain];
r14 = @selector(tm_pnt_send);
_objc_msgSend(rbx, r14);
xmm1 = intrinsic_xorpd(xmm1, xmm1);
xmm0 = intrinsic_ucomisd(xmm0, xmm1);
COND = xmm0 <= 0x0;
if (!COND) {
_objc_msgSend(rbx, r14);
xmm1 = intrinsic_movsd(xmm1, var_30);
xmm1 = intrinsic_subsd(xmm1, xmm0);
xmm0 = intrinsic_movapd(xmm0, xmm1);
[rbx setTm_dur_end:rdx];
}
}
Для сетевых запросов, инициированных путем вызова dataTaskWithRequest:completionHandler: Метод, завершенный обратный вызов находится в completionHandler , поэтому метод finishAt следует вызвать в завершенном обратном вызове. Аналогичные методы включают ___72-[_priv_NSURLSession_NBS nbs_downloadTaskWithRequest:completionHandler:]_block_invoke , ___79-[_priv_NSURLSession_NBS nbs_uploadTaskWithRequest:fromData:completionHandler:]_block_invoke и другие методы.
int ___68-[_priv_NSURLSession_NBS nbs_dataTaskWithRequest:completionHandler:]_block_invoke(int arg0, int arg1, int arg2, int arg3) {
rdi = *(r12 + 0x20);
xmm0 = intrinsic_movsd(xmm0, var_68);
[rdi finishAt:rdx];
}
Тем не менее, для сетевых запросов, которые вызывают dataTaskWithRequest: Метод, вам необходимо подключить URLSession:task:didCompleteWithError: Метод NSURLSessionTaskDelegate .
void -[_priv_NBSLensAllMethodsDlgt_urlSess nbs_URLSession:task:didCompleteWithError:](void * self, void * _cmd, void * arg2, void * arg3, void * arg4) {
rbx = [[NSDate date] retain];
[rbx timeIntervalSince1970];
xmm0 = intrinsic_mulsd(xmm0, *0x1000b9990);
var_40 = intrinsic_movsd(var_40, xmm0);
[rbx release];
rax = objc_getAssociatedObject(r13, @"m_SessAssociatedKey");
rax = [rax retain];
_objc_msgSend(r12, @selector(finishAt:), var_58);
}