经常羡慕前端开发、RN 、Flutter 和 SwiftUI 开发的 live rendering, 即时渲染html
别家项目的框架,UI 开发,修改了,很容易渲染出来。无需每次手动漫长的编译前端
其实,lldb 自带了一个 python 解析器。要调试代码逻辑,不须要从新编译, kill 进程。python
当前进程下,能够直接调试。git
经常使用的是,苹果封装的一些命令。也能够根据本身的工程,本身写定制化的 python 脚本github
p
、po
、 v
lldb 很强大,开发者经常使用 p
、po
、 v
express
p
、po
、 v
, 查看当前函数帧的变量po
, expression --object-description , 使用对象描述的表达式,简单理解为 print object ,打印对象bash
p
, expression, 就是不带对象描述的 po,app
p
、po
只是 lldb 中,简单的别名。框架
v
, frame variable, 函数帧变量编辑器
Swift 中,编译时声明的类型,能够与运行时赋值过去的类型,不一致。
v
多了一个类型解析功能,Type Resolution
p
、po
,是根据编译时的数据类型操做。固然能够用as
作一个类型强转,cast 到运行时的类型。
v
,能够直接取出实际数据,也就是运行时的数据。( 若是编译与运行类型不一致,lldb 中须要手动输入字符串,没有代码自动补全)
p
、po
,执行代码语句v
优先。执行代码语句, p
、po
优先。v
直接访问调试信息,直接读内存。步骤相对少,
p
、po
须要去解析和执行。多了一些 JITing,跑代码的工做。
这些在 Xcode 的控制台,点一下暂定按钮, 开启 lldb, 均可以知道的
p
(lldb) help p
Evaluate an expression on the current thread.
// ...
'p' is an abbreviation for 'expression --'
// p 是 expression -- 的缩写
复制代码
po
(lldb) help po
Evaluate an expression on the current thread. Displays any returned value
// ...
'po' is an abbreviation for 'expression -O --'
// po 是 expression -O -- 的缩写
复制代码
v
(lldb) help v
// v 怎么用的,说的很清楚
// ...
'v' is an abbreviation for 'frame variable'
// v 是 frame variable 的缩写
复制代码
代码
struct Trip{
var name: String
var destinations: [String]
}
extension Trip: CustomDebugStringConvertible{
var debugDescription: String{
"Trip 的描述"
}
}
func one(){
let cruise = Trip(name: "Alone", destinations: ["Pain", "Great", "V", "me"])
// 断点于此
print("Place Holder")
}
one()
print("Place Holder")
复制代码
结果:
(lldb) po cruise
▿ Trip 的描述
- name : "Alone"
▿ destinations : 4 elements
- 0 : "Pain"
- 1 : "Great"
- 2 : "V"
- 3 : "me"
(lldb) expression --object-description -- cruise
▿ Trip 的描述
- name : "Alone"
▿ destinations : 4 elements
- 0 : "Pain"
- 1 : "Great"
- 2 : "V"
- 3 : "me"
(lldb) p cruise
(One.Trip) $R18 = {
name = "Alone"
destinations = 4 values {
[0] = "Pain"
[1] = "Great"
[2] = "V"
[3] = "me"
}
}
(lldb) expression cruise
(One.Trip) $R20 = {
name = "Alone"
destinations = 4 values {
[0] = "Pain"
[1] = "Great"
[2] = "V"
[3] = "me"
}
}
复制代码
bt
一下用 bt
,看下当前函数调用状况
bt
, thread backtrace, 线程函数调用栈回溯
关心的源代码旁边打断点,对于代码的执行时机的控制,很是强力。
添加大段代码,就不是很方便了。不是像文本编辑器那样好写。写错了,修改不便
并且断点里面,写好的执行代码,对复制粘贴大法,不友好。
lldb 里面内嵌了 python 的解释器,使用 python 脚本操纵 lldb, 经过各类 SB 打头的工具。
操做简易
( 对哪一个控件,感兴趣。就去取他的地址 )
再转换地址为你须要的对象
(目前,UIKit 框架,仍是 Objective-C 写的 )
换一下颜色:
先改颜色,再刷新界面
须要手动调用,由于当前进程冻结了,RunLoop 卡住了
本来 RunLoop 会自动处理 CATransaction, 作 FPS
(lldb) po ((UILabel *)0x100f2c100).backgroundColor = [UIColor redColor]
UIExtendedSRGBColorSpace 1 0 0 1
(lldb) po [CATransaction flush]
nil
复制代码
( 用的真机调试,效果很清楚 )
刷新页面的 Python 代码:
下面的这个是,很经典的代码
def flush(debugger, command, result, internal_dict):
debugger.HandleCommand("e (void)[CATransaction flush]")
复制代码
应该很老的缘故,如今跑不通了。 会报错:
error: property 'flush' not found on object of type 'CATransaction'
能够当伪代码看看。
我从 Chisel 找了些代码
import lldb
def flushUI(debugger, command, result, internal_dict):
frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
options = lldb.SBExpressionOptions()
options.SetLanguage(lldb.eLanguageTypeObjC)
options.SetTrapExceptions(False)
options.SetTryAllThreads(False)
frame.EvaluateExpression('@import UIKit', options)
frame.EvaluateExpression("(void)[CATransaction flush]", options)
复制代码
try_one.py
(lldb) command script import /Users/jzd/lldb/try_one.py
// 这个是我电脑的路径,拖文件到 Xcode 编辑区,生成文件路径
OK for the first
// 这个细节,不用在乎
(lldb) script import try_one
(lldb) command script add -f try_one.flushUI flush
(lldb) flush
(lldb) po ((UILabel *)0x104a25900).backgroundColor = [UIColor redColor]
UIExtendedSRGBColorSpace 1 0 0 1
(lldb) flush
复制代码
以下图,上面代码作的事情,就是改 UILable 的背景色,刷新 UI
lldb 有一个内置的方法 __lldb_init_module
,
lldb 载入该模块的时候,会作一些里面的配置工做。
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f try_one.flushUI flush')
print("OK for the first")
复制代码
~/.lldbinit
文件,自动加载模块( ~/.lldbinit
文件,若是没有,本身建立)
里面输入
command script import /Users/jzd/lldb/try_one.py
// 请填入你的路径
复制代码
这样就取代了手动的三步
(lldb) command script import /Users/jzd/lldb/try_one.py
(lldb) script import try_one
(lldb) command script add -f try_one.flushUI flush
复制代码
nudge 是一个改控件中心点坐标的 python 脚本
调 UI layout 细节的时候,颇有用。要改控件几个 pt 的位置,反复编译, run 程序,使人崩溃
nudge 解决了这个场景的问题
~/.lldbinit
文件中,输入
command script import /Users/jzd/lldb/nudge.py
// 请替换为,你的文件路径
复制代码
跑程序,会报错
error: module importing failed: Missing parentheses in call to 'print'. Did you mean print('The "nudge" command has been installed, type "help nudge" for detailed help.')? (nudge.py, line 204)
File "temp.py", line 1, in <module>
复制代码
这里有一个 Python 的编译错误,由于 Python 的语法错误
// 把最后一行
print 'The "nudge" ... ` // 改成 print('The "nudge" ... `)
复制代码
再对模块作一下刷新, 就行了
(lldb) command source ~/.lldbinit
Executing commands in '/Users/jzd/.lldbinit'.
nege OK, ready to nudge
(lldb) command script import /Users/jzd/lldb/nudge.py
The "nudge" command has been installed, type "help nudge" for detailed help.
// nudge 命令,安装好了
复制代码
就开始调用 nudge,修改控件位置
(lldb) nudge 100 0 0x127d23710
Total offset: (100.0, 0.0)
(CGRect) $17 = (origin = (x = 20, y = 12), size = (width = 120.5, height = 20.5))
(lldb) nudge 100 0 0x127d23710
Total offset: (100.0, 0.0)
(CGRect) $20 = (origin = (x = 120, y = 12), size = (width = 120.5, height = 20.5))
(lldb)
复制代码
前两步,跟上面的同样,先进入 UI 调试界面
进入 UI 调试界面操做
上面两行调用 nudge
的代码,可看出一个 bug.
第一处,调用
(lldb) nudge 100 0 0x127d23710
复制代码
没有生效。
反复测试发现,加载 nudge
后,
第一次调用,改位置,不成功
传进去的参数 Total offset
正常,
获取控件的坐标 (CGRect)
正常,
以后的操做,都正常。
实际作事情的是这个方法,
def nudge(self, x_offset, y_offset, target, command_result):
复制代码
该方法里面,注释很清楚
# 先经过设置中心点,修改控件的位置
# Set the new view.center.
setExpression = "(void)[(UIView *)%s setCenter:(CGPoint){%f, %f}]" %(self.target_view.GetValue(), center_x, center_y)
target.EvaluateExpression(setExpression, exprOptions)
# 改完 UI, 叫 CoreAnimation 去刷新界面
# Tell CoreAnimation to flush view updates to the screen.
target.EvaluateExpression("(void)[CATransaction flush]", exprOptions)
复制代码
设置中心点以后,先刷新下 UI 就行了
先执行这个,再走上面的逻辑
target.EvaluateExpression("(void)[CATransaction flush]", exprOptions)
复制代码
这样解决了 RunLoop 的问题。CPU 绘制,GPU 渲染。RunLoop 维护 FPS 刷新界面
调用界面图,相似上
(lldb) nudge 10 0 0x102e23510
Total offset: (10.0, 0.0)
(CGRect) $17 = (origin = (x = 30, y = 12), size = (width = 120.5, height = 20.5))
(lldb) nudge 10 0 0x102e23510
Total offset: (20.0, 0.0)
(CGRect) $20 = (origin = (x = 40, y = 12), size = (width = 120.5, height = 20.5))
(lldb) nudge 10 0 0x102e23510
Total offset: (30.0, 0.0)
(CGRect) $23 = (origin = (x = 50, y = 12), size = (width = 120.5, height = 20.5))
(lldb) nudge 10 0
Total offset: (40.0, 0.0)
(CGRect) $25 = (origin = (x = 60, y = 12), size = (width = 120.5, height = 20.5))
(lldb) nudge 10 0
Total offset: (50.0, 0.0)
(CGRect) $27 = (origin = (x = 70, y = 12), size = (width = 120.5, height = 20.5))
(lldb) nudge 10 0
Total offset: (60.0, 0.0)
(CGRect) $29 = (origin = (x = 80, y = 12), size = (width = 120.5, height = 20.5))
复制代码
读源代码,业务逻辑,关心的是,有没有能够执行的视图,不关心第三个参数。
由于 python 命令行库 argparse
, 项目这么配置的
def create_options(self):
方法中,这么配置参数
# 必定要传两个 float,
# nargs 为 2,要传两个参数
# type 为 float,两个参数,做为 float 来处理
# Parse two floating point arguments for the x,y offset.
self.parser.add_argument(
'offsets',
metavar='offset',
type=float,
nargs=2,
help='x/y offsets')
# nargs 为 *,他的参数是任意个,0 个、1 个与多个
# type 为 str,他的参数,都做为 str 来处理
# Parse all remaining arguments as the expression to evalute for the target view.
self.parser.add_argument(
'view_expression',
metavar='view_expression',
type=str,
nargs='*',
help='target view expression')
复制代码
argparse 的使用,python 文档,写的很清楚
由于 NudgeCommand
这个类里面,有一个属性 target_view
, 持有传进去的 view.
class NudgeCommand:
target_view = None
复制代码
def __call__(self, debugger, command, exe_ctx, result):
// ...
# If optional 3rd argument is supplied, then evaluate it to get the target view reference.
if len(args.view_expression) > 0:
view_expression = ' '.join(args.view_expression)
expr_error = self.evaluate_view_expression(view_expression, target)
# 若是有第三个参数,经过判断第三个参数的长度
# 就把第三个参数传进去,走这个方法 evaluate_view_expression
复制代码
evaluate_view_expression
def evaluate_view_expression(self, view_expression, target):
# Expression options to evaluate the view expression using the language of the current stack frame.
exprOptions = lldb.SBExpressionOptions()
exprOptions.SetIgnoreBreakpoints()
# Get a pointer to the view by evaluating the user-provided expression (in the language of the current frame).
# 字符串 view_expression,转当前语言的指针
result = target.EvaluateExpression(view_expression, exprOptions)
if result.GetValue() is None:
return result.GetError()
else:
# 判断以前有没有 target_view,
# 若是有 ,是否是同一个视图
# 若是不是,以前保留的其余数据,清 0
if self.target_view and self.target_view.GetValue() != result.GetValue():
# Reset center-point offset tracking for new target view.
self.original_center = None
self.total_center_offset = (0.0, 0.0)
# 将视图的指针,赋值给属性 target_view
self.target_view = result
return None # No error
复制代码
__call__
方法,调用 nudge
方法def __call__(self, debugger, command, exe_ctx, result):
# 走真正作事的逻辑以前
# 里面有这么一段
// ...
# Cannot continue if no target view has been specified.
# 若是拿不到 target_view,就直接报错了
if self.target_view is None:
result.SetError("No view expression has been specified.")
return
# 拿到了 target_view,才会往下走,走到去改位置,去 nedge
# X and Y offsets are already parsed as floats.
x_offset = args.offsets[0]
y_offset = args.offsets[1]
# We have everything we need to nudge the view.
self.nudge(x_offset, y_offset, exe_ctx.target, result)
复制代码
nudge
的源代码,功能完善,至关简单本来只有一个逻辑,用参数修改位置, 如今要加逻辑,直接复原修改
就要用到 python 命令行库 argparse
的选项功能,options
nudge
,就把以前对 View 的 Layout 操做, 都撤销了原来必须给命令,传两个参数的设计,留不得
def create_options(self):
方法中,
self.parser.add_argument("-c","--center",type = float, dest = 'offsets', help = "x/y offsets", default = [0, 0], nargs = 2)
# 默认给的 offsets,是一个 python 的 list, 里面是两个 0
复制代码
def nudge(self, x_offset, y_offset, target, command_result, toReset):
#...
# 若是要撤销,就改一下偏移的逻辑,
# 改回去
if self.original_center is not None and toReset:
center_x = self.original_center[0]
center_y = self.original_center[1]
else:
# Adjust the x,y center values by adding the offsets.
center_x += x_offset
center_y += y_offset
复制代码
把 __call__
方法,调用 nudge
方法的逻辑,稍做加工,就行了
# ,,,
toReset = False
# 就是看,输入的偏移,是否是默认值。
# 若是不是,就走撤销改偏移的逻辑
if x_offset == 0 and y_offset == 0:
toReset = True
# We have everything we need to nudge the view.
self.nudge(x_offset, y_offset, exe_ctx.target, result, toReset)
复制代码
这样调用:
(lldb) nudge 0x105426bf0
Total offset: (0.0, 0.0)
(CGRect) $1a7 = (origin = (x = 20, y = 12), size = (width = 155.5, height = 20.5))
// 移动位置
(lldb) nudge -c 20 0 0x105426bf0
Total offset: (20.0, 0.0)
(CGRect) $20 = (origin = (x = 40, y = 12), size = (width = 155.5, height = 20.5))
// 撤销移动位置
(lldb) nudge
Total offset: (0.0, 0.0)
(CGRect) $22 = (origin = (x = 20, y = 12), size = (width = 155.5, height = 20.5))
// 移动位置
(lldb) nudge -c 20 0
Total offset: (20.0, 0.0)
(CGRect) $24 = (origin = (x = 40, y = 12), size = (width = 155.5, height = 20.5))
// 撤销移动位置
(lldb) nudge
Total offset: (0.0, 0.0)
(CGRect) $26 = (origin = (x = 20, y = 12), size = (width = 155.5, height = 20.5))
(lldb)
复制代码