DDLog源码解析一:框架结构

导语:

DDLog,即CocoaLumberjack是iOS开发用的最多的日志框架,出自大神Robbie Hanson之手(还有诸多知名开源框架如 XMPPFramework、 CocoaAsyncSocket,都是即时通信领域很基础应用很多的框架)。了解DDLog的源码将有助于我们更好的输出代码中的日志信息,便于定位问题,也能对我们在书写自己的日志框架或者其他模块时有所启发。

此系列文章将分为以下几篇:
- DDLog源码解析一:框架结构
- DDLog源码解析二:设计初衷
- DDLog源码解析三:FileLogger

引言:为什么需要DDLog?

我们在iOS入门阶段最早能通过代码得到的反馈,可能就是打印日志,那时我们通常会遇到第一个朋友:NSLog,这是iOS系统的默认打印日志的方式。当我们在初级开发阶段NSLog已经足够好,帮我们留下必要信息便于定位问题。

但随着App复杂度的增加,调试起来变得麻烦,NSLog的性能也渐渐成为了瓶颈,我们也开始有了一些个性的需求,比如想把某一类日志信息用红色显示在控制台,比如写在文件中的日志只写我们认为很关键模块的部分...... 这时候NSLog已经无法满足我们的需求,我们会发现除了自己造轮子,就只能找轮子了,幸好有DDLog。

需求:DDLog能干什么?

功能上的需求可能包括:

  • 把日志写到Xcode台上
  • 把日志写到文件里
  • 把日志写到iOS系统日志中
  • 把日志规定多个级别,我们的日志可以归类到不同级别;
  • 根据日志级别,我们可以只输出某个级别的日志;
  • 根据日志级别,我们可以对某些级别日志加颜色显示;
  • 对某个类设定日志级别;
  • ......

但是,具备这些功能后,我们可能就要关注三个指标:
准确!
快速!
安全!
准确是最基本的,我们要保证日志如实的记录我们记录的东西,内容和日志顺序、时间等都是正确的; 快速也是比较重要的点,试想我们的高清视频通话的功能在通话时,如果实时打印出很多信息并且写到文件中,如果性能不过关,就可能会影响视频通话的效果;安全范围很宽泛,除了记录内容的线程安全外,最直接的就是不对app造成过大侵犯,比如写到文件中内容过多,将导致app大小剧增,对于手机容量有限的用户将造成很大体验上的影响。

而DDLog的设计上考虑了这几点,所以我们有必要解析一下DDLog在哪些方面的设计来满足这些需求:

正文

想知道DDLog如何此般强大,我们首先对DDLog的框架进行解析,先看下官方的框架示意图(已经与代码部分不符合,但不影响理解):

DDLog源码解析一:框架结构_第1张图片
image.png

本文将主要对上图中几个重要的类(DDLog、DDLogger、DDAbstractLogger、DDTTYLogger、DDOSLogger、DDFileLogger、DDASLLogger)及其之间的关系进行分析,DDLog主要是通过四种logger分别提供给开发者四个方面日志输出的能力,对应于上面的顺序依次是
DDTTYLogger:写到Xcode控制台、
DDOSLogger:写到iOS10之后的系统日志、
DDFileLogger:写到文件中、
DDASLLogger:写到iOS10之前的系统日志,
而DDLog类是对这四种logger进行管理,统一处理日志的输出的问题。其余类包含fomatter(自定义输出日志的格式和内容)之类的处理等,本文不做解析。

下图是我整理后的图:

DDLog源码解析一:框架结构_第2张图片
image.png

DDLogger

这个协议主要定义了logger一些通用的行为:

@protocol DDLogger 
- (void)logMessage:(DDLogMessage *)logMessage NS_SWIFT_NAME(log(message:));
@property (nonatomic, strong) id  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 
{
    @public
    id  _logFormatter;
    dispatch_queue_t _loggerQueue;
}

@property (nonatomic, strong, nullable) id  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  {
    DDLogFileInfo *_currentLogFileInfo;
}

DDASLLogger

DDASLLogger继承自DDAbstractLogger,主要功能是将日志写到ASL中,代码逻辑简单,但需要了解ASL相关api才能了解清楚,本文不做解析。

DDOSLogger

DDOSLogger继承自DDAbstractLogger,主要功能是将日志写到os_log中,代码逻辑简单,但需要了解os_log相关api才能了解清楚,本文不做解析。

你可能感兴趣的:(DDLog源码解析一:框架结构)