如何在iOS上获取用户代理?

19

在iOS上,是否有一种方法可以获取设备的用户代理(User Agent)?我不想硬编码它,因为我需要所有设备的用户代理,并且我需要将用户代理添加到一个URL中。

谢谢。

7个回答

44

在iOS中确定用户代理的更简单的方法是直接从UIWebView获取它,使用这个SO文章中被接受的答案。引用那个答案:

解决方案是创建一个UIWebView,然后只需使用javascript提取用户代理即可。

UIWebView* webView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString* secretAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];

不需要发起网络请求并等待其实现即可获取HTTP头。对我来说,这就是答案!谢谢 - Jorge Rodrigues dos Santos
1
谢谢,这个可行。一个重要的注意事项是,你需要在主线程中调用它。因此,在某些情况下,你可能需要将你的答案代码包装在dispatch_async(dispatch_get_main_queue(), ^{ ... });中。 - haxpor
UIWebview已经不再存在。 - Muhammad Shauket
这并不完全准确,用于URLSession的用户代理可能与用于UIWebView的用户代理不同。 - marius
UIWebView已被弃用,因此不应再使用此解决方案 :) - Axemasta

14

每个请求的移动应用程序都必须发送带有构建版本和设备信息的User-Agent头。

用户代理配置信息

因此,用户代理应该是这样的:

User-Agent: <AppName>/version (<system-information>) <platform> (<platform-details>) <extensions>

对于iOS:

User-Agent: <AppName/<version> (<iDevice platform>; <Apple model identifier>; iOS/<OS version>) CFNetwork/<version> Darwin/<version>

如何获取每个组件?

  1. 标头键 - 您可以硬编码或使用一些常量值

  2. 应用名称和版本 - 从Info.plist中获取

 let infoPlist = try? PListFile<InfoPlist>()
 let appName = infoPlist.data.bundleName
 let version = infoPlist.data.versionNumber
 let build = infoPlist.data.buildNumber
  • 关于设备的信息

  • modelName - 您可以像这里描述的那样获得。

        let modelName = UIDevice.current.modelName
    

    其他组件:

        let platform = UIDevice.current.systemName
        let operationSystemVersion = ProcessInfo.processInfo.operatingSystemVersionString
    
  • CFNetwork 版本

  •  static var cfNetworkVersion: String? {
       guard
         let bundle = Bundle(identifier: "com.apple.CFNetwork"),
          let versionAny = bundle.infoDictionary?[kCFBundleVersionKey as String],
          let version = versionAny as? String
           else { return nil }
       return version
     }
    

    来自这里

    1. Darwin 版本

      var systemInfo = utsname()
      uname(&systemInfo)
      let machineMirror = Mirror(reflecting: systemInfo.release)
      let darvinVersionString = machineMirror.children.reduce("") { identifier, element in
        guard let value = element.value as? Int8,
          value != 0 else {
            return identifier
        }
    
        return identifier + String(UnicodeScalar(UInt8(value)))
      }
    

    从这里

    结果:

    MyApp/1.8.199 (iOS; iPhone XS; Version 13.3 (Build 17C45)) CFNetwork/1121.2.1 Darvin/19.3.0


    6

    您实际上不需要发起请求即可获取用户代理。只需从以下委托方法返回NO并保留用户代理标头:

    -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
    

    可能看起来像这样:

    -(BOOL)webView:(UIWebView *)webView
     shouldStartLoadWithRequest:(NSURLRequest *)request
     navigationType:(UIWebViewNavigationType)navigationType
    {  
         userAgent = [[request valueForHTTPHeaderField:@"User-Agent"] copy]; 
         NSLog(@"user-agent: %@", userAgent);
    
         _webView.delegate = nil; 
         [_webView release]; 
    
         return NO; 
    }
    

    3

    (iOS 8.0, *)

    自从iOS 12中UIWebView被废弃,您应该改用WKWebView。

    由于WKWebView.evaluateJavaScript(_:)结果现在是异步的,因此此实现解决了一个常见要求,在自己的REST API调用中需要用户代理。

    import WebKit
    
    class UAString {
    
        static var userAgent : String = ""
    
        @discardableResult init(view parent: UIView) {
    
            if UAString.userAgent.isEmpty {
    
                let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
    
                webView.translatesAutoresizingMaskIntoConstraints = false
                parent.addSubview(webView)
    
                webView.evaluateJavaScript("navigator.userAgent") { result, _ in
                    UAString.userAgent = result as? String ?? ""
                }
            }
        }
    
    }
    

    现在最后一部分,您可以按照以下方式在初始视图控制器中实现此类:

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    
        UAString(view: self.view)
    }
    

    那么您可以访问属性,如UAString.userAgent

    1

    Objective-C

    WKWebView *webView = [WKWebView new];
    res = [webView valueForKey:@"userAgent"];
    

    Swift

    let ua = WKWebView().value(forKey: "userAgent")
    

    不要忘记导入WebKit。

    0

    Swift 3.x、4.x、5.x及以上版本

    由于传统的UIWebView有时会出现内存泄漏的问题,因此建议始终使用WKWebView(比UIWebView更好)。

    import WebKit
    
    var webViewForUserAgent: WKWebView?
    

    通过调用下面的函数获取userAgent,您还可以将其设置为其他变量

    func getUserAgent() {
    
        webViewForUserAgent = WKWebView() // must initialize
    
        webViewForUserAgent?.evaluateJavaScript("navigator.userAgent") { (result, error) in
    
            //
            if error != nil {
                print("Error occured to get userAgent")
                return
            }
    
            //
            if let unwrappedUserAgent = result as? String {
                print("userAgent: \(unwrappedUserAgent)")
            } else {
                print("Failed to get userAgent")
            }
        }
    }
    

    enter image description here


    这段代码无法编译,因为WKWebView()需要传入参数。 - Trevor Cox
    请分享您的代码片段,确保将webView声明为可选项。 - iAj

    -1
    在iOS中确定用户代理的一种更简单的方法是直接从UIWebView中获取它,使用this SO post中的被接受的答案。但这种方式有两个缺点:
    1、UIWebView的第一个分配可能需要太多时间来初始化webview上下文。
    2、代码必须在主线程中执行。这可能会卡住主线程。
    如果您知道如何在避免应用商店审核拒绝的情况下使用私有方法的技巧。
    您可以尝试以下代码:
        #define 调用私有实例方法(x,sel,q)\
        {\
        SEL selector = NSSelectorFromString([NSString stringWithFormat:@"%@",@#sel]);\
        if ([x respondsToSelector:selector]) {\
        _Pragma("clang diagnostic push")\
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")\
        q=[x performSelector:selector];\
        _Pragma("clang diagnostic pop")\
        }\
        }\
    #define 调用私有类方法带一个参数(x,sel,p,q)\ {\ SEL selector = NSSelectorFromString([NSString stringWithFormat:@"_%@:",@#sel]);\ if ([x respondsToSelector:selector]) {\ _Pragma("clang diagnostic push")\ _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")\ q=[x performSelector:selector withObject:p];\ _Pragma("clang diagnostic pop")\ }\ }\
    + (NSString *)标准用户代理{ NSString *buildVersion = nil; 调用私有实例方法([UIDevice currentDevice], buildVersion,buildVersion);
    Class webViewCls = NSClassFromString([NSString stringWithFormat:@"%@%@",@"Web",@"View"]); NSString *standardUA = nil; NSString *versions = [NSString stringWithFormat:@"Mobile/%@",buildVersion]; 调用私有类方法带一个参数(webViewCls, standardUserAgentWithApplicationName,versions,standardUA); return standardUA; }

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接