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才能了解清楚,本文不做解析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容