在分析FLEX之前无意中看到过如何收集iOS日志,这篇文章讲得比较详细。有兴趣的同学可以详细看看。
其实我们大多数的需求或者技术难点,都是已经有人提过或者遇到过的。很多优秀的第三方库就把平时所遇到的问题或者需要的技术都包含了。所以多读源码还是有用的。比如在FLEX系统收集中就用到了ASL这东西。之前从来没有听说过。
NSLog背后的故事:其内部其实是使用Apple System Log(ASL:苹果自己实现的输出日志的一套接口)的API.在iOS真机设备上,使用ASL记录的log被缓存在一个文件中,直到设备被重启.
ASL读取日志
除了这种方式还有重定向(Unix系统文件管理)的方法,但是重定向就不会写到系统日志中区了,所以通过ASL接口读取日志是最常用的手段。这里摘取FLEX获取系统日志的主要代码。
- 获取所有系统日志
+ (NSArray *)allLogMessagesForCurrentProcess
{
asl_object_t query = asl_new(ASL_TYPE_QUERY);
// Filter for messages from the current process. Note that this appears to happen by default on device, but is required in the simulator.
NSString *pidString = [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]];
asl_set_query(query, ASL_KEY_PID, [pidString UTF8String], ASL_QUERY_OP_EQUAL);
aslresponse response = asl_search(NULL, query);
aslmsg aslMessage = NULL;
NSMutableArray *logMessages = [NSMutableArray array];
while ((aslMessage = asl_next(response))) {
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
asl_release(response);
return logMessages;
}
- 将aslmsg转换为自定义的模型类
+(instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
{
FLEXSystemLogMessage *logMessage = [[FLEXSystemLogMessage alloc] init];
const char *timestamp = asl_get(aslMessage, ASL_KEY_TIME);
if (timestamp) {
NSTimeInterval timeInterval = [@(timestamp) integerValue];
const char *nanoseconds = asl_get(aslMessage, ASL_KEY_TIME_NSEC);
if (nanoseconds) {
timeInterval += [@(nanoseconds) doubleValue] / NSEC_PER_SEC;
}
logMessage.date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
}
const char *sender = asl_get(aslMessage, ASL_KEY_SENDER);
if (sender) {
logMessage.sender = @(sender);
}
const char *messageText = asl_get(aslMessage, ASL_KEY_MSG);
if (messageText) {
logMessage.messageText = @(messageText);
}
const char *messageID = asl_get(aslMessage, ASL_KEY_MSG_ID);
if (messageID) {
logMessage.messageID = [@(messageID) longLongValue];
}
return logMessage;
}
平时用得不多的方法
- 获取当前进程id。
NSString *pidString = [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]];
- 查询系统日志,通过迭代器实现遍历。
while ((aslMessage = asl_next(response))) {
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
- 重写
isEqual
和hash
自定义相等性。
FLEXSystemLogTableViewController
过滤数组
因为支持搜索过滤,所以对数组过滤就必不可少。之前数组过滤一般用的是最低级的方式。定义一个新的数组,然后遍历之前的数组,满足条件的就加到新的数组里面。其实苹果的API早就提供了过滤数组的功能。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 使用谓词过滤
NSArray *filteredLogMessages = [self.logMessages filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXSystemLogMessage *logMessage, NSDictionary *bindings) {
NSString *displayedText = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage];
return [displayedText rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
}]];
dispatch_async(dispatch_get_main_queue(), ^{
if ([searchController.searchBar.text isEqual:searchString]) {
self.filteredLogMessages = filteredLogMessages;
[self.tableView reloadData];
}
});
});
关键用到了[NSPredicate predicateWithBlock:],block返回值是一个bool类型,如果YES则过滤,否则就不过滤。如上代码所示。
高亮显示
高亮显示搜索的关键字,这一点上其实就是cellForRow的时候,给cell的一个高亮属性highlightedText设置值,如果cell.highlightedText = self.searchController.searchBar.text;
。如果相等则改变显示的颜色。
其实逻辑很简单。
定时刷新
主要用的NSTimer。在控制器中使用timer,一定要注意防止循环引用。
创建:
// 不停更新系统日志内容
self.logUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:updateInterval target:self selector:@selector(updateLogMessages) userInfo:nil repeats:YES];
防止循环引用
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// 让timer失效
[self.logUpdateTimer invalidate];
}
滑动到底部
滑动到底部是为了显示最新的日志内容。在每次刷新后都需要调用。FLEX中设置的值是100.0。如果超过了100则向下偏移。
思路很简单。BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
确定是否偏移,之后便宜到最后一个cell的位置。
代码:
- (void)scrollToLastRow
{
NSInteger numberOfRows = [self.tableView numberOfRowsInSection:0];
if (numberOfRows > 0) {
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:numberOfRows - 1 inSection:0];
// 滑动到最后一个cell。indexPatch
[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}
THE END
这部分很简单,没有多少高深的知识。