
Obwohl APM immer beliebter wird, haben sich professionelle APM -Hersteller aller Größen nach einem Regen wie Pilze entstanden, und es gibt auch viele technische Artikel auf APM auf dem Markt, die meisten von ihnen sind nur ein einfacher Geschmack und haben sich nicht tief in die Implementierungsdetails eintauchen. Dieser Artikel zielt darauf ab, das interne Arbeitsprinzip des iOS-SDK bekannter APM-Hersteller aufzudecken, indem die spezifischen Implementierungsdetails des SDK analysiert werden. Ich glaube, dass die Leser vor dem Lesen dieses Artikels auch wie der Autor genau über die Implementierungsdetails des APM SDK waren. Glücklicherweise führt Sie in dem Artikel, den Sie lesen, Schritt für Schritt den wahren Kontext von APM. Zu den in diesem Artikel analysierten APM -SDKs gehören Tingyun , Oneapm und Firebase Performance Monitoring usw. Der Autor ist ein wenig talentiert und sachkundig. Wenn es Fehler gibt, wird er sie nicht korrigieren, um sie nachzudenken und perfekter zu machen.
Die in diesem Artikel analysierte Version von Tingyun SDK ist 2.3.5, was sich geringfügig von der neuesten Version unterscheidet. Ich habe jedoch den Code der neuen Version grob gelesen, aber der Unterschied ist nicht groß und wirkt sich nicht auf die Analyse aus.
Die Überwachung des Seitenrenders scheint sehr einfach zu sein, aber es wird immer noch viele Probleme im tatsächlichen Entwicklungsprozess geben. Was sich leichter vorstellen kann, sind mehrere wichtige Lebenszyklusmethoden von Hakenseiten, wie z. B. viewDidLoad , viewDidAppear: usw., um die Rendernzeit der Seite zu berechnen und schließlich die langsam beladene Seite zu finden. Wenn Sie es jedoch wirklich durch die oben genannten Ideen erreichen, werden Sie auf Schwierigkeiten stoßen. Wie kann ich den Lebenszyklus aller Seiten im APM SDK angewendet werden? Was ist, wenn ich versuche, UIViewController zu haken? Die Hook UIViewController -Methode ist offensichtlich nicht machbar, da sie nur auf UIViewController -Methode funktioniert und die meisten Ansichtscontroller in der Anwendung von UIViewController erben. Diese Methode ist daher nicht machbar. Tingyun SDK kann jedoch implementiert werden. Die Logik des Seitenhakens wird hauptsächlich in der Klasse _priv_NBSUIAgent implementiert. Das Folgende ist die Definition der Klasse _priv_NBSUIAgent , unter der hook_viewDidLoad und andere Methoden Hinweise sind.
@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
}
Lassen Sie uns zunächst unsere Aufmerksamkeit auf eine weitere verdächtige Methode wenden: hookSubOfController , die spezifische Implementierung lautet wie folgt:
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;
}
Aus der Benennung von _subMetaClassNamesInMainBundle_c und dem eingehenden Parameter "UIViewController" kann grundsätzlich geschlossen werden, dass diese C -Funktion eine Unterklasse aller UIViewController in Mainbund ist. Wenn Sie die Montagelinie nach Abschluss des Funktionsaufrufs durch LLDB brechen, werden Sie feststellen, dass das zurückgegebene Array tatsächlich eine Unterklasse von UIViewController ist. Die folgende if feststellt, dass das r12 -Register nicht nil ist und count des r12 -Registers nicht gleich 0 ist, bevor die Logik in if ausgeführt wird. Das r12 -Register speichert den Rückgabewert der Funktion _subMetaClassNamesInMainBundle_c , die das Array UIViewController -Unterklasse ist.
_subMetaClassNamesInMainBundle_c lautet wie folgt:
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;
}
Die Subroutine loc_10001db4d in _subMetaClassNamesInMainBundle_c ruft die Funktion _classNamesInMainBundle_c auf, und der Funktionscode lautet wie folgt:
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;
}
Die Implementierung der Funktion von _classNamesInMainBundle_c ist offensichtlich. Es ist nichts anderes als das Aufrufen objc_copyClassNamesForImage , um die Namen aller Klassen mainBundle -Pfades zu erhalten. Die Anzahl der Sets ist der outCount -Variablen zugeordnet, und der Anrufer kann outCount verwenden, um sie zu durchqueren.
static inline char ** WDTClassNamesInMainBundle ( unsigned int *outCount) {
NSString *executablePath = [[ NSBundle mainBundle ] executablePath ];
char **classNames = objc_copyClassNamesForImage ([executablePath UTF8String ], outCount);
return classNames;
} Wenn Sie sich nicht für die Details interessieren, ist die Implementierung der Funktion _subMetaClassNamesInMainBundle_c ebenfalls sehr klar, weshalb der Rückgabewert der Funktion objc_copyClassNamesForImage durchqueren soll. Wenn es sich bei dem Element um eine Unterklasse von UIViewController handelt, wird metaClass der Klasse erhalten und zum variablen Array var_38 hinzugefügt.
Konzentrieren wir uns als nächstes auf die Aussage von do-while Loop im Inneren. Die Erklärung zum Schleifenurteil ist var_98 + 0x1 < rax . var_98 weist das rdx -Register zu Beginn der Schleife zu, und das rdx -Register wird außerhalb der Schleife auf 0 initialisiert, sodass var_98 der Zähler ist und das rax -Register die dem r12 -Register zugewiesene count ist. Basierend darauf durchquert do-while tatsächlich das Array UIViewController Unterklasse. Das Traversalverhalten besteht darin, initialize und nbs_jump_initialize: Methoden durch _nbs_Swizzle_orReplaceWithIMPs auszutauschen.
Der Code für nbs_jump_initialize lautet wie folgt:
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;
}
Der Code von nbs_jump_initialize ist etwas lang, aber aus der Routine von loc_1000501a3 kann beobachtet werden, dass die Hauptlogik drei Methoden ausführt, hook_viewDidLoad , hook_viewWillAppear und hook_viewDidAppear , wodurch diese drei Methoden UIViewController -Subclass -.
Verwenden Sie zunächst hook_viewDidLoad: Methode als Beispiel zum Erklären. Der folgende Code kann etwas dunkel sein und erfordert eine sorgfältige Analyse.
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: Der Parameter arg2 in der Methode ist die Klasse ViewController -Hakens, erhalten Sie den Klassennamen arg2 und weisen Sie ihn dem rbx -Register zu und verwenden Sie dann rbx , um die String nbs_%s_viewDidLoad var_C0 konstruieren, wie nbs_XXViewController_viewDidLoad . __NSConcreteStackBlock in den folgenden Sätzen ist das Blockobjekt des erstellten Speicherstacks. Dieser Block erhält dann den IMP -Funktion Zeiger über die imp_implementationWithBlock -Methode. _nbs_Swizzle_orReplaceWithIMPs ist eine Funktion, die den Methodenaustausch implementiert, und die Parameter sind: arg2 ist die Klasse von ViewController ; @selector(viewDidLoad) ist der Selektor von viewDidLoad ; var_C0 ist der Selektor von nbs_%s_viewDidLoad , r14 ist der IMP der zweiten __NSConcreteStackBlock ; var_D0 ist der IMP des ersten __NSConcreteStackBlock .
Die gesamte Logik von hook_viewDidLoad: Ist grob klar, aber hier gibt es eine Frage, warum nicht direkt zwei Imps ausgetauscht, sondern zuerst zwei Blöcke erstellen und dann die IMPS von zwei Blöcken austauschen? Der Grund dafür ist, dass das Ergebnis der übergeordneten Klasse von ViewController , dh class_getSuperclass , als Parameter an die ausgetauschte Methode übergeben werden muss. Auf diese Weise ist die Anzahl der von den beiden ausgetauschten Selektoren unterzeichneten Parametern inkonsistent, und dieses Problem muss durch den Bau eines Blocks geschickt gelöst werden. Tatsächlich führt der erste __NSConcreteStackBlock nbs_jump_viewDidLoad:superClass: Methode von _priv_NBSUIHookMatrix aus. Wie bereits erwähnt, enthält die Parameter dieser Methode superClass . Wenn dieser Parameter benötigt wird, werde ich ihn später vorstellen.
Warum führt der zweite __NSConcreteStackBlock nbs_jump_viewDidLoad:superClass: Methode aus? Deaktivieren Sie die Option Remove potentially dead code , der Code lautet wie folgt:
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;
}
Schauen wir uns den Code von _nbs_getInstanceImpOf an:
void _nbs_getInstanceImpOf() {
rax = class_getInstanceMethod(rdi, rsi);
method_getImplementation(rax);
return;
}
Die Funktion der Funktion _nbs_getInstanceImpOf ist sehr offensichtlich. Um den IMP von rsi -Selektor in rdi -Klasse zu erhalten, werden die Leser feststellen, dass _nbs_getInstanceImpOf in hook_viewDidLoad: zweimal aufgerufen wurde. Der erste rdi ist die _priv_NBSUIHookMatrix -Klasse, rdx ist @selector(nbs_jump_viewDidLoad:superClass:) , der zweite rdi ist ViewController -Klasse rdx ist @selector(viewDidLoad) .
Schauen wir uns als nächstes den ersten __NSConcreteStackBlock an, was den Block bedeutet, nbs_jump_viewDidLoad:superClass: Der Code ist wie folgt:
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;
}
Das r8 -Register ist der IMP von nbs_jump_viewDidLoad:superClass: und dieser Code nennt diesen IMP nur. Die Parameter der IMP -Funktion sind die gleichen wie 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;
}
Die Startzeit wird als Beispiel mit der Firebase Performance Monitoring SDK erklärt. Das FPM SDK wird als Abkürzung verwendet, um es zu beschreiben. Das FPM SDK implementiert Statistiken zur Kaltstartzeit, und die Hauptlogik wird in FPRAppActivityTracker -Klasse implementiert.
Schauen Sie sich zunächst die +load der Klasse an, und der Dekompilierungscode lautet wie folgt:
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;
}
Offensichtlich ist _appStartTime eine statische NSDate -Instanz, mit der die Startzeit des gesamten Anwendungsstarts speichert wird. Daher markiert der FPM SDK die Startzeit des Anwendungsstarts im +load von FPRAppActivityTracker . Leser, die die +load Lastmethode verstehen, sollten wissen, dass die Methode eine Hakenmethode ist, bevor main aufgerufen wird. Der genaue Zeitpunkt ist, wenn das Bild auf die Laufzeit geladen wird und die +load Lastmethode fertig ist und +load aufgerufen wird. Darüber hinaus beziehen sich verschiedene Arten von +load auch mit der Dateireihenfolge von Build Phases->Compile Sources . Wir glauben, dass diese keinen signifikanten Einfluss auf die Statistiken der Startzeit haben.
Anschließend ist die Benachrichtigung der UIWindowDidBecomeVisibleNotification registriert. Diese Benachrichtigung wird ausgelöst, wenn UIWindow -Objekt aktiviert und auf der Schnittstelle angezeigt wird. Leser können diese Benachrichtigung registrieren und das Benachrichtigungsobjekt dann mit LLDB drucken. Das Beispiel lautet wie folgt:
NSConcreteNotification 0x7fc94a716f50 {name = UIWindowDidBecomeVisibleNotification; object = <UIStatusBarWindow: 0x7fc94a5092a0; frame = (0 0; 320 568); opaque = NO; gestureRecognizers = <NSArray: 0x7fc94a619f30>; layer = <UIWindowLayer: 0x7fc94a513f50>>}
Das erste Mal, dass ich die Benachrichtigung über die UIWindowDidBecomeVisibleNotification erhielt, war früher als - application:didFinishLaunchingWithOptions: Callback wurde diese Benachrichtigung ausgelöst, als window der Statusleiste erstellt wurde. Diese Implementierung fühlt sich etwas schwierig an und kann nicht sicherstellen, dass Apple den Zeitpunkt des Anrufs in Zukunft anpasst.
Unten finden Sie die offizielle Beschreibung der UIWindowDidBecomeVisibleNotification .
Gepostet, wenn ein UIWindow -Objekt sichtbar wird. Das Benachrichtigungsobjekt ist das Fensterobjekt, das sichtbar geworden ist. Diese Benachrichtigung enthält kein UserInfo -Wörterbuch. Durch das Schalten zwischen Apps werden keine Sichtbarkeitsnotifikationen für Windows generiert. Änderungen der Sichtbarkeit von Fenstern spiegeln Änderungen in der versteckten Eigenschaft des Fensters wider und spiegeln nur die Sichtbarkeit des Fensters in der App wider.
Das Folgende ist die Methode zur Bearbeitung von Benachrichtigungen. Ich habe die Methode in Objective-C Pseudo-Code wiederhergestellt, mit der der dekompilierte Pseudo-Code verglichen werden kann.
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];
}
Die Methode meldet sich die Benachrichtigung über die UIWindowDidBecomeVisibleNotification am Ende an, da die Benachrichtigung mehrmals aufgerufen wird und wir sie nur einmal ausführen müssen. Rufen Sie zunächst die Methode für die -startAppActivityTracking an, um die Aktivität der App zu verfolgen. Diese Methode wird später eingehend erörtert.
Erstens sind wir klar, dass die Netzwerkanfragen, die wir hier diskutieren, keine speziellen Anweisungen haben, um sich auf HTTP -Anfragen zu beziehen. Tingyun SDK verwendet hauptsächlich zwei Methoden, um die Netzwerküberwachung zu implementieren: Erstes besteht darin, die API, die von iOS -Netzwerkprogrammierung verwendet wird, zu hängen, die hauptsächlich auf native Netzwerkanforderungen abzielt. Die zweite besteht darin, NSURLProtocol zur Implementierung von Netzwerkanforderungen zu erben, die hauptsächlich Netzwerkanforderungen in UIWebView abzielen.
Die SDK hängt die API zum Bau von NSURLSessionDataTask , NSURLSessionUploadTask und NSURLSessionDownloadTask in allen Netzwerkanforderungen an. Die Logik des Hakens befindet sich in der C-Funktion _nbs_hook_NSURLSession , und der Pseudo-Code lautet wie folgt:
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 ist die C -Funktion, die die Cloud zum Implementieren von Methoden im Swizzling hört.
Aus dem Code können wir sehen, dass zusätzlich zur Verwendung _nbs_Swizzle für NSURLSessionDataTask -APIs NSURLSessionUploadTask und NSURLSessionDownloadTask auch die Implementierung sessionWithConfiguration:delegate:delegateQueue: Methode ersetzt. Ich werde erklären, warum diese Methode später begeistert ist.
Alle Hook -Methoden -Implementierungen sind in der Klasse _priv_NSURLSession_NBS definiert.
Der Kerncode von nbs_dataTaskWithRequest:completionHandler: lautet wie folgt:
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 ist ein Modell der Leistungsparameter im Zusammenhang mit HTTP -Anforderungen in der SDK. Diese Klassenstruktur ist wie folgt:
@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:
}
In der folgenden Tabelle werden die Bedeutungen einiger Schlüsselattribute aufgeführt:
| Eigentum | Bedeutung |
|---|---|
| tm_pnt_send | Startzeit anfordern |
| tm_dur_dns | DNS -Auflösungszeit |
| tm_dur_cnnct | TCP -Verbindungsaufbauzeit |
| tm_dur_firstp | Erste Packzeit |
| TM_DUR_SSL | SSL -Handshake -Zeit |
| Statuscode | HTTP -Statuscode |
Die Reaktionszeit ist ein guter quantitativer Indikator, mit dem die Wartezeit für Benutzer anfordern kann. Es ist im Allgemeinen definiert als der Zeitraum, in dem der Benutzer eine Anforderung sendet und der Antwortinhalt auf dem Server den Client erreicht.
Die folgende Abbildung ist eine detaillierte Erklärung von HTTP -Anforderungen

Aus der obigen Abbildung können wir feststellen, dass die Antwortzeit die Auflösungszeit der DNS -Domänennamen, die mit dem Server festgelegte Verbindungszeit, die Serverabarbeitungszeit und die Zeit, in der die Antwort am Client eintrifft, festgelegt ist.
Wenn Sie Charles verwenden, um HTTP -Anforderungen abzufangen, können Sie die Antwortzeitdaten in der Registerkarte "Überblick über den Überblick" anzeigen. Duration in der folgenden Abbildung stellt die Gesamtantwortzeit der Anforderung dar, die auch DNS (DNS -Domänennamenauflösungszeit), Connect (Verbindungsbetriebszeit) und SSL Handshake (SSL -Handshake -Zeit) enthält. Da es sich bei dieser Anfrage um eine HTTP -Anforderung handelt, bleibt das SSL Handshake -Feld leer.

Nachdem wir das Merkmal der Reaktionszeit im SDK entwickelt haben, können wir auch die Richtigkeit der Ergebnisse auf diese Weise überprüfen. Natürlich ist die im SDK erhaltene Zeit nicht genau gleich Charles, da die beiden Implementierungsmethoden völlig unterschiedlich sind, aber der Unterschied zwischen ihnen sollte in einem vernünftigen Bereich liegen. Dieser Aspekt wird nachstehend ausführlich erörtert.
Durch die obige Einführung können wir uns leicht eine Idee vorstellen: Durch die Funktion, wenn die Hook -Anfrage ausgestellt wird, zeichnen Sie die Anfragezeit auf und hängen Sie dann die Rückrufantwort in das iOS -SDK, zeichnen Sie die Endzeit auf und berechnen Sie die Differenz, um die Antwortzeit dieser Anfrage zu erhalten. Gleiches gilt für die allgemeine Idee von Tingyun, aber es gibt viele Details, auf die geachtet werden müssen. Lassen Sie uns als nächstes seinen spezifischen Implementierungsplan ausführlich erörtert.
Listening Cloud ist die resume von nsurlSessionTask in der Funktion _nbs_hook_NSURLSessionTask , um den Zweck der Aufzeichnung des Beginns der Anforderung zu erreichen.
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;
}
Stellen Sie den oben genannten Pseudocode wie folgt in den Objektiv-C-Code wieder her:
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];
}
Wir wissen, dass im Foundation Framework einige Klassen tatsächlich Klassencluster wie NSDictionary und NSArray sind. NSURLSessionTask ist auch eine Klassenfamilie, und die Erbschaltketten unterscheiden sich in verschiedenen Systemversionen. Offensichtlich können Sie die NSURLSessionTask -Klasse offensichtlich nicht direkt anschließen. Hier wird eine clevere Methode angewendet, um eine kurzlebige Sitzung durch die ephemeralSessionConfiguration -Methode aufzubauen. Es ähnelt der Standardsitzung, aber es werden keine Daten auf der Festplatte gespeichert, und alle Caches, Cookies, Anmeldeinformationen usw. werden in RAM gespeichert und mit der Sitzung verbunden. Auf diese Weise werden sie automatisch gelöscht, wenn die Sitzung ungültig ist. Dann wird durch diese kurze Sitzung ein Sitzungsobjekt erstellt und das Task -Objekt schließlich erstellt, und die reale Klasse wird über dieses Task -Objekt erhalten.
Der obige clevere Ansatz wird ursprünglich von Tingyun nicht erstellt. Es bezieht sich tatsächlich auf den Ansatz von AFNetworking. Um Benachrichtigungen in AFNetworking hinzuzufügen, werden auch in AFURLSessionManager resume und suspend -Methoden von Hook NSURLSessionTask implementiert.
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_copyMethodListwird in der Methode_nbs_slow_isClassItSelfHasMethodaufgerufen, um die Methodenliste dieser Klasse zu erhalten. Beachten Sie, dass die von dieser Methode erhaltene Methodenliste nicht die Methoden der übergeordneten Klasse enthält, sodass_nbs_slow_isClassItSelfHasMethodtatsächlich darin besteht, zu beurteilen, ob diecls-Klasse selbst@selector(resume)enthält.
Tatsächlich wird die obige Logik auch im Flex der Open -Source -Bibliothek implementiert, es gibt jedoch geringfügige Unterschiede in der Implementierung. Flex unterscheidet es von Hook __NSCFLocalSessionTask , nsurlSessionTask und __nscfurlSessionTask nach der Systemversion. Ich persönlich bin der Meinung, dass die Implementierung der Hörwolke eleganter ist als die harte Codierung von Flex . Weil __NSCFLocalSessionTask und __NSCFURLSessionTask private Klassen sind, werden Split und Spleißen angewendet, um die Ablehnung der Prüfung zu vermeiden.
+ (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);
});
}
Die Implementierung des Austauschs des obigen ursprünglichen resume wird durch imp_implementationWithBlock implementiert, und der ersetzte Block ist wie folgt:
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]);
}
}
}
In der obigen Pseudo-Code wird die irrelevante Logik in ___nbs_hook_NSURLSessionTask_block_invoke ignoriert. Sie können sehen, dass ein Zeitstempel generiert wird und der Zeitstempel als Eintragsparameter [rbx startWithIP:0x0 DNSTime:var_30 atTimePoint:r8 withObject:r9] verwendet wird. rbx ist eine Instanz von _priv_NBSHTTPTransaction , und diese Instanz wird durch das zugehörige Objekt von NSURLSessionDataTask erhalten. Die Logik des Erstellens _priv_NBSHTTPTransaction und der Einstellung des zugehörigen Objekts befindet sich in der -[_priv_NSURLSession_NBS nbs_dataTaskWithRequest:completionHandler:] Methode.
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];
tm_pnt_send -[_priv_NBSHTTPTransaction startWithIP:DNSTime:atTimePoint:withObject:]
-[_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));
}
Natürlich enthält die folgende Methode zusätzlich zu den -[_priv_NSURLSession_NBS nbs_dataTaskWithRequest:completionHandler:] Methode auch diese Logik:
nbs_downloadTaskWithRequest:nbs_downloadTaskWithRequest:completionHandler:nbs_downloadTaskWithResumeData:completionHandler:nbs_uploadTaskWithRequest:fromData:completionHandler:nbs_uploadTaskWithRequest:fromFile:completionHandler:nbs_uploadTaskWithRequest:fromFile:nbs_uploadTaskWithRequest:fromData:nbs_uploadTaskWithStreamedRequest: Die endgültige Antwortzeit wird in der finishAt -Methode berechnet und tm_dur_end zugewiesen.
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];
}
}
Für Netzwerkanfragen, die mit dem Aufrufen dataTaskWithRequest:completionHandler: Methode initiiert wurden, befindet sich der abgeschlossene Rückruf im completionHandler , sodass finishAt -Methode im abgeschlossenen Rückruf aufgerufen werden sollte. Ähnliche Methoden umfassen ___72-[_priv_NSURLSession_NBS nbs_downloadTaskWithRequest:completionHandler:]_block_invoke , ___79-[_priv_NSURLSession_NBS nbs_uploadTaskWithRequest:fromData:completionHandler:]_block_invoke aus methods.
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];
}
Für Netzwerkanfragen, die dataTaskWithRequest: Methode aufrufen, müssen Sie URLSession:task:didCompleteWithError: Methode von NSURLSessionTaskDelegate anschließen.
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);
}