debugserver+lldb很好用,但啓動起來太麻煩?咱們開發了一款iOS SpringBoard tweak小插件,簡化debugserver啓動過程。老鐵,請雙擊!ios
咱們常常要經過debugserver對App進行調試,有書籍和論壇對相關的技術和實踐進行了說明,但實際應用起來仍是有些麻煩。先要重籤拷貝,再要啓動終端ssh到iPhone啓動debugserver,各類ls加grep找到想調試的應用,敲命令啓動debugserver,而後Mac本地終端啓動lldb。這樣折騰下來,至少要開兩個終端,有的時候甚至更多。GitHub上有個issh工具對上述操做有封裝和優化,可是仍是須要敲命令找App,再運行debugserver。git
因此作個tweak提高一下生產力。只需雙擊應用圖標,便可一鍵啓動debugserver。github
代碼見:Githubapp
https://github.com/TalkingDat...ssh
運行界面異步
咱們所用的開發環境是iOS 13.3,可是並無用到特殊版本的API,低版本手機應該也OK。函數
下面簡單分享開發過程:工具
從界面找邏輯,逆向發現SpringBoard的圖標是SBIconView。而且有一個叫屬性 applicationBundleIdentifierForShortcuts 返回的是圖標對應的App的Bundle ID。經過Bundle ID構造LSApplicationProxy對象,而且得到canonicalExecutablePath屬性,也就是應用的可執行文件路徑。學習
Class LSApplicationProxy_class = objc_getClass("LSApplicationProxy");NSObject* proxyObj = [LSApplicationProxy_class performSelector:@selector(applicationProxyForIdentifier:) withObject:bundle];NSString * canonicalExecutablePath = [proxyObj performSelector:@selector(canonicalExecutablePath)];複製代碼
接續看SBIconView,圖標上有兩個手勢對象:測試
因此,咱們來給圖標交互加個雙擊擴展。
%hook SBIconView
- (void)didMoveToWindow{ %orig; UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleDoubleClick:)]; [doubleTap setNumberOfTapsRequired:2]; [self addGestureRecognizer:doubleTap]; NSArray * ges = self.gestureRecognizers; for(UITapGestureRecognizer * each in ges){ if([each isKindOfClass:[UITapGestureRecognizer class]]){ [each requireGestureRecognizerToFail: doubleTap]; } }}複製代碼
這裏額外說一句,[each requireGestureRecognizerToFail: doubleTap]添加了雙擊手勢指揮,因爲iOS內部維護了手勢的狀態機,咱們進行單擊操做的時候,其實產生了兩種Possible State。第一種是識別爲單擊,而後結束。第二種是識別爲雙擊的第一下並等待第二下的發生,而後根據兩次點擊之時間間隔閾值來判斷是否是合法的雙擊。
因此咱們手動增長了約束,至關於指定了識別的優先級,只有雙擊失敗了,才繼續執行單擊回調。這種操做會帶來一點幾乎無感的瑕疵:單擊後等待雙擊識別失敗的延遲,延遲的值就是雙擊識別執行的閾值(大約零點幾秒)。
debugserver是一個二進制文件,狗神的教程裏有如何重籤,issh把這些過程給簡化了。先看一下debugserver的權限:
-rwxr-xr-x 1 root admin 9876848 Jan 19 11:28 /iOSRE/tools/debugserver*
再來看一下SpringBoard的權限:
-rwxr-xr-x 1 root wheel 71264 Dec 5 13:15 SpringBoard*
屬主用戶都是root,沒毛病。找個函數調用一下:
代碼以下:
task = [[NSTask alloc]init];[task setLaunchPath:bin_serverpath];[task setArguments:args];[task launch];複製代碼
每次server在launch以前,要把以前的task結束掉。
- (void)interrupt; // Not always possible. Sends SIGINT.複製代碼
- (void)terminate; // Not always possible. Sends SIGTERM.複製代碼
NSTask頭文件里居然告訴我 Not always possible。事實上,在調用的時候,還真的不怎麼possible,實際測試第一次server正常啓動,後續因爲沒成功關閉,因此第二次就無法啓動了。
因此仍是換種方式關閉吧。簡單粗暴的 kill 函數:
NSTask * task = [TaskManager sharedManager].runningTask;if(task){ kill(task.processIdentifier,SIGKILL); task = nil;}複製代碼
直接用Alert,又有按鈕又有輸入框,不過UIAlertView已經被廢棄掉了,須要用UIAlertController。因爲彈出Controller須要父Controller,經過View找到當前的Controller,作正向的應該都寫過這段代碼吧:
@implementation UIView(find)-(UIViewController*)findViewController{ UIResponder* target= self; while (target) { target = target.nextResponder; if ([target isKindOfClass:[UIViewController class]]) { break; } } return (UIViewController*)target;}@end複製代碼
輸入框裏的IP和debugserver的path,每一個人都不同,因此在第一次輸入完成以後,把這些值用NSUserDefault持久化存儲起來,下次直接讀取填充。
以前在相關技術論壇讀到討論用Root身份運行App的帖子,學習完帖子裏的技巧,加強對操做系統的理解以及實踐以後,發現若是真的想RootApp運行,其實SpringBoard自己就是一個RootApp,咱們把SpringBoard當作RootViewController,很容易把一些系統工具作出界面,從而提高生產力。好比砸殼、重籤、拷貝App等。
**
做者:TalkingData小張同窗
本文版權歸TalkingData全部,如需轉載請註明來源**