ios autolayout debug调试技巧
2015-03-31 11:16
666 查看
这篇文章并没有具体介绍自动布局的一些基本概念,主要讲解了一些高级的调试技巧。
这篇文章不是用来介绍Auto
Layout的。如果你还没用过它,那还是先去WWDC 2012看看基础教程吧(1,2,3)。
如果我们在iOS中遇到不可满足的约束条件,我们只能在输出的日志中看到视图的内存地址。尤其是在更复杂的布局中,有时很难辨别出视图的哪一部分出了问题。然而,在这种情况下,还有几种方法可以帮到我们。
首先,当你在不可满足的约束条件错误信息中看到NSLayoutResizingMaskConstraints时,你肯定忘了为你某一个视图设定translatesAutoResizingMaskIntoConstraints为NO。Interface Builder中会自动设置,但是使用代码时,你需要为所有的视图手动设置。
如果不是很明确那个视图计算问题,你需要通过内存地址来辨认视图。最简单的方法是使用调试控制台。你可以打印视图本身或它父视图的描述,甚至递归描述的树视图。这通常会提示你需要处理哪个视图。
![](http://img.blog.csdn.net/20150331110628956?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdwaW5nODcx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
一个更直观的方法是在控制台修改有问题的视图,这样你可以在屏幕上标注出来。比如,你可以改变它的背景颜色:
确保重新执行程序后改变不会在屏幕上显示出来。还要注意将内存地址转换为(UIView *),以及额外的圆括号,这样我们就可以使用点操作。另外,你当然也可以通过发送消息:
另一种方法是使用Instrument的allocation模板,根据图表分析。一旦你从错误消息中得到内存地址(运行Instruments时,你从控制台中获得的错误消息),你可以将Instrument切换到Objects List的详细视图,并且用Cmd-F搜索那个内存地址。这将会为你显示分配视图对象的方法,这通常是一个很好的暗示(至少找到创建视图对象的代码了)。
你也可以在iOS中弄懂不可满足的约束条件错误,这比改善错误消息来的更简单。我们可以在一个category中重写NSLayoutConstraint的描述,并且将视图的tags包含进去:
如果是整数的属性标签信息是不够的,我们还可以得到更多新奇的东西,为视图类增加我们自己命名的属性,然后可以打印到错误消息中。我们甚至可以在Interface Builder中,使用identity inspector中的 “User Defined Runtime Attributes”为自定义属性分配值。
通过这种方法错误消息变得更可读,并且你不需要找出内存地址对应的视图。然而,对你而言,你需要做一些额外的工作以确保每次为视图分配的名字都是有意义。
另一个技巧为你提供更好的错误消息并且不需要额外的工作:对于每个布局约束条件,都需要将调用栈的标志融入到错误消息中。这样就很容易看出来问题涉及到的约束了。要做到这一点,你需要swizzle UIView或者NSView的addConstraint:/addConstraints:方法,以及布局约束的描述方法。在添加约束的方法中,你需要为每个约束条件关联一个对象,这个对象描述了当前调用栈堆栈的第一个frame。(或者任何你从中得到的信息):
一旦你已经为每个约束对象提供这些信息,你可以简单的修改UILayoutConstraint的描述方法将其包含到输出日志中。
检出这个GitHub仓库,了解这一技术的代码示例。
有歧义的布局
另一个常见的问题就是有歧义的布局。如果我们忘记添加一个约束条件,我们经常会想为什么布局看起来不像我们所期望的那样。UIView和NSView提供三种方式来查明有歧义的布局:hasAmbiguousLayout,exerciseAmbiguityInLayout,和私有方法_autolayoutTrace。
顾名思义,如果视图存在有歧义的布局,那么hasAmbiguousLayout返回YES。我们可以使用私有方法_autolayoutTrace,而不需要自己遍历视图层并记录这个值。这将返回一个描述整个视图树的字符串→类似于recursiveDescription(当视图存在有歧义的布局时,这个方法会告诉你)。
由于这个方法是私有的,确保正式产品里面不要包含这个方法调用的任何代码。为了防止你犯这种错误,你可以在视图的category中这样做:
_autolayoutTrace打印的结果如下:
![](http://img.blog.csdn.net/20150331110623293?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdwaW5nODcx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
正如不可满足约束条件的错误消息一样,我们仍然需要弄明白打印出的内存地址所对应的视图。
另一个标识出有歧义布局更直观的方法就是使用exerciseAmbiguityInLayout。这将会在有效值之间随机改变视图的frame。然而,每次调用这个方法只会改变frame一次。所以当你启动程序的时候,你根本不会看到改变。创建一个遍历所有视图层级的辅助方法是一个不错的主意,并且让所有的视图都有一个歧义的布局“jiggle”。
这篇文章不是用来介绍Auto
Layout的。如果你还没用过它,那还是先去WWDC 2012看看基础教程吧(1,2,3)。
如果我们在iOS中遇到不可满足的约束条件,我们只能在输出的日志中看到视图的内存地址。尤其是在更复杂的布局中,有时很难辨别出视图的哪一部分出了问题。然而,在这种情况下,还有几种方法可以帮到我们。
首先,当你在不可满足的约束条件错误信息中看到NSLayoutResizingMaskConstraints时,你肯定忘了为你某一个视图设定translatesAutoResizingMaskIntoConstraints为NO。Interface Builder中会自动设置,但是使用代码时,你需要为所有的视图手动设置。
如果不是很明确那个视图计算问题,你需要通过内存地址来辨认视图。最简单的方法是使用调试控制台。你可以打印视图本身或它父视图的描述,甚至递归描述的树视图。这通常会提示你需要处理哪个视图。
一个更直观的方法是在控制台修改有问题的视图,这样你可以在屏幕上标注出来。比如,你可以改变它的背景颜色:
(lldb) expr ((UIView *)0x7731880).backgroundColor = [UIColor purpleColor]
确保重新执行程序后改变不会在屏幕上显示出来。还要注意将内存地址转换为(UIView *),以及额外的圆括号,这样我们就可以使用点操作。另外,你当然也可以通过发送消息:
(lldb) expr [(UIView *)0x7731880 setBackgroundColor:[UIColor purpleColor]]
另一种方法是使用Instrument的allocation模板,根据图表分析。一旦你从错误消息中得到内存地址(运行Instruments时,你从控制台中获得的错误消息),你可以将Instrument切换到Objects List的详细视图,并且用Cmd-F搜索那个内存地址。这将会为你显示分配视图对象的方法,这通常是一个很好的暗示(至少找到创建视图对象的代码了)。
你也可以在iOS中弄懂不可满足的约束条件错误,这比改善错误消息来的更简单。我们可以在一个category中重写NSLayoutConstraint的描述,并且将视图的tags包含进去:
@implementation NSLayoutConstraint (AutoLayoutDebugging) #ifdef DEBUG - (NSString *)description { NSString *description = super.description; NSString *asciiArtDescription = self.asciiArtDescription; return [description stringByAppendingFormat:@" %@ (%@, %@)", asciiArtDescription, [self.firstItem tag], [self.secondItem tag]]; } #endif @end
如果是整数的属性标签信息是不够的,我们还可以得到更多新奇的东西,为视图类增加我们自己命名的属性,然后可以打印到错误消息中。我们甚至可以在Interface Builder中,使用identity inspector中的 “User Defined Runtime Attributes”为自定义属性分配值。
@interface UIView (AutoLayoutDebugging) - (void)setAbc_NameTag:(NSString *)nameTag; - (NSString *)abc_nameTag; @end @implementation UIView (AutoLayoutDebugging) - (void)setAbc_NameTag:(NSString *)nameTag { objc_setAssociatedObject(self, "abc_nameTag", nameTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)abc_nameTag { return objc_getAssociatedObject(self, "abc_nameTag"); } @end @implementation NSLayoutConstraint (AutoLayoutDebugging) #ifdef DEBUG - (NSString *)description { NSString *description = super.description; NSString *asciiArtDescription = self.asciiArtDescription; return [description stringByAppendingFormat:@" %@ (%@, %@)", asciiArtDescription, [self.firstItem abc_nameTag], [self.secondItem abc_nameTag]]; } #endif @end
通过这种方法错误消息变得更可读,并且你不需要找出内存地址对应的视图。然而,对你而言,你需要做一些额外的工作以确保每次为视图分配的名字都是有意义。
另一个技巧为你提供更好的错误消息并且不需要额外的工作:对于每个布局约束条件,都需要将调用栈的标志融入到错误消息中。这样就很容易看出来问题涉及到的约束了。要做到这一点,你需要swizzle UIView或者NSView的addConstraint:/addConstraints:方法,以及布局约束的描述方法。在添加约束的方法中,你需要为每个约束条件关联一个对象,这个对象描述了当前调用栈堆栈的第一个frame。(或者任何你从中得到的信息):
static void AddTracebackToConstraints(NSArray *constraints) { NSArray *a = [NSThread callStackSymbols]; NSString *symbol = nil; if (2 < [a count]) { NSString *line = a[2]; // Format is // 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 // 8 MyCoolApp 0x0000000100029809 -[MyViewController loadView] + 99 // // Don't add if this wasn't called from "MyCoolApp": if (59 <= [line length]) { line = [line substringFromIndex:4]; if ([line hasPrefix:@"My"]) { symbol = [line substringFromIndex:59 - 4]; } } } for (NSLayoutConstraint *c in constraints) { if (symbol != nil) { objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingShort, symbol, OBJC_ASSOCIATION_COPY_NONATOMIC); } objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingCallStackSymbols, a, OBJC_ASSOCIATION_COPY_NONATOMIC); } } @end
一旦你已经为每个约束对象提供这些信息,你可以简单的修改UILayoutConstraint的描述方法将其包含到输出日志中。
- (NSString *)objcioOverride_description { // call through to the original, really NSString *description = [self objcioOverride_description]; NSString *objcioTag = objc_getAssociatedObject(self, &ObjcioLayoutConstraintDebuggingShort); if (objcioTag == nil) { return description; } return [description stringByAppendingFormat:@" %@", objcioTag]; }
检出这个GitHub仓库,了解这一技术的代码示例。
有歧义的布局
另一个常见的问题就是有歧义的布局。如果我们忘记添加一个约束条件,我们经常会想为什么布局看起来不像我们所期望的那样。UIView和NSView提供三种方式来查明有歧义的布局:hasAmbiguousLayout,exerciseAmbiguityInLayout,和私有方法_autolayoutTrace。
顾名思义,如果视图存在有歧义的布局,那么hasAmbiguousLayout返回YES。我们可以使用私有方法_autolayoutTrace,而不需要自己遍历视图层并记录这个值。这将返回一个描述整个视图树的字符串→类似于recursiveDescription(当视图存在有歧义的布局时,这个方法会告诉你)。
由于这个方法是私有的,确保正式产品里面不要包含这个方法调用的任何代码。为了防止你犯这种错误,你可以在视图的category中这样做:
@implementation UIView (AutoLayoutDebugging) - (void)printAutoLayoutTrace { #ifdef DEBUG NSLog(@"%@", [self performSelector:@selector(_autolayoutTrace)]); #endif } @end
_autolayoutTrace打印的结果如下:
正如不可满足约束条件的错误消息一样,我们仍然需要弄明白打印出的内存地址所对应的视图。
另一个标识出有歧义布局更直观的方法就是使用exerciseAmbiguityInLayout。这将会在有效值之间随机改变视图的frame。然而,每次调用这个方法只会改变frame一次。所以当你启动程序的时候,你根本不会看到改变。创建一个遍历所有视图层级的辅助方法是一个不错的主意,并且让所有的视图都有一个歧义的布局“jiggle”。
@implementation UIView (AutoLayoutDebugging) - (void)exerciseAmiguityInLayoutRepeatedly:(BOOL)recursive { #ifdef DEBUG if (self.hasAmbiguousLayout) { [NSTimer scheduledTimerWithTimeInterval:.5 target:self selector:@selector(exerciseAmbiguityInLayout) userInfo:nil repeats:YES]; } if (recursive) { for (UIView *subview in self.subviews) { [subview exerciseAmbiguityInLayoutRepeatedly:YES]; } } #endif } @end
相关文章推荐
- iOS开发技巧——Autolayout动画
- iOS UI设计: autolayout约束的一点技巧
- iOS开发之Xcode常用调试(Debug)技巧总结
- iOS开发技巧(系列十五:autolayout自动布局)
- iOS 调试技巧:如何利用 LLDB 来 Debug
- iOS开发技巧之autolayout自动布局
- iOS开发bug调试技巧之Debug View Hierarchy
- iOS开发技巧(autolayout自动布局)
- iOS开发技巧(系列十五:autolayout自动布局)
- iOS开发技巧(系列十五:autolayout自动布局)
- iOS陆先森开发随笔(三)iOS调试技巧(debug)
- iOS调试技巧(debug)
- iOS开发之Xcode常用调试(Debug)技巧总结
- iOS Autolayout 介绍 2 Interface Builder 技巧
- iOS 调试技巧:如何利用 LLDB 来 Debug
- 芒果iOS开发bug调试技巧之Debug View Hierarchy
- iOS开发之Xcode常用调试(Debug)技巧总结
- iOS开发技巧(系列十五:autolayout自动布局)
- iOS开发技巧(系列十五:autolayout自动布局)
- IOS开发之Xcode下LLDB调试技巧_Debug_更改BOOL类型的值