iOS SDK开发指南

本文档介绍了阿里云公共DNS iOS SDK的接入和开发方式。

1.概述

阿里云公共DNS SDK是阿里云面向广大移动开发者提供DNS域名解析服务的开发工具包。

开发者利用本SDK,可以在自己的iOS APP中轻松接入阿里云公共DNS,解决域名解析异常的问题,低成本实现域名解析精准调度。您可以参考Demo示例工程源码了解如何使用本SDK。

iOS 14 开始系统原生支持两种标准规范的 Encrypted DNS,分别是 DNS over TLS 与 DNS over HTTPS,您可以参考iOS14原生加密DNS方案了解如何设置阿里云公共DNS为加密DNS默认解析器。

SDK当前版本封装了阿里公共DNSDoH JSON API,提供接口函数给iOS APP进行域名解析,并且提供了基于TTLLRU策略的高效域名缓存功能。在公共DNS原有功能的基础上,SDK还可以为用户带来以下优势:

  • 简单易用:

    用户仅需集成我们提供的SDK,便可接入阿里云公共DNS业务。接入方法简单易用,为用户提供更为轻松便捷的解析服务。

  • 零延迟:

    SDK内部实现了LRU的缓存机制,将每次域名解析后的IP缓存到本地;并且主动更新TTL过期缓存,保证缓存及时有效,从而帮助用户达到域名解析零延迟的效果。

2.SDK集成

移动解析阿里公共DNS提供以下两种集成方式供iOS 开发者选择:

  • 通过 CocoaPods 集成。

  • 手动集成。

2.1 通过CocoaPods集成

1.Podfile中指定仓库位置:(Master仓库不要遗漏)

source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/aliyun/aliyun-specs.git'

2.为工程target添加依赖:

pod 'AlicloudPDNS'

2.2 手动集成

  1. 通过控制台的链接获取阿里公共DNS iOS SDK

  2. 获取到SDKpdns-sdk-ios.framework后,手动集成到自己工程中。

  3. 引入系统库:

    • Foundation.framework

    • SystemConfiguration.framework

    • CoreFoundation.framework

    • CoreTelephony.framework

  4. 在工程Build Settings中的Other linker flag 里加入 -ObjC 标志

2.3 SDK初始化

您需先在控制台注册自己的应用,获取应用的唯一标识和鉴权参数、,SDK集成之后进行SDK初始化

重要

为了用户更好的使用SDK,避免出现解析不到IP地址的情况,请将SDK初始化的时间尽量提前。

application:didFinishLaunchingWithOptions:初始化SDK。

DNSResolver *resolver = [DNSResolver share];
//setAccountId:@"******":星号内容填您在控制台“接入配置”页面展示的 Account ID
//andAccessKeyId:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey ID
//andAccesskeySecret:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey Secret
[resolver setAccountId:@"******" andAccessKeyId:@"********" andAccesskeySecret:@"********"];
//指定域名自动更新过期缓存,当前限制数组中最多包含10个域名
[resolver setKeepAliveDomains:@[@"用户指定域名1",@"用户指定域名2"]];
//对后续可能要解析的域名进行预加载
[resolver preloadDomains:@[@"域名1", @"域名2", @"域名3"] complete:^{
    //所有域名预加载完成
       
}];

3.API介绍

3.1 常用设置

3.1.1 Account ID和鉴权

必传参数,您在控制台注册自己的应用后,控制台会为此应用生成唯一标识Account ID,鉴权功能来保障用户身份安全,防止被第三方未授权者盗用。用户请参考产品鉴权文档在控制台创建AccessKey,并在APP中通过如下代码设置:

//setAccountId:@"******":星号内容填您在控制台“接入配置”页面展示的 Account ID
//andAccessKeyId:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey ID
//andAccesskeySecret:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey Secret
[[DNSResolver share] setAccountId:@"******" andAccessKeyId:@"********" andAccesskeySecret:@"********"];

3.1.2 解析协议设置

SDK支持设置DNS解析请求协议类型,可自主选择通过HTTPHTTPS协议解析,具体可通过scheme属性进行设置。

SDK默认并推荐使用HTTPS协议进行解析,因为HTTPS协议安全性更好。公共DNS的计费项是按HTTP的解析次数进行收费,其中HTTPS是按5HTTP流量进行计费,开发者可以根据自身实际业务需要选择scheme类型。如下设置:

[DNSResolver share].scheme = DNSResolverSchemeHttps;

3.1.3 设置是否开启缓存

SDK可设置开启缓存功能,如果缓存功能开启,在第一次解析过域名后,后续再解析时,优先获取缓存中的数据,可大大提高解析速度。

SDK默认开启缓存。若要设置不开启缓存,则需要通过以下代码设置:

[DNSResolver share].cacheEnable=NO;

3.1.4 设置域名缓存保持

SDK在缓存功能已开启的情况下,可设置针对某些域名开启缓存保持功能,如果该功能开启,SDK会自动更新这些域名的过期缓存,保障用户缓存数据及时更新,但是可能会带来域名解析次数和客户端流量消耗的增多。如果不设置该功能,那么SDK不会自动进行过期缓存更新,只有当用户调用解析方法时,才会再次进行缓存更新。若要设置某些域名的缓存保持需要通过以下代码设置:

//当前限制数组中最多包含10个域名
[[DNSResolver share] setKeepAliveDomains:@[@"www.taobao.com",@"www.aliyun.com"]];
说明

优势

1. 可及时(ttl过期前)更新记录

2. 配合预加载可降低首次解析延迟(0ms)

劣势

1. 根据ttl *75% 作为重新请求的时机,会带来额外的费用

3.1.5 预解析

由于SDK可以设置开启缓存功能,在第一次解析完域名产生缓存后,后续再次解析此域名时解析速度可提升至0时延。所以建议在app启动后,对app中可能要解析的域名进行预解析。

代码示例:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   // Override point for customization after application launch.
   
   DNSResolver *resolver = [DNSResolver share];
   //setAccountId:@"******":星号内容填您在控制台“接入配置”页面展示的 Account ID
   //andAccessKeyId:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey ID
   //andAccesskeySecret:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey Secret
   [resolver setAccountId:@"******" andAccessKeyId:@"********" andAccesskeySecret:@"********"];
   resolver.cacheEnable = YES;
   //对后续可能要解析的域名进行预加载
   [resolver preloadDomains:@[@"域名1", @"域名2", @"域名3"] complete:^{
       //所有域名预加载完成
       
   }];
   
   return YES;
}

3.2 其它高级设置

3.2.1 设置是否使用服务端IPV6地址

阿里云公共DNS服务支持IPv4、IPv6双栈访问。SDK默认使用IPv4地址访问DNS服务器进行解析。

如果需要通过IPv6地址访问DNS服务器进行解析(当前网络需要支持IPv6),则需要通过以下代码设置:

[DNSResolver share].ipv6Enable = YES;

3.2.2 short模式

阿里公共DNSDoH JSON API返回数据分为全量JSON和简要IP数组格式。SDK默认为全量JSON格式。

若要设置为简要IP数组格式,则需要通过以下代码设置:

[DNSResolver share].shortEnable = YES;

3.2.3 设置缓存数量

SDK若开启了缓存功能,可以自定义缓存数量(支持范围100~500之间)。

SDK默认的缓存数量为100个域名。若要自定义缓存数量,可通过cacheCountLimit属性进行设置:

[DNSResolver share].cacheCountLimit = 200;

3.2.4 设置是否开启IP测速

SDK可设置开启IP测速,如果IP测速开启,解析结果会优先返回速度最快的解析结果IP地址,数组会按照测速结果由快到慢的顺序排列。

SDK默认没有开启IP测速。若要设置开启IP测速,则需要通过以下代码设置:

[DNSResolver share].speedTestEnable=YES;

3.2.5 设置IP测速方式

SDK可设置IP测速的方式,如果IP测速开启,并且设置该参数为0,则使用ICMP探测;如果设置该参数为80、443或其它支持的端口号,则为socket指定端口探测。

SDK默认该参数为0。若要设置socket指定端口探测,则需要通过以下代码设置:

[DNSResolver share].speedPort = 80;

3.2.6 设置是否开启依据ISP网络区分域名缓存

SDK可设置是否开启依据ISP网络区分域名缓存。如果开启,则在不同网络环境下域名缓存数据分别存储互不影响。如果不开启,则不同网络下使用同一份域名缓存数据。

SDK默认没有开启依据ISP网络区分域名缓存功能,若要开启该功能,则需要通过以下代码设置:

[DNSResolver share].ispEnable = YES;

3.2.7设置否定缓存最大TTL时间

SDK可设置否定缓存(域名没有配置对应的IP地址,导致解析结果没有IP返回的无效缓存)的最大TTL时间,如果设置了该时间,则会限制否定缓存的最大TTL不超过该设置时间。

SDK默认该参数为30s。若要设置否定缓存最大TTL时间,则需要通过以下代码设置:

[DNSResolver share].maxNegativeCache = 30;

3.2.8 设置缓存最大TTL时间

SDK可设置缓存的最大TTL时间,如果设置了该时间,则会限制缓存的最大TTL不超过该设置时间。

SDK默认该参数为3600s。若要设置缓存最大TTL时间,则需要通过以下代码设置:

[DNSResolver share].maxCacheTTL= 3600;

3.2.9 设置是否开启缓存永不过期

[DNSResolver share].immutableCacheEnable = NO;
重要

用户可以设置缓存永不过期功能(true为开启缓存永不过期功能,false为关闭缓存永不过期功能),SDK内部共有三种缓存更新机制详细描述请查看 阿里公共DNS功能问题汇总

3.2.10 timeout

timeout属性为域名解析的超时时间。默认超时时间为3s,用户可自定义超时时间,建议设置在2~5s之间。

4.服务API

代码示例:

/// 预解析域名信息,可在程序启动时调用,解析结果会存入缓存,加快后续域名解析速度
/// 自动感知网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)解析得到适用于当前网络环境的ip
/// @param domainArray  域名数组
/// @param complete     解析完成后回调
- (void)preloadDomains:(NSArray<NSString *> *)domainArray complete:(void(^)(void))complete;

/// 获取域名解析后的ip数组,自动感知网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)得到适用于当前网络环境的ip,
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain           域名
/// @param complete       回调(所有ip地址)
- (void)getIpsDataWithDomain:(NSString *)domain complete:(void(^)(NSArray<NSString *> *dataArray))complete;

/// 自动感知网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)直接从缓存中获取适用于当前网络环境的ip数组,无需等待.  
/// 如无缓存,则返回 nil;如果有缓存并且enable设为YES,则会返回缓存数据给用户,若缓存数据过期则异步解析域名更新缓存数据;如果有缓存并且enable设为NO,那么当缓存过期时会返回nil给用户并异步解析域名更新缓存数据。
/// @param domain   域名
/// @param enable   是否允许返回过期ip
- (NSArray<NSString *> *)getIpsByCacheWithDomain:(NSString *)domain andExpiredIPEnabled:(BOOL)enable;

/// 获取域名解析后的IPv4信息数组
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain           域名
/// @param complete       回调(所有域名信息)
- (void)getIpv4InfoWithDomain:(NSString *)domain complete:(void(^)(NSArray<DNSDomainInfo *> *domainInfoArray))complete;

/// 获取域名解析后的IPv6信息数组
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain           域名
/// @param complete       回调(所有域名信息)
- (void)getIpv6InfoWithDomain:(NSString *)domain complete:(void(^)(NSArray<DNSDomainInfo *> *domainInfoArray))complete;

/// 获取域名解析后的IPv4信息
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain           域名
/// @param complete       回调(所有域名信息中随机一个)
- (void)getRandomIpv4InfoWithDomain:(NSString *)domain complete:(void(^)(DNSDomainInfo *domainInfo))complete;

/// 获取域名解析后的IPv6信息
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain           域名
/// @param complete       回调(所有域名信息中随机一个)
- (void)getRandomIpv6InfoWithDomain:(NSString *)domain complete:(void(^)(DNSDomainInfo *domainInfo))complete;

/// 获取域名解析后的IPv4地址数组
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain           域名
/// @param complete       回调(所有ip地址)
- (void)getIpv4DataWithDomain:(NSString *)domain complete:(void(^)(NSArray<NSString *> *dataArray))complete;

/// 获取域名解析后的IPv6地址数组
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain           域名
/// @param complete       回调(所有ip地址)
- (void)getIpv6DataWithDomain:(NSString *)domain complete:(void(^)(NSArray<NSString *> *dataArray))complete;

/// 获取域名解析后的IPv4地址
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain            域名
/// @param complete        回调(所有ip地址中随机一个)
- (void)getRandomIpv4DataWithDomain:(NSString *)domain complete:(void(^)(NSString *data))complete;

/// 获取域名解析后的IPv6地址
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain            域名
/// @param complete        回调(所有ip地址中随机一个)
- (void)getRandomIpv6DataWithDomain:(NSString *)domain complete:(void(^)(NSString *data))complete;

/// 预解析域名IPv4信息,可在程序启动时调用,将解析结果存入缓存,加快后续域名解析速度
/// @param domainArray  域名数组
/// @param complete         解析完成后回调
- (void)preloadIpv4Domains:(NSArray<NSString *> *)domainArray complete:(void(^)(void))complete;

/// 预解析域名IPv6信息,可在程序启动时调用,将解析结果存入缓存,加快后续域名解析速度
/// @param domainArray  域名数组
/// @param complete         解析完成后回调
- (void)preloadIpv6Domains:(NSArray<NSString *> *)domainArray complete:(void(^)(void))complete;

/// 直接从缓存中获取ipv4解析结果,无需等待.  
/// 如无缓存,则返回 nil;如果有缓存并且enable设为YES,则会返回缓存数据给用户,若缓存数据过期则异步解析域名更新缓存数据;如果有缓存并且enable设为NO,那么当缓存过期时会返回nil给用户并异步解析域名更新缓存数据。
/// @param domain   域名
/// @param enable   是否允许返回过期ip
- (NSArray<NSString *> *)getIpv4ByCacheWithDomain:(NSString *)domain andExpiredIPEnabled:(BOOL)enable;

/// 直接从缓存中获取ipv6解析结果,无需等待.  
/// 如无缓存,则返回 nil;如果有缓存并且enable设为YES,则会返回缓存数据给用户,若缓存数据过期则异步解析域名更新缓存数据;如果有缓存并且enable设为NO,那么当缓存过期时会返回nil给用户并异步解析域名更新缓存数据。
/// @param domain   域名
/// @param enable   是否允许返回过期ip
- (NSArray<NSString *> *)getIpv6ByCacheWithDomain:(NSString *)domain andExpiredIPEnabled:(BOOL)enable;

///统计信息收集
-(NSArray *)getRequestReportInfo;

5. API使用示例

5.1 设置基本信息

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   // Override point for customization after application launch.
   //唯一初始化方式
   DNSResolver *resolver = [DNSResolver share];
   //setAccountId:@"******":星号内容填您在控制台“接入配置”页面展示的 Account ID
   //andAccessKeyId:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey ID
   //andAccesskeySecret:@"********":星号内容填您在控制台“接入配置”创建的密钥的 AccessKey Secret
   [resolver setAccountId:@"******" andAccessKeyId:@"********" andAccesskeySecret:@"********"];
   //指定域名自动更新过期缓存,当前限制数组中最多包含10个域名
	 [resolver setKeepAliveDomains:@[@"用户指定域名1",@"用户指定域名2"]];
   //对后续可能要解析的域名进行预加载,可提前获取解析结果并存入缓存
   [resolver preloadDomains:@[@"域名1", @"域名2", @"域名3"] complete:^{
       //所有域名预加载完成
       
   }];
   return YES;
}

5.2 域名解析接口

SDK提供了多种域名解析方法供用户选择和使用,可在DNSResolver.h头文件中查看。 下面以其中一个自动区分网络环境(IPv4-only、IPv6-only、IPv4IPv6双栈)的解析方法为例,进行说明。

接口声明:

/// 获取域名解析后的ip数组,自动区分网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)得到适用于当前网络环境的ip
/// 如果开启了缓存开关,那么优先获取缓存中的数据返回,如果没有缓存或者缓存过期,那么会通过网络请求获取对应的IP返回;如果没有开启缓存开关,那么直接通过网络请求获取对应的IP返回。
/// @param domain           域名
/// @param complete       回调(所有ip地址)
- (void)getIpsDataWithDomain:(NSString *)domain complete:(void(^)(NSArray<NSString *> *dataArray))complete;

接口调用示例:

[[DNSResolver share] getIpsDataWithDomain:@"www.taobao.com" complete:^(NSArray<NSString *> *dataArray) {
    //dataArray为域名www.taobao.com所对应的IP地址数组
   if (dataArray.count > 0) {
       //TODO: 使用IP地址进行url连接
   }    
}];

5.3 直接从缓存中获取解析结果

接口声明:

/// 自动区分网络环境(ipv4-only、ipv6-only、ipv4和ipv6双栈)直接从缓存中获取适用于当前网络环境的ip数组,无需等待.  
/// 如无缓存,则返回 nil;如果有缓存并且enable设为YES,则会返回缓存数据给用户,若缓存数据过期则异步解析域名更新缓存数据;如果有缓存并且enable设为NO,那么当缓存过期时会返回nil给用户并异步解析域名更新缓存数据。
/// @param domain   域名
/// @param enable   是否允许返回过期ip
- (NSArray<NSString *> *)getIpsByCacheWithDomain:(NSString *)domain andExpiredIPEnabled:(BOOL)enable;

调用示例:

NSArray *result = [[DNSResolver share] getIpsByCacheWithDomain:@"域名" andExpiredIPEnabled:YES];
//拿到缓存结果
if (result.count > 0) {
   //TODO: 使用ip地址进行url连接
   
}

注意:直接从缓存中获取缓存结果,速度较快,但无缓存、或者有缓存但缓存的解析结果已过期并且enableNO时,返回结果会为nil。

5.4 删除缓存

接口声明:

///hostArray为需要清除的host域名数组。如果需要清空全部数据,传nil或者空数组即可
-(void)clearHostCache:(NSArray <NSString *>*)hostArray;

调用示例:

[[DNSResolver share] clearHostCache:@[@"域名1", @"域名2"]];

5.5 统计信息收集

接口声明:

///统计信息收集
-(NSArray *)getRequestReportInfo;

调用示例:

NSArray *array = [[DNSResolver share] getRequestReportInfo];

数据格式:

 (
      {
         avgRtt = "1";                                // 域名平均解析时间ms
         cacheDnsNum = 0;                             // 命中缓存次数                       
         domain = "www.taobao.com";                   // 解析的域名
         gobackLocaldnsNum = 0;                       // 降级到LocalDNS的次数
         localErro = 0;                               // LocalDNS解析失败次数
         maxRtt = "60";                               // 域名最大解析时间ms
         noPermissionErro = 0;                        // 用户鉴权失败次数
         noResponseErro = 0;                          // 请求超时无应答次数          
         requestPDnsNum = 1;                          // 递归查询的次数
         sp = 中国移动;                                // 运营商名字
         successNum = 1;                              // 解析成功次数
         timeoutErro = 0;                             // 网络超时错误次数
         type = 28;                                   // ip类型1代表ipv4、28代表ipv6
         urlParameterErro = 0;                        // 请求参数格式错误次数
         urlPathErro = 0;                             // URL错误次数
      }
         ......
 );

6.注意事项

  1. pdns-sdk-ios.framework支持最低版本为iOS9.0。

  2. 使用HTTP协议请求时,需要在Info.plist中设置App Transport Security Settings->Allow Arbitrary LoadsYES

  3. 通过阿里公共DNS获得域名的IP地址后,客户端可以使用这个IP发送业务请求,HTTP请求头的Host字段需改为原来的域名。

    例如:

    //ip为原域名解析后的ip地址
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", ip]];
    NSMutableURLRequest *mutableReq = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval: 10];
    //设置host
    [mutableReq setValue:@"原域名" forHTTPHeaderField:@"host"];
  4. 为了保证用户业务正常,当通过SDK没有获取到域名的IP时,需要开发者使用原域名的地址进行请求来作为兜底,示例代码如下:

    NSArray *array = [[DNSResolver share] getIpsByCacheWithDomain:@"原域名" andExpiredIPEnabled:YES];
    NSString *ip = array.firstObject;
    if (ip.length > 0) {
        //替换url中的hostip进行接口请求
        
    }else{
        //添加兜底处理(使用原url进行接口请求)
    }
  5. 当存在中间HTTP代理时,客户端发起的请求中请求行会使用绝对路径的URL,在您开启公共DNS并采用IP URL进行访问时,中间代理将识别您的IP信息并将其作为真实访问的HOST信息传递给目标服务器,这时目标服务器将无法处理这类无真实HOST信息的HTTP请求。我们建议您检测当前设备是否开启了网络代理,然后在代理模式下不使用公共DNS进行域名解析。

  6. 为帮助用户更快的使用阿里公共DNS iOS SDK,我们为开发者提供Demo程序,您可以

    下载到本地作为参考。请点击这里,下载Demo程序。