2017-03-07 07:12:22 Nathan1987_ 阅读数 1401

OPTIMIZE :用于release和debug的判断,当选择了OPTIMIZE 时,可以让代码在release时执行,在debug时不执行。
示例如下:

#ifndef __OPTIMIZE__  

    //这里是debug模式下  

else  

    //这里是release模式下  

#endif
2015-12-15 10:34:51 liyun123gx 阅读数 4101

在iOS开发中经常需要靠记录日志来调试应用程序,最常见的做法是使用NSLog来输出相关的信息。大量的使用NSLog存在一定的弊端,将设备连接到电脑,打开XCode中的Device->Console,就可以从console查看到每条日志信息(或者是使用iTools的实时日志,推荐使用)。试想如果将很多核心的算法或者是信息都通过NSLog打印到控制台上,那么很有可能会被其他人获取到相关信息造成很多安全隐患,另外这样的应用也极有可能被Apple拒绝审核通过。

console_log.png

使用宏来处理

常用的做法是在PCH文件添加如下的代码:

1
2
3
4
5
#ifdef DEBUG
#define DebugLog(format, ...) NSLog((@"%s [Line %d] " format), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define DebugLog(...) do { } while (0)
#endif

然后将NSLog替换为DebugLog即可。

使用CocoaLumberjack替代NSLog

LumberjackLogo.png

CocoaLumberjack是Mac和iOS上一个集快捷、简单、强大和灵活于一身的日志框架。CocoaLumberjack类似于流行的日志框架(如log4j),但它是专为Objective-C设计的,利用了多线程、GCD(如果可用)、无锁原子操作Objective-C运行时的动态特性。

CocoaLumberjack基本组件

CocoaLumberjack是由DDASLLoggerDDTTYLoggerDDFileLogger三个Log组件组成,各自的功能描述如下:

  • DDASLLogger:支持将调试语句写入到苹果的日志中。一般正对Mac开发。。
  • DDTTYLogger:支持将调试语句写入xCode控制台。在iOS开发中使用。
  • DDFileLogger:支持将调试语句写入到文件系统。。

1、快速集成

(1)下载CocoaLumberjack,引入CocoaLumberjack的头文件

#import "DDLog.h"

(2)指定日志的记录级别

static const int ddLogLevel = LOG_LEVEL_VERBOSE;

或者是

1
2
3
4
5
#ifdef DEBUG
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#else
static const int ddLogLevel = LOG_LEVEL_OFF;
#endif

日志的级别有如下几种:

  • LOG_LEVEL_ERROR:如果设置为LOG_LEVEL_ERROR,仅仅能看到Error相关的日志输出。
  • LOG_LEVEL_WARN:如果设置为LOG_LEVEL_WARN,能看到Error、Warn相关的日志输出。
  • LOG_LEVEL_INFO:如果设置为LOG_LEVEL_INFO,能够看到Error、Warn、Info相关的日志输出。
  • LOG_LEVEL_DEBUG:如果设置为LOG_LEVEL_DEBUG,能够看到Error/Warn/Info/Debug相关的日志输出。
  • LOG_LEVEL_VERBOSE:如果设置为LOG_FLAG_VERBOSE,能够看到所有级别的日志输出。
  • LOG_LEVEL_OFF:不输出日志。

(3)使用DDLogError/DDLogWarn/DDLogDebug/DDLogVerbose来替代NSLog

1
2
3
4
5
DDLogError(@"[Error]:%@", @"输出错误信息");//输出错误信息
DDLogWarn(@"[Warn]:%@", @"输出警告信息");//输出警告信息
DDLogInfo(@"[Info]:%@", @"输出描述信息");//输出描述信息
DDLogDebug(@"[Debug]:%@", @"输出调试信息");//输出调试信息
DDLogVerbose(@"[Verbose]:%@", @"输出详细信息");//输出详细信息

2、结合XcodeColor让日志带上颜色

有了解过Android开发的朋友都会知道,在Android开发中LogCat的日志查看功能是十分强大的,特别是不同级别的日志输出显示的颜色是不同的,例如错误信息的颜色是红色的,其实在Xcode中结合XcodeColor插件也是可以实现该效果的,具体的配置步骤如下:

(1)下载安装插件

https://github.com/DeepIT/XcodeColors下载XcodeColors插件,直接使用Xcode打开XcodeColors.xcodeproj文件,然后Command+B编译项目可以自动将插件安装至~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/XcodeColors.xcplugin路径下。

也可以使用Alcatraz来安装插件,具体请参考《使用Alcatraz来管理Xcode插件》

(2)重启Xcode,运行测试用例

彻底退出Xcode,重新启动Xcode。再次打开XcodeColors.xcodeproj运行TestXcodeColors的target,测试插件是否安装成功。

(2)CocoaLumberjack开启颜色分级

[DDTTYLogger sharedInstance].colorsEnabled = YES;

各个的级别的颜色如下:

1
2
3
4
5
DDLogError(@"Error");//红色
DDLogWarn(@"Warn");//黄色
DDLogInfo(@"Info");//默认是黑色
DDLogDebug(@"Debug");//默认是黑色
DDLogVerbose(@"Verbose");//默认是黑色

如果要设置不同分级的颜色值,可以使用如下代码:

1
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor orangeColor] backgroundColor:nil forFlag:LOG_FLAG_INFO];//设置INFO级别的日志的颜色为橙色

效果如下图所示:

log_color_level.png

说明:

可能在Xcode中无法正常显示颜色,需要配置Xcode的环境变量,设置“Edit Scheme”-> “Run” –> “Arguments”(Environment Variabl)环境变量,添加一个叫做XcodeColors并且设置值为YES,如下图所示。

xcode_color_config.png

原文:点击打开链接

参考资料

1、《XcodeColors.md》

2、《CocoaLumberjack——带颜色的Log》

3、《iOS第三方库-CocoaLumberjack-DDLog 》

2015-03-31 11:16:41 zhangping871 阅读数 1937

这篇文章并没有具体介绍自动布局的一些基本概念,主要讲解了一些高级的调试技巧。

这篇文章不是用来介绍Auto Layout的。如果你还没用过它,那还是先去WWDC 2012看看基础教程吧(1,2,3)。

如果我们在iOS中遇到不可满足的约束条件,我们只能在输出的日志中看到视图的内存地址。尤其是在更复杂的布局中,有时很难辨别出视图的哪一部分出了问题。然而,在这种情况下,还有几种方法可以帮到我们。
 
首先,当你在不可满足的约束条件错误信息中看到NSLayoutResizingMaskConstraints时,你肯定忘了为你某一个视图设定translatesAutoResizingMaskIntoConstraints为NO。Interface Builder中会自动设置,但是使用代码时,你需要为所有的视图手动设置。
 
如果不是很明确那个视图计算问题,你需要通过内存地址来辨认视图。最简单的方法是使用调试控制台。你可以打印视图本身或它父视图的描述,甚至递归描述的树视图。这通常会提示你需要处理哪个视图。
 
一个更直观的方法是在控制台修改有问题的视图,这样你可以在屏幕上标注出来。比如,你可以改变它的背景颜色:
  1. (lldb) expr ((UIView *)0x7731880).backgroundColor = [UIColor purpleColor] 
 
确保重新执行程序后改变不会在屏幕上显示出来。还要注意将内存地址转换为(UIView *),以及额外的圆括号,这样我们就可以使用点操作。另外,你当然也可以通过发送消息:
  1. (lldb) expr [(UIView *)0x7731880 setBackgroundColor:[UIColor purpleColor]] 
 
另一种方法是使用Instrument的allocation模板,根据图表分析。一旦你从错误消息中得到内存地址(运行Instruments时,你从控制台中获得的错误消息),你可以将Instrument切换到Objects List的详细视图,并且用Cmd-F搜索那个内存地址。这将会为你显示分配视图对象的方法,这通常是一个很好的暗示(至少找到创建视图对象的代码了)。
 
你也可以在iOS中弄懂不可满足的约束条件错误,这比改善错误消息来的更简单。我们可以在一个category中重写NSLayoutConstraint的描述,并且将视图的tags包含进去:
  1. @implementation NSLayoutConstraint (AutoLayoutDebugging) 
  2.   
  3. #ifdef DEBUG 
  4.   
  5. - (NSString *)description 
  6.     NSString *description = super.description; 
  7.     NSString *asciiArtDescription = self.asciiArtDescription; 
  8.     return [description stringByAppendingFormat:@" %@ (%@, %@)", asciiArtDescription, [self.firstItem tag], [self.secondItem tag]]; 
  9.   
  10. #endif 
  11.   
  12. @end 
 
如果是整数的属性标签信息是不够的,我们还可以得到更多新奇的东西,为视图类增加我们自己命名的属性,然后可以打印到错误消息中。我们甚至可以在Interface Builder中,使用identity inspector中的 “User Defined Runtime Attributes”为自定义属性分配值。
  1. @interface UIView (AutoLayoutDebugging) 
  2. - (void)setAbc_NameTag:(NSString *)nameTag; 
  3. - (NSString *)abc_nameTag; 
  4. @end 
  5.   
  6. @implementation UIView (AutoLayoutDebugging) 
  7. - (void)setAbc_NameTag:(NSString *)nameTag 
  8.     objc_setAssociatedObject(self, "abc_nameTag", nameTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
  9.   
  10. - (NSString *)abc_nameTag 
  11.     return objc_getAssociatedObject(self, "abc_nameTag"); 
  12. @end 
  13.   
  14. @implementation NSLayoutConstraint (AutoLayoutDebugging) 
  15. #ifdef DEBUG 
  16. - (NSString *)description 
  17.     NSString *description = super.description; 
  18.     NSString *asciiArtDescription = self.asciiArtDescription; 
  19.     return [description stringByAppendingFormat:@" %@ (%@, %@)", asciiArtDescription, [self.firstItem abc_nameTag], [self.secondItem abc_nameTag]]; 
  20. #endif 
  21. @end 
 
通过这种方法错误消息变得更可读,并且你不需要找出内存地址对应的视图。然而,对你而言,你需要做一些额外的工作以确保每次为视图分配的名字都是有意义。
 
另一个技巧为你提供更好的错误消息并且不需要额外的工作:对于每个布局约束条件,都需要将调用栈的标志融入到错误消息中。这样就很容易看出来问题涉及到的约束了。要做到这一点,你需要swizzle UIView或者NSView的addConstraint:/addConstraints:方法,以及布局约束的描述方法。在添加约束的方法中,你需要为每个约束条件关联一个对象,这个对象描述了当前调用栈堆栈的第一个frame。(或者任何你从中得到的信息):
  1. static void AddTracebackToConstraints(NSArray *constraints) 
  2.     NSArray *a = [NSThread callStackSymbols]; 
  3.     NSString *symbol = nil; 
  4.     if (2 < [a count]) 
  5.     { 
  6.         NSString *line = a[2]; 
  7.     // Format is 
  8.     //           1         2         3         4         5 
  9.     // 012345678901234567890123456789012345678901234567890123456789 
  10.     // 8 MyCoolApp 0x0000000100029809 -[MyViewController loadView] + 99 // 
  11.     // Don't add if this wasn't called from "MyCoolApp": 
  12.     if (59 <= [line length]) 
  13.     { 
  14.         line = [line substringFromIndex:4]; 
  15.         if ([line hasPrefix:@"My"]) { 
  16.         symbol = [line substringFromIndex:59 - 4]; 
  17.         } 
  18.     } 
  19.     for (NSLayoutConstraint *c in constraints) { 
  20.         if (symbol != nil) { 
  21.         objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingShort, symbol, OBJC_ASSOCIATION_COPY_NONATOMIC); 
  22.         } 
  23.         objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingCallStackSymbols, a, OBJC_ASSOCIATION_COPY_NONATOMIC); 
  24.     } 
  25. } @end 
 
一旦你已经为每个约束对象提供这些信息,你可以简单的修改UILayoutConstraint的描述方法将其包含到输出日志中。
  1. - (NSString *)objcioOverride_description { 
  2.     // call through to the original, really 
  3.     NSString *description = [self objcioOverride_description]; 
  4.     NSString *objcioTag = objc_getAssociatedObject(self, &ObjcioLayoutConstraintDebuggingShort); 
  5.     if (objcioTag == nil) { 
  6.         return description; 
  7.     } 
  8.     return [description stringByAppendingFormat:@" %@", objcioTag]; 
检出这个GitHub仓库,了解这一技术的代码示例。
 
有歧义的布局
另一个常见的问题就是有歧义的布局。如果我们忘记添加一个约束条件,我们经常会想为什么布局看起来不像我们所期望的那样。UIView和NSView提供三种方式来查明有歧义的布局:hasAmbiguousLayout,exerciseAmbiguityInLayout,和私有方法_autolayoutTrace。
 
顾名思义,如果视图存在有歧义的布局,那么hasAmbiguousLayout返回YES。我们可以使用私有方法_autolayoutTrace,而不需要自己遍历视图层并记录这个值。这将返回一个描述整个视图树的字符串→类似于recursiveDescription(当视图存在有歧义的布局时,这个方法会告诉你)。
 
由于这个方法是私有的,确保正式产品里面不要包含这个方法调用的任何代码。为了防止你犯这种错误,你可以在视图的category中这样做:
  1. @implementation UIView (AutoLayoutDebugging) 
  2. - (void)printAutoLayoutTrace { 
  3.     #ifdef DEBUG 
  4.     NSLog(@"%@", [self performSelector:@selector(_autolayoutTrace)]); 
  5.     #endif 
  6. @end 
 
_autolayoutTrace打印的结果如下:
 

正如不可满足约束条件的错误消息一样,我们仍然需要弄明白打印出的内存地址所对应的视图。
 
另一个标识出有歧义布局更直观的方法就是使用exerciseAmbiguityInLayout。这将会在有效值之间随机改变视图的frame。然而,每次调用这个方法只会改变frame一次。所以当你启动程序的时候,你根本不会看到改变。创建一个遍历所有视图层级的辅助方法是一个不错的主意,并且让所有的视图都有一个歧义的布局“jiggle”。
  1. @implementation UIView (AutoLayoutDebugging) 
  2. - (void)exerciseAmiguityInLayoutRepeatedly:(BOOL)recursive { 
  3.     #ifdef DEBUG 
  4.     if (self.hasAmbiguousLayout) { 
  5.         [NSTimer scheduledTimerWithTimeInterval:.5 
  6.                                          target:self 
  7.                                        selector:@selector(exerciseAmbiguityInLayout) 
  8.                                        userInfo:nil 
  9.                                         repeats:YES]; 
  10.     } 
  11.     if (recursive) { 
  12.         for (UIView *subview in self.subviews) { 
  13.             [subview exerciseAmbiguityInLayoutRepeatedly:YES]; 
  14.         } 
  15.     } 
  16.     #endif 
  17. } @end 

2014-08-01 14:47:14 zz1732818683 阅读数 2190

+(void)logMessageByUUID:(NSString *)UUID{

    

    NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);

    NSFileManager * filemangage =[NSFileManager defaultManager];

    NSArray *paths= NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *documentsDir = [paths objectAtIndex:0];

    

    [filemangage changeCurrentDirectoryPath:[documentsDir stringByExpandingTildeInPath]];

    

    NSString *fileName =[NSString stringWithFormat:@"%@.txt",[KBDateTimeUtil longlongyyyyMMddHHmmss:[NSDate date]]];

    

    [filemangage createFileAtPath:fileName contents:nil attributes:nil];

    

    NSString * fullfilename=[documentsDir stringByAppendingPathComponent:fileName];

    

    NSLog(@"%@",fullfilename);

    

    const  char* pcpath = [fullfilename cStringUsingEncoding:NSASCIIStringEncoding];

    

    stderr=fopen(pcpath, "w+");

}

这样就会吧debug写入一个文件,在测试过程中出现bug 把手机中得日志倒出来就可以查看日志了。


当然在plist文件中新加 Application supports iTunes file sharing 设置成YES  


这样就可以调试了。



对于stderr 文件的解释来自


重定向 stderr 到文件,方便查找 bug

为某个外部附件开发应用程序的情况下,开发者无法用 Xcode 进行调试或者用 GDB 查看 NSLog 的输出,定位和查找 bug 变得非常困难。好在我们可以通过重定向,将标准的错误输出stderr定向到文件。

    NSArray *paths = NSSearchPathForDirectoriesInDomains(
                            NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDir = [paths objectAtIndex:0];
    NSString *logPath = [documentDir stringByAppendingPathComponent:@"decrypt.log"];
    freopen([logPath cStringUsingEncoding:NSASCIIStringEncoding], "w+", stderr);

    让我们看下这段代码,它的作用就是在程序内部的文件夹 Documents 中新建一个名为 decrypt.log 的文件,并且将所有的 stderr 输入到 decrypt.log
文件中。如此一来所有关于 stderr 的输出都将被重定向到 decrypt.log,在 gdb 中你将无法再看到这些信息。

    选择 Xcode->Windows->Organizer,找到你的设备,在 summary 一栏中找到你的程序,将其展开,点选右边的箭头,这样程序内部的所有文件夹都将导出到你所指定的位置。Organizer 左边一栏的 PROJECTS&SOURCES 展开便能找到这个文件。

关于 printf, fprintf 和 NSLog:

    可能你大量使用了外部的 c 库,它们以 printf 或者 fprintf 来输出信息 ,那么它们的区别又是什么?标准的 printf 是将调试信息输入到标准输出流 stdout,也就是说你用上面提供的函数是无法将此重定向的,你必须

reopen([logPath cStringUsingEncoding:NSASCIIStringEncoding], "w+", stdout);

    而 fprintf 则可以指定你所要输出的调试信息的位置,包括 stdout、stderr 甚至是一般的文件。这里我们输出到 stderr

fprintf(stderr, "hello\n");

    NSLog 本质上其实也是将调试信息输出到 stderr,但与 fprintf 不同它还有些不一样的特性。我们在使用 Xcode 进行调试的时候无论采用哪种方式输出错误信息总是第一时间刷新在屏幕上,但定向到文件却有所不同。NSLog 总是能第一时间写到文件,而 printf 或 fprintf 似乎将是信息保存在缓存中,等待某种契机或者信号才会将信息写到文件中。如此一来辛苦准备的调试信息却可能因为程序的崩溃而什么也无法看到。所以我们可以通过 fflush 来强制它进行刷新。如下,我们强制刷新 stderr












2013-08-14 16:23:03 meegomeego 阅读数 1267


 今天在这里分享一个很实用的小技巧。 我们平时在开发应用的时候,经常会用到NSLog来调试我们的程序,而随着项目越来越大,这些用于调试的日志输出就会变得很难管理。 我们在发布正式版的时候一定要屏蔽掉所有后台输出,因为这些输出还是比较消耗系统资源的。  往往到了这个时候,我们不得不去一行一行的找到NSLog调用,然后注释掉。 这样做在项目小的时候还比较有效,但随着项目规模的增长,就会变得越来越难控制。  下面就给大家介绍一个简单的方法,让我们在生成Release版本时不需要进行任何更改即可屏蔽所有的Log输出。

1. 首先我们先要定义这样一段预处理命令,文件名随便起,例如 CLog.h

#ifdef DEBUG
#define CLog(format, ...) NSLog(format, ## __VA_ARGS__)
#else
#define CLog(format, ...)
#endif

  这里我们判断DEBUG这个宏是否定义,如果有定义我们就将这个CLog宏替换成NSLog调用,而如果没有定义过DEBUG标志我们就直接跳过。这点应该不难理解。

2. 检查DEBUG标志是否正确定义,xcode一般会在debug运行配置项里面已经定义号了DEBUG标志,如果没定义我们就自己写上,以我的xcode 4 为例,如下图:

 

  找到PreProcessor Macros 这个属性,对于Debug配置我们给他写上DEBUG,而在Release配置中把它留空。 这样我们刚才那段预处理命令就可以根据这个标志来判断我们编译的时调试版本还是发布版本,从而控制NSLog的输出。 (因为xcode 4 会把debug/release 两个配置项同时对比展现出来,而3.x版本的只能分别设置, 如果你用的时xcode 3.x 开发工具, 那么就分别对Debug/Release 都检查一下)。

3. 到了这里我们这个判断工作就都进行完了,不过这里还有一点比较麻烦,就是我们如果想实用CLog宏,就必须要导入 CLog.h 这个头文件。 不过xcode为我们提供了一种非常巧妙的解决办法。 我们自己看一下项目里的文件,是不是有一个叫做 xxx-prefix.pch 的文件,只要注意到pch这个扩展名就可以了。 这个文件是做什么用的呢? 下面是一个pch文件的样本:

//
// Prefix header for all source files 
//

#import <Availability.h>

#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
#endif

  这里引入了一些头文件, 其实是xcode 的一种预编译机制,我们在编译一个项目的时候,会有很多常用的源文件,并且这些代码文件几乎不被修改,所以xcode对这些文件只在早期进行一次编译,以便我们以后的多次构建过程中反复实用。 例如这里的UIKit和Foundation ,这样的机制可以加快我们每次构建项目的速度。 当然这里我们不必太深究它,知道它的作用后,我们就可以利用它来为我们的开发提供便利。 我们只需要将刚刚建立的CLog.h 也在这里面引入一下,这样我们项目中的所有文件就都能够访问到我们刚刚定义的CLog宏了。 下面是完成后的pch文件:

#import <Availability.h>

#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import "CLog.h"
#endif

  这样,我们的CLog就完成了,现在可以在任何一个源文件中实用CLog宏来输出日志,预处理命令会自动判断当前的编译配置,如果是Debug,就会输出日志,反之则什么都不会输出。 

ios debug release nslog

阅读数 654

没有更多推荐了,返回首页