引入
在iOS開發(fā)中咆课,日志系統(tǒng)是很重要的一個(gè)部分唤崭,尤其是在修復(fù)代碼中的bug棕孙,通常會(huì)用NSLog來將這些信息打印到XCode控制臺(tái)中顯示舔亭,但在日志信息較多的時(shí)候,會(huì)出現(xiàn)一些性能上的問題蟀俊。因?yàn)镹SLog在使用的時(shí)候占用資源較多钦铺,其設(shè)計(jì)也是基于ASL(Apple System Log)的高層封裝,針對(duì)error log肢预。所以在對(duì)性能要求較高的地方矛洞,不能用NSLog進(jìn)行調(diào)試。
作為NSLog的替代烫映,CocoaLumberjack是一款優(yōu)秀的第三方日志系統(tǒng)沼本。官方的介紹如下:
CocoaLumberjack is a fast & simple, yet powerful & flexible logging framework for Mac and iOS.
It is similar in concept to other popular logging frameworks such as log4j, yet is designed specifically
for Objective-C, and takes advantage of features such as multi-threading, grand central dispatch (if available),
lockless atomic operations, and the dynamic nature of the Objective-C runtime.
支持用CocoaPods和Carthage部署到項(xiàng)目中,當(dāng)前的系統(tǒng)要求如下:
在設(shè)計(jì)中锭沟,CocoaLumberjack使用了大量的GCD語法抽兆,使性能更好;宏定義的使用族淮,讓API接口語法更加簡便辫红;加上整體架構(gòu)的設(shè)計(jì)獨(dú)特,使這套第三方日志系統(tǒng)更利于根據(jù)一些特殊需求來定制功能祝辣。
代碼分析
使用CocoaLumberjack的時(shí)候贴妻,會(huì)引用其頭文件,Oc的項(xiàng)目引用CocoaLumberjack.h蝙斜,swift項(xiàng)目CocoaLumberjack.swift名惩。
這里,我們從CocoaLumberjack.h展開孕荠,文件開頭給出了大段的注釋信息绢片,詳細(xì)介紹了關(guān)于該日志系統(tǒng)的使用(在該源碼的其他頭文件中,同樣可以看到消息的注釋信息岛琼,對(duì)該接口甚至類的設(shè)計(jì)進(jìn)行解說,可見一套優(yōu)秀的開源項(xiàng)目在細(xì)節(jié)上是做的多么細(xì)致巢株,值得學(xué)習(xí)槐瑞!),隨后阁苞,引入了一系列的頭文件:
DDLog
是該日志系統(tǒng)中核心的部分困檩,接口調(diào)用都通過該類進(jìn)行調(diào)用祠挫,在該類中封裝了許多的協(xié)議和基礎(chǔ)類,也算是作為整個(gè)日志系統(tǒng)的執(zhí)行中樞悼沿,給整套架構(gòu)打下基礎(chǔ)等舔。DDLogMacros.h
和DDAssertMacros.h
這兩個(gè)文件中定義了大量的宏,都是對(duì)DDLog
的接口進(jìn)行的封裝糟趾,封裝后慌植,對(duì)DDLog
的使用變得更加簡單。DDASLLogCapture
是用來捕獲ASL消息的工具類义郑。DDTTYLogger
蝶柿、DDASLLogger
、DDFileLogger
和DDOSLogger
是提供具體日志功能的類非驮,分別對(duì)應(yīng)著控制臺(tái)日志功能交汤、ASL日志功能、文件日志功能和oslog日志功能劫笙。如果需要增加新的日志功能芙扎,可以模仿這幾個(gè)類,實(shí)現(xiàn)新功能填大。
DDLog類
是單例戒洼,其內(nèi)部維持了一個(gè)dispatch_queue_t
、dispatch_group_t
和信號(hào)鎖dispatch_semaphore_t
(最大為1000個(gè)線程同時(shí)訪問),通過這類GCD屬性可以很好地發(fā)揮出多線程的優(yōu)勢(shì)栋盹。此外在該類中還維持著一個(gè)loggers數(shù)組施逾,數(shù)組里面的每個(gè)元素為DDLoggerNode
類型的元素。DDLog
用DDLoggerNode
來保存被添加的logger(同之前介紹的logger)例获,并將添加logger時(shí)的level
和logger中維持的線程也一并添加到DDLoggerNode
里汉额。這樣當(dāng)DDLog
需要log一條message的時(shí)候,就會(huì)去遍歷這個(gè)數(shù)組榨汤,然后根據(jù)level
在相應(yīng)的dispatch_queue_t
中執(zhí)行蠕搜。
再DDLog
中,定義了創(chuàng)建logger需要的一些接口和基礎(chǔ)類收壕,DDLogger
和DDLogFormatter
是兩個(gè)協(xié)議妓灌,DDLogger
是創(chuàng)建的logger必須遵循的協(xié)議,內(nèi)部的定義如下:
- (void)logMessage:(DDLogMessage *)logMessage NS_SWIFT_NAME(log(message:));
@property (nonatomic, strong) id <DDLogFormatter> logFormatter;
@optional
- (void)didAddLogger;
- (void)didAddLoggerInQueue:(dispatch_queue_t)queue;
- (void)willRemoveLogger;
- (void)flush;
@property (nonatomic, DISPATCH_QUEUE_REFERENCE_TYPE, readonly) dispatch_queue_t loggerQueue;
@property (nonatomic, readonly) NSString *loggerName;
其中logMessage
是用來被調(diào)用執(zhí)行l(wèi)og的接口蜜宪,在DDLog
中被調(diào)用;logFormatter
是一個(gè)遵循DDLogFormatter
協(xié)議的類虫埂,主要用來控制log的格式(控制臺(tái)輸出或者存入文件);接下來的did和will開頭的接口為事件響應(yīng),可以選擇去實(shí)現(xiàn)在相應(yīng)事件發(fā)生時(shí)候的處理圃验;flush
是在一些關(guān)系內(nèi)存和io操作需要實(shí)現(xiàn)的類掉伏,它可以控制在內(nèi)存不足的時(shí)候?qū)uffer存入磁盤或者相關(guān)的數(shù)據(jù)庫;最后loggerQueue
和loggerName
用來獲取在logger中維持的線程隊(duì)列,loggerName
是用來創(chuàng)建隊(duì)列用的名字斧散,在DDAbstractLogger
中可以看到logger線程隊(duì)列是如何維持的供常。
在創(chuàng)建logger的時(shí)候,只需繼承于DDAbstractLogger
鸡捐,其內(nèi)部會(huì)維持一個(gè)CDG隊(duì)列栈暇,然后遵循DDLogger
協(xié)議,接口都在協(xié)議里面定義好了箍镜。在logger里面只需專注于功能的實(shí)現(xiàn)源祈,這樣可以省去很多接口定義和屬性維持方面的事。
同樣在DDLog
里面會(huì)出現(xiàn)很多類似 id<DDLogger>
的屬性鹿寨,通過協(xié)議新博,即使不知道其具體實(shí)現(xiàn),也可以調(diào)用接口脚草。通過DDAbstractLogger
基類和DDLogger
協(xié)議赫悄,為拓展做了很好的基礎(chǔ)。
CocoaLumberjack的架構(gòu)關(guān)系如下圖:
對(duì)比實(shí)驗(yàn)
蘋果官方在2016年的WWDC上放出了新的日志系統(tǒng)(The unified logging system)馏慨,提供了日志信息分類統(tǒng)計(jì)和關(guān)鍵信息捕捉等功能埂淮,而且在速度上會(huì)更快、使用更方便写隶、提供給開發(fā)者的可控性也更多倔撞。
最后對(duì)命令行輸出效率進(jìn)行實(shí)驗(yàn),這里對(duì)NSLog
慕趴、printf
痪蝇、os_log
和writev
在命令行的輸出性能進(jìn)行了對(duì)比:
CFAbsoluteTime startNSLog = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < 10000; i++) {
NSLog(@"%d", i);
}
CFAbsoluteTime endNSLog = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime startPrintf = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < 10000; i++) {
printf("%d\n", i);
}
CFAbsoluteTime endPrintf = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime startOsLog = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < 10000; i++) {
os_log(OS_LOG_DEFAULT, "%d\n", i);
}
CFAbsoluteTime endOsLog = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime startWritev = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < 10000; i++) {
struct iovec v[1];
v[0].iov_base = "a\n";
v[0].iov_len = 2;
writev(STDERR_FILENO, v, 1);
}
CFAbsoluteTime endWritev = CFAbsoluteTimeGetCurrent();
NSLog(@"NSLog time: %lf, printf time: %lf", endNSLog - startNSLog, endPrintf - startPrintf);
NSLog(@"OsLog time: %lf, writev time: %lf", endOsLog - startOsLog, endWritev - startWritev);
Mac電腦上的運(yùn)行時(shí)間:
虛擬機(jī)運(yùn)行時(shí)間:
真機(jī)運(yùn)行時(shí)間:
通過對(duì)比可知,采用writev和printf這種底層的命令行輸出效率最高冕房。
總結(jié):
- CocoaLumberjack架構(gòu)設(shè)計(jì)精妙躏啰,接口易于拓展定制功能。
- 蘋果提供了新的日志系統(tǒng)API耙册,但在命令行的輸出上底層接口的效率更高给僵。
參考:
NSLog效率低下的原因及嘗試lldb斷點(diǎn)打印Log
WWDC2016 session721
Logging