iOS性能優化 — 一、crash監控及防崩潰處理

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大家好,歡迎來到 iOS性能優化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本篇文章將爲大家講解下crash監控及防崩潰處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何收集crash利用bugly、友盟等第三方收集監控crash原理"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"防崩潰處理常見崩潰類型防崩潰處理方案hook方案安全接口"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"如何收集crash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在平常開發過程中,由於代碼的不嚴謹比如不對入參做校驗,使用C++野指針等會造成程序crash。crash應該算是最嚴重的bug了,尤其是線上crash,如果App用戶量大的話可能造成很大的影響,所以需要有一套機制來收集項目中的crash並及時解決。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"利用bugly、友盟等第三方收集"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大部分公司都是採用第三方平臺來收集crash。業內用的比較多有bugly、友盟、talkingdata。筆者比較推薦bugly,騰訊研發,比較輕量,用來監控crash和卡頓還是很方便的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"監控crash原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一線大廠,大部分都會自研crash捕獲框架。這個時候瞭解crash捕獲原理就很有必要了,大家可以閱讀開源庫"},{"type":"link","attrs":{"href":"https://github.com/kstenerud/KSCrash","title":null},"content":[{"type":"text","text":"kscrash"}]},{"type":"text","text":"或者"},{"type":"link","attrs":{"href":"https://github.com/microsoft/plcrashreporter","title":null},"content":[{"type":"text","text":"plcrashreporter"}]},{"type":"text","text":"其實捕獲crash原理很簡單。主要需要處理兩種情況:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、OC類異常。NSException異常是OC代碼導致的crash。我們可以先通過NSGetUncaughtExceptionHandler保存先前註冊的異常處理器,然後通過NSSetUncaughtExceptionHandler設置我們自己的異常處理器,我們不監控了,"},{"type":"text","marks":[{"type":"strong"}],"text":"需要再設置回原理的異常處理器"},{"type":"text","text":",在我們自己的uncaughtHandleException處理器裏面,"},{"type":"text","marks":[{"type":"strong"}],"text":"需要手動調用下原來的處理器"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"static NSUncaughtExceptionHandler* g_previousUncaughtExceptionHandler;\n\nvoid installUncaughtExceptionHandler(void){\n g_previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();\n NSSetUncaughtExceptionHandler(&uncaughtHandleException);\n\n}\n\nvoid uninstallUncaughtExceptionHandler(void){\n if(g_previousUncaughtExceptionHandler){\n NSSetUncaughtExceptionHandler(g_previousUncaughtExceptionHandler);\n }\n\n}\n\nvoid uncaughtHandleException(NSException *exception)\n{\n // 異常的堆棧信息\n NSArray *stackArray = [exception callStackSymbols];\n\n // 出現異常的原因\n NSString *reason = [exception reason];\n\n // 異常名稱\n NSString *name = [exception name];\n\n NSString *exceptionInfo = [NSString stringWithFormat:@\"Exception reason:%@\\nException name:%@\\nException stack:%@\",name, reason, stackArray];\n NSLog(exceptionInfo);\n\n if (g_previousUncaughtExceptionHandler != NULL)\n {\n g_previousUncaughtExceptionHandler(exception);\n }\n\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、Signal信號捕獲。Signal信號是由iOS底層mach信號異常轉換後以signal信號拋出的異常。既然是兼容posix標準的異常,我們同樣可以通過sigaction函數註冊對應的信號。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"static struct sigaction* g_previousSignalHandlers = NULL; //舊的信號處理函數結構體數組\nstatic int g_fatalSignals[] = {\n SIGHUP,\n SIGINT,\n SIGQUIT,\n SIGABRT,\n SIGILL,\n SIGSEGV,\n SIGFPE,\n SIGBUS,\n SIGPIPE\n};\nstatic int g_fatalSignalsCount = (sizeof(g_fatalSignals) / sizeof(g_fatalSignals[0]));\n\nconst int* kssignal_fatalSignals(void){\n return g_fatalSignals;\n}\n\nint kssignal_numFatalSignals(void){\n return g_fatalSignalsCount;\n}\n\nvoid signalExceptionHandler(int signo, siginfo_t *info, void *uapVoid){\n\n void *frames[128];\n int i, len = backtrace(frames, 128);\n //堆棧信息\n char **symbols = backtrace_symbols(frames, len);\n NSMutableString *exceptionContent = [[NSMutableString alloc] init];\n [exceptionContent appendFormat:@\"signal name:%d \\n signal stack:\\n\",signo];\n for (i = 0; i < len; ++i)\n {\n [exceptionContent appendFormat:@\"%s\\r\\n\", symbols[i]];\n }\n //釋放緩存\n free(symbols);\n\n raise(signo);\n}\n\nvoid installSignalHandler(void){\n const int* fatalSignals = kssignal_fatalSignals();\n\n int fatalSignalsCount = kssignal_numFatalSignals();\n\n if(g_previousSignalHandlers == NULL){\n g_previousSignalHandlers = (struct sigaction *)malloc(sizeof(*g_previousSignalHandlers)\n * (unsigned)fatalSignalsCount);\n }\n //初始化處理函數結構體\n struct sigaction action = {{0}};\n\n action.sa_flags = SA_SIGINFO | SA_ONSTACK;\n sigemptyset(&action.sa_mask);\n action.sa_sigaction = &signalExceptionHandler;\n\n for(int i = 0; i < fatalSignalsCount; i++)\n {\n if(sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i]) != 0)\n {\n // 取消已監聽的handler\n for(i--;i >= 0; i--)\n {\n sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);\n }\n break;\n }\n }\n}\n\nvoid uninstallSignalHandler(void){\n\n const int* fatalSignals = kssignal_fatalSignals();\n int fatalSignalsCount = kssignal_numFatalSignals();\n\n for(int i = 0; i < fatalSignalsCount; i++)\n {\n sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"防崩潰處理"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"常見崩潰類型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據筆者經驗來看,oc中大部分崩潰都是源於沒有對調用方法進行入參判斷,比如數組添加object沒有判空,訪問數組元素越界等;還有一些C++崩潰,比如使用野指針。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"防崩潰處理方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於oc中大部分崩潰都是來源於未對入參進行判斷,所以調用方法對入參進行判斷就能解決崩潰。如何統一地解決這類崩潰,有兩種方案:hook方案和安全接口"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"hook方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該方案對系統常見類的方法進行hook,進行入參判斷。比如對hook NSMutableArray的addObject方法,進行判空操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"@implementation NSMutableArray (akSafe)\n\n+ (void)load {\n [self swizzMethodOriginalSelector:@selector(addObject:)\n swizzledSelector:@selector(akSafe_addObject:)];\n}\n\n+ (void)swizzMethodOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {\n Method originalMethod = class_getInstanceMethod(self.class, originalSelector);\n Method swizzledMethod = class_getInstanceMethod(self.class, swizzledSelector);\n BOOL didAddMethod = class_addMethod(self.class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));\n if (didAddMethod) {\n class_replaceMethod(self.class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));\n } else {\n method_exchangeImplementations(originalMethod, swizzledMethod);\n }\n}\n\n- (void)aksafe_AddObject:(id)anObject {\n\n if (anObject) {\n [self aksafe_AddObject:anObject];\n }\n}\n\n@end\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"安全接口"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該方案對系統常見類的方法進行一層封裝,進行入參判斷。大家統一調用安全接口,比如封裝NSMutableArray的addObject方法爲aksafe_AddObject,大家統一調用aksafe_AddObject添加對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"@implementation NSMutableArray (aksafe)\n\n- (void)aksafe_AddObject:(id)anObject {\n\n if (anObject) {\n [self addObject:anObject];\n }\n}\n@end\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"兩種方案各有優缺點,hook方案優點是業務方直接調用系統方法就行,缺點是由於要進行hook,有損性能;安全接口方案是業務方要統一調用安全接口,優點則是輕量。"},{"type":"text","marks":[{"type":"strong"}],"text":"筆者推薦方案二,輕量並且可以作爲編碼規範。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"資料推薦"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你正在跳槽或者正準備跳槽不妨動動小手,添加一下咱們的交流羣"},{"type":"link","attrs":{"href":"https://links.jianshu.com/go?to=https%3A%2F%2Fjq.qq.com%2F%3F_wv%3D1027%26k%3D5JFjujE","title":null},"content":[{"type":"text","text":"1012951431"}]},{"type":"text","text":"來獲取一份詳細的大廠面試資料爲你的跳槽多添一份保障。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/92/92394154a3e96888899d492b73de1e69.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章