import JavaScriptCore import Alamofire func downloadJSFile(url: URL, completion: @escaping (Result) -> Void) { AF.download(url).responseData { (response) in switch response.result { case .success(let data): if let jsString = String(data: data, encoding: .utf8) { completion(.success(jsString)) } else { completion(.failure(NSError(domain: "Download Error", code: -1, userInfo: nil))) } case .failure(let error): completion(.failure(error)) } } } func createJSContext() -> JSContext { let ctx = JSContext() // 注入 console.log ctx?.evaluateScript("var console = { log: function(message) { _consoleLog(message) } }") let consoleLog: @convention(block) (String) -> Void = { message in print("JS 打印: \(message)") } ctx?.setObject(unsafeBitCast(consoleLog, to: AnyObject.self), forKeyedSubscript: "_consoleLog" as (NSCopying & NSObjectProtocol)?) // 注入AF ctx?.evaluateScript("var AF = { request: function(url, method, data, headers, callback) { _request(url, method, data, headers, callback) } }") let af: @convention(block) (String, String, String?, [String: String]?, JSValue?) -> Void = { url, method, data, headers, callback in var request = URLRequest(url: URL(string: url)!) request.httpMethod = method if method == "POST" { if let data = data { request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = data.data(using: .utf8) } } if let headers = headers { request.headers = HTTPHeaders(headers) } AF.request(request).response { response in if let data = response.data { callback?.call(withArguments: [String(data: data, encoding: .utf8), nil]) } if let error = response.error { debugPrint(response) callback?.call(withArguments: [nil, error.localizedDescription]) } } } ctx?.setObject(unsafeBitCast(af, to: AnyObject.self), forKeyedSubscript: "_request" as (NSCopying & NSObjectProtocol)?) // 捕捉JS运行异常 ctx?.exceptionHandler = { context, exception in if let exception = exception { debugPrint(exception) print("JS 执行异常: \(exception)") } } return ctx! } func testDetail(url: String, ctx: JSContext) -> Void { if let detailFunction = ctx.objectForKeyedSubscript("detail") { let result = detailFunction.call(withArguments: [url]) let completionHandler: @convention(block) (JSValue) -> Void = { result in print("详情结果!!: \(result.toDictionary())") } let completionFunction = unsafeBitCast(completionHandler, to: AnyObject.self) result?.invokeMethod("then", withArguments: [completionFunction]) } } func testSearch(keyword: String, ctx: JSContext) -> Void { if let search = ctx.objectForKeyedSubscript("search") { let result = search.call(withArguments: [keyword]) let completionHandler: @convention(block) (JSValue) -> Void = { result in print("搜索结果!!: \(result.toDictionary())") } let completionFunction = unsafeBitCast(completionHandler, to: AnyObject.self) result?.invokeMethod("then", withArguments: [completionFunction]) } } let ctx = createJSContext() if let url = URL(string: "http://hubgit.cn/ben/be-ytb/raw/master/js/info.js") { downloadJSFile(url: url) { result in switch result { case .success(let jsString): print("下载远程JS成功") ctx.evaluateScript(jsString) testDetail(url: "https://www.youtube.com/watch?v=d0R-JyU4Btk", ctx: ctx) // testSearch(keyword: "周杰伦", ctx: ctx) case .failure(let error): print("Download Error: \(error)") } } } RunLoop.main.run()