IOS14本地网络网络权限检测

8,823 阅读5分钟

本地网络

因为最近在搞IOS的基于MultipeerConnectivit的文件传输APP,在开发过程中发现,如果本地网络权限没有打开,那么就无法搜索到周围的设备。而自身设备也无法被其他人搜索到。

image.png

经查阅资料发现MultipeerConnectivit是基于Bonjour来做搜索服务的,而如果您的应用程序使用 Bonjour 或其他本地网络协议与设备交互,您必须在 iOS 14 中添加对本地网络隐私权限的支持。

检测方法

但是苹果又没有提供相关的权限检测方案,只能自己硬着头皮去开发者文档中寻找。因为是bonjour中会使用到本地网络,所以我就寻思着能不能在bonjour相关的框架下去寻找本地网络是否可以使用的判断依据。

image.png

经过不懈的努力(疯狂的google)终于在dnssd这个框架中找到了bonjour关于本地网络的一些判断。

image.png

对没有错就是这个错误码代表了本地网络权限关闭。下面让我们来看看如何来获取到这个错误码

导入框架

首先我尝试去直接添加这个dnssd库,然而并不能找到😶 image.png

最后发现dnssd居然在Network库中,太尼玛坑了有木有。

image.png

DNSServiceBrowse

浏览附近可以使用的bonjour服务,是一个C函数


DNSServiceErrorType DNSServiceBrowse(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, const char *regtype, const char *domain, DNSServiceBrowseReply callBack, void *context);

参数

sdRef

传入一个向未初始化的 DNSServiceRef 的指针,如果调用成功,那么它会初始化 DNSServiceRef,如果创建成功DNSServiceBrowse函数会返回一个kDNSServiceErr_NoError,代表DNSService服务创建成功,该DNSService服务将无限期地运行,直到客户端通过将此 DNSServiceRef 传递给 DNSServiceRefDeallocate 来终止它。

flags

flag标记,一般填0

interfaceIndex

指定浏览服务的接口,如果需要浏览所有可用接口则传入0

regtype

浏览服务的类型,后面跟协议,用点分隔(例如“_ftp._tcp”)。 传输协议必须是“_tcp”或“_udp”。 客户端可以选择指定单个子类型来执行过滤浏览:例如 浏览“_primarytype._tcp,_subtype”只会发现那些在注册子类型列表中指定“_subtype”的注册“_primarytype._tcp”实例。 此外,还可以在子类型之前指定组标识符,例如 _primarytype._tcp:GroupID,它将仅发现使用 GroupID 注册服务的成员。

注意服务需要在info.plist里面注册一次

<Bonjour services>
    <item 0>_yourServiceName._tcp</item 0>
</Bonjour services>

domain

指定需要浏览服务的域,如果不指定则使用默认域

callBack

也就是回调函数,是DNSServiceBrowseReply类型的函数

DNSServiceBrowseReply


typedef void (*DNSServiceBrowseReply)(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context);

typedef的功能是定义新的类型

简化下结构

typedef void(*Fun) (void)

定义一个新的类型Fun 代表一个函数指针,指向的是一个返回值为void,参数为void的函数


#include<stdio.h>

typedef char (*PTRFUN)(int) ;

PTRFUN pfun

char glFun(int a)

{

return char(a);

}

int main()

{

pFun = glFun;

printf("%c\n",(*pFun)(2));

printf("%c\n",pFun(2));

return 0;

}

由此可以类推出,DNSServiceBrowseReply serviceRef是一个指向参数为(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) void类型的函数的函数指针。

定义一个静态函数browseReply,让其返回值和参数满足DNSServiceBrowseReply所需要的格式,


static void browseReply( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context )

{

if (errorCode == kDNSServiceErr_PolicyDenied) {

//本地网络权限未开启

}

else {

//本地网络权限已开启

}

}

然后在object-c 方法中通过函数名来获取其函数指针

context

传递给回调函数的应用程序上下文指针,一般情况下传null

DNSServiceProcessResult

从守护进程中读取服务的回复,也就是获取结果,

在收到守护程序的响应之前,此调用将阻塞。

如果在启动的时候,也就是appdelegate中调用DNSServiceProcessResult来查询服务,一定不要再主线程执行,一定不要在主线程执行,一定不要在主线程执行,不然会导致app卡在启动阶段。

如果没有使用DNSServiceProcessResult,向守护进程查询服务,那么在DNSServiceBrowse中,传入的callback回调函数则不会执行,不会返回结果。

参数

DNSServiceRef,需要传入的服务。由第一步DNSServiceBrowse中初始化的服务。

返回值

成功时返回 kDNSServiceErr_NoError,否则返回指示发生的特定失败的错误代码。

DNSServiceRefDeallocate

终止与守护程序的连接并释放与 DNSServiceRef 关联的内存。也就是说释放掉DNSServiceRef所占的资源。避免内存泄漏

调用这个方法之后DNSServiceBrowse会立即停止搜索。

参数

传入需要释放的DNSServiceRef,通常是我们在DNSServiceBrowse函数中初始化完成的DNSServiceRef

完整例子


+ (void) requestLocalNetworkAuthorization:(void(^)(BOOL isAuth)) complete {

    result = complete;

    if(@available(iOS 14, *)) {

        //IOS14需要进行本地网络授权

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            const char* strc = [[AMNearbyServicesManager.sharedManager bonjourServicesName] UTF8String];

            DNSServiceRef serviceRef = nil;

            DNSServiceBrowse( &serviceRef, 0, 0, strc, nil, browseReply, nil);

            DNSServiceProcessResult(serviceRef);

            DNSServiceRefDeallocate(serviceRef);
        });
    }
    else {
       //IOS14以下默认返回yes,因为IOS14以下设备默认开启本地网络权限
        complete(YES);
    }

}

///函数回调授权结果
static void browseReply( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context )

{
    //主线程返回获取结果
    dispatch_async(dispatch_get_main_queue(), ^{

        if (result) {

            if (errorCode == kDNSServiceErr_PolicyDenied) {

                result(NO);

            }
            else {
                result(YES);
            }

        }

    });
}

注意browseReply是一个普通的C函数,碰巧是在您的.m文件中定义的,但实际上并不是对象(类)的“一部分”。它恰好卡在同一文件中。

也就是说在browseReply中,无法使用self来访问类中的变量与方法。因为C语言函数压根不知道对象的存在。

所以这里,我们通过单例对象中的属性或方法或者类中的静态属性和静态方法来获取browseReply回调函数中的结果。

Demo

如果觉得有用,可以给我点个star哦

Demo