The original article start my blog: blog.cocosdever.com/2019/07/25/…

What’s New

  • This page was last updated on July 25, 2019
  • First updated on July 25, 2019

preface

In the world of programming, design patterns are often heard and rarely done. Many programmers have heard of design mode, but it is rarely their own manual implementation of some real sense of design mode, just a few days in the review of design mode, and then today saw iOS popular with flexible, high scalability of the open source logging framework CocoaLumberjack source code, feel, Now I’d like to talk about my insights and uncover, step by step, how this highly flexible framework is designed.

The use of CocoaLumberjack

Let’s take a quick look at CocoaLumberjack. CocoaLumberjack provides users with several log modes, console, system log, sandbox log, in addition to the indispensable log formatting function Formatter. The following example takes the most complex sandbox log:

// You need to define this variable to determine what level of logging is actually required.
static const DDLogLevel ddLogLevel = DDLogLevelDebug;

// Create a file log to define the configuration information
DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
fileLogger.logFormatter = [[DDDispatchQueueLogFormatter alloc] init];
    
fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
fileLogger.logFileManager.maximumNumberOfLogFiles = 7;

// Add a Logger
[DDLog addLogger:fileLogger];

// You can add more than one console logger at a time. For example, add a console logger, so that both the file and the console have log output
DDTTYLogger *log2 = [DDTTYLogger sharedInstance];
log2.logFormatter = [[CCLogFormatter alloc] init];
[DDLog addLogger:log2];

// Use the built-in macro directly to start logging
DDLogDebug(@"Debug");

Copy the code

The above simple code, completed the log format customization, log output customization, log sandbox file configuration and other functions, flexible Settings. In addition, it also demonstrates the user extended formatting class, used to format the output content, such a flexible design, is really appreciated, let’s take a look at the source code, this is how to achieve.

CocoaLumberjack source analysis

After expanding the macro to get the following real execution entry, the following main analysis of the sandbox log source

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

Copy the code

The above class method is called layer by layer until it reaches the following code:

// DDLog.m

// Encapsulate the parameter into
[self.sharedInstance log:asynchronous message:message level:level flag:flag context:context file:file function:function line:line tag:tag];
Copy the code

This is the first design pattern encountered, and the most common, singletons, nothing to say, so let’s move on:

// DDLog.m
// The program starts calling the following methods to enter the initialization phase

- (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag {
	 // omit other code
	[self lt_log:logMessage];
}

Copy the code

The program executes the LT_log method, passes the message in, and continues:

// DDLog.m

- (void)lt_log:(DDLogMessage *)logMessage {
    // omit other code
    for (DDLoggerNode *loggerNode in self._loggers) {
        // skip the loggers that shouldn't write this message based on the log level
        // Retrieve all stored Logger nodes from the current DDLog instance, which contains the Logger object I passed in
	    if(! (logMessage->_flag & loggerNode->_level)) {continue;
	    }
	
	    dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool{ [loggerNode->_logger logMessage:logMessage]; }}); }}Copy the code

The _logger is of type ID . This is the second programming mode I want to talk about. For abstract programming, the code only cares about the object that implements the interface. I don’t care about the specific type of the object. Continue with this:

// DDFileLogger.m

// The Logger analyzed here is a sandbox log
- (void)logMessage:(DDLogMessage *)logMessage {
	// omit other code
	// Here you can see that the framework starts using the Formatter object I passed in
	if(_logFormatter ! =nil) { message = [_logFormatter formatLogMessage:logMessage]; isFormatted = message ! = logMessage->_message; } [self lt_logData:[message dataUsingEncoding:NSUTF8StringEncoding]];
}
Copy the code

Note that the _logFormatter variable is of type ID . As mentioned in the previous step, there is also a third design pattern: bridge mode (a bit like policy mode, explained later). The framework finally executes the lt_logData: method before it actually starts writing logs. From the above analysis, you can see how my incoming Logger and Formatter work. You can also see that the underlying framework uses an interface programming routine that is compatible with any incoming Logger and Formatter that is appropriate, giving users the flexibility to extend their logging needs.

The bridge model

I don’t care about 100% to tell you that CocoaLumberjack mainly uses bridge mode to achieve extensible and highly flexible functions, because there are several design patterns like this, and I’m not writing a paper analysis, let’s call it bridge mode. In fact, bridge mode is similar to strategy mode. Policy patterns are more about representing behavioral algorithms, and more about providing simple stateless algorithm functionality when classes that themselves implement a policy are aggregated into the main class. As the CocoaLumberjack framework can see, classes like Formatter and Logger themselves contain more complex logic and internal object properties, so I think it’s more appropriate to call them bridging modes. Specific definition can be their own Google search, he is long is the article above the analysis of that, here I quote the novice tutorial inside the description of the bridge mode, I think it is appropriate. That’s what it looks like. It’s wonderful. Read the source code, the following to customize the output format, the whole process almost does not need to search any information online, self-sufficient.

Extension CocoaLumberjack

As long as my class implements the DDLogFormatter protocol, I can configure DDLog as a valid Formatter object. Here’s the source for the extended Formatter:


//
// CCLogFormatter.h
// OCSimpleView
//
// Customize the formatting information for the CocoaLumberjack framework
// 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

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

#import "CCLogFormatter.h"


@interface CCLogFormatter(a){
    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
Copy the code

Actually very simple, I realized the formatLogMessage: this method, the other code directly from DDLogFileFormatterDefault class copy to come over, Of course, that is directly make CCLogFormatter inherit DDLogFileFormatterDefault initialization code here can be omitted. In CCLogFormatter I change the format from the original:

2019/07/25 14:33:36:239  Debug
Copy the code

In the following format:

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

That’s the end of the extension. Other more complex functions, which are constantly changing, follow the same pattern.

conclusion

This article is mainly through the popular framework CocoaLumberjack source code analysis, find out the design patterns, and then understand the importance of design patterns, learn one of the flexible extension framework implementation methods. It is said that there are more than 30 design patterns in total, which is too many to learn. However, I think it is the most important to keep a young heart and live to learn.