私有API是指蘋果未公開的一些方法,通常情況下這些方法不允許開發者使用,通常情況是指上架AppStore。私有API可以實現一些開放API不能實現的效果,功能強大,效果非凡。蘋果不允許使用,是因為有些私有API會侵犯用戶隱私,但使用私有API也并非一定會侵犯用戶隱私,這要看開發者怎么用了。
企業級賬號發布供內部人使用的APP,可以使用私有API。發布到其他APP平臺供越獄手機下載的APP,也有可能使用了私有API。這兩者沒有蘋果審核把關,私有API可以隨便使用。蘋果明令禁止使用私有API的APP上架AppStore!但凡事無絕對,AppStore上也不乏使用私有API的應用,使用辦法有很多,比如熱更新。不被蘋果發現就行,發現了輕者下架,重者封號。
我想看看私有API到底能干些啥,寫了一個測試私有API的小項目,涉及到的知識點大多數來源于網絡,在此感謝大神們的知識共享。項目很小,知識很淺,歡迎拍磚吐槽。
項目代碼: privateApiApps
開發環境: Xcode 8.2.1,iPhone 6,iOS 10.2
項目截圖:
獲取iPhone上安裝的app
app相關信息
參考資料:
iOS-Runtime-Headers
獲取iOS設備上安裝的應用列表
獲取iPhone中安裝的APP列表
Class LSAppClass = objc_getClass("LSApplicationWorkspace");NSObject *workspace = [LSAppClass performSelector:@selector(defaultWorkspace)];NSArray *appsArray = [workspace performSelector:@selector(allApplications)];
這里面使用了runtime的方法- (id)performSelector:(SEL)aSelector;
,需要引入<objc/runtime.h>
。該方法的作用是給接收者傳遞進去一個方法,返回值就是這個方法執行后的返回值。
由于調用的是私有API,傳入的方法相當于一個字符串,編譯器不會檢測該方法是否正確,相反會一直報警告。例如上面傳遞的方法defaultWorkspace
,這是個私有API的方法。通常還有另一種調用performSelector
方法的寫法:
NSObject *workspace = [LSAppClass performSelector:NSSelectorFromString(@"defaultWorkspace")];
如果傳遞進去的私有API方法名寫錯了,程序就會找不到該方法而崩潰。所以通常會通過下面方法檢測接收者或接收者的父類是否實現了傳遞進去的方法。
- (BOOL)respondsToSelector:(SEL)aSelector;
使用方法:
if ([LSAppClass respondsToSelector:@selector(defaultWorkspace)]) { NSObject *workspace = [LSAppClass performSelector:@selector(defaultWorkspace)];}
真實情況中,該方法并非必要。傳入的私有API方法名都是固定的,程序崩潰了說明方法名寫錯了,改成正確的即可。使用該方法不會崩潰,反而不易發現錯誤。
獲取每個APP的相關信息
[appsArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { AppsObject *appsObj = [[AppsObject alloc] init]; appsObj.appName = [obj performSelector:@selector(localizedName)]; appsObj.version = [obj performSelector:@selector(shortVersionString)]; appsObj.bundleId = [obj performSelector:@selector(applicationIdentifier)]; appsObj.appFullName = [obj performSelector:@selector(itemName)]; appsObj.appType = [obj performSelector:@selector(applicationType)]; appsObj.appVendorName = [obj performSelector:@selector(vendorName)]; appsObj.appRating = [obj performSelector:@selector(ratingLabel)]; [_appsObjArray addObject:appsObj];}]
遍歷獲取的APP列表,通過私有API獲取每個app的各種信息:
私有API方法名
用途
localizedName
app名字
shortVersionString
版本號
applicationIdentifier
Bundle Identifier
itemName
app在AppStore顯示的名字
applicationType
app類型,分為:System和User
vendorName
app供應商
ratingLabel
app評級
獲取APP圖標
上述中并沒有獲取APP圖標的方法,獲取app圖標比較麻煩。
獲取APP圖標路徑
NSDictionary *dict = [object performSelector:@selector(boundIconsDictionary)];NSString *appIconPath = [NSString stringWithFormat:@"%@/%@.png",[[object performSelector:@selector(resourcesDirectoryURL)] path],[[[dict objectForKey:@"CFBundlePrimaryIcon"] objectForKey:@"CFBundleIconFiles"] lastObject]];
iOS10.2親測,該方法只能獲取模擬器上的APP圖標,真機無效。
獲取圖標data數據
appsObj.iconData = [obj performSelector:@selector(iconDataForVariant:) withObject:@(2)];
該data數據并不能直接轉為UIImage,需要對data數據進行截取轉換,方法如下:
- (UIImage *)getAppIcon:(NSData *)iconData { NSInteger lenth = iconData.length; NSInteger width = 87; NSInteger height = 87; uint32_t *pixels = (uint32_t *)malloc(width * height * sizeof(uint32_t)); [iconData getBytes:pixels range:NSMakeRange(32, lenth - 32)]; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef ctx = CGBitmapContextCreate(pixels, width, height, 8, (width + 1) * sizeof(uint32_t), colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); CGImageRef cgImage = CGBitmapContextCreateImage(ctx); CGContextRelease(ctx); CGColorSpaceRelease(colorSpace); UIImage *icon = [UIImage imageWithCGImage: cgImage]; CGImageRelease(cgImage); return icon;}
打開APP
在iOS 9以后要想打開其他app需要添加URL Scheme,設置白名單,否則將無法打開,白名單的上限為50個。上文中我們可以獲取APP的Bundle Id
,依靠Bundle Id
使用私有API可以打開其他APP,并沒有數量限制。
Class LSAppClass = NSClassFromString(@"LSApplicationWorkspace");id workSpace = [(id)LSAppClass performSelector:@selector(defaultWorkspace)];[workSpace performSelector:@selector(openApplicationWithBundleID:) withObject:self.appsObj.bundleId];