作者 | mao2020
来源 | 掘金,点击阅读原文查看作者更多文章

前言

在我初学iOS的时候,经常需要NSLog打印用于调试,有时候还需要打印多个变量:

NSLog(@"xxxx frame=%@ tag=%ld isHidden=%d", NSStringFromCGRect(view.frame), view.tag, view.isHidden);

仅考虑把NSLog用来调试输出,那写种代码就太麻烦了,主要存在着这样几个问题:

  • 需要格式化,如%@,%ld,%d

  • 需要设置标签,以便知道输出值对应的变量,如frame=,tag=,isHidden=

  • 需要类型转换,如NSStringFromCGRect

  • 有时候还需要写一些指定字符串以便于在Console输出中搜索或过滤,如xxxx

后来接触到各种各样的Debug Log,主要利用 __LINE__ 和 __func__ 可以很方便定位到输出的位置,但是依然还存在前面3个问题。另外LLDB可以很方便获取变量值,但在变量较多或需要连续打印的情况下也不够方便快捷。

那个时候我就产生了一个想法,能不能自己写一个Debug Log,解决上面这些困扰我的问题?这就是我开发HMLog的初衷,源码仅有一个HMLog.h文件。

基本用法

项目源码及demo:https://github.com/chenhuimao/HMLog

以下用法均可在HMLogDemo项目中找到。

HMLog

HMLog最终是基于NSLog输出,根据前面的例子,使用HMLog的代码和输出是这样的:

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 30, 40)];view.tag = 333;HMLog(view.frame);HMLog(view.frame, view.tag, view.isHidden);

// 输出如下// 2020-10-17 15:49:33.356890+0800 HMLogDemo[85956:1573131] // ================  -[ViewController viewDidLoad] [45]  ================// 0: view.frame = NSRect: {{10, 20}, {30, 40}}// 2020-10-17 15:49:33.357017+0800 HMLogDemo[85956:1573131] // ================  -[ViewController viewDidLoad] [46]  ================// 0: view.frame = NSRect: {{10, 20}, {30, 40}}// 1: view.tag = 333// 2: view.isHidden = NO

Demo中的一个例子,用截图展示:

HMPrint

HMPrint则基于printf:

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 30, 40)];view.tag = 333;HMPrint(view.frame);HMPrint(view.frame, view.tag, view.isHidden);

// 输出如下// ================  -[ViewController viewDidLoad] [45]  ================// 0: view.frame = NSRect: {{10, 20}, {30, 40}}// // ================  -[ViewController viewDidLoad] [46]  ================// 0: view.frame = NSRect: {{10, 20}, {30, 40}}// 1: view.tag = 333// 2: view.isHidden = NO

HMFormatString

如果只是需要自动格式化的目标字符串,可以使用

HMFormatString:self.displayLab.text = HMFormatString(self.view.frame, self.view.tag, @selector(viewDidLoad));printf("%s", self.displayLab.text.UTF8String);

// ================  -[ViewController getFormatString1] [80]  ================// 0: self.view.frame = NSRect: {{0, 0}, {414, 896}}// 1: self.view.tag = 0// 2: @selector(viewDidLoad) = SEL: viewDidLoad

可选参数

所有可选参数都应该在#import "HMLog.h"之前定义好

HMLogEnable / HMPrintEnable

分别控制HMLog和HMPrint的是否生效,默认生效,不生效情况下调用没有任何效果。如只需要在Debug模式下开启HMPrint:

// Only enable HMPrint in Debug configuration#ifdef DEBUG#define HMPrintEnable 1#else#define HMPrintEnable 0#endif#import "HMLog.h"

HMLogHeaderFormatString(FUNC, LINE)

控制头部字符串(注意可以重用FUNC和LINE,或者不使用):

#define HMLogHeaderFormatString(FUNC, LINE) \        [NSString stringWithFormat:@"%s  ?????  %s:\n", FUNC, FUNC]#import "HMLog.h"...

HMPrint(self.navigationItem.title);HMPrint(self.view.bounds.size, self.view.alignmentRectInsets, self.title, self.automaticallyAdjustsScrollViewInsets, self.navigationController, [self class], @selector(viewDidAppear:));

// 输出如下// -[ViewController print2]  ?????  -[ViewController print2]:// 0: self.navigationItem.title = HMLogDemo// // -[ViewController print2]  ?????  -[ViewController print2]:// 0: self.view.bounds.size = NSSize: {414, 896}// 1: self.view.alignmentRectInsets = {0, 0, 0, 0}// 2: self.title = (null)// 3: self.automaticallyAdjustsScrollViewInsets = YES// 4: self.navigationController = // 5: [self class] = ViewController// 6: @selector(viewDidAppear:) = SEL: viewDidAppear:

HMLogPrefix(index, valueString)

控制每个变量输出的前缀。例如只需要展示下标,则按下面的方式定义:

// Only show index prefix#define HMLogPrefix(index, valueString) [NSString stringWithFormat:@"%d: ", index]#import "HMLog.h"...

HMPrint(self.navigationItem.title);HMPrint(self.view.bounds.size, self.view.alignmentRectInsets, self.title, self.automaticallyAdjustsScrollViewInsets, self.navigationController, [self class], @selector(viewDidAppear:));

// ================  -[ViewController print2] [79]  ================// 0: HMLogDemo// // ================  -[ViewController print2] [80]  ================// 0: NSSize: {414, 896}// 1: {0, 0, 0, 0}// 2: (null)// 3: YES// 4: // 5: ViewController// 6: SEL: viewDidAppear:

HMLogTypeExtension

默认情况下不支持CGVector和CLLocationCoordinate2D类型,可以额外匹配需要格式化的类型:

#define HMLogTypeExtension \else if (strcmp(type, @encode(CGVector)) == 0) { \CGVector actual = (CGVector)va_arg(v, CGVector); \            obj = NSStringFromCGVector(actual); \        } else if (strcmp(type, @encode(CLLocationCoordinate2D)) == 0) { \            CLLocationCoordinate2D actual = (CLLocationCoordinate2D)va_arg(v, CLLocationCoordinate2D); \            obj = [NSString stringWithFormat:@"latitude: %lf, longitude: %lf", actual.latitude, actual.longitude]; \        }

#import #import "HMLog.h"...

CGVector vector = CGVectorMake(110, 119);CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(22.512145, 113.9155);HMPrint(vector, coordinate);

// ================  -[CustomizeFormatViewController log] [65]  ================// 0: vector = {110, 119}// 1: coordinate = latitude: 22.512145, longitude: 113.915500

使用注意

  • 项目需要Foundation和UIKit框架。C语言标准为gnu99,Xcode项目的C Language Dialect选项设置为gnu99或gnu11

  • HMLog项目的实现仅有一个文件HMLog.h,可以在pch文件导入,也可以在每个需要的文件分别导入。所有可选参数都应该在#import "HMLog.h"之前定义好

  • 一次调用最多支持20个变量

  • 没有支持所有的数据类型,默认支持的类型参考源码,可以使用HMLogTypeExtension进行扩展

设计思路

只考虑1个变量

首先考虑1个变量的情况,即HMLog只能传入1个变量。

  • 要格式化1个变量,就要先知道这个变量的类型。变量可以通过__typeof__获取类型,比如我们常常这样用__weak __typeof__(self) weakSelf = self;

  • 取得变量类型后,还需要比较判断,之后才能把id类型格式化为"%@",把long类型格式化为"%ld"。类型如何做判断呢?if(long == id)显然是不行的,OC类型编码@encode会返回一个char *字符串,这样就可以利用strcmp函数做比较了:

NSString *format;if (strcmp(@encode(__typeof__(self.view)), @encode(id)) == 0) {format = @"%@";} else if (strcmp(@encode(__typeof__(self.view)), @encode(long)) == 0) {format = @"%ld";} else if ...

参考苹果的文档,也可以写成if (strcmp(@encode(__typeof__(self.view)), "@") == 0)的形式。不过HMLog并没有采用这种简化的形式,@encode是编译器指令,并不影响运行时效率,上面的代码块中的形式更加直观。

  • 要把这个功能写成一个通用函数,那如何表示任意的类型?换句话说,如果value是NSObject对象可以用id value表示,但如果value可能是任何类型,id该换成什么?这个时候,可变参数函数派上用场了,可变参数最后的...,是不需要写明数据类型的,这样可以把变量(value)和变量的类型编码@encode(__typeof__(value))同时传入进去,同时利用宏把一个变量替换为这两种形式:

#define MyLog(value) _MyLog(__func__, __LINE__, @encode(__typeof__(value)), (value))

static void _MyLog(const char *func, int line, ...) {NSMutableString *result = [[NSMutableString alloc] init];    [result appendFormat:@"\n===== %s [%d] =====\n", func, line];

    va_list v;    va_start(v, line);char *type = va_arg(v, char *);if (strcmp(type, @encode(id)) == 0) {        id actual = (id)va_arg(v, id);        [result appendFormat:@"id: %@\n", actual];    } else if (strcmp(type, @encode(long)) == 0) {        long actual = (long)va_arg(v, long);        [result appendFormat:@"long: %ld\n", actual];    }    va_end(v);NSLog(@"%@", result);}

// 可以愉快地打印id和long类型了MyLog(self.view);MyLog(self.view.tag);

把上面的例子的条件语句补充好需要的类型,MyLog宏就可以打印任意类型的1个变量了。

考虑多个变量

接下来要考虑的是如何同时打印多个变量。

  • 按照前面的思路,打印多个变量,需要把每个变量(value)和变量的类型编码@encode(__typeof__(value))都传给可变参数函数_MyLog,另外还需要一个数量count表示一共有几组变量及其类型编码。为了实现这个需求,这里使用了获取宏参数个数以及递归宏的技巧,请阅读完这篇文章,了解C语言宏定义使用总结与递归宏。

  • 最后补充好细节,使变量名称化为字符串作为提示标签,定制化使用的可选参数,这就完成了HMLog。

整体思路很清晰,源码也只有一个200多行的HMLog.h文件,难点基本上只有递归宏的使用。除此之外值得一提的还有两点:

  • float类型的值,通过va_arg获取值先传入double类型,然后再强制类型转换为float类型:float actual = (float)va_arg(v, double);,这是因为有个规则叫默认参数提升,还有一些char、short等类型也是如此。

  • [result appendFormat:@"%@%@\n", ((void)(valueString), HMLogPrefix(i, valueString)), obj];这行代码,为了消除宏HMLogPrefix(i, valueString)可能没有用到valueString导致的警告,使用了逗号运算符,这是宏定义使用中常用的一个运算符。

参考资料

[1]https://juejin.im/post/6884575803523203080
[2]https://github.com/SnapKit/Masonry/blob/master/Masonry/MASUtilities.h

frame中src怎么设置成一个变量_自动格式化打印变量HMLog介绍相关推荐

  1. frame中src怎么设置成一个变量_Go 语言设计哲学之七:变量声明须一致

    Go 语言,使用变量之前需要先进行变量的声明. var s string = "Golang"n := 666 Go 语言有两类变量 包级别(package varible):在 ...

  2. frame中src怎么设置成一个变量_OpenCV图像人脸检测及视频中的人脸检测(附源码)...

    文章目录 一.数据和知识准备 1. 下载HAAR与LBP数据 2. opencv相关知识 二.python+opencv实现人脸检测 1. 图像单人脸检测 2. 图像多人脸检测 3. 视频中人脸检测 ...

  3. frame中src怎么设置成一个变量_webpack中Css的处理

    摘要:这个时代对速度和性能要求越来越高,前端开发中兼容性问题是一个让人头疼的问题,也许在这台设备上明明好好的,但是换一个设备就会让人哭笑不得,对于用户体验和流量资源的开销也是要考虑的一个问题,当下我们 ...

  4. frame中src怎么设置成一个变量_在 Figma 中如何选择 group(组)或 frame(画框)?...

    导读:在 Figma 中可以使用 group(组)或 frame(画框)来组织元素,但它们又有一些不同.合理地选择如何使用它们,能够帮助我们更好地设计.本文来自 Figma 官方博客,由卓浩翻译. 你 ...

  5. 环境变量_配置JAVA环境变量

    本文标识 :  J00001本文编辑 :  YiKi编程工具 :  IDEA阅读时长 :  3分钟 什么是环境变量?环境变量是在操作系统中一个具有特定名字的对象, 它包含了一个或者多个应用程序所将使用 ...

  6. MySQL数据库变量_数据库参数_MySQL变量_系统变量_用户变量

    文章目录 MySQL 变量分类 系统变量 查看系统变量 设置系统变量 如何通过配置文件来设置变量值 通过命令行选项来设置变量值 动态设置全局级的系统变量 设置静态的系统变量 设置会话级的系统变量 引用 ...

  7. css 变量_如何将CSS变量用于动画

    css 变量 当我们在讨论中提到CSS时,我们通常将其称为愚蠢的语言. 一种声明性语言,缺乏逻辑和洞察力: 但这不是真实的现实. 多年来,开发人员一直渴望标准CSS中的变量,长期以来一直被诸如LESS ...

  8. java线程条件变量_多线程同步条件变量(转载)

    最近看<UNIX环境高级编程>多线程同步,看到他举例说条件变量pthread_cond_t怎么用,愣是没有看懂,只好在网上找了份代码,跑了跑,才弄明白 #include #include ...

  9. python 分类变量转为哑变量_机器学习笔记——哑变量处理

    在机器学习的特征处理环节,免不了需要用到类别型特征,这类特征进入模型的方式与一般数值型变量有所不同. 通常根据模型的需要,类别型特征需要进行哑变量处理,即按照特征类别进行编码,一般一个类别为k的特征需 ...

最新文章

  1. linux docker run 设置环境变量
  2. (48)逆向分析 KiFastCallEntry 函数填充 _KTRAP_FRAME 部分
  3. python中item是什么类型的游戏_文本冒险游戏(Python)中的Item类
  4. 对不起,我把APP也给爬了
  5. css3-13 css3的3D动画如何实现
  6. 配置LANMP环境(1)-- 安装虚拟机VMware与安装CentOS7.2系统
  7. CentOS6.5安装python环境
  8. python系统下载-python
  9. oracle第三天笔记
  10. Numpy基础语法--linspace与zeros与ones
  11. 基于SSM的校园二手交易平台
  12. Swagger怎么下载文件
  13. 19108期计算机开机号,排列三19108期藏机图诗汇总
  14. linux如何回到下一级,linux如何返回上一级目录
  15. 使用C++读取二进制文件(dat格式)
  16. 孙俪主演的热播剧《安家》房似锦,让我们看到比贫穷更可怕的是原生家庭
  17. Detecting Faces in Images: A Survey( 翻译)
  18. Linux系统编程之捕捉SIGCHLD
  19. java-十六进制转八进制
  20. 漏洞原理——ssrf

热门文章

  1. 华为将升级鸿蒙,华为将弃用安卓?Mate40将成为首款可升级鸿蒙OS的手机
  2. java数组怎样插入元素,Java如何在给定位置将元素插入数组?
  3. python如何绘制曲线图_python pandas plot画折线图如何显示x轴的值?
  4. 16*64点阵屏的c语言程序,16*64点阵程序 - 单片机/MCU论坛 - 电子技术论坛 - 广受欢迎的专业电子论坛!...
  5. [蓝桥杯][算法提高VIP]开灯游戏(dfs)
  6. android 百度移动搜索 url 参数,百度刷站内快排算法参数-百度搜索URL参数比较详解...
  7. 如果让我重做一次研究生--王泛森院士
  8. 着墨中文lisp登入_Lisp的本质 - climbdream的个人空间 - OSCHINA - 中文开源技术交流社区...
  9. python def函数报错详解_Python函数详解
  10. spark教程python案例_Spark实战(四)spark+python快速入门实战小例子(PySpark)