Objective-C下Xcode9中AVAudioPlayer加载本地文件遇ATS问题
嘿,这个问题我在iOS11.x版本迭代时也碰到过,看似是ATS针对本地资源的误判,其实背后是旧网络API和AVFoundation的ATS上下文冲突,而且iPad的系统网络栈处理逻辑和iPhone略有差异,才会出现设备不一致的问题。
问题根源分析
你看到的ATS报错其实是个误导——本地file://协议的资源本来就不受ATS约束,但为什么会被拦截?核心问题出在AppDelegate里的NSURLConnection请求:NSURLConnection是iOS9就标记为过时的API,在iOS11.3之后,它的ATS处理逻辑和AVFoundation的资源加载上下文出现了冲突。当你先发起HTTP的NSURLConnection请求后,系统的ATS检查上下文被错误地延续到了后续的本地音频URL加载上,而iPad的系统网络栈对这种上下文的延续更敏感,所以只有iPad会触发这个问题。
至于你说后来App莫名恢复正常,大概率是某次启动时那个NSURLConnection请求没有成功发起(比如网络异常、启动顺序变化),导致系统没有建立错误的ATS上下文,所以本地音频加载就正常了,但这种情况完全不可靠。
靠谱的解决方案
1. 替换过时的NSURLConnection为URLSession(最根本)
苹果已经彻底停止维护NSURLConnection的兼容性,替换成URLSession能从根源上避免上下文冲突问题。把你AppDelegate里的NSURLConnection请求改成如下实现:
NSURL *url = [NSURL URLWithString:@"你的HTTP请求地址"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // 处理请求结果的逻辑 }]; [task resume];
替换后,系统的ATS上下文不会被错误污染,本地音频的prepareToPlay就能正常执行,同时还能获得URLSession带来的性能和稳定性提升。
2. 绕开URL,直接用文件路径初始化AVAudioPlayer(快速解决)
如果暂时不想替换网络API,可以换一种初始化AVAudioPlayer的方式——用文件路径而非URL,完全避开URL加载的ATS检查路径:
NSString *medPath = [[NSBundle mainBundle] pathForResource:@"MyFile" ofType:@"m4a"]; NSError *playerError = nil; AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfFile:medPath error:&playerError]; self.appSoundPlayer = newPlayer; [self.appSoundPlayer prepareToPlay];
这个方法更直接,initWithContentsOfFile:是直接读取本地文件,不会触发任何网络相关的ATS检查,既能解决误拦截问题,又能保留prepareToPlay带来的快速启动优势。
3. 调整ATS配置(可选,仅作为临时过渡)
你之前添加的ATS配置里,NSAllowsArbitraryLoadsForMedia本来应该允许媒体资源的任意加载,但因为上下文冲突没生效。如果坚持用NSURLConnection,可以尝试简化ATS配置(冗余配置可能反而导致规则失效):
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoadsForMedia</key> <true/> </dict>
不过这个方案不如前两个可靠,因为苹果对过时API的兼容性不会再优化,后续版本可能还会出现其他问题。
内容的提问来源于stack exchange,提问作者Alan Moore

