iOS问题记录 - iOS 17通过NSUserDefaults设置UserAgent无效

打印 上一主题 下一主题

主题 813|帖子 813|积分 2439


前言

最近维护一个老项目时遇到的问题。提及这老项目我就有颔首疼,一个快十年前的项目,这么说你可能不觉得有什么,但是你想想Swift也才发布不到十年(2014年6月发布,现2023年12月)。
开辟环境



  • Xcode: 15.1
  • iOS: 17.2
问题描述

项目运行在iOS 17.2设备时,应用内网页无法乐成获取设置后的UserAgent。
项目中设置UserAgent的关键源码:
  1. [self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
  2.     NSString *userAgent = [NSString stringWithFormat:@"%@", result];
  3.     NSString *newUserAgent = [userAgent stringByAppendingString:@" App/1.0.0"];
  4.     [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": newUserAgent}];
  5. }];
复制代码
问题分析

从项目源码看,设置UserAgent是通过NSUserDefaults实现的。大致使用如下:

  • 起首应用启动后创建一个WKWebView实例对象,无需加载任何网页
  • 然后通过navigator.userAgent获取当前的UserAgent并修改
  • 接着调用registerDefaults方法将修改后的UserAgent注册到NSUserDefaults,成为应用的默认设置
  • 最后烧毁该WKWebView实例对象
经过以上使用,后续创建WKWebView实例对象时都能从NSUserDefaults中获取到修改后的UserAgent用于初始化,从而实现全局设置。需要注意一点,registerDefaults方法设置默认值的使用不会进行持久化存储,所以应用每次启动都需要设置一遍。
虽然理了一遍设置逻辑感觉没什么问题,但是这个方法有点老了,现在一般通过customUserAgent属性设置,难道这个方法逾期了?
为了排除其他可能存在的干扰,新建一个iOS项目用于测试,源码如下:
  1. import UIKit
  2. import WebKit
  3. class ViewController: UIViewController, WKNavigationDelegate {
  4.    
  5.     private let htmlString = """
  6.     <html>
  7.         <head>
  8.             <meta name="viewport" content="width=device-width">
  9.             <style>
  10.                 button {
  11.                     font-size: 24px;
  12.                 }
  13.             </style>
  14.             <script>
  15.                 function getUserAgent() {
  16.                     var userAgent = navigator.userAgent;
  17.                     var paragraph = document.createElement('p');
  18.                     paragraph.textContent = 'UserAgent: ' + userAgent;
  19.                     document.body.appendChild(paragraph);
  20.                 }
  21.             </script>
  22.         </head>
  23.         <body style="text-align: center;">
  24.             <button οnclick="getUserAgent()">获取 UserAgent</button>
  25.         </body>
  26.     </html>
  27.     """
  28.    
  29.     private var webView: WKWebView!
  30.    
  31.     override func viewDidLoad() {
  32.         super.viewDidLoad()
  33.         
  34.         // 在WKWebView初始化前设置UserAgent
  35.         UserDefaults.standard.register(defaults: ["UserAgent": "App/1.0.0"])
  36.         
  37.         // 创建WKWebView
  38.         let webViewConfiguration = WKWebViewConfiguration()
  39.         webView = WKWebView(frame: view.bounds, configuration: webViewConfiguration)
  40.         webView.navigationDelegate = self
  41.         view.addSubview(webView)
  42.         
  43.         // 加载HTML
  44.         webView.loadHTMLString(htmlString, baseURL: nil)
  45.     }
  46. }
复制代码
iOS 17.2测试结果:

iOS 16.4测试结果:

这里补充一下,当前最新的iOS 16版本应该是iOS 16.7.3,之所以测试没用这个版本,原因有两点:

  • Xcode中iOS 16模仿器的最高版本为iOS 16.4,后续的版本没有
  • iOS 16.4以后的版本基本只是小修小补,对本次测试影响不大
参考文档:关于 iOS 16 更新 - 官方 Apple 支持
实测iOS 17.0和iOS 17.2测试结果一样,现在基本可以确定从iOS 17开始,通过NSUserDefaults设置UserAgent都无法生效。开端判断,这个设置方法在iOS 17及以上逾期了。个人猜测可能是WKWebView初始化时不再从NSUserDefaults获取默认值导致的问题(今天天太冷了,着实扛不住,后面偶然机再翻翻相关源码对导致该问题的原因进一步分析)。
2024/01/01更新:假如你想进一步了解导致该问题的原因,请看这篇文章iOS问题记录 - iOS 17通过NSUserDefaults设置UserAgent无效(续)
既然这个方法失效了,那在iOS 17上通过customUserAgent属性设置能正常生效吗?
对前面的测试源码做简单修改,页面加载完成后自动获取一次未修改的UserAgent,然后再修改UserAgent并通过customUserAgent属性设置,接动手动点击按钮获取一次UserAgent,最后对比两次获取结果判断是否设置乐成。
  1. import UIKit
  2. import WebKit
  3. class ViewController: UIViewController, WKNavigationDelegate {
  4.    
  5.     private let htmlString = """
  6.     <html>
  7.         <head>
  8.             <meta name="viewport" content="width=device-width">
  9.             <style>
  10.                 button {
  11.                     font-size: 24px;
  12.                 }
  13.             </style>
  14.             <script>
  15.                 function getUserAgent() {
  16.                     var userAgent = navigator.userAgent;
  17.                     var paragraph = document.createElement('p');
  18.                     paragraph.textContent = 'UserAgent: ' + userAgent;
  19.                     document.body.appendChild(paragraph);
  20.                 }
  21.                 /* 页面加载完成后获取一次UserAgent */
  22.                 window.onload = function() {
  23.                     getUserAgent();
  24.                 };
  25.             </script>
  26.         </head>
  27.         <body style="text-align: center;">
  28.             <button οnclick="getUserAgent()">获取 UserAgent</button>
  29.         </body>
  30.     </html>
  31.     """
  32.    
  33.     private var webView: WKWebView!
  34.    
  35.     override func viewDidLoad() {
  36.         super.viewDidLoad()
  37.         
  38.         // 创建WKWebView
  39.         let webViewConfiguration = WKWebViewConfiguration()
  40.         webView = WKWebView(frame: view.bounds, configuration: webViewConfiguration)
  41.         webView.navigationDelegate = self
  42.         view.addSubview(webView)
  43.         
  44.         // 加载HTML
  45.         webView.loadHTMLString(htmlString, baseURL: nil)
  46.     }
  47.    
  48.     func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
  49.         // 获取当前的UserAgent
  50.         webView.evaluateJavaScript("navigator.userAgent") { (result, error) in
  51.             guard let userAgent = result as? String else {
  52.                 return
  53.             }
  54.             let newUserAgent = userAgent + " App/1.0.0"
  55.             // 设置UserAgent
  56.             webView.customUserAgent = newUserAgent
  57.         }
  58.     }
  59. }
复制代码
测试结果:

从测试结果看,通过customUserAgent属性设置UserAgent一切正常。不外这种方法不好实现全局设置,每次创建新的WKWebView实例对象都需要再设置一遍。
解决方案

通过NSUserDefaults设置UserAgent改为通过customUserAgent属性设置UserAgent,更多详情请参考前面的问题分析。
假如你只需要设置一次,还可以参考文章续篇中的方法(位于UserAgent的设置优先级)。
最后

假如这篇文章对你有所帮助,点赞

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

北冰洋以北

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表