DDLogger
1.isArchived在fileInfo中如何保存这个状态? 这里利用系统API<sys/xattr.h>的setxattr方法将该flag直接保存在文件描述中,getxattr用来获取,removexattr用于删除flag。
2.容错处理
@try {
NSFileHandle *handle = [self lt_currentLogFileHandle];
[handle seekToEndOfFile];
[handle writeData:data];
} @catch (NSException *exception) {
exception_count++;
if (exception_count <= 10) {
NSLogError(@"DDFileLogger.logMessage: %@", exception);
if (exception_count == 10) {
NSLogError(@"DDFileLogger.logMessage: Too many exceptions -- will not log any more of them.");
}
}
}
3.避免多次响应
static BOOL implementsDeprecatedWillLog = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
implementsDeprecatedWillLog = [self respondsToSelector:@selector(willLogMessage)];
});
if (implementsDeprecatedWillLog) {
[self willLogMessage];
} else {
[self willLogMessage:_currentLogFileInfo];
}
4.同时还利用消息转发,将过期方法转移至 dummyMethod 避免 unrecognized selector sent to instance crash
即在执行当前系统不支持的api时,将消息转发到一个存在的空方法中
- (void)dummyMethod {}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(willLogMessage) || aSelector == @selector(didLogMessage)) {
// Ignore calls to deprecated methods.
return [self methodSignatureForSelector:@selector(dummyMethod)];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector != @selector(dummyMethod)) {
[super forwardInvocation:anInvocation];
}
}
5.buffer机制
DDFileLogger+Buffering.h
bufferProxy 拦截了这两个方法 logMessage 和 flush,其余的都通过 message forwarding 转回 filgLogger了。
会不断地将 data 写入 buffer,如果满了就写入 log file 并 flushBuffer。通过这种方式,有效的减小了文件写入的 I/O 操作。
6.在执行前需要进行 loggingQueue 和 loggerQueue 的检查 loggerQueue,logger本身的操作,由于日志打印均为异步操作,所以会为每个 logger 分配一个 dispatch_queue_t。如果 logger 未提供 loggerQueue,那么 DDLog 为根据你所指定的 loggerName 主动为你生成。
loggingQueue,ddlog初始化的时候创建,用于异步打印日志,logMessage。
_loggingQueue
全局的 log queue 用于保证 FIFO 的操作顺序,所有 logger 会通过它来顺序执行各 logger 的 logMessage: 。
_loggingGroup
由于每个 logger 添加时候都配置了对应的 log queue。因此,loggers 之间的记录行为是并发执行的。而 dispatch group 可以同步所有 loggers 的操作,确保记录行为顺利完成。
_queueSemaphore
防止所使用的队列过爆。由于大多数记录都是异步操作,因此,可能遭到恶意线程大量的增加 log 影响正常的记录行为。最大限制数为 DDLOG_MAX_QUEUE_SIZE (1000),也就是说当队列数超过限制,则会主动阻塞线程,以待执行队列降至安全水平。
例如:在大型循环中随意添加日志语句时会发生过💥。
7.@autoreleasepool的大量使用
这种用法可以避免在短时间内大量积攒autorelease对象导致内存暴涨,即@autoreleasepool中的内容在代码块结束后便会释放内存。而默认的autoreleasepool的释放时机会比较靠后。
8.线程死锁问题如下代码,dispatch_get_specific(GlobalLoggingQueueIdentityKey)的判断,针对同步执行代码,做了处理,避免在同一个队列中同步执行任务(导致死锁 )。类似于main thread 执行了 dispatch_sync 开启了 main queue 的同步等待
dispatch_block_t logBlock = ^{
// We're now sure we won't overflow the queue.
// It is time to queue our log message.
@autoreleasepool {
[self lt_log:logMessage];
}
};
if (asyncFlag) {
dispatch_async(_loggingQueue, logBlock);
} else if(dispatch_get_specific(GlobalLoggingQueueIdentityKey)) {
// We've logged an error message while on the logging queue...
logBlock();
} else {
dispatch_sync(_loggingQueue, logBlock);
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
需要强调一点,logger在内部最好直接访问 FORMATTER VARIABLE ,如果需要的话。一旦使用 self. 可能会导致线程死锁。如下代码,如果self.logFormatter的方式访问,相当于sync的方式在loggerQueue队列中执行操作,类似上述8种的主线程死锁问题.
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
__block id <DDLogFormatter> result;
dispatch_sync(globalLoggingQueue, ^{
dispatch_sync(self->_loggerQueue, ^{
result = self->_logFormatter;
});
});
return result;