What is JLConsoleLog?

JLConsoleLog is an awesome tool In-App to help swift developer log information in both development and production environment. You won't miss any key and useful logs about the bugs in non-debug mode. You also can integrate it in your project's backdoor toolkit, it will help you solve vital issues.

How to operate it?

JLConsoleLog supports three types of style (display mode) — Floating, Bubble and FullScreen




There are four buttons on the option view of floating mode. The first button is setting button where you can clear all logs from this console, filter categories and levels. The second one is for switching between floating and fullscreen mode. If the third one is pressed, the console will be a translucent bubble that only displays warning and error count. The last one is close button.

The floating console could become translucent automatically after 5s, if you don't touch it. Additionally, you can drag floating console and bubble to anywhere to avoid disturb you.

While you tap a log cell, you can enter the detail page of log.

Performance monitor is a new function. You can invoke a monitor chart from bubble button now.

How to use it in your project?

The JLConsoleController is the console's controller opened for developers. It contains a shared instance. You could set style and logEnabled via it. While you set its style (display mode), the console will immediately show in terms of you given. Like this:

if == .Hidden { = .Floating //show console in floating fashion
        } else { = .Hidden //hide console

If logEnabled is true, the console will collect log data, vice verse.

JLConsoleController.shared.logEnabled = true

A series functions are offered to log.

func JLLevelLog(level: JLConsoleLogLevel, category: JLConsoleLogCategory, hasFollowingAction:Bool = false, needPrint:Bool = false, contextData:Dictionary<String,Any> , formats: String...)

func JLVerboseLog( category: JLConsoleLogCategory, hasFollowingAction:Bool = false, needPrint:Bool = false, contextData:Dictionary<String,Any> , formats: String...) 

func JLDebugLog( category: JLConsoleLogCategory, hasFollowingAction:Bool = false, needPrint:Bool = false, contextData:Dictionary<String,Any> , formats: String...)

func JLInfoLog( category: JLConsoleLogCategory, hasFollowingAction:Bool = false, needPrint:Bool = false, contextData:Dictionary<String,Any> , formats: String...)

func JLWarningLog( category: JLConsoleLogCategory, hasFollowingAction:Bool = false, needPrint:Bool = false, contextData:Dictionary<String,Any> , formats: String...)

func JLErrorLog( category: JLConsoleLogCategory, hasFollowingAction:Bool = false, needPrint:Bool = false, contextData:Dictionary<String,Any> , formats: String...)

The JLConsoleLogLevel is an enum to sort by different levels. The numbers of warning and error are displayed on option view and bubble.

The JLConsoleLogCategory is your business category and is an alias of String. You can define your own categories met your demand, such as Video, TrackPage, Commodity Detail… If you need to filter your categories, you must register in this way in your code:

let SubPageTestLog:JLConsoleLogCategory = "com.consolelog.mybusiness" //declare a category

JLConsoleController.shared.register(newCategory: SubPageTestLog) //register it

The parameter, contextData, is a serializable Dictionary. The data will be shown on the detail page in Json.

The parameters, formats, is variadic parameters of String. The first value will be shown on the cell's title in console.

If needPrint equals true, the log information will print in your Xcode console in Debug environment.

Otherwise, JLConsoleController provides a followingAction to operate other actions when you finish logging. For example, you can send a track point log to statistics server such as Firebase in followingAction closure. Meanwhile, please don't forget to set hasFollowingAction as true while you log.

JLErrorLog(category: SubPageTestLog, hasFollowingAction: true ,needPrint: true, contextData: ["test":5], formats: "Error!",#function,String(#line))

This is an error log example.

Performance Monitor

JLConsoleLog provides a performance monitor. You can add these to turn on it.

JLConsoleController.shared.performanceMonitable = true

The git address is


  • iOS 12
  • Swift 5.2
  • Xcode 11



Talk about Aspect Oriented Programming in iOS

1. What's the Aspect Oriented Programming?

The Apsect Oriented Programming (AOP) is usually used in backend development. But it's more and more popular in clients programming recent days.

You can find out the definition in Wikipedia:

In computing, AOP is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a "pointcut" specification, such as "log all function calls when the function's name begins with 'set'". This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code, core to the functionality. AOP forms a basis for aspect-oriented software development.

2. How to implement AOP in iOS?

When we develop a statistic system of client, sometimes we don't want to add any codes to the business logic. AOP is a good choice for some situation of logging. We can hook methods in which we need to make some logs. Such as logging page appear and disappear.

Method Swizzling

In iOS programming, we can implement AOP by Method Swizzling. Method Swizzling is way that replace original method with another.

Talking about some Runtime knowledge is necessary to how to explain Method Swizzling. Method in Objective-C consists of SEL(Selector) and IMP(implemention).

  • Method selectors are used to represent the name of a method at runtime. A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.

  • IMP is a pointer to the start of the function that implements the method.

Method Swizzling exchanges IMPs mapped selectors in order to replace methods. Implementing a Method Swizzling, the core C language API at runtime is:

OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) 

Method Swizzling Example

Suppose we want to print out a log statement whenever -viewDidLoad() is called on a UIViewController. Creating a category of UIViewController, and implement its +load().

#import "UIViewController+swizzling.h"
#import @implementation UIViewController (swizzling)
+ (void)load {
    [super load];
    // class_getInstanceMethod() get method structure from method list
    Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
    Method toMethod = class_getInstanceMethod([self class], @selector(swizzling_ViewDidLoad));
    if (!class_addMethod([self class], @selector(viewDidLoad), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
        method_exchangeImplementations(fromMethod, toMethod);
//  define method by myself, replacing original method
- (void)swizzling_ViewDidLoad {
    NSLog(@"Log something... for %@",[self class]);
    [self swizzling_ViewDidLoad];

3. Aspects

Obviously, if we use this way to hook every method needed to add log, it will be a huge project. Maybe there is a third party libarary that we use easier. YES, Aspects is an awesome tool for AOP in Objective-C.

Aspects provides two methods for developer:

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                            error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                            error:(NSError **)error;

An instance method and a class method, they have same name and same parameters. It's thread safe for all invoking, and Aspects makes some perfermance loss due to message passing of Objective-C. Otherwise I don't suggest use Aspects for methods in frequent invoked.


I use the Aspects instead of complex Method Swizzling, hook -viewWillApear() in all UIViewControllers.

#import "ViewControllerLogger.h"
#import @implementation ViewControllerLogger
- (instancetype)init {
    if (self = [super init]) {
        [UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
            NSLog(@"ViewController will appear:--> %@", NSStringFromClass([aspectInfo.instance class]));
        } error:NULL];


There are some disadvantages at Aspects.

  • Perfermance loss

    I have memtioned that above paragraph.

  • Can't hook class method

    If you hook a class method, the block won't callback and you'll get failure said Aspects: Block signature doesn't match (null). The reason is that runtime just obtain member method list, if you want to get class method, you must get it from MetaClass of this class. You can get MetaClass by object_getClass(newClass).

    Fortunately, there is a gay coding a function for class method hooking:

    static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    Class klass = aspect_hookClass(self, error);
    //TODO:Edit by JackYong
    Method targetMethod;
    IMP targetMethodIMP;
    if (class_isMetaClass(klass)) {
    targetMethod = class_getClassMethod(klass, selector);
    targetMethodIMP = method_getImplementation(targetMethod);
    } else {
    targetMethod = class_getInstanceMethod(klass, selector);
    targetMethodIMP = method_getImplementation(targetMethod);