DDLog源碼解析一:框架結構

導語:

DDLog,即CocoaLumberjack是iOS開發用的最多的日志框架,出自大神Robbie Hanson之手(還有諸多知名開源框架如 XMPPFrameworkCocoaAsyncSocket,都是即時通信領域很基礎應用很多的框架)。了解DDLog的源碼將有助于我們更好的輸出代碼中的日志信息,便于定位問題,也能對我們在書寫自己的日志框架或者其他模塊時有所啟發。

此系列文章將分為以下幾篇:
- DDLog源碼解析一:框架結構
- DDLog源碼解析二:設計初衷
- DDLog源碼解析三:FileLogger

引言:為什么需要DDLog?

我們在iOS入門階段最早能通過代碼得到的反饋,可能就是打印日志,那時我們通常會遇到第一個朋友:NSLog,這是iOS系統的默認打印日志的方式。當我們在初級開發階段NSLog已經足夠好,幫我們留下必要信息便于定位問題。

但隨著App復雜度的增加,調試起來變得麻煩,NSLog的性能也漸漸成為了瓶頸,我們也開始有了一些個性的需求,比如想把某一類日志信息用紅色顯示在控制臺,比如寫在文件中的日志只寫我們認為很關鍵模塊的部分...... 這時候NSLog已經無法滿足我們的需求,我們會發現除了自己造輪子,就只能找輪子了,幸好有DDLog。

需求:DDLog能干什么?

功能上的需求可能包括:

  • 把日志寫到Xcode臺上
  • 把日志寫到文件里
  • 把日志寫到iOS系統日志中
  • 把日志規定多個級別,我們的日志可以歸類到不同級別;
  • 根據日志級別,我們可以只輸出某個級別的日志;
  • 根據日志級別,我們可以對某些級別日志加顏色顯示;
  • 對某個類設定日志級別;
  • ......

但是,具備這些功能后,我們可能就要關注三個指標:
準確!
快速!
安全!
準確是最基本的,我們要保證日志如實的記錄我們記錄的東西,內容和日志順序、時間等都是正確的; 快速也是比較重要的點,試想我們的高清視頻通話的功能在通話時,如果實時打印出很多信息并且寫到文件中,如果性能不過關,就可能會影響視頻通話的效果;安全范圍很寬泛,除了記錄內容的線程安全外,最直接的就是不對app造成過大侵犯,比如寫到文件中內容過多,將導致app大小劇增,對于手機容量有限的用戶將造成很大體驗上的影響。

而DDLog的設計上考慮了這幾點,所以我們有必要解析一下DDLog在哪些方面的設計來滿足這些需求:

正文

想知道DDLog如何此般強大,我們首先對DDLog的框架進行解析,先看下官方的框架示意圖(已經與代碼部分不符合,但不影響理解):

框架結構

本文將主要對上圖中幾個重要的類(DDLog、DDLogger、DDAbstractLogger、DDTTYLogger、DDOSLogger、DDFileLogger、DDASLLogger)及其之間的關系進行分析,DDLog主要是通過四種logger分別提供給開發者四個方面日志輸出的能力,對應于上面的順序依次是
DDTTYLogger:寫到Xcode控制臺、
DDOSLogger:寫到iOS10之后的系統日志、
DDFileLogger:寫到文件中、
DDASLLogger:寫到iOS10之前的系統日志,
而DDLog類是對這四種logger進行管理,統一處理日志的輸出的問題
。其余類包含fomatter(自定義輸出日志的格式和內容)之類的處理等,本文不做解析。

下圖是我整理后的圖:


image.png

DDLogger

這個協議主要定義了logger一些通用的行為:

@protocol DDLogger <NSObject>
- (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;
@end

DDAbstractLogger

作為遵守了DDLogger協議的基類,主要是通過一些屬性和方法,描述子類一些通用的行為和能力:

@interface DDAbstractLogger : NSObject <DDLogger>
{
    @public
    id <DDLogFormatter> _logFormatter;
    dispatch_queue_t _loggerQueue;
}

@property (nonatomic, strong, nullable) id <DDLogFormatter> logFormatter;
@property (nonatomic, DISPATCH_QUEUE_REFERENCE_TYPE) dispatch_queue_t loggerQueue;
@property (nonatomic, readonly, getter=isOnGlobalLoggingQueue)  BOOL onGlobalLoggingQueue;
@property (nonatomic, readonly, getter=isOnInternalLoggerQueue) BOOL onInternalLoggerQueue;
@end

此基類的通用init方法中定義了子類都要使用的串行隊列:

_loggerQueue = dispatch_queue_create(loggerQueueName, NULL);
// loggerQueueName由各個子類名字構成

void *key = (__bridge void *)self;
 void *nonNullValue = (__bridge void *)self;

dispatch_queue_set_specific(_loggerQueue, key, nonNullValue, NULL);

需要注意的是,子類init再調用這個基類的init方法時,實際self是相應的子類,這樣就會根據不同子類生成不同的_loggerQueue,并且通過dispatch_get_specific和dispatch_queue_set_specific一對好基友來標識識別每個隊列。

- (NSString *)loggerName {
    return NSStringFromClass([self class]);
}

- (BOOL)isOnGlobalLoggingQueue {
    return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL);
}

- (BOOL)isOnInternalLoggerQueue {
    void *key = (__bridge void *)self;

    return (dispatch_get_specific(key) != NULL);
}

DDLog

真正的BOSS,管理各個logger的add和remove,并暴露各種記錄日志的log方法,這一步將在[下一節](DDLog源碼解析二:線程)詳細解析,主要是線程的保護機制,這里的線程保護機制包括并不限于:保證log語句按順序記錄下來,保證每個logger的添加、移除和level的改變等機制都能立刻再后面的log語句中生效,如何保證各個logger中最終記錄的下來的日志是相同的(不會發生某一個logger的日志比其他的多幾條)......

注意,由于initialize是在類或者其子類的第一個方法被調用前調用,并且只會調用一次,在DDLog的類、子類或實例中可能用到DDLog中定義的這些資源,這里DDLog將相關公用的資源申請放在 類方法 +(void)initialize中,保證DDLog在第一次使用時就已經申請好公用資源。

+ (void)initialize {
    static dispatch_once_t DDLogOnceToken;
    
    dispatch_once(&DDLogOnceToken, ^{        
        _loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL);
        _loggingGroup = dispatch_group_create();
        
        void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null
        dispatch_queue_set_specific(_loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL);
        
        _queueSemaphore = dispatch_semaphore_create(DDLOG_MAX_QUEUE_SIZE);
 
        _numProcessors = MAX([NSProcessInfo processInfo].processorCount, (NSUInteger) 1);
    });
}

這里申請的_queueSemaphore、_loggingQueue、_loggingGroup都是下一節將重點分析的部分。

DDFileLogger

DDFileLogger繼承自DDAbstractLogger,在實例化時將調用DDAbstractLogger的init方法,從而得到自己的_loggerQueue,并實現了自己的logMessage方法和其他文件處理相關方法,第三節將具體介紹。

@interface DDFileLogger : DDAbstractLogger <DDLogger> {
    DDLogFileInfo *_currentLogFileInfo;
}

DDASLLogger

DDASLLogger繼承自DDAbstractLogger,主要功能是將日志寫到ASL中,代碼邏輯簡單,但需要了解ASL相關api才能了解清楚,本文不做解析。

DDOSLogger

DDOSLogger繼承自DDAbstractLogger,主要功能是將日志寫到os_log中,代碼邏輯簡單,但需要了解os_log相關api才能了解清楚,本文不做解析。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,076評論 25 708
  • 附上原文作者連接:作者:金誠 一.榜單介紹 排行榜包括四大類: 單一框架:僅提供路由、網絡層、UI層、通信層或其他...
    這個美嘉不姓陳閱讀 2,278評論 1 35
  • 學習筆記/2個快速做海報的網站
    谷小香閱讀 165評論 0 1
  • 改變畫板大小 》選擇左邊工具欄的畫板工具 Tab鍵 》可將工具欄與條板隱藏或再次顯現 空格鍵 》類似捉手工具 CT...
    葡萄紫耶閱讀 610評論 0 0
  • 無論明不明白,我已經不是神仙了,我只明白一件事,愛一個人是那么的痛苦 — 紫霞仙子 《大話西游》 已經有很長一段時...
    銀劍小王子閱讀 315評論 0 2