前言
為啥我一個(gè)做社交坦辟、直播折柠、圖片后編輯方向的iOS開(kāi)發(fā)突然想學(xué)輸入法開(kāi)發(fā)呢,這一切還得從我看到搜狗輸入法的招聘JD說(shuō)起....
我看到搜狗輸入法的招聘里寫(xiě)到一條:
了解逆向優(yōu)先
,此時(shí)我有個(gè)疑問(wèn)带膜,做輸入法App開(kāi)發(fā)和逆向有什么關(guān)系? 于是就有了想了解輸入法App開(kāi)發(fā)的興趣吩谦,也就有個(gè)這篇文章首先在寫(xiě)這個(gè)前言的時(shí)候,我是壓根不知道輸入法怎么開(kāi)發(fā)的膝藕。當(dāng)想到要做一個(gè)輸入法App時(shí)式廷,我有如下疑問(wèn)
- 為什么安裝了搜狗輸入法App后,系統(tǒng)鍵盤(pán)設(shè)置里會(huì)出現(xiàn)搜狗輸入法芭挽,且有完全訪(fǎng)問(wèn)的選項(xiàng)(UISwith)滑废,完全訪(fǎng)問(wèn)是做什么的蝗肪?
- 為什么安裝了搜狗輸入法App后,在我自己的App里調(diào)起輸入法后蠕趁,調(diào)起的是第三方的搜狗輸入法鍵盤(pán)
- 搜狗輸入法App和搜狗輸入法鍵盤(pán)兩者之間有什么關(guān)聯(lián)薛闪? 如何關(guān)聯(lián)?
- 第三方輸入法和逆向之間有什么關(guān)聯(lián)俺陋?逆向知識(shí)能為輸入法類(lèi)App帶來(lái)什么?
如果你也有以上疑問(wèn)豁延,請(qǐng)耐心看下去(其實(shí)寫(xiě)到此處時(shí)能不能解答我也不知道... 下文均有解答)
本文目錄:
- 調(diào)研著手
- App實(shí)現(xiàn)(簡(jiǎn)單實(shí)現(xiàn)一個(gè)能在其它App內(nèi)使用的輸入法App)
- 輸入法類(lèi)App原理
- 輸入法App和逆向(個(gè)人瞎猜,可能魯迅人本身根本沒(méi)這么想)
1. 調(diào)研著手
1.1 砸殼
因?yàn)榱私廨斎敕ˋpp的念頭起源于逆向
腊状,所以此處我先砸殼诱咏,看看什么發(fā)現(xiàn)...
在砸完搜狗輸入法
殼后,看ipa內(nèi)部的文件缴挖,我并沒(méi)有看到與其它App有什么區(qū)別袋狞,無(wú)非是icon資源
,infoPlist
,.mpa資源
,Frameworks
、lottie json文件夾
映屋、開(kāi)啟完全訪(fǎng)問(wèn)的.mp4教程
苟鸯、MJRefresh等第三方庫(kù)
、/PlugIns/SogouAction.appex
以及mach-o
秧荆。
通過(guò)ipa倔毙,我發(fā)現(xiàn)兩個(gè)關(guān)鍵點(diǎn):
- 搜狗工程師比較喜歡用plist做配置類(lèi)數(shù)據(jù)存儲(chǔ)(這點(diǎn)我覺(jué)得挺挺好的,便于管理埃仪,當(dāng)然用.json也沒(méi)問(wèn)題)
- 在
plugins
路徑下乙濒,有一個(gè)SogouAction.appex
文件,.appex
一般是iOS拓展Extension
生成的文件卵蛉,比如接入NotificationService
同樣會(huì)生成push.appex
文件
此時(shí)颁股,我對(duì)百度輸入法
同樣進(jìn)行砸殼操作,發(fā)現(xiàn)在PlugIns
文件夾下傻丝,同樣有BaiduInputMethod.appex
甘有、NotificationContent.appex
這兩個(gè).appex結(jié)尾的文件
此時(shí)根據(jù)直覺(jué),我覺(jué)得輸入法類(lèi)App的關(guān)鍵在添加了一個(gè)類(lèi)似于inputKeyboard
的拓展Extension
實(shí)現(xiàn)
1.2 class-dump
頭文件里搜索了SogouAction
仍然一無(wú)所獲
1.3 Reveal
簡(jiǎn)單看了下搜狗的UI架構(gòu)葡缰,想通過(guò)Reveal看看能不能找出對(duì)應(yīng)的class亏掀,結(jié)果不但沒(méi)找到,Reveal里壓根沒(méi)顯示出來(lái)鍵盤(pán)的UI(鍵盤(pán)是系統(tǒng)層UI泛释,所有Reveal不到)滤愕。想到我們的目標(biāo)是了解如何開(kāi)發(fā)一個(gè)輸入法App。此時(shí)我們暫時(shí)停止逆向怜校,直接去百度...
1.3 利用搜索引擎
百度搜到的東西很少间影,大多是檢測(cè)鍵盤(pán)彈出高度,只搜到關(guān)鍵性的三篇文章:
iOS輸入法_開(kāi)發(fā)系統(tǒng)架構(gòu)
其中文章一是蘋(píng)果官方文檔,主要講構(gòu)建輸入法App用到的API
其中文章二主要將輸入法App的App架構(gòu)與通信茄茁,通過(guò)此文章我們大概知道為什么搜狗的iOS需要逆向經(jīng)驗(yàn)
其中文章三是構(gòu)建一個(gè)簡(jiǎn)單的輸入法App
我決定跟著文章三開(kāi)發(fā)一個(gè)簡(jiǎn)單的App魂贬,了解其原理
2. App搭建
2.1 創(chuàng)建一個(gè)名為InputApp的項(xiàng)目(為了練練手巩割,我們采用OC編碼,與鏈接內(nèi)不同付燥,后續(xù)我應(yīng)該會(huì)創(chuàng)建Swift版本上傳Github)
2.2 創(chuàng)建鍵盤(pán)Target
拓展Target命名為CustomKeyboard
彈出Activate “CustomKeyboard” scheme?
選擇activate
此時(shí)我們注意到宣谈,添加CustomKeyboard后,默認(rèn)生成了一個(gè)類(lèi)KeyboardViewController
!!! 注意一個(gè)很容易忽視的問(wèn)題:記得修改CustomKeyboard
Target支持的最低版本机蔗,如果支持的最低版本高于設(shè)備版本蒲祈,xcode編譯時(shí)不會(huì)報(bào)錯(cuò),但運(yùn)行時(shí)這個(gè)target不會(huì)運(yùn)行,添加NotificationService時(shí)同樣
有這個(gè)容易忽視的問(wèn)題
此時(shí)我們啟動(dòng)App萝嘁,在設(shè)置-通用-鍵盤(pán)-添加鍵盤(pán)里能看到我們的自定義鍵盤(pán)梆掸,同時(shí),也有開(kāi)啟完全訪(fǎng)問(wèn)的選項(xiàng)
<center class="half">
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cd521dabf02e448f9c5e13561773e081~tplv-k3u1fbpfcp-watermark.image" width="200"/> <> <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c0064cb4727d40d8b0fc809060529240~tplv-k3u1fbpfcp-watermark.image" width="200"/> <> <img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c3fc8f9bb244e5aa16fb69c8064c84e~tplv-k3u1fbpfcp-watermark.image"
width = "200">
</center>
添加鍵盤(pán)后牙言,我們將鍵盤(pán)切換到我們自定義的鍵盤(pán)酸钦,任意App內(nèi)調(diào)起鍵盤(pán)可以看到如圖
<center class="half">
<img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79496fdbb6cc435b9a24de9928df0e44~tplv-k3u1fbpfcp-watermark.image" width="200"/> <> <img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/38a51d7209194ab1a071807cb8146a25~tplv-k3u1fbpfcp-watermark.image" width="200"/>
</center>
此時(shí)彈出我們的自定義鍵盤(pán),可以看到咱枉,鍵盤(pán)有一個(gè)添加Extension時(shí)默認(rèn)生成代碼的button卑硫。我們將會(huì)在KeyboardViewController
類(lèi)里做鍵盤(pán)的自定義布局
2.3 鍵盤(pán)UI布局
我把源碼上傳到了Github
CCInput
鍵盤(pán)的UI布局我以簡(jiǎn)單以搜狗輸入法的數(shù)字鍵盤(pán)為例,這里附上代碼說(shuō)明蚕断,具體請(qǐng)查看Demo代碼
KeyboardViewController
創(chuàng)建keyboard extension時(shí)欢伏,系統(tǒng)自動(dòng)創(chuàng)建的vc,繼承自UIInputViewController
,鍵盤(pán)的布局亿乳、邏輯處理都在此類(lèi)中
CCLeftTableView
左側(cè)符號(hào)輸入硝拧,是一個(gè)TabView,,支持增加符號(hào)數(shù)據(jù)源
CCCenterView
中間數(shù)字鍵盤(pán)及底部切換葛假、數(shù)字0障陶、空格功能
CCRightView
右側(cè)刪除、句號(hào)聊训、@符號(hào)抱究、換行功能
CCKeyboardModel
鍵盤(pán)數(shù)據(jù)源Model,Model有兩個(gè)屬性带斑,分別是
NSString *string
用于鍵盤(pán)按鈕文本的展示
CCKeyboardAction keyboardAction
是點(diǎn)擊事件的枚舉類(lèi)型鼓寺,點(diǎn)擊鍵盤(pán)的時(shí)候通過(guò)此屬性統(tǒng)一處理簡(jiǎn)單的寫(xiě)了個(gè)通過(guò)runtime自動(dòng)獲取屬性解析json為model的方法
主要是UI和數(shù)據(jù)結(jié)構(gòu),此處不深入探究勋磕,感興趣請(qǐng)看demo妈候,此處有個(gè)自定義鍵盤(pán)高度的坑注意下:
- 通過(guò)簡(jiǎn)單的setframe無(wú)法更改鍵盤(pán)默認(rèn)高度
- 需要通過(guò)設(shè)置
NSLayoutConstraint
的方式,切在viewDidLoad方法中設(shè)置無(wú)效- 需在
viewDidAppear
之前設(shè)置鍵盤(pán)高度
這個(gè)問(wèn)題的解決方案因?yàn)閲?guó)內(nèi)做鍵盤(pán)的公司比較少朋凉,百度搜索不到相關(guān)資料州丹,附上stackoverflow鏈接
stackoverflow: iOS 8 Custom Keyboard: Changing the Height
Demo效果:
同時(shí)附上Demo中代碼
static CGFloat KEYBOARDHEIGHT = 256;
@interface KeyboardViewController ()
<CCTopBarDelegate,
CCLeftViewDelegate,
CCCenterViewDelegate,
CCRightViewDelegate>
/// 用于設(shè)置鍵盤(pán)自定義高度
@property (nonatomic, assign) NSLayoutConstraint *heightConstraint;
@end
@implementation KeyboardViewController
- (void)prepareHeightConstraint {
if (self.heightConstraint == nil) {
UILabel *dummyView = [[UILabel alloc] initWithFrame:CGRectZero];
dummyView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:dummyView];
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:KEYBOARDHEIGHT];
self.heightConstraint.priority = 750;
[self.view addConstraint:self.heightConstraint];
} else {
self.heightConstraint.constant = KEYBOARDHEIGHT;
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self prepareHeightConstraint];
}
/// 重寫(xiě)的父類(lèi)方法,蘋(píng)果建議在此處updateViewConstraints
- (void)updateViewConstraints {
[super updateViewConstraints];
if (self.view.frame.size.width == 0 && self.view.frame.size.height == 0) {
return;
}
[self prepareHeightConstraint];
}
UIInputViewController 涉及到的方法說(shuō)明:
-
advanceToNextInputMode
切換到下一個(gè)輸入法 -
dismissKeyboard
退出鍵盤(pán)(相當(dāng)于resignFirstResponder
) -
insertText
插入文本(尾部) -
deleteBackward
刪除輸入框內(nèi)上一個(gè)文本
3. 輸入法類(lèi)App原理
3.1 主程序與鍵盤(pán)拓展的關(guān)系
第三方輸入法,分為主程序(containing App)墓毒、鍵盤(pán)(extension)吓揪, 分別對(duì)應(yīng)Demo中的InputApp
崎弃、CustomKeyboard
, 主程序在桌面可見(jiàn)膳叨,Host App則是使用輸入法的其它App,
主程序和鍵盤(pán)Extension正常情況是無(wú)法共享數(shù)據(jù)的(沙箱隔離)
在開(kāi)啟完全訪(fǎng)問(wèn)時(shí),主程序和鍵盤(pán)Extension可以共享數(shù)據(jù)(通過(guò)app groups)
主程序和Host App無(wú)法共享數(shù)據(jù)(沙箱隔離)
鍵盤(pán)Extension和Host App只能共享文本數(shù)據(jù)(通過(guò)系統(tǒng)UITextDocumentProxy)
3.2 完全訪(fǎng)問(wèn)
在設(shè)置-通用-鍵盤(pán)中走触,蘋(píng)果有對(duì)完全訪(fǎng)問(wèn)做說(shuō)明主胧,摘錄如下
第三方鍵盤(pán)提供了另一種途徑來(lái)鍵入鍵盤(pán)數(shù)據(jù)叭首。這些鍵盤(pán)可以訪(fǎng)問(wèn)您鍵入的所有數(shù)據(jù),包括銀行賬戶(hù)踪栋、信用卡號(hào)碼焙格、街道地址及其他個(gè)人信息與敏感信息。這些鍵盤(pán)還可能訪(fǎng)問(wèn)相鄰文本及數(shù)據(jù)夷都,這些信息對(duì)改進(jìn)自動(dòng)更正功能卓有幫助眷唉。
如果您啟用“完全訪(fǎng)問(wèn)”,開(kāi)發(fā)者即獲得許可訪(fǎng)問(wèn)囤官、收集與傳輸您鍵入的數(shù)據(jù)冬阳。此外,如果附帶鍵盤(pán)的第三方應(yīng)用程序獲得您的許可訪(fǎng)問(wèn)地理位置信息党饮、照片肝陪、或其他個(gè)人數(shù)據(jù),那么此鍵盤(pán)也可收集并將該信息傳輸至鍵盤(pán)開(kāi)發(fā)者的服務(wù)器上刑顺。如果您停用某第三方鍵盤(pán)的“完全訪(fǎng)問(wèn)”氯窍,之后再重新啟用,那么鍵盤(pán)開(kāi)發(fā)者則可能能夠訪(fǎng)問(wèn)捏检、收集并傳輸網(wǎng)絡(luò)訪(fǎng)問(wèn)禁用期間所鍵入的信息荞驴。
如果您不啟用“完全訪(fǎng)問(wèn)”不皆,那么開(kāi)發(fā)者則不可收集與傳輸您鍵入的數(shù)據(jù)贯城。未經(jīng)您許可,任何未授權(quán)的數(shù)據(jù)收集或傳輸行為均違反其開(kāi)發(fā)者協(xié)議霹娄。此外能犯,技術(shù)限制同樣在防止未經(jīng)許可的訪(fǎng)問(wèn)方面起作用。任何試圖破壞此類(lèi)限制的嘗試同樣違反開(kāi)發(fā)者協(xié)議犬耻。
你任何時(shí)候均可選擇停用第三方鍵盤(pán)踩晶。打開(kāi)“設(shè)置”,輕點(diǎn)“鍵盤(pán)”枕磁,將該鍵盤(pán)從鍵盤(pán)列表中移除即可渡蜻。
如果您使用第三方鍵盤(pán),即需遵守鍵盤(pán)開(kāi)發(fā)者的條款、隱私政策及做法茸苇。使用此類(lèi)鍵盤(pán)App與服務(wù)之前排苍,您應(yīng)仔細(xì)閱讀其條款、隱私政策和做法学密,以了解他們?nèi)绾问褂媚臄?shù)據(jù)及其他信息淘衙。
總結(jié)就是,只有開(kāi)啟了完全訪(fǎng)問(wèn)腻暮,輸入法App才能:
- 訪(fǎng)問(wèn)彤守、收集、傳輸輸入的數(shù)據(jù)
- 訪(fǎng)問(wèn)并上傳位置哭靖、照片具垫、個(gè)人數(shù)據(jù)(通訊錄之類(lèi)權(quán)限隱私數(shù)據(jù))
- 如果開(kāi)啟之后又關(guān)閉,那么開(kāi)發(fā)者還是能夠傳輸禁用期間的數(shù)據(jù)
- 如果不開(kāi)啟试幽,那么因?yàn)樘O(píng)果的技術(shù)限制(沙箱)做修,開(kāi)發(fā)者并不能上傳鍵盤(pán)Extension獲取到的數(shù)據(jù)
3.3 App Groups數(shù)據(jù)共享
官方文檔:Sharing Data with Your Containing App
簡(jiǎn)單來(lái)說(shuō),開(kāi)啟了app groups抡草,相當(dāng)于生成一個(gè)中間數(shù)據(jù)共享區(qū)(shared container)來(lái)將App's container
和Extension's container
的數(shù)據(jù)關(guān)聯(lián)并共享
通過(guò)以下方式就可以共享數(shù)據(jù)
// Create and share access to an NSUserDefaults object
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName: @"com.example.domain.MyShareExtension"];
// Use the shared user defaults object to update the user's account
[mySharedDefaults setObject:theAccountName forKey:@"lastAccountName"];
4 輸入法App和逆向(個(gè)人瞎猜饰及,可能魯迅人本身根本沒(méi)這么想)
通過(guò)上文3.1
3.2
3.3
我們知道, 主App和Extension之間要想共享數(shù)據(jù),必須開(kāi)啟完全訪(fǎng)問(wèn)通過(guò)App Groups的方式共享數(shù)據(jù),對(duì)于一款鍵盤(pán)類(lèi)App康震,他肯定是希望App和Extension能實(shí)現(xiàn)數(shù)據(jù)共享燎含,以達(dá)到以下需求:
- 實(shí)時(shí)更新網(wǎng)絡(luò)高頻熱詞,用于聯(lián)想輸入
- 共享鍵盤(pán)皮膚
- 存儲(chǔ)用戶(hù)高頻輸入的詞匯腿短、通訊錄等數(shù)據(jù),上傳服務(wù)端屏箍,當(dāng)用戶(hù)換設(shè)備時(shí),能同步并共享給新設(shè)備的Extension
但是由于沙箱的限制橘忱,如果用戶(hù)沒(méi)有開(kāi)啟完全訪(fǎng)問(wèn)赴魁,以上三點(diǎn)需求就達(dá)不成了。事實(shí)上以上三點(diǎn)需求對(duì)我這樣的用戶(hù)來(lái)說(shuō)非常重要钝诚,無(wú)縫銜接切換設(shè)備的快感是無(wú)法形容的颖御,比如當(dāng)我在iPhone上輸入ma
后,我選擇了聯(lián)想詞匯表中的碼代碼的小馬
凝颇,然后當(dāng)我在mac上使用搜狗輸入法同樣輸入ma
,mac也聯(lián)想到了碼代碼的小馬
潘拱,這樣會(huì)將我的輸入效率提高很多
此時(shí)就用到了逆向技術(shù)
蘋(píng)果有沙箱隔離,逆向里有沙箱逃脫(沙箱逃脫詳細(xì)解釋請(qǐng)參考我之前文章沙箱逃脫)
沙箱逃脫簡(jiǎn)單來(lái)說(shuō)就是跨進(jìn)程訪(fǎng)問(wèn)數(shù)據(jù)共享數(shù)據(jù),放在這里就是拧略,即使用戶(hù)不開(kāi)啟完全訪(fǎng)問(wèn)芦岂,主App也能訪(fǎng)問(wèn)Extension的數(shù)據(jù),以實(shí)現(xiàn)如上三點(diǎn)需求垫蛆。
只是目前沙箱逃脫技術(shù)只能在越獄設(shè)備實(shí)現(xiàn)禽最,也許未來(lái)某一天某個(gè)大牛實(shí)現(xiàn)了在未越獄設(shè)備的沙箱逃脫腺怯,那對(duì)于逆向開(kāi)發(fā)者來(lái)說(shuō),簡(jiǎn)直是這盛世如你所愿川无,大好河山任你看...