frame中src怎么设置成一个变量_自动格式化打印变量HMLog介绍
作者 | 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函數做比較了:
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))同時傳入進去,同時利用宏把一個變量替換為這兩種形式:
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介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 星链卫星太多影响天文观测?SpaceX已
- 下一篇: python圆面积函数_python函数