从CocoaLumberjack的高效易扩展谈为什么要学习设计模式

从CocoaLumberjack的高效易扩展谈为什么要学习设计模式

IOS小彩虹2021-08-14 14:28:34180A+A-

原创文章首发本人博客: blog.cocosdever.com/2019/07/25/…

文档更新说明

  • 最后更新 2019年07月25日
  • 首次更新 2019年07月25日

前言

  在编程领域里, 听的多做得少的就是设计模式. 很多程序员都听说过设计模式, 但是却很少自己手动实现一些真正意义上的设计模式, 这几天刚好在复习设计模式, 然后今天又看了iOS上流行的以灵活,扩展性高著称的开源日志框架CocoaLumberjack的源码, 有感而发, 下面我想好好谈谈心得, 一步一步揭开这个高度灵活的框架是如何设计的.   

CocoaLumberjack的使用

  先来简单看一下CocoaLumberjack的使用. CocoaLumberjack为用户提供了几种日志模式, 有控制台, 系统日志, 沙盒日志, 此外还有日志不可或缺的格式化功能Formatter. 下面例子以最复杂的沙盒日志为例:   

// 自己需要定义这个变量, 确定实际上需要的日志级别.
static const DDLogLevel ddLogLevel = DDLogLevelDebug;

// 先创建一个文件日志, 定义好相关配置信息
DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
fileLogger.logFormatter = [[DDDispatchQueueLogFormatter alloc] init];
    
fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
fileLogger.logFileManager.maximumNumberOfLogFiles = 7;

// 添加一个logger
[DDLog addLogger:fileLogger];

// 可以同时加入多个, 比如再加入一个控制台logger, 这样文件和控制台都会有日志信息输出
DDTTYLogger *log2 = [DDTTYLogger sharedInstance];
log2.logFormatter = [[CCLogFormatter alloc] init];
[DDLog addLogger:log2];

// 直接使用内置的宏即可开始记录日志
DDLogDebug(@"Debug");

上面简单的代码,就完成了日志格式的定制, 日志的输出定制, 日志的沙盒文件配置等功能, 灵活设置. 此外还演示了用户自行扩展的格式化类, 用于自行格式化输出内容, 像这样灵活的设计, 确实很值得赞赏, 下面一起来看看源码, 这是如何实现.

CocoaLumberjack源码分析

  展开宏之后得到下面的真实执行入口, 下面主要分析沙盒日志源码

[DDLog
         log:NO
       level:ddLogLevel
        flag:DDLogFlagDebug
     context:0
        file:__FILE__
    function:__FUNCTION__
        line:__LINE__
         tag:nil
      format:@"Debug"
];

上面这个类方法, 一层一层调用下去, 直到下面代码:

// DDLog.m

// 将参数封装成
[self.sharedInstance log:asynchronous message:message level:level flag:flag context:context file:file function:function line:line tag:tag];

这是遇到的第一个设计模式, 也是最常见的, 单例, 没什么好说的, 接着往下执行:

// DDLog.m
// 程序开始调用下面方法, 进入初始化完成后的阶段

- (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag {
	 ///省略其他代码
	[self lt_log:logMessage];
}

程序执行了lt_log方法, 把消息传进去, 接着往下执行:

// DDLog.m

- (void)lt_log:(DDLogMessage *)logMessage {
    ///省略其他代码
    for (DDLoggerNode *loggerNode in self._loggers) {
        // skip the loggers that shouldn't write this message based on the log level
        // 从当前的DDLog实例里取出所有存入的logger节点, 里面就有我传入的logger对象
	    if (!(logMessage->_flag & loggerNode->_level)) {
	        continue;
	    }
	
	    dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool {
	        [loggerNode->_logger logMessage:logMessage];
	    } });
	}
}

上面代码, 注意需要注意的是 _logger的类型是id <DDLogger>, 这就是我要讲的第二种编程模式, 针对抽象编程, 也就是代码只关心对象实现了那些接口, 只要实现了规定的接口, 下面就可以安全调用接口即可, 不关心对象具体的类型. 继续往下执行:

// DDFileLogger.m

// 这里分析的Logger是沙盒日志
- (void)logMessage:(DDLogMessage *)logMessage {
	/// 省略其他代码
	// 这里可以看到框架开始使用我传入的Formatter对象了
	if (_logFormatter != nil) {
        message = [_logFormatter formatLogMessage:logMessage];
        isFormatted = message != logMessage->_message;
    }
    [self lt_logData:[message dataUsingEncoding:NSUTF8StringEncoding]];
}

上面代码, 需要注意的地方是_logFormatter这个变量的类型是id <DDLogFormatter>, 和上一步提到的原理一样, 这里其实也是第三种设计模式: 桥接模式(有点像策略模式, 后面解释). 框架最后执行到了lt_logData:方法后, 才开始真正写日志.通过上面的分析, 可以知道我传入的Logger和Formatter是如何起作用的, 也可以看出来框架底层采用接口编程这个套路, 兼容了传入的任何适合的Logger和Formatter, 让用户可以灵活扩展自己的日志需求.

桥接模式

  写到这里我其实也不管100%告诉你CocoaLumberjack主要利用桥接模式实现可扩展高度灵活的功能, 因为有好几种设计模式都是类似这样的, 我也不是写论文似的分析, 姑且就叫桥接模式吧.   实际上桥接模式和策略模式是有点像, 策略模式更多的应该是表示行为算法, 本身实现了某个策略的类被聚合进主类中时, 更多的是提供简单的无状态算法功能. 而CocoaLumberjack框架可以看到, 像Formatter和Logger这些类本身是包含比较复杂的逻辑和内部对象属性, 所以我觉得更适合把他们叫做桥接模式.   说了这么多, 那什么是桥接模式呢? 具体定义可以自行谷歌搜索一下, 他长得就是文章上面分析的那样, 这里我引用一下菜鸟教程里面的关于桥接模式的表述, 我觉得很合适.   学习了桥接模式的设计方法之后, 再看CocoaLumberjack框架的源码, 就会发现, 好像是这么回事, 很精彩. 看懂了源码之后, 下面就来自行定制一下输出格式, 这整个过程几乎不需要上网查任何资料, 自给自足即可.   

扩展CocoaLumberjack

  通过上面分析可知, 只要我的类实现了DDLogFormatter协议, 就可以作为一个合法的Formatter对象做为DDLog的配置. 下面看看扩展的Formatter源码:   


//
// CCLogFormatter.h
// OCSimpleView
//
// 自定义格式化信息, 用于CocoaLumberjack框架
// Created by Cocos on 2019/7/25.
// Copyright  2019 Cocos. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CocoaLumberjack/CocoaLumberjack.h>

NS_ASSUME_NONNULL_BEGIN

@interface CCLogFormatter : NSObject <DDLogFormatter>

/** * Default initializer */
- (instancetype)init;

/** * Designated initializer, requires a date formatter */
- (instancetype)initWithDateFormatter:(NSDateFormatter * __nullable)dateFormatter NS_DESIGNATED_INITIALIZER;

@end

NS_ASSUME_NONNULL_END

//
// CCLogFormatter.m
// OCSimpleView
//
// Created by Cocos on 2019/7/25.
// Copyright  2019 Cocos. All rights reserved.
//

#import "CCLogFormatter.h"


@interface CCLogFormatter () {
    NSDateFormatter *_dateFormatter;
}

@end

@implementation CCLogFormatter


- (instancetype)init {
    return [self initWithDateFormatter:nil];
}

- (instancetype)initWithDateFormatter:(NSDateFormatter * __nullable)aDateFormatter {
    if ((self = [super init])) {
        if (aDateFormatter) {
            _dateFormatter = aDateFormatter;
        } else {
            _dateFormatter = [[NSDateFormatter alloc] init];
            [_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
            [_dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"];
        }
    }
    
    return self;
}

- (NSString * _Nullable)formatLogMessage:(nonnull DDLogMessage *)logMessage {
    NSString *dateAndTime = [_dateFormatter stringFromDate:(logMessage->_timestamp)];
    return [[NSString alloc] initWithFormat:@"[%@]\n%@:\n[%@]%@", logMessage.file, logMessage.function, dateAndTime, logMessage.message];
}

@end

其实还是挺简单的, 我实现了formatLogMessage:这个方法, 其他的代码直接从DDLogFileFormatterDefault类抄过来, 当然这里直接让CCLogFormatter继承DDLogFileFormatterDefault的话这里初始化代码就可以省略了. 在CCLogFormatter里我让这个格式从原来:

2019/07/25 14:33:36:239  Debug

换成了如下格式:

[~/Xcode/Study/OCSimpleView/OCSimpleView/ViewController.m]
-[ViewController loggerFunc]:
[2019/07/25 14:58:07:358]Debug

这样扩展就结束了. 其他更多复杂的功能, 百变不离其宗, 都是这个套路.

总结

  这篇文章主要是通过对热门框架CocoaLumberjack源码的分析, 找出其中的设计模式, 进而了解设计模式的重要性, 学会灵活扩展框架的实现方法之一. 还有很多设计模式, 传说一共有30多种, 实在是太多了, 学不完, 但是保持一颗年轻的心, 活到老学到老, 我觉得是最重要的 : )

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们