iOS14苹果添加本地网络权限
使用苹果的bonjour技术在iOS14开始被检测,并且会弹窗访问权限弹窗,很鸡肋,即便点击拒绝也是可以正常访问本地网络的,一旦苹果检测到NSNetService相关api,就会在app的设置界面出现一个选项叫做本地网络,很难受,GCDWebServer使用套接字实现,直接访问CFNetwork层,可以跳过这个权限的检测
下载链接
原理
GCDWebServer负责启动本地server,地址:@"http://127.0.0.1:6123",端口号随意写一个,只要避开主流协议端口如443、8080就行,避免端口被占用导致的server启动失败,然后通过openURL的方式访问本地服务,会跳转到浏览器,此时浏览器地址栏中的地址就是上面的地址,会访问到我们写的html,书签存储的就是地址栏中的内容,因此需要重定向修改地址栏中的内容,iOS14系统添加了本地网络权限的同时Safari禁止了以h5标签的形式重定向,呵呵,怎么办,只有由服务器直接重定向了,以@"data:text/html;charset=UTF-8"开头,后面拼接html存储到沙盒,然后重定向访问fileURL,这样地址栏中的地址就变成我们需要的地址了
流程
- 创建服务目录
- (instancetype)init {
if (self = [super init]) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = paths[0];
self.webRootDir = [documentsPath stringByAppendingPathComponent:@"server/web"];
BOOL isDirectory = YES;
BOOL exsit = [[NSFileManager defaultManager] fileExistsAtPath:_webRootDir isDirectory:&isDirectory];
if(!exsit){
[[NSFileManager defaultManager] createDirectoryAtPath:_webRootDir withIntermediateDirectories:YES attributes:nil error:nil];
}
return self;
}
+ (NSString *)webRedirectPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = paths[0];
NSString *webRootDir = [documentsPath stringByAppendingPathComponent:@"server/web"];
BOOL isDirectory = YES;
BOOL exsit = [[NSFileManager defaultManager] fileExistsAtPath:webRootDir isDirectory:&isDirectory];
if(!exsit){
[[NSFileManager defaultManager] createDirectoryAtPath:webRootDir withIntermediateDirectories:YES attributes:nil error:nil];
}
return [NSString stringWithFormat:@"%@/server/web/redirectPath",documentsPath];
}
- 把index.html写入目录
这里有个注意点显示的html与写入文件html有一点不同,写入到本地的html需要在显示的内容前面拼接@"data:text/html;charset=UTF-8"
+ (NSString *)createRedirectHtmlWithModel:(BookmarkModel *)model {
NSString *title = model.title;
NSString *urlScheme = model.urlScheme;
NSString *moduleID = model.moduleID;
NSString *imageName = model.imageName;
NSMutableString *taragerUrl = [NSMutableString stringWithFormat:@"%@ ",title];
NSString *htmlUrlScheme = [NSString stringWithFormat:@"",@"10000",moduleID,@"1",@(1)];
UIImage *image = [UIImage imageNamed:imageName];
NSData *imageData = UIImagePNGRepresentation(image);
NSString *base6ImageStr = [imageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];
NSString *imageUrlStr = [NSString stringWithFormat:@"";
NSString *lastHtmlStr = [NSString stringWithFormat:@"document.getElementsByTagName(\"head\")[0].appendChild(addObj); document.getElementById(\"msg\").innerHTML=' 添加快捷方式到主屏幕 ';}%@", base6ImageStr, base6BubbleImageStr,orientationchangeJS];
[taragerUrl appendString:htmlUrlScheme];
[taragerUrl appendString:dataUrlStr];
NSString *dataUrlEncode = [[NSString stringWithFormat:@"data:text/html;charset=UTF-8,%@", taragerUrl] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *imageUrlEncode = [imageUrlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *lastHtmlStrEncode = [lastHtmlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSData *data = [[NSString stringWithFormat:@"%@%@%@",dataUrlEncode,imageUrlEncode,lastHtmlStrEncode] dataUsingEncoding:NSUTF8StringEncoding];
[data writeToFile:[DesktopBookmark webRedirectPath] atomically:YES];
NSString *finalHtml = [NSString stringWithFormat:@"%@%@%@",taragerUrl,imageUrlStr,lastHtmlStr];
return finalHtml;
}
- 前后台事件监听
GCDWebServer这里有个问题,默认内部监听了前后台事件,而且后台直接关闭了服务,可以配置options参数,保持服务后台运行,如果想自己控制,需要把内部的通知移除,[[NSNotificationCenter defaultCenter] removeObserver:_webServer],然后自己控制,如下,后台延时两秒关闭保证服务能被访问到,进入前台如果服务还在运行,直接关闭
- (void)applicationDidBecomeActive {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
if (self.webServer.isRunning) {
[self.webServer stop];
self.webServer = nil;
}
});
}
- (void)applicationDidEnterBackground {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
[self.webServer stop];
self.webServer = nil;
});
}
- 服务生命周期
由于服务需要持续运行,_webServer对象直到服务关闭才能允许被销毁,否则会异常,因此可以写一个单例来管理server
+ (instancetype)sharedInstance {
static DesktopBookmark *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- 重写server的response,把我们写好的html以字符串的形式配置进去
NSString *html = [DesktopBookmark createRedirectHtmlWithModel:model];
self.webServer = [[GCDWebServer alloc] init];
[_webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {
return [GCDWebServerDataResponse responseWithHTML:html];
}];
- 配置response的重定向地址
[_webServer addHandlerForMethod:@"GET" path:@"/" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {
NSError *error;
NSString *redirectPath = [[NSString alloc] initWithContentsOfFile:[DesktopBookmark webRedirectPath] encoding:NSUTF8StringEncoding error:&error];
NSURL *url = [NSURL URLWithString:redirectPath];
return [GCDWebServerResponse responseWithRedirect:url permanent:NO];
}];
- 启动server
注意这里要用固定的端口号和bonjourName为nil,以成功绕过苹果的本地网络权限检测
[_webServer startWithPort:6123 bonjourName:nil];
- 跳转到浏览器
NSString *urlStrWithPort = @"http://127.0.0.1:6123";
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStrWithPort]];