OC跟webview交互系列
# OC与 JS交互方式有哪些?
- 拦截url(适用于UIWebView和WKWebView)
- JavaScriptCore(只适用于UIWebView,iOS7+)
- WKScriptMessageHandler(只适用于WKWebView,iOS8+)
- WebViewJavascriptBridge(适用于UIWebView和WKWebView,属于第三方框架)
# 通过JS调用OC代码(url拦截)一
通过拦截url(适用于UIWebView和WKWebView)
- 和后端同事协定好协议,如jxaction://scan表示启动二维码扫描,jxaction://location表示获取定位。
- 实现UIWebView代理的shouldStartLoadWithRequest:navigationType:方法,在方法中对url进行拦截,如果是步奏<1>中定义好的协议则执行对应原生代码,返回false,否则返回true继续加载原url。
// js调用原生1
(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *url = request.URL.absoluteString; if ([url rangeOfString:@"需要跳转源生界面的URL判断"].location != NSNotFound) {
//跳转原生界面 return NO;} return YES; }
h5代码: 扫一扫(拦截url)
// 原生调用js:直接调用UIWebView的stringByEvaluatingJavaScriptFromString:方法,或者WKWebView的 evaluateJavaScript:completionHandler:方法。 若(1)中扫描二维码结束后,需要把扫描结果返回给web页,直接调用UIWebView的stringByEvaluatingJavaScriptFromString:方法,或者WKWebView的 evaluateJavaScript:completionHandler:方法。 [self.webView stringByEvaluatingJavaScriptFromString:@"scanResult('我是扫描结果~')"];
### 方法二JavaScriptCore
方法一web调用原生只适合简单的调用,如果要传递参数,虽然也可以拼接在url上,如jxaction://scan?method=aaa,但是需要我们自行对字符串进行分割解析,并且特殊字符需要编码。在iOS7系统提供了JavaScriptCore,可以更优雅地实现js与原生的交互。
**js调用原生**
1. 新建类继承自NSObject(如AppJSObject)。
2. h文件中声明一个代理并遵循JSExport,代理内的方法和js定义的方法名一致。
3. m文件中实现<2>代理中对应的方法,可以在方法内处理事件或通知代理。
AppJSObject.h
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h> #import <JavaScriptCore/JavaScriptCore.h>
@protocol AppJSObjectDelegate
-(void)scan:(NSString *)message;
@end
@interface AppJSObject : NSObject
@property(nonatomic,weak) id
@end
AppJSObject.m
#import "AppJSObject.h"
@implementation AppJSObject
-(void)scan:(NSString *)message{ [self.delegate scan:message]; }
@end
h5代码:
扫描结果:
4. 在UIWebView加载完成的代理中把AppJSObject实例对象类注入到JS中,那么在js中调用方法就会调用到原生AppJSObject实例对象中对应的方法了。
-(void)webViewDidFinishLoad:(UIWebView *)webView { JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
AppJSObject *jsObject = [AppJSObject new];
jsObject.delegate = self;
context[@"app"] = jsObject;
}
也可以通过block实现而不创建新类AppJSObject:
context[@"openAlbum"] = ^(){ NSLog(@"js调用oc打开相册"); };
**原生调用js**
可以通过一中的方法,也可以通过JSContext:
2
3
JSContext *context=[_mainWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; NSString *alertJS= [NSString stringWithFormat:@"%@('%@')",_photoMethod,fileUrl]; [context evaluateScript:alertJS];
### 方法三. WKScriptMessageHandler
现在很多app都是支持iOS8+,很多人使用WKWebView代替了UIWebView,但是WKWebView并不支持方法二。此时我们可以使用WKWebView的WKScriptMessageHandler
1. 初始化WKWebView时,调用addScriptMessageHandler:name:方法,name为js中的方法名,如scan:
2
3
4
(void)setupWKWebView{ WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; configuration.userContentController = [[WKUserContentController alloc] init]; [configuration.userContentController addScriptMessageHandler:self name:@"scan"];
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration]; webView.UIDelegate = self; }
h5:1window.webkit.messageHandlers.scan.postMessage()
1
- 实现WKScriptMessageHandler代理方法,当js调用scan方法时,会回调此代理方法:
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"scan"]) {
//调用原生扫码
}
}
2
3
4
5
# 方法四 WebViewJavascriptBridge
是一个第三方框架,官方文档和demo都很完整,不再累赘,GitHub地址: WebViewJavascriptBridge (opens new window)
# WKWebView 白屏问题
WKWebView是一个多进程的组件,Network Loading以及UI Rendering在其他进程中执行。初次适配WKWebView的时候,我们也惊讶于打开WKWebView后, App进程内存消耗反而大幅度下降,但仔细观察会发现,Other Process的内存占用会增加。在一些用webGL渲染的复杂页面,使用WKWebView总体的内存占用【App process Memory + other Process Memory】,不见得比UIWebView少很多。
UIWebView上当内存占用太大的时候, App Process会crash;而在WKWebView上当总体的内存占用比较大的时候,WebContent Process会crash, 从而出现白屏现象
这个时候WKWebView.URL会变成nil,简单的reload刷新操作已经失效,对于一些长驻的H5页面影响比较大。
# 解决方案
- 借助WKNavigationDelegate
iOS9之后增加的回调函数
/*! @abstract Invoked when the web view's web content process is terminated. @param webView The web view whose underlying web content process was terminated. */1
2
3
(void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macos(10.11), ios(9.0));
1
在WKWebView总体内存占用过大的时候,页面即将出现白屏,在上面这个系统回调方法中执行[webview reload]来解决白屏问题。
- 检测webView.title是否为空
并不是所有的H5页面白屏的时候都会调用上面的回调函数; 场景:最近遇到的一个高内存消耗的H5页面上present系统相机,拍照完毕后返回原来页面的时候出现白屏现象(拍照过程消耗了大量内存,导致内存紧张, WebContent Process被系统挂起),但上面的回调函数并没有被调用。在WKWebView白屏的时候,另一种现象是webView.title会被置空,因此,可以在viewWillAppear的时候检测webView.title是否为空来reload页面。
# WKWebView Cookie问题
# WKWebView Cookie存储
业界普遍认为 WKWebView 拥有自己的私有存储,不会将 Cookie 存入到标准的 Cookie 容器 NSHTTPCookieStorage 中。
实践发现 WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,在iOS 8上,当页面跳转的时候,当前页面的 Cookie 会写入 NSHTTPCookieStorage 中,而在 iOS 10 上,JS 执行 document.cookie 或服务器 set-cookie 注入的 Cookie 会很快同步到 NSHTTPCookieStorage 中,FireFox 工程师曾建议通过 reset WKProcessPool 来触发 Cookie 同步到 NSHTTPCookieStorage 中,实践发现不起作用,并可能会引发当前页面 session cookie 丢失等问题。
WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie。
比如,NSHTTPCookieStorage 中存储了一个 Cookie:
提示
name=Nicholas;value=test;domain=y.qq.com;expires=Sat, 02 May 2019 23:38:25 GMT;
通过 UIWebView 发起请求http://y.qq.com, 则请求头会自动带上 cookie: Nicholas=test; 而通过 WKWebView发起请求http://y.qq.com, 请求头不会自动带上 cookie: Nicholas=test。
# WKProcessPool
- WKProcessPool定义:A WKProcessPool object represents a pool of Web Content process。
通过让所有 WKWebView 共享同一个 WKProcessPool 实例,可以实现多个 WKWebView 之间共享 Cookie(session Cookie and persistent Cookie)数据。 不过WKWebView WkProcessPool实例在app杀进程重启后会被重置,导致WKProcessPool 中的cookie/session Cookie数据丢失,目前也无法实现WKProcessPool实例本地化保存。
# Workround
H5的业务都是依赖于Cookie作登陆态校验,而WKWebView上请求不会自动携带Cookie,目前的主要解决方案是:
WKWebView loadRequest前,在request header中设置Cookie,解决首个请求Cookie带不上的问题
WKWebView * webView = [WKWebView new]; NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://h5.qzone.qq.com/mqzone/index"]];1
2
[request addValue:@"skey=skeyValue" forHTTPHeaderField:@"Cookie"]; [webView loadRequest:request];
2. 通过document.cookie设置Cookie解决后续页面(同域)Ajax, iframe 请求的cookie问题 注意:document.cookie() 无法跨域设置cookie
WKUserContentController* userContentController = [WKUserContentController new]; WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"document.cookie = 'skey=skeyValue';" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:cookieScript];
这种方案无法解决302请求的Cookie问题,比如:第一个请求时www.a.com,我们通过在request header里带上Cookie解决该请求的Cookie问题,接着页面302跳转到www.b.com, 这个时候www.b.com 这个请求就可能因为没有携带cookie而无法访问。当然,由于每一次页面跳转都会调用回调函数:
(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
1
可以在该回调函数里拦截302请求,copy request, 在request header中带上cookie并重新loadRequest。不过这种方法依然解决不了页面的iframe跨域请求的cookie问题,毕竟-[WKWebView loadRequest]只适合加载mainiFrame请求。
# WKWebView NSURLProtocol 问题
WKWebView在独立于App进程之外的进程中执行网络请求,请求数据不经过主进程,因此,在WKWebView上直接使用NSURLProcol无法拦截请求。
苹果开源的WebKit源码暴露了私有API
+ [WKBrowsingContextController registerSchemeForCustomProtocol:]
通过注册http(s) scheme 后, WKWebView将可以使用NSURLProtocol拦截http(s)请求:
Class cls = NSClassFromString(@"WKBrowsingContextController”);
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([(id)cls respondsToSelector:sel]) {
// 注册http(s) scheme, 把 http和https请求交给 NSURLProtocol处理
[(id)cls performSelector:sel withObject:@"http"];
[(id)cls performSelector:sel withObject:@"https"];
}
2
3
4
5
6
7
# 缺点
- post请求body数据被清空 由于WKWebView在独立进程里网络请求。一旦注册http(s) scheme后,网络请求将从Network process发送到App Process,这样NSURLProtocol才能拦截网络请求。 在webkit2的设计里使用了messageQueue进行进程之间的通信, Network Process会将请求encode成一个Message,然后通过IPC发送给App Process。 出于性能的原因,encode的时候HTTPBody和HTTPBodyStream这两个字段丢弃掉了。
因此,如果通过registerSchemeForCustomProcol注册了http(s) scheme,那么由WKWebView发起的所有http(s)请求都会通过IPC传给进程NSURLProtocol处理,导致post请求body被清空;
- 对ATS支持不足 打开ATS开关: Allow Arbitrary Loads选项设置为NO,同时通过registerSchemeForCustomProtocol注册了http(s) scheme;WKWebView发起的所有http网络请求将被阻塞(即便将Allow Arbitrary Loads in Web Content 选项设置为YES) WKWebView可以注册customScheme,比如dynamic://,因此希望使用离线功能,又不使用post方式的请求可以通过customScheme发起请求,eg:dynamic://www.dynamicalbumlocalimage.com/,然后在 app 进程 NSURLProtocol 拦截这个请求并加载离线数据。不足: 使用post方式的请求该方案依然不适用,同时需要H5侧修改请求scheme以及CSP规则。
# WKWebView loadRequest问题
在WKWebView上通过loadRequest发起的post请求body数据会丢失;
//同样是由于进程间通信性能问题,HTTPBody字段被丢弃
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[@"bodyData" dataUsingEncoding:NSUTF8StringEncoding]];
[wkwebview loadRequest: request];
2
3
4
workround: 假设想通过-[WKWebView loadRequest:]加载post请求,request1: http://h5.qzone.qq.com/mqzone/index,可以通过以下步骤实现:
- 替换请求scheme,生成新的post请求request2:post://h5.qzone.qq.com/mqzone/index,同时将request1的body字段复制到request2的header中(WebKit不会丢弃header字段)
- 通过-[WKWebView loadRequest:]加载新的post请求request2;
- 通过 +[WKbrowsingContextController registerSchemeForCustom Protocol:]注册scheme:post://;
- 注册 NSURLProtocol 拦截请求post://h5.qzone.qq.com/mqzone/index ,替换请求 scheme, 生成新的请求 request3: http://h5.qzone.qq.com/mqzone/index,将 request2 header的body 字段复制到 request3 的 body 中,并使用 NSURLConnection 加载 request3,最后通过 NSURLProtocolClient 将加载结果返回 WKWebView;
# WKWebView页面样式问题
适配过程中,发现h5页面元素位置向下偏移或被拉伸变形,追踪后发现主要是h5页面高度值异常导致:
问题1:
空间H5页面有透明导航、透明导航下拉刷新、全屏等需求,因此之前 webView 整个是从(0, 0)开始布局,通过调整webView.scrollView.contentInset 来适配特殊导航栏需求。而在 WKWebView 上对 contentInset 的调整会反馈到webView.scrollView.contentSize.height的变化上,比如设置 webView.scrollView.contentInset.top = a,那么contentSize.height的值会增加a,导致H5页面长度增加,页面元素位置向下偏移;
解决方案:
调整WKWebView布局方式,避免调整webView.scrollView.contentInset。实际上,即便在 UIWebView 上也不建议直接调整webView.scrollView.contentInset的值,这确实会带来一些奇怪的问题。如果某些特殊情况下非得调整 contentInset 不可的话,可以通过下面方式让H5页面恢复正常显示:
/**设置contentInset值后通过调整webView.frame让页面恢复正常显示
*参考:http://km.oa.com/articles/show/277372
*/
webView.scrollView.contentInset = UIEdgeInsetsMake(a, 0, 0, 0);
webView.frame = CGRectMake(webView.frame.origin.x, webView.frame.origin.y, webView.frame.size.width, webView.frame.size.height - a);
2
3
4
5
问题2:接入now直播问题
在接入 now 直播的时候,我们发现在 iOS 9 上 WKWebView 会出现页面被拉伸变形的情况,最后发现是window.innerHeight值不准确导致(在WKWebView上返回了一个非常大的值),而H5同学通过获取window.innerHeight来设置页面高度,导致页面整体被拉伸。通过查阅相关资料发现,这个bug只在 iOS 9 的几个系统版本上出现,苹果后来fix了这个bug。我们最后的解决方案是:延迟调用window.innerHeight。
setTimeout(function(){height = window.innerHeight},0);
或者
Use shrink-to-fit meta-tag
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1, shrink-to-fit=no">
2
3
4
# WKWebView截屏问题
空间玩吧H5小游戏有截屏分享的功能,WKWebView 下通过 -[CALayer renderInContext:]实现截屏的方式失效,需要通过以下方式实现截屏功能:
@implementation UIView (ImageSnapshot)
- (UIImage*)imageSnapshot {
UIGraphicsBeginImageContextWithOptions(self.bounds.size,YES,self.contentScaleFactor);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
@end
2
3
4
5
6
7
8
9
然而这种方式依然解决不了 webGL 页面的截屏问题,笔者已经翻遍苹果文档,研究过 webKit2 源码里的截屏私有API,依然没有找到合适的解决方案,同时发现 Safari 以及 Chrome 这两个全量切换到 WKWebView 的浏览器也存在同样的问题:对webGL 页面的截屏结果不是空白就是纯黑图片。无奈之下,我们只能约定一个JS接口,让游戏开发商实现该接口,具体是通过 canvas getImageData()方法取得图片数据后返回 base64 格式的数据,客户端在需要截图的时候,调用这个JS接口获取 base64 String 并转换成 UIImage。
# WKWebView crash问题
WKWebView 放量后,外网新增了一些 crash, 其中一类 crash 的主要堆栈如下:
注意
... 28 UIKit 0x0000000190513360 UIApplicationMain + 208 29 Qzone 0x0000000101380570 main (main.m:181) 30 libdyld.dylib 0x00000001895205b8 _dyld_process_info_notify_release + 36 Completion handler passed to -[QZWebController webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:] was not called
主要是JS调用window.alert()函数引起的,从 crash 堆栈可以看出是 WKWebView 回调函数:
+ (void) presentAlertOnController:(nonnull UIViewController*)parentController title:(nullable NSString*)title message:(nullable NSString *)message handler:(nonnull void (^)())completionHandler;
completionHandler 没有被调用导致的。在适配 WKWebView 的时候,我们需要自己实现该回调函数,window.alert()才能调起 alert 框,我们最初的实现是这样的:
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { completionHandler(); }]];
[self presentViewController:alertController animated:YES completion:^{}];
}
2
3
4
5
6
如果 WKWebView 退出的时候,JS刚好执行了window.alert(), alert 框可能弹不出来,completionHandler 最后没有被执行,导致 crash;另一种情况是在 WKWebView 一打开,JS就执行window.alert(),这个时候由于 WKWebView 所在的 UIViewController 出现(push或present)的动画尚未结束,alert 框可能弹不出来,completionHandler 最后没有被执行,导致 crash。我们最终的实现大致是这样的:
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
if (/*UIViewController of WKWebView has finish push or present animation*/) {
completionHandler();
return;
}
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { completionHandler(); }]];
if (/*UIViewController of WKWebView is visible*/)
[self presentViewController:alertController animated:YES completion:^{}];
else
completionHandler();
}
2
3
4
5
6
7
8
9
10
11
12
13
确保上面两种情况下 completionHandler 都能被执行,消除了 WKWebView 下弹 alert 框的 crash,WKWebView 下弹 confirm 框的 crash 的原因与解决方式与 alert 类似。
另一个 crash 发生在 WKWebView 退出前调用:
-[WKWebView evaluateJavaScript: completionHandler:]
执行JS代码的情况下。WKWebView 退出并被释放后导致completionHandler变成野指针,而此时 javaScript Core 还在执行JS代码,待 javaScript Core 执行完毕后会调用completionHandler(),导致 crash。这个 crash 只发生在 iOS 8 系统上,参考Apple Open Source,在iOS9及以后系统苹果已经修复了这个bug,主要是对completionHandler block做了copy(refer: https://trac.webkit.org/changeset/179160);对于iOS 8系统,可以通过在 completionHandler 里 retain WKWebView 防止 completionHandler 被过早释放。我们最后用 methodSwizzle hook 了这个系统方法:
+ (void) load
{
[self jr_swizzleMethod:NSSelectorFromString(@"evaluateJavaScript:completionHandler:") withMethod:@selector(altEvaluateJavaScript:completionHandler:) error:nil];
}
/*
* fix: WKWebView crashes on deallocation if it has pending JavaScript evaluation
*/
- (void)altEvaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler
{
id strongSelf = self;
[self altEvaluateJavaScript:javaScriptString completionHandler:^(id r, NSError *e) {
[strongSelf title];
if (completionHandler) {
completionHandler(r, e);
}
}];
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 其它问题
问题1
视频自动播放 WKWebView 需要通过WKWebViewConfiguration.mediaPlaybackRequiresUserAction设置是否允许自动播放,但一定要在 WKWebView 初始化之前设置,在 WKWebView 初始化之后设置无效。
问题2 goBack API问题 WKWebView 上调用 -[WKWebView goBack], 回退到上一个页面后不会触发window.onload()函数、不会执行JS。
问题3
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
}
2
3
登录后cookie携带不上问题 现象:未登录访问h5页面,点击按钮弹出登录页,登录成功后再次点击按钮,又弹出登录页,抓包cookie为空 原因:登录后登录状态写进cookie里了,并保存在NSHTTPCookieStorage里,但是没有同步到WKHTTPCookieStore里,所以h5获取不到cookie 解决,登录后重新加载 loadRequest
Cookie异常丢失
由于WKWebView加载网页得到的Cookie会同步到NSHTTPCookieStorage中的特点,有时候你强行添加的Cookie会在同步过程中丢失。抓包你就会发现,点击一个链接时,Request的header中多了Set-Cookie字段,其实Cookie已经丢了
解决:把自己需要的Cookie主动保存起来,每次调用[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies方法时,保证返回的数组中有自己需要的Cookie,用了runtime的Method Swizzling,代码如下:
适当的时候保存cookie
//比如登录成功,保存Cookie
NSArray *allCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
for (NSHTTPCookie *cookie in allCookies) {
if ([cookie.name isEqualToString:DAServerSessionCookieName]) {
NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:DAUserDefaultsCookieStorageKey];
if (dict) {
NSHTTPCookie *localCookie = [NSHTTPCookie cookieWithProperties:dict];
if (![cookie.value isEqual:localCookie.value]) {
NSLog(@"本地Cookie有更新");
}
}
[[NSUserDefaults standardUserDefaults] setObject:cookie.properties forKey:DAUserDefaultsCookieStorageKey];
[[NSUserDefaults standardUserDefaults] synchronize];
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
读取时如果cookie为空则添加
@implementation NSHTTPCookieStorage (Utils)
+ (void)load
{
class_methodSwizzling(self, @selector(cookies), @selector(da_cookies));
}
- (NSArray<NSHTTPCookie *> *)da_cookies
{
NSArray *cookies = [self da_cookies];
BOOL isExist = NO;
for (NSHTTPCookie *cookie in cookies) {
if ([cookie.name isEqualToString:DAServerSessionCookieName]) {
isExist = YES;
break;
}
}
if (!isExist) {
//CookieStroage中添加
NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:DAUserDefaultsCookieStorageKey];
if (dict) {
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:dict];
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
NSMutableArray *mCookies = cookies.mutableCopy;
[mCookies addObject:cookie];
cookies = mCookies.copy;
}
}
return cookies;
}
@end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32