SDWebImage加载图片报Error code=-1003/Err(-65554)问题求助
你遇到的这个DNS解析失败问题在iOS网络请求里挺常见的,尤其是在WiFi网络不稳定或者DNS缓存异常的场景下。下面给你几个适合上线环境的解决方案,不用依赖VPN或者手动切换网络:
1. 利用SDWebImage自带的重试机制优化
SDWebImage其实有内置的失败重试能力,只是默认没开启或者重试次数不够。你可以通过两个方式配置:
方式一:全局设置下载器重试次数
在App启动的时候(比如application:didFinishLaunchingWithOptions:里)配置全局的下载器重试次数,这样所有图片请求都会生效:
SDWebImageDownloader *sharedDownloader = [SDWebImageDownloader sharedDownloader]; sharedDownloader.maxRetryCount = 3; // 设置失败后重试3次,你可以根据需求调整
方式二:单个请求启用重试选项
在加载图片的代码里,把SDWebImageRetryFailed选项加入到options中,这样这个请求失败时会自动重试:
[cell.i_imageView sd_setImageWithURL:[NSURL URLWithString:item.imageUrl] placeholderImage:[UIImage imageNamed:@"no-image"] options:SDWebImageRefreshCached | SDWebImageRetryFailed completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { if(!image) NSLog(@"image not loaded %@", error.description); }];
这个选项会让SDWebImage在遇到DNS错误这类网络失败时自动重试,配合全局的重试次数设置,能大大降低加载失败的概率。
2. 自定义NSURLSessionConfiguration优化网络请求
iOS 11之后,NSURLSessionConfiguration新增了waitsForConnectivity属性,它能让请求在网络不可用时等待,而不是立刻返回失败。同时你还可以延长超时时间,给DNS解析更多时间:
// 创建自定义的Session配置 NSURLSessionConfiguration *customConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; customConfig.timeoutIntervalForResource = 30; // 把资源超时时间设为30秒,默认是60秒,你可以按需调整 if (@available(iOS 11.0, *)) { customConfig.waitsForConnectivity = YES; // 开启等待网络连接的能力 } // 用这个配置初始化SDWebImageManager SDWebImageManager *customManager = [[SDWebImageManager alloc] initWithCache:[SDImageCache sharedImageCache] downloader:[[SDWebImageDownloader alloc] initWithSessionConfiguration:customConfig]]; // 使用自定义manager加载图片 [customManager loadImageWithURL:[NSURL URLWithString:item.imageUrl] options:SDWebImageRefreshCached progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (image) { cell.i_imageView.image = image; } else { NSLog(@"image not loaded %@", error.description); } }];
这个配置的好处是,当WiFi网络DNS解析失败但网络还在连接过程中,或者临时断网后恢复,请求会自动继续,不需要你自己写重试逻辑。
3. 针对特定URL强制使用蜂窝网络(iOS 13+)
如果你的场景中,蜂窝网络的DNS解析更可靠,可以针对失败的请求切换到蜂窝网络的Session来重试。iOS 13之后,NSURLSessionConfiguration提供了控制网络类型的属性:
// 先监听当前网络状态,或者在请求失败后判断错误类型 [cell.i_imageView sd_setImageWithURL:[NSURL URLWithString:item.imageUrl] placeholderImage:[UIImage imageNamed:@"no-image"] options:SDWebImageRefreshCached completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { if (!image && error.code == -1003) { // 确认是DNS错误 // 创建仅允许蜂窝网络的Session配置 NSURLSessionConfiguration *cellularConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; cellularConfig.allowsCellularAccess = YES; cellularConfig.allowsExpensiveNetworkAccess = YES; // 允许使用蜂窝这类"昂贵"网络 cellularConfig.allowsConstrainedNetworkAccess = YES; // 如果系统开启低数据模式也允许使用 // 用这个配置创建下载器并重试请求 SDWebImageDownloader *cellularDownloader = [[SDWebImageDownloader alloc] initWithSessionConfiguration:cellularConfig]; [cellularDownloader downloadImageWithURL:[NSURL URLWithString:item.imageUrl] options:SDWebImageRefreshCached progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) { if (image) { cell.i_imageView.image = image; } }]; } }];
这个方法需要注意用户的蜂窝数据权限,不过一般App默认都有蜂窝数据权限,而且只有在WiFi请求失败时才会触发,不会影响正常场景。
4. 自定义DNS解析绕过系统DNS
如果上述方法都无法解决,你可以自己做DNS解析,拿到域名对应的IP后,直接用IP构造请求并设置Host头,绕过系统的DNS解析:
// 异步解析域名(避免阻塞主线程) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *domain = [NSURL URLWithString:item.imageUrl].host; struct addrinfo hints = {0}, *res; hints.ai_family = AF_UNSPEC; // 支持IPv4和IPv6 hints.ai_socktype = SOCK_STREAM; int result = getaddrinfo([domain UTF8String], NULL, &hints, &res); if (result == 0 && res) { char ipBuffer[INET6_ADDRSTRLEN]; // 提取IP地址 if (res->ai_family == AF_INET) { struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr; inet_ntop(AF_INET, &ipv4->sin_addr, ipBuffer, sizeof(ipBuffer)); } else { struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr; inet_ntop(AF_INET6, &ipv6->sin6_addr, ipBuffer, sizeof(ipBuffer)); } freeaddrinfo(res); NSString *ipStr = [NSString stringWithUTF8String:ipBuffer]; // 构造用IP替换域名的URL NSString *ipBasedUrlStr = [item.imageUrl stringByReplacingOccurrencesOfString:domain withString:ipStr]; NSURL *ipURL = [NSURL URLWithString:ipBasedUrlStr]; // 构造请求并设置Host头(必须,否则服务器无法识别域名) NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:ipURL]; [request setValue:domain forHTTPHeaderField:@"Host"]; // 回到主线程加载图片 dispatch_async(dispatch_get_main_queue(), ^{ [cell.i_imageView sd_setImageWithURLRequest:request placeholderImage:[UIImage imageNamed:@"no-image"] options:SDWebImageRefreshCached completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { if (!image) NSLog(@"image not loaded %@", error.description); }]; }); } });
这个方法需要处理DNS解析的异常情况,比如解析失败的场景,但能彻底绕过系统DNS的问题,适合DNS解析问题比较顽固的情况。
优先推荐前两种方案,因为它们利用SDWebImage和系统API的原生能力,稳定性高且代码侵入性小。如果你的用户经常遇到WiFi DNS问题,第三种方案可以作为补充。第四种方案则是最后的兜底选项。
内容的提问来源于stack exchange,提问作者Frostmourne

