ios autolayout debug调试技巧

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

这篇文章不是用来介绍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. #ifdef DEBUG
  3. - (NSString *)description
  4. {
  5. NSString *description = super.description;
  6. NSString *asciiArtDescription = self.asciiArtDescription;
  7. return [description stringByAppendingFormat:@" %@ (%@, %@)", asciiArtDescription, [self.firstItem tag], [self.secondItem tag]];
  8. }
  9. #endif
  10. @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. @implementation UIView (AutoLayoutDebugging)
  6. - (void)setAbc_NameTag:(NSString *)nameTag
  7. {
  8. objc_setAssociatedObject(self, "abc_nameTag", nameTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  9. }
  10. - (NSString *)abc_nameTag
  11. {
  12. return objc_getAssociatedObject(self, "abc_nameTag");
  13. }
  14. @end
  15. @implementation NSLayoutConstraint (AutoLayoutDebugging)
  16. #ifdef DEBUG
  17. - (NSString *)description
  18. {
  19. NSString *description = super.description;
  20. NSString *asciiArtDescription = self.asciiArtDescription;
  21. return [description stringByAppendingFormat:@" %@ (%@, %@)", asciiArtDescription, [self.firstItem abc_nameTag], [self.secondItem abc_nameTag]];
  22. }
  23. #endif
  24. @end

通过这种方法错误消息变得更可读,并且你不需要找出内存地址对应的视图。然而,对你而言,你需要做一些额外的工作以确保每次为视图分配的名字都是有意义。

另一个技巧为你提供更好的错误消息并且不需要额外的工作:对于每个布局约束条件,都需要将调用栈的标志融入到错误消息中。这样就很容易看出来问题涉及到的约束了。要做到这一点,你需要swizzle UIView或者NSView的addConstraint:/addConstraints:方法,以及布局约束的描述方法。在添加约束的方法中,你需要为每个约束条件关联一个对象,这个对象描述了当前调用栈堆栈的第一个frame。(或者任何你从中得到的信息):

  1. static void AddTracebackToConstraints(NSArray *constraints)
  2. {
  3. NSArray *a = [NSThread callStackSymbols];
  4. NSString *symbol = nil;
  5. if (2 < [a count])
  6. {
  7. NSString *line = a[2];
  8. // Format is
  9. //           1         2         3         4         5
  10. // 012345678901234567890123456789012345678901234567890123456789
  11. // 8 MyCoolApp 0x0000000100029809 -[MyViewController loadView] + 99 //
  12. // Don‘t add if this wasn‘t called from "MyCoolApp":
  13. if (59 <= [line length])
  14. {
  15. line = [line substringFromIndex:4];
  16. if ([line hasPrefix:@"My"]) {
  17. symbol = [line substringFromIndex:59 - 4];
  18. }
  19. }
  20. }
  21. for (NSLayoutConstraint *c in constraints) {
  22. if (symbol != nil) {
  23. objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingShort, symbol, OBJC_ASSOCIATION_COPY_NONATOMIC);
  24. }
  25. objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingCallStackSymbols, a, OBJC_ASSOCIATION_COPY_NONATOMIC);
  26. }
  27. } @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];
  9. }

检出这个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. }
  7. @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
时间: 2024-11-05 14:52:41

ios autolayout debug调试技巧的相关文章

iOS中debug调试输出日志

在.pch中写: #ifdef DEBUG  // 调试阶段 #define CXSLog(...) NSLog(__VA_ARGS__) #else // 发布阶段 #define CXSLog (...) #endif iOS中debug调试输出日志,布布扣,bubuko.com

【链接】Eclipse的Debug调试技巧

Eclipse的Debug调试技巧大全 https://mp.weixin.qq.com/s/bORg9YxJiby2WenYRrXY-w 使用Eclipse调试Java程序的10个技巧 https://mp.weixin.qq.com/s/VLIlZFl3WxIfR7naZzh_Cg 原文地址:https://www.cnblogs.com/xiaostudy/p/10188785.html

Eclipse的Debug调试技巧

作为开发人员,掌握开发环境下的调试技巧十分有必要.我们在编写java程序的过程中,经常会遇到各种莫名其妙的问题,为了检测程序是哪里出现问题,经常需要增加日志,看变量的值,这样调试很麻烦.假设我每天花费1小时在调试我的应用程序上的话,那累积起来的话也是很大量的时间.由于这个原因,用这些时间来重视并了解所有使我们调试更方便的功能.那能为你省下一些时间,也将会使你的生活更安逸.轻松. 首先我们需要注意三个点:不要使用System.out.println作为调试工具\使用一个日志分析器来阅读日志\启用所

Eclipse的Debug调试技巧大全

一.Debug视图 调试中最常用的窗口是: 窗口 说明 Debug窗口 主要显示当前线程方法调用栈, 以及代码行数(有调试信息的代码) 断点Breakpoints窗口 => 断点列表窗口,可以方便增加断点,设置断点条件,删除断点等 变量Variables窗口 => 显示当前方法的本地变量,非static方法,包含this应用,可以修改变量值 代码编辑窗口 => 这个不用多说了 输出Console窗口 => 日志等输出内容,调试时,可以将关注的组件级别设置低一点,以便获得跟多输出信息

Intellij IDEA Debug调试技巧

1.这里以一个web工程为例,点击图中按钮开始运行web工程. 2.设置断点 3.使用postman发送http请求 4.请求发送之后会自动跳到断点处,并且在断点之前会有数据结果显示 5.按F8 在 Debug 模式下,进入下一步,如果当前行断点是一个方法,则不进入当前方法体内,跳到下一条执行语句. 6.按F7在 Debug 模式下,进入下一步,如果当前行断点是一个方法,则进入当前方法体内,如果该方法体还有方法,则会进入该内嵌的方法中 . 7.继续按F7,则跳到StopWatch() 构造方法中

Docker debug调试技巧

『重用』容器名 但我们在编写/调试Dockerfile的时候我们经常会重复之前的command,比如这种docker run --name jstorm-zookeeper zookeeper:3.4,然后就容器名就冲突了. $ docker run --name jstorm-zookeeper zookeeper:3.4 ... $ docker run --name jstorm-zookeeper zookeeper:3.4 docker: Error response from dae

iOS各种调试技巧豪华套餐

转载自http://www.cnblogs.com/daiweilai/p/4421340.html 目录 前言 逼优鸡 知己知彼 百战不殆 抽刀断Bug 普通操作 全局断点(Global BreakPoint) 条件断点(Condational Breakpoints) 打印的艺术 NSLog 开启僵尸对象(Enable NSZombie Objects) 进击的码农 Console(lldb 命令) Profile(instruments) Xcode视图调试 结语 前言 最近博主临近毕业季

iOS开发之Xcode常用调试技巧总结

两种最常见最普通的方法: 1.NSLog,最简单的方法,查看变结 中是否有值,有什么值,是不是自己需要的值,然后找到bug. 2.po命令,在程序进入断点处,在控制台中输入po 变量名,也可以像NSLog一样查看变量是否有值,有什么值. 今天主要介绍点高大上的方法. 一.Memory Graph Xcode8新增:Memory Graph解决闭包引用循环问题 这个时候就进入了断点模式,可以查看issue面板,注意选择右边Runtime: 有很多叹号说明就有问题了.看内存中object的名字,有一

iOS开发调试技巧总结(持续更新中)

作者:乞力马扎罗的雪  原文 对于软件开发而言,调试是必须学会的技能,重要性不言而喻.对于调试的技能,基本上是可以迁移的,也就是说你以前在其他平台上掌握的很多调试技巧,很多也是可以用在iOS开发中.不同语言.不同IDE.不同平台的调试,有同性也有个性.今天我们就来学习一下iOS开发中的调试技巧,语言暂用为OC,IDE当然是强大的Xcode.首先说明下,Xcode已经为我们调试项目提供了极大的方便. [1.普通断点] 断点(Breakpoint)绝对是调试程序的第一大选择,也是掌握的基础技能.顾名