
Meskipun APM menjadi semakin populer, produsen APM profesional dari semua ukuran telah muncul seperti jamur setelah hujan, dan ada juga banyak artikel teknis di APM di pasaran, kebanyakan dari mereka hanya selera sederhana dan belum menggali jauh ke dalam detail implementasi. Artikel ini bertujuan untuk mengekspos prinsip kerja internal iOS SDK dari produsen APM terkenal dengan menganalisis detail implementasi spesifik SDK. Saya percaya bahwa sebelum membaca artikel ini, pembaca juga ingin tahu tentang detail implementasi APM SDK seperti penulisnya. Untungnya, artikel yang Anda baca akan membawa Anda untuk mengungkap konteks sebenarnya dari APM langkah demi langkah. APM SDK yang dianalisis dalam artikel ini termasuk Tingyun , OneapM dan pemantauan kinerja Firebase , dll. Penulis sedikit berbakat dan berpengetahuan luas. Jika ada kesalahan, dia tidak akan memperbaikinya untuk mencetak ulang dan membuatnya lebih sempurna.
Versi Tingyun SDK yang dianalisis dalam artikel ini adalah 2.3.5, yang sedikit berbeda dari versi terbaru. Namun, saya secara kasar membaca kode versi baru, tetapi perbedaannya tidak besar dan tidak mempengaruhi analisis.
Pemantauan rendering halaman tampaknya sangat sederhana, tetapi masih akan ada banyak masalah dalam proses pengembangan yang sebenarnya. Yang lebih mudah dipikirkan adalah beberapa metode siklus hidup utama dari halaman kait, seperti viewDidLoad , viewDidAppear: dll., Untuk menghitung waktu rendering halaman dan akhirnya menemukan halaman yang dimuat dengan lambat. Namun, jika Anda benar -benar mulai mencapainya melalui ide -ide di atas, Anda akan mengalami kesulitan. Bagaimana cara menghubungkan siklus hidup semua halaman diterapkan di APM SDK? Bagaimana jika saya mencoba mengaitkan UIViewController ? Metode Hook UIViewController jelas tidak layak karena hanya berfungsi pada metode UIViewController , dan sebagian besar pengontrol tampilan dalam aplikasi mewarisi dari UIViewController , jadi metode ini tidak layak. Tapi Tingyun SDK dapat diimplementasikan. Logika kait halaman terutama diimplementasikan di kelas _priv_NBSUIAgent . Berikut ini adalah definisi kelas _priv_NBSUIAgent , di antaranya hook_viewDidLoad dan metode lain adalah petunjuk.
@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
}
Pertama -tama mari kita mengalihkan perhatian kita ke metode lain yang lebih mencurigakan: hookSubOfController , implementasi spesifiknya adalah sebagai berikut:
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;
}
Dari penamaan _subMetaClassNamesInMainBundle_c dan parameter "UIViewController" yang masuk, pada dasarnya dapat disimpulkan bahwa fungsi C ini adalah subkelas dari semua UIViewController di MainBundle. Bahkan, jika Anda memecahkan jalur perakitan setelah panggilan fungsi diselesaikan melalui LLDB, Anda akan menemukan bahwa array yang dikembalikan memang subclass dari UIViewController . Pernyataan if berikut menentukan bahwa register r12 bukan nil dan count register r12 tidak sama dengan 0 sebelum logika dalam if dieksekusi. r12 Register menyimpan nilai pengembalian fungsi _subMetaClassNamesInMainBundle_c , yang merupakan array dari subkelas UIViewController .
Kode fungsi _subMetaClassNamesInMainBundle_c adalah sebagai berikut:
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;
}
Subrutin loc_10001db4d di fungsi _subMetaClassNamesInMainBundle_c memanggil fungsi _classNamesInMainBundle_c , dan kode fungsi adalah sebagai berikut:
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;
}
Implementasi fungsi _classNamesInMainBundle_c sudah jelas, tidak lebih dari menyebut objc_copyClassNamesForImage untuk mendapatkan nama semua kelas jalur yang dapat dieksekusi mainBundle . Jumlah set ditetapkan ke variabel outCount , dan penelepon dapat menggunakan outCount untuk melintasi itu.
static inline char ** WDTClassNamesInMainBundle ( unsigned int *outCount) {
NSString *executablePath = [[ NSBundle mainBundle ] executablePath ];
char **classNames = objc_copyClassNamesForImage ([executablePath UTF8String ], outCount);
return classNames;
} Jika Anda tidak peduli dengan detailnya, maka implementasi fungsi _subMetaClassNamesInMainBundle_c juga sangat jelas, yaitu untuk melintasi nilai pengembalian fungsi objc_copyClassNamesForImage . Jika item tersebut merupakan subkelas dari UIViewController , maka metaClass kelas diperoleh dan ditambahkan ke array variabel var_38 .
Selanjutnya, mari kita fokus pada pernyataan do-while loop di dalamnya. Pernyataan untuk penilaian loop adalah var_98 + 0x1 < rax . var_98 menetapkan register rdx di awal loop, dan register rdx diinisialisasi ke 0 di luar loop, jadi var_98 adalah penghitung, dan register rax adalah metode count yang ditetapkan untuk register r12 . Berdasarkan hal ini do-while sebenarnya melintasi array subkelas UIViewController . Perilaku traversal adalah untuk bertukar initialize dan nbs_jump_initialize: metode melalui _nbs_Swizzle_orReplaceWithIMPs .
Kode untuk nbs_jump_initialize adalah sebagai berikut:
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;
}
Kode nbs_jump_initialize agak panjang, tetapi dari rutin loc_1000501a3 , dapat diamati bahwa logika utama akan menjalankan tiga metode, hook_viewDidLoad , hook_viewWillAppear dan hook_viewDidAppear , dengan demikian menghubungkan ketiga metode subklass UIViewController ini.
Pertama, gunakan hook_viewDidLoad: metode sebagai contoh untuk dijelaskan. Kode berikut mungkin agak tidak jelas dan membutuhkan analisis yang cermat.
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: Parameter arg2 dalam metode ini adalah kelas dari ViewController untuk mengaitkan, dapatkan nama kelas arg2 dan tetapkan ke register rbx , dan kemudian gunakan rbx untuk membangun string nbs_%s_viewDidLoad , seperti nbs_XXViewController_viewDidLoad , mendapatkan selektor nbs_xxviewController_viewDid, mendapatkan selektor dari string dan nbs_xxviewController_viewDid, dan mendapatkan selektor nbs_xxviewController_viewDid, dan mendapatkan selektor var_C0 , dan mendapatkan selektor itu. __NSConcreteStackBlock dalam kalimat berikut adalah objek blok dari tumpukan penyimpanan yang dibuat. Blok ini kemudian akan mendapatkan penunjuk fungsi IMP melalui metode imp_implementationWithBlock . _nbs_Swizzle_orReplaceWithIMPs adalah fungsi yang mengimplementasikan pertukaran metode, dan parameternya adalah: arg2 adalah kelas ViewController ; @selector(viewDidLoad) adalah pemilih viewDidLoad ; var_C0 adalah pemilih nbs_%s_viewDidLoad , r14 adalah imp dari __NSConcreteStackBlock kedua; var_D0 adalah IMP dari __NSConcreteStackBlock pertama.
Seluruh logika hook_viewDidLoad: kira -kira jelas, tetapi ada pertanyaan di sini mengapa tidak secara langsung bertukar dua IMP, tetapi sebaliknya membangun dua blok terlebih dahulu dan kemudian menukar IMP dari dua blok? Alasannya adalah bahwa hasil kelas induk dari ViewController , yaitu, class_getSuperclass , perlu dilewati sebagai parameter ke metode yang dipertukarkan. Dengan cara ini, jumlah parameter yang ditandatangani oleh dua pemilih yang dipertukarkan tidak konsisten, dan masalah ini perlu diselesaikan dengan cerdik dengan membangun blok. Faktanya, __NSConcreteStackBlock pertama mengeksekusi nbs_jump_viewDidLoad:superClass: metode _priv_NBSUIHookMatrix . Seperti yang disebutkan sebelumnya, ada superClass dalam parameter metode ini. Mengenai mengapa parameter ini diperlukan, saya akan memperkenalkannya nanti.
Mengapa __NSConcreteStackBlock kedua mengeksekusi nbs_jump_viewDidLoad:superClass: metode? Hapus centang pada opsi Remove potentially dead code Hopper, kode ini adalah sebagai berikut:
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;
}
Mari kita lihat kode _nbs_getInstanceImpOf :
void _nbs_getInstanceImpOf() {
rax = class_getInstanceMethod(rdi, rsi);
method_getImplementation(rax);
return;
}
Fungsi fungsi _nbs_getInstanceImpOf sangat jelas. Untuk mendapatkan IMP pemilih rsi di kelas rdi , pembaca akan menemukan bahwa _nbs_getInstanceImpOf telah dipanggil dua kali dalam metode hook_viewDidLoad: : rdi pertama adalah kelas _priv_NBSUIHookMatrix , rdx adalah @selector(nbs_jump_viewDidLoad:superClass:) , rdi kedua adalah kelas ViewController , rdx adalah @selector(viewDidLoad) .
Selanjutnya, mari kita lihat __NSConcreteStackBlock pertama, yang berarti blok yang akan memanggil nbs_jump_viewDidLoad:superClass: Kodenya adalah sebagai berikut:
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;
}
Register r8 adalah imp dari nbs_jump_viewDidLoad:superClass: dan kode ini hanya memanggil imp ini. Parameter fungsi IMP sama dengan 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;
}
Waktu startup dijelaskan dengan Firebase Performance Monitoring SDK sebagai contoh. FPM SDK digunakan sebagai singkatan untuk menggambarkannya. FPM SDK mengimplementasikan statistik pada waktu startup yang dingin, dan logika utama diimplementasikan di kelas FPRAppActivityTracker .
Pertama -tama lihat metode +load kelas, dan kode dekompilasi adalah sebagai berikut:
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;
}
Jelas, _appStartTime adalah instance NSDate statis yang digunakan untuk menghemat waktu mulai dari seluruh startup aplikasi, sehingga SDK FPM menandai waktu mulai startup aplikasi di +load FPRAppActivityTracker . Pembaca yang memahami metode +load harus tahu bahwa metode ini adalah metode pengait sebelum fungsi main dipanggil. Waktu yang tepat adalah ketika gambar dimuat ke runtime dan metode +load siap, dan kemudian metode +load akan mulai dipanggil. Selain itu, berbagai jenis metode +load juga terkait dengan urutan file Build Phases->Compile Sources . Kami percaya bahwa ini tidak memiliki dampak signifikan pada statistik waktu startup.
Setelah itu, pemberitahuan UIWindowDidBecomeVisibleNotification terdaftar. Pemberitahuan ini dipicu ketika objek UIWindow diaktifkan dan ditampilkan pada antarmuka. Pembaca dapat mendaftarkan pemberitahuan ini dan kemudian mencetak objek pemberitahuan dengan LLDB. Contohnya adalah sebagai berikut:
NSConcreteNotification 0x7fc94a716f50 {name = UIWindowDidBecomeVisibleNotification; object = <UIStatusBarWindow: 0x7fc94a5092a0; frame = (0 0; 320 568); opaque = NO; gestureRecognizers = <NSArray: 0x7fc94a619f30>; layer = <UIWindowLayer: 0x7fc94a513f50>>}
Pertama kali saya menerima pemberitahuan UIWindowDidBecomeVisibleNotification lebih awal dari - application:didFinishLaunchingWithOptions: Callback, pemberitahuan ini dipicu ketika window bilah status dibuat. Implementasi ini terasa sedikit rumit dan tidak dapat memastikan bahwa Apple akan menyesuaikan waktu panggilan di masa depan.
Di bawah ini adalah deskripsi resmi UIWindowDidBecomeVisibleNotification .
Diposting saat objek UIWindow menjadi terlihat. Objek pemberitahuan adalah objek jendela yang telah terlihat. Pemberitahuan ini tidak berisi kamus UserInfo. Beralih antar aplikasi tidak menghasilkan pemberitahuan terkait visibilitas untuk Windows. Perubahan visibilitas jendela mencerminkan perubahan pada properti tersembunyi jendela dan hanya mencerminkan visibilitas jendela di dalam aplikasi.
Berikut ini adalah metode penanganan pemberitahuan. Saya memulihkan metode ini untuk kode pseudo objektif-C, yang dapat membandingkan kode pseudo yang didekompilasi.
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];
}
Metode ini akan mencatat pemberitahuan UIWindowDidBecomeVisibleNotification di akhir, karena pemberitahuan akan disebut beberapa kali, dan kami hanya perlu untuk melaksanakannya sekali. Pertama, hubungi metode -startAppActivityTracking untuk mulai melacak aktivitas aplikasi. Metode ini akan dibahas secara mendalam nanti.
Pertama -tama, kami jelas bahwa permintaan jaringan yang kami diskusikan di sini tidak memiliki instruksi khusus untuk merujuk ke permintaan HTTP. Tingyun SDK terutama menggunakan dua metode untuk mengimplementasikan pemantauan jaringan: yang pertama adalah mengaitkan API yang digunakan oleh pemrograman jaringan iOS, yang terutama menargetkan permintaan jaringan asli; Yang kedua adalah mewarisi NSURLProtocol untuk mengimplementasikan permintaan jaringan, yang terutama menargetkan permintaan jaringan di UIWebView.
SDK mengaitkan API untuk membangun NSURLSessionDataTask , NSURLSessionUploadTask dan NSURLSessionDownloadTask di semua permintaan jaringan. Logika hook ada dalam fungsi C _nbs_hook_NSURLSession , dan kode pseudo adalah sebagai berikut:
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 adalah fungsi C yang mendengarkan cloud untuk mengimplementasikan metode swizzling.
Dari kode, kita dapat melihat bahwa selain menggunakan _nbs_Swizzle untuk NSURLSessionDataTask , NSURLSessionUploadTask dan NSURLSessionDownloadTask API yang disebutkan di atas, itu juga menggantikan metode sessionWithConfiguration:delegate:delegateQueue: metode. Saya akan menjelaskan mengapa metode ini ketagihan nanti.
Semua implementasi metode kait didefinisikan dalam kelas _priv_NSURLSession_NBS .
Kode inti nbs_dataTaskWithRequest:completionHandler: adalah sebagai berikut:
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 adalah model parameter kinerja yang terkait dengan permintaan HTTP di SDK. Struktur kelas ini adalah sebagai berikut:
@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:
}
Tabel berikut mencantumkan makna dari beberapa atribut utama:
| milik | arti |
|---|---|
| tm_pnt_send | Minta Waktu Mulai |
| tm_dur_dns | Waktu Resolusi DNS |
| tm_dur_cnnct | Waktu Pembentukan Koneksi TCP |
| tm_dur_firstp | Waktu paket pertama |
| tm_dur_ssl | Waktu Jabat tangan SSL |
| kode status | Kode Status HTTP |
Waktu respons adalah indikator kuantitatif yang baik, yang dapat digunakan untuk mengukur waktu tunggu bagi pengguna untuk meminta layanan. Secara umum didefinisikan sebagai periode ketika pengguna mengirim permintaan dan konten respons di server mencapai klien.
Gambar berikut adalah penjelasan terperinci tentang permintaan HTTP

Dari gambar di atas, kita dapat mengamati bahwa waktu respons mencakup waktu resolusi nama domain DNS, waktu koneksi yang ditetapkan dengan server, waktu pemrosesan server, dan waktu ketika respons tiba di klien.
Jika Anda menggunakan Charles untuk mencegat permintaan HTTP, Anda dapat melihat data waktu respons di kolom timing tab Ikhtisar. Duration pada gambar di bawah ini mewakili waktu respons total dari permintaan, yang juga mencakup DNS (waktu resolusi nama domain DNS), Connect (waktu pembentukan koneksi) dan SSL Handshake (waktu jabat tangan SSL) yang disebutkan di atas. Karena permintaan ini adalah permintaan HTTP, bidang SSL Handshake dibiarkan kosong.

Bahkan, setelah mengembangkan fitur waktu respons di SDK, kami juga dapat memeriksa kebenaran hasil dengan cara ini. Tentu saja, waktu yang diperoleh dalam SDK tidak persis sama dengan Charles, karena dua metode implementasi sama sekali berbeda, tetapi perbedaan di antara mereka harus berada dalam kisaran yang masuk akal. Aspek ini akan dibahas secara rinci di bawah ini.
Melalui pengantar di atas, kita dapat dengan mudah memikirkan sebuah ide: melalui fungsi ketika permintaan hook dikeluarkan, catat waktu permintaan, lalu kaitkan respons panggilan balik di iOS SDK, catat waktu akhir, dan hitung perbedaan untuk mendapatkan waktu respons dari permintaan ini. Hal yang sama berlaku untuk ide umum Tingyun, tetapi ada banyak detail yang perlu diperhatikan. Mari kita bahas rencana implementasinya secara rinci selanjutnya.
Mendengarkan Cloud adalah metode resume dari nsurlSessionTask dalam fungsi _nbs_hook_NSURLSessionTask untuk mencapai tujuan merekam awal permintaan.
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;
}
Kembalikan pseudocode di atas ke kode Objective-C sebagai berikut:
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];
}
Kita tahu bahwa dalam kerangka dasar Foundation , beberapa kelas sebenarnya adalah kelompok kelas, seperti NSDictionary dan NSArray . NSURLSessionTask juga merupakan keluarga kelas, dan rantai warisan berbeda dalam versi sistem yang berbeda, jadi jelas Anda tidak dapat secara langsung mengaitkan kelas NSURLSessionTask . Metode yang cerdas diadopsi di sini untuk membangun sesi sesaat melalui metode konfigurasi ephemeralSessionConfiguration . Ini mirip dengan sesi default, tetapi tidak ada data yang disimpan di disk, dan semua cache, cookie, kredensial, dll disimpan dalam RAM dan terkait dengan sesi. Dengan cara ini mereka akan secara otomatis dihapus ketika sesi tidak valid. Kemudian, melalui sesi singkat ini, objek sesi dibuat, dan objek tugas akhirnya dibangun, dan kelas nyata diperoleh melalui objek tugas ini.
Pendekatan pintar di atas awalnya tidak dibuat oleh Tingyun. Ini sebenarnya mengacu pada pendekatan AFNetworking. Untuk menambahkan pemberitahuan di Afnetworking, resume dan suspend metode Hook NSURLSessionTask juga diimplementasikan di 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];
}
Metode
class_copyMethodListakan dipanggil dalam metode_nbs_slow_isClassItSelfHasMethoduntuk mendapatkan daftar metode kelas ini. Perhatikan bahwa daftar metode yang diperoleh dengan metode ini tidak berisi metode kelas induk, sehingga metode_nbs_slow_isClassItSelfHasMethodsebenarnya adalah untuk menilai apakah kelasclsitu sendiri berisi@selector(resume).
Faktanya, logika di atas juga diimplementasikan di Flex Perpustakaan Open Source, tetapi ada sedikit perbedaan dalam implementasi. Flex akan membedakannya dari Hook __NSCFLocalSessionTask , NsurlSessionTask dan __nscFurlSessionTask sesuai dengan versi sistem. Saya pribadi merasa bahwa implementasi Cloud Mendengarkan lebih elegan daripada pengkodean yang keras dari Flex . Karena __NSCFLocalSessionTask dan __NSCFURLSessionTask adalah kelas pribadi, split dan splicing diadopsi untuk menghindari penolakan audit.
+ (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);
});
}
Implementasi penggantian resume asli di atas diimplementasikan melalui imp_implementationWithBlock , dan blok yang diganti adalah sebagai berikut:
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]);
}
}
}
Dalam pseudo-code di atas, logika yang tidak relevan di ___nbs_hook_NSURLSessionTask_block_invoke diabaikan. Anda dapat melihat bahwa stempel waktu dihasilkan dan cap waktu digunakan sebagai parameter entri [rbx startWithIP:0x0 DNSTime:var_30 atTimePoint:r8 withObject:r9] metode. rbx adalah instance dari _priv_NBSHTTPTransaction , dan instance ini diperoleh melalui objek terkait dari NSURLSessionDataTask . Logika untuk membuat instance _priv_NBSHTTPTransaction dan mengatur objek yang terkait ada di -[_priv_NSURLSession_NBS nbs_dataTaskWithRequest:completionHandler:] metode.
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:] metode akan menetapkan waktu parameter ke properti tm_pnt_send -nya.
-[_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));
}
Tentu saja, di samping -[_priv_NSURLSession_NBS nbs_dataTaskWithRequest:completionHandler:] Metode, metode berikut ini juga berisi logika ini:
nbs_downloadTaskWithRequest:nbs_downloadTaskWithRequest:completionHandler:nbs_downloadTaskWithResumeData:completionHandler:nbs_uploadTaskWithRequest:fromData:completionHandler:nbs_uploadTaskWithRequest:fromFile:completionHandler:nbs_uploadTaskWithRequest:fromFile:nbs_uploadTaskWithRequest:fromData:nbs_uploadTaskWithStreamedRequest: Waktu respons akhir dihitung dalam metode finishAt dan menugaskannya ke atribut 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];
}
}
Untuk permintaan jaringan yang diprakarsai dengan menelepon dataTaskWithRequest:completionHandler: Metode, panggilan balik yang sudah selesai ada di completionHandler , sehingga metode finishAt harus dipanggil dalam panggilan balik yang sudah selesai. Metode serupa termasuk ___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];
}
Namun, untuk permintaan jaringan yang memanggil dataTaskWithRequest: Metode, Anda perlu mengaitkan URLSession:task:didCompleteWithError: Metode 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);
}