經常羨慕前端開發、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)
複製代碼