API 的分類
iOS 中的 API 大致分為三種:Published API(公開的 API)熊户、UnPublished API(未公開的 API)和 Private API(私有 API)嚷堡。 我們?nèi)粘J褂玫?API 都是公開的 API艇棕,存放在 Frameworks 框架中串塑。而未公開的 API 是指雖然也存放在 Frameworks 框架中北苟,但是卻沒有在蘋果的官方文檔中有使用說明、代碼介紹等記錄的 API友鼻。 私有 API 則是指存放在 PrivateFrameworks 框架中的 API彩扔。通常,這兩者都被稱作私有 API借杰,不過在使用方法上還是有一定區(qū)別的进泼。蘋果明確規(guī)定上架 Appstore 的應(yīng)用不能使用私有 API,不過自己私下玩一玩還是挺有意思的绞惦。私有 api 的頭文件在 Xcode 中是無法查看的洋措,需要使用class-dump導(dǎo)出,不過早有大神導(dǎo)出了完整的頭文件供我們使用王滤,大家可以前往 Github 查看滓鸠。
UnPublished API(未公開 API)
未公開的 API 雖然也存放在 Frameworks 框架中,但是卻沒有在蘋果的官方文檔中有使用說明糜俗、代碼介紹等記錄。按蘋果的說法珠月,未公開的 API 還不夠成熟楔敌,可能還會變動,等完全成型了之后就會變成公開的 API庆聘,但是目前不對其提供承諾,系統(tǒng)版本升級后可能會失效掏觉。下面用一個例子來說明未公開 API 的使用方法。在 MobileCoreServices.framework
框架中有一個叫做LSApplicationWorkspace
的類织盼,利用該類可以獲取到手機上應(yīng)用的各種信息酱塔,包括已安裝列表,正在安裝列表等等唐全,如圖:
接下來我們就嘗試利用代碼調(diào)用該 API蕊玷,示例程序如下:
Class LSApplicationWorkspace_Class = NSClassFromString(@"LSApplicationWorkspace");
NSObject *workspace = [LSApplicationWorkspace_Class performSelector:NSSelectorFromString(@"defaultWorkspace")];
NSArray *appList = [workspace performSelector:NSSelectorFromString(@"allApplications")];
for (id app in appList) {
NSLog(@"%@", [app performSelector:NSSelectorFromString(@"applicationIdentifier")]);
}
我們獲取到的數(shù)組中存放的實際上是LSApplicationProxy
類型的對象垃帅,該對象有一個名為 applicationIdentifier 的屬性,如圖所示:
調(diào)用此屬性方庭,即可得到應(yīng)用的包名信息酱固,如下圖所示:
可以看到,未公開 API 的調(diào)用實際上只需要將類名龄减、方法名等從字符串進行轉(zhuǎn)化扇苞,隨后利用 performSelector 方法進行調(diào)用即可欺殿,相當簡單。
Private API(私有 API)
私有 API 是指存放在 PrivateFrameworks 框架中的 API鳖敷。私有 API 的調(diào)用與未公開 API 唯一的差別在于調(diào)用私有 API 之前需要先加載私有 API 所在的庫到內(nèi)存當中脖苏。下面我們用MobileContainerManager.framework
中的一個類MCMAppContainer
來做介紹,利用該API可以根據(jù)包名來判斷某APP是否存在定踱,不過無法確定應(yīng)用的狀態(tài)為安裝中或已安裝棍潘,調(diào)用方法如下:
NSBundle *container = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/MobileContainerManager.framework"];
if ([container load]) {
Class appContainer = NSClassFromString(@"MCMAppContainer");
id test = [appContainer performSelector:@selector(containerWithIdentifier:error:) withObject:@"com.tencent.xin" withObject:nil];
if (test) {
NSLog(@"存在該應(yīng)用");
}
}
當然,還有另外一種加載方法,如下:
#import <dlfcn.h>
void *lib = dlopen("/System/Library/PrivateFrameworks/MobileContainerManager.framework", RTLD_LAZY);
if (lib) {
Class appContainer = NSClassFromString(@"MCMAppContainer");
id test = [appContainer performSelector:@selector(containerWithIdentifier:error:) withObject:@"com.tencent.xin" withObject:nil];
if (test) {
NSLog(@"存在該應(yīng)用");
}
dlclose(lib);
}
繞過審核
雖然公開 API 中已經(jīng)提供了大量封裝好的方法亦歉,但是架不住產(chǎn)品經(jīng)理的各種奇葩需求恤浪。工作過程中很有可能會遇到公開 API 解決不了問題的時候。這個時候我們就不得不求助于私有 API 了肴楷。可是一旦使用私有 API赛蔫,上架 Appstore 又成為了一個難題砂客。這里提供一種繞過審核的方法,不過不太提倡使用呵恢,被逼無奈的情況下可以嘗試一下鞠值。當然,這種方法也還是會有審核時被查出的風險渗钉。
蘋果審核時可能會通過檢索關(guān)鍵詞來檢查私有 API 的使用情況彤恶,因此我們可以嘗試先將類名、方法名鳄橘、路徑名等進行加密處理声离,當調(diào)用私有 API 時,將加密后的字符串傳入解密方法中進行解密處理挥唠。如下所示:
//base64編碼
- (NSString *)encodeString:(NSString *)string
{
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
NSString *encodedStr = [data base64EncodedStringWithOptions:0];
return encodedStr;
}
//base64解碼
- (NSString *)decodeString:(NSString *)string
{
NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
NSString *decodedStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return decodedStr;
}
//調(diào)用私有api
- (void)testPrivateApi
{
NSString *path = [self decodeString:@"L1N5c3RlbS9MaWJyYXJ5L1ByaXZhdGVGcmFtZXdvcmtzL01vYmlsZUNvbnRhaW5lck1hbmFnZXIuZnJhbWV3b3Jr"];
NSBundle *container = [NSBundle bundleWithPath:path];
if ([container load]) {
Class appContainer = NSClassFromString([self decodeString:@"TUNNQXBwQ29udGFpbmVy"]);
NSString *sel = [self decodeString:@"Y29udGFpbmVyV2l0aElkZW50aWZpZXI6ZXJyb3I6"];
id test = [appContainer performSelector:NSSelectorFromString(sel) withObject:@"com.tencent.xin" withObject:nil];
if (test) {
NSLog(@"存在該應(yīng)用");
}
}
}
由于代碼中沒有出現(xiàn)類名抵恋、方法名焕议、路徑名等關(guān)鍵詞宝磨,可以極大降低審核時被發(fā)現(xiàn)的可能性。當然盅安,此方法僅供參考唤锉,不建議使用。