Swift之深入解析如何使用Xcode和LLDB v2修改UI元素

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift之深入解析如何使用Xcode和LLDB v2修改UI元素相关的知识,希望对你有一定的参考价值。

一、前言

  • 在上一篇博客中,已经详细地介绍如何使用 LLDB 表达式修改 UI 元素,具体请参考:Swift之深入解析如何将代码添加为自定义LLDB命令
  • 在这篇博客中,将继续讨论相同的问题需求,并将重点讨论如何最大限度地利用 LLDB 表达式,如果工具太过复杂,那么它就无法工作并获得用户的采用,出于这个原因,本文将分享一些使 LLDB 表达式更容易使用的替代方法。

二、命令别名

  • 首先,必须输入(或复制)这个长度的命令(例如 po [[[UIApplication sharedApplication] keyWindow] recursiveDescription])可能会阻碍开发者使用这些 LLDB 命令。幸运的是,这个问题有解决方案,它被称为命令别名,惟一需要做的就是编辑位于 ~/.lldbinit 目录中的文件,Lldbinit(或者通过键入 touch ~/.lldbinit 创建它),并添加如下命令:
command alias views expression -l objc -O -- [[[UIApplication sharedApplication] keyWindow] recursiveDescription]

command alias flush expression -l objc -- (void)[CATransaction flush]
command regex change_color 's/(.+) (.+)/e (void)[(id)%1 setBackgroundColor:[UIColor %2]]/'
  • 前两个命令在Swift之深入解析如何将代码添加为自定义LLDB命令已经介绍使用过,第一个是打印视图层次结构,获取按钮的内存地址,第二个是刷新 UI。
  • 最后一个命令演示了如何使用 regex 创建带参数的命令,在这个特定的情况下,需要一个内存地址和颜色的名称(blueColor),结果按钮的背景颜色将会改变。例如,运行 change_color 0x7f9f7e40cd70 blueColor 并刷新将导致将按钮的背景颜色更改为蓝色。
  • 使用别名将提高使用此工具的可能性,但在某些情况下需要更高级的别名。比如说,如何通过按钮标签的文本找到按钮的内存地址。在这种情况下,Python 可以提供可扩展性。

三、LLDB 与 Python

  • 现在开始编写 Python 命令的一个简单方法是使用 Xcode 控制台,输入命令脚本添加帮助并遵循说明:

  • 或者,可以创建一个脚本文件,它可以添加到回收和版本控制系统中,然后在 Xcode 控制台上运行命令 command script import <script_file_path>,或者更好的方法是将它添加到 ~/.lldbinit 文件中。
  • 要写一个 Python 函数用作新的 LLDB 命令,需要实现一个带有四个参数的函数:
def command_function(debugger, command, result, internal_dict):
    # Your code goes here
  • 根据 LLDB python 参考,这些变量的类型和描述如下:
    • debugger (类型:lldb.SBDebugger):当前调试器对象;
    • command (类型:python string):一个包含命令所有参数的 Python 字符串,如果需要分割参数,请尝试使用 shlex 模块的 shlex.split(命令)来正确地提取参数;
    • result (类型: lldb.SBCommandReturnObject):一个返回对象,它封装了命令的成功/失败信息,以及作为命令结果需要打印的输出文本,普通的 Python“print” 命令也可以工作,但默认情况下文本不会进入结果(它作为临时日志记录工具很有用);
    • internal_dict (类型:python dict object): 当前嵌入脚本会话的字典,其中包含所有变量和函数。
  • 如果使用命令脚本导入方法,可以像下面这样定义 __lldb_init_module 函数:
def __lldb_init_module(debugger, internal_dict):
    # Command Initialization code goes here
    debugger.HandleCommand('command script add -f filter.filter_button_by_label filter_button_by_label')
  • 其中调试器和 internal_dict 如上所述,当加载模块时,这个函数将被调用,允许在当前调试器中添加任何想要的命令。debugger.HandleCommand 的参数描述如下:
    • command script add:使用 LLDB 命令添加脚本;
    • -f argument:指定命令将执行的 Python 函数的名称,它遵循的格式:module name.function name;如同上面的例子中:
      • filter:是模块的名称(在 Python 中只是不带 .py 扩展名的文件名);
      • filter_button_by_label:命令功能,描述同 command_function(debugger, command, result, internal_dict)) 一致;
    • argument(例如 filter_button_by_label):是 Xcode 控制台中用来调用此函数的命令。
  • 如下所示,展示了一个如何从按钮标签的文本中获取按钮内存地址的示例:
import lldb
import commands
import optparse
import shlex
import re

def create_options():
    usage = "usage: filter_button_by_label [options]"
    description='''
        This command is used to find a UIButton with a label matching the option provided as option
    '''
    parser = optparse.OptionParser(description=description, prog='filter_button_by_label', usage=usage)
    parser.add_option('-n', '--needle', type='string', dest='needle', help='Text to search on UIButton labels.')

    return parser

def filter_button_by_label(debugger, command, result, internal_dict):

    target = debugger.GetSelectedTarget()
    process = target.GetProcess()
    mainThread = process.GetThreadAtIndex(0)
    currentFrame = mainThread.GetSelectedFrame()

    # Parse arguments and options
    command_args = shlex.split(command)
    parser = create_options()
    try:
        (options, args) = parser.parse_args(command_args)
        # if needle is not provided
        if not options.needle:
            parser.print_help()
            return
    except:
        return

    view_hierarchy_command = '(id)[[[UIApplication sharedApplication] keyWindow] recursiveDescription]'
    view_hierarchy = currentFrame.EvaluateExpression(view_hierarchy_command).GetObjectDescription()

    for match in re.finditer('.*<UIButton: (0x[0-9a-fA-F]*);.*', view_hierarchy, re.IGNORECASE):

        view = match.groups()[-1]

        created_command = '(NSString *)[ (id)' + view + ' currentTitle]'
        title = currentFrame.EvaluateExpression(created_command).GetObjectDescription()

        if title == options.needle:
            print >>result, view
        else:
            print >>result, "Not Found"

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f ' + __name__ + '.filter_button_by_label filter_button_by_label')
  • 它可以像 filter_button_by_label -n “Press me” 这样调用,它会返回那个按钮的内存地址。因此改变按钮颜色的整个流程就变成:

  • 与第一个实现相比,代码更短,更容易记忆,因此更容易使用。

四、Chisel

  • 除此之外,Facebook 已经提供了一个名为 Chisel 的开源 LLDB 命令集,它提供了大量的命令,可能解决了任何 ios 开发者可能面临的大多数问题。
  • 如下所示,个人最喜欢的两个命令:
    • findinstances:可以用来查找指定 ObjC 类的实例;
    • pcurl:它可以被用来打印作为 curl 命令的 NSURLRequest,稍后可以用于调试目的。

以上是关于Swift之深入解析如何使用Xcode和LLDB v2修改UI元素的主要内容,如果未能解决你的问题,请参考以下文章

Swift之深入解析Xcode13对Swift对象生命周期的优化

Xcode:如何退出 lldb swift repl

Swift之深入解析“结果生成器”的工作原理

iOS之深入解析Xcode 13(iOS 15)正式版发布的新特性

使用 Xcode LLDB 控制台在 Swift 中调试闭包

Swift之深入解析如何避免单元测试中的强制解析