├── .DS_Store ├── NSURLSession-NSURLProtocol-WebviewCache.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── NSURLSession-NSURLProtocol-WebviewCache ├── ViewController.h ├── AppDelegate.h ├── TURLSessionProtocol.h ├── main.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── ViewController.m ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── AppDelegate.m └── TURLSessionProtocol.m ├── .gitignore └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangli4659507/NSURLSession-NSURLProtocol-webviewCache/HEAD/.DS_Store -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // NSURLSession-NSURLProtocol-WebviewCache 4 | // 5 | // Created by Mark on 16/5/20. 6 | // Copyright © 2016年 Mark. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // NSURLSession-NSURLProtocol-WebviewCache 4 | // 5 | // Created by Mark on 16/5/20. 6 | // Copyright © 2016年 Mark. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/TURLSessionProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // TURLSessionProtocol.h 3 | // CachedWebView 4 | // 5 | // Created by Mark on 16/5/19. 6 | // 7 | // 8 | 9 | #import 10 | 11 | extern NSString *const KProtocolHttpHeadKey; 12 | 13 | @interface TURLSessionProtocol : NSURLProtocol 14 | //添加需要过滤的请求前缀 15 | + (NSSet *)filterUrlPres; 16 | + (void)setFilterUrlPres:(NSSet *)filterUrlPre; 17 | @end 18 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // NSURLSession-NSURLProtocol-WebviewCache 4 | // 5 | // Created by Mark on 16/5/20. 6 | // Copyright © 2016年 Mark. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // NSURLSession-NSURLProtocol-WebviewCache 4 | // 5 | // Created by Mark on 16/5/20. 6 | // Copyright © 2016年 Mark. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "TURLSessionProtocol.h" 11 | @interface ViewController () 12 | @property (nonatomic, strong) NSMutableData *cacheData; 13 | @property (weak, nonatomic) IBOutlet UIWebView *webView; 14 | @end 15 | 16 | @implementation ViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | 21 | // Do any additional setup after loading the view, typically from a nib. 22 | [self actionRefrash:nil]; 23 | 24 | 25 | } 26 | 27 | - (IBAction)actionRefrash:(id)sender { 28 | 29 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.233.com"]]; 30 | 31 | [self.webView loadRequest:request]; 32 | 33 | self.webView.scalesPageToFit = YES; 34 | } 35 | 36 | - (void)didReceiveMemoryWarning { 37 | [super didReceiveMemoryWarning]; 38 | // Dispose of any resources that can be recreated. 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NSURLSession-NSURLProtocol-webviewCache 2 | 3 | ##产生缘由 4 | 其实项目已经上线了一年多了,一直没时间来将一些有用的东西摘出来。最近还算是比较闲,就将一些自认为值得拿出来分享的东西摘了出来。 5 | 6 | ##与其他公共类比较 7 | 之前在GitHub上star最多的莫过于[RNCachingURLProtocol](https://github.com/rnapier/RNCachingURLProtocol),我也认真的看了他的源码,写的非常不错,不的不说有点过时了。而且有一点没处理的好,如果放入直接的将其使用,他会将所有的http请求都缓存下来,不管你是不是来自UIWebView的请求,所以有很大的必要去改进。 8 | 9 | ##与NSURLCache比较 10 | 相信对使用过UIWebView的同学都会知道,其实UIWebView本身是有缓存的,那就是基于NSURLCache来实现的。之前到网上看到过基于他来对NSURLCache实现离线缓存,我开始也封装了个,但是发现不是特别的好用,显得特别的笨重。后面看了一篇关于介绍[NSURLProtocol](https://www.raywenderlich.com/59982/nsurlprotocol-tutorial)的文章,英文还行的同学可以去看看。 11 | 12 | 另外,之前的请求是基于NSURLConnection来封装的,但是现在看到苹果最近发的通告,所以就改进成了NSURLSession。 13 | 14 | ##使用方法 15 | 1.在Appdelegate中的`application:didFinishLaunchingWithOptions:`方法中加上: 16 | `[NSURLProtocol registerClass:[TURLSessionProtocol class]];` 17 | 18 | `注:在使用NSURLSession做请求,必须这样写才能截获该请求:` 19 | 20 | ` NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];` 21 | 22 | ` config.protocolClasses = @[[TURLSessionProtocol class]];` 23 | 24 | 对于拦截NSURLSession请求,可以参考这篇博客[objc](http://objccn.io/issue-5-4/) 25 | 26 | 27 | 2.在你创建请求时,给请求添加一个请求头: 28 | `[request setValue:@(YES) forHTTPHeaderField:KProtocolHttpHeadKey];`需要注意key必须是`KProtocolHttpHeadKey`,不然会拦截不到你的请求。 29 | 30 | 这样就大功告成。 31 | 32 | ###备注 33 | 如果需要修改缓存时间,请在`TURLSessionProtocol.m`文件中去将`static NSUInteger const KCacheTime = 30;`修改为你需要缓存时长的时间,单位是秒。 34 | 35 | 另外就是,最终的缓存内容是以归档的方式存入到沙盒的`cache`目录下,要是清空缓存也会将缓存的内容删除,如果不想一起删除,可以自定义目录。 36 | 37 | ##联系方式 38 | 如果你在使用过程中遇到任何的问题或者说有Bug和好的建议,请将你的需求发到我的Email: 39 | 40 | 欢迎交流。 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // NSURLSession-NSURLProtocol-WebviewCache 4 | // 5 | // Created by Mark on 16/5/20. 6 | // Copyright © 2016年 Mark. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "TURLSessionProtocol.h" 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | [NSURLProtocol registerClass:[TURLSessionProtocol class]]; 20 | // Override point for customization after application launch. 21 | return YES; 22 | } 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 27 | } 28 | 29 | - (void)applicationDidEnterBackground:(UIApplication *)application { 30 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | - (void)applicationWillEnterForeground:(UIApplication *)application { 35 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 36 | } 37 | 38 | - (void)applicationDidBecomeActive:(UIApplication *)application { 39 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 40 | } 41 | 42 | - (void)applicationWillTerminate:(UIApplication *)application { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache/TURLSessionProtocol.m: -------------------------------------------------------------------------------- 1 | // 2 | // TURLSessionProtocol.m 3 | // CachedWebView 4 | // 5 | // Created by Mark on 16/5/19. 6 | // 7 | // 8 | 9 | #import "TURLSessionProtocol.h" 10 | #import 11 | #import 12 | NSString *const KProtocolHttpHeadKey = @"KProtocolHttpHeadKey"; 13 | 14 | static NSUInteger const KCacheTime = 360;//缓存的时间 默认设置为360秒 可以任意的更改 15 | 16 | static NSObject *TURLSessionFilterUrlPreObject; 17 | static NSSet *TURLSessionFilterUrlPre; 18 | 19 | @interface NSURLRequest(MutableCopyWorkaround) 20 | - (id)mutableCopyWorkaround; 21 | @end 22 | 23 | @interface NSString (MD5) 24 | - (NSString *)md5String; 25 | @end 26 | 27 | @interface TURLProtocolCacheData : NSObject 28 | @property (nonatomic, strong) NSDate *addDate; 29 | @property (nonatomic, strong) NSData *data; 30 | @property (nonatomic, strong) NSURLResponse *response; 31 | @property (nonatomic, strong) NSURLRequest *redirectRequest; 32 | @end 33 | 34 | 35 | @interface TURLSessionProtocol () 36 | @property (nonatomic, strong) NSURLSession *session; 37 | @property (nonatomic, strong) NSURLSessionDataTask *downloadTask; 38 | @property (nonatomic, strong) NSURLResponse *response; 39 | @property (nonatomic, strong) NSMutableData *cacheData; 40 | @end 41 | 42 | @implementation NSURLRequest (MutableCopyWorkaround) 43 | 44 | -(id)mutableCopyWorkaround { 45 | 46 | NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequest alloc] initWithURL:[self URL] 47 | cachePolicy:[self cachePolicy] 48 | timeoutInterval:[self timeoutInterval]]; 49 | [mutableURLRequest setAllHTTPHeaderFields:[self allHTTPHeaderFields]]; 50 | if ([self HTTPBodyStream]) { 51 | [mutableURLRequest setHTTPBodyStream:[self HTTPBodyStream]]; 52 | } else { 53 | [mutableURLRequest setHTTPBody:[self HTTPBody]]; 54 | } 55 | [mutableURLRequest setHTTPMethod:[self HTTPMethod]]; 56 | 57 | return mutableURLRequest; 58 | } 59 | 60 | @end 61 | 62 | @implementation NSString(MD5) 63 | 64 | - (NSString *)md5String { 65 | 66 | NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; 67 | uint8_t digest[CC_SHA1_DIGEST_LENGTH]; 68 | 69 | CC_SHA1(data.bytes, data.length, digest); 70 | 71 | NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; 72 | 73 | for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) { 74 | [output appendFormat:@"%02x", digest[i]]; 75 | } 76 | 77 | return output; 78 | } 79 | 80 | @end 81 | 82 | @implementation TURLProtocolCacheData 83 | - (void)encodeWithCoder:(NSCoder *)aCoder { 84 | 85 | unsigned int count; 86 | Ivar *ivar = class_copyIvarList([self class], &count); 87 | for (int i = 0 ; i < count ; i++) { 88 | Ivar iv = ivar[i]; 89 | const char *name = ivar_getName(iv); 90 | NSString *strName = [NSString stringWithUTF8String:name]; 91 | //利用KVC取值 92 | id value = [self valueForKey:strName]; 93 | [aCoder encodeObject:value forKey:strName]; 94 | } 95 | free(ivar); 96 | } 97 | 98 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 99 | self = [super init]; 100 | if (self != nil) { 101 | unsigned int count = 0; 102 | Ivar *ivar = class_copyIvarList([self class], &count); 103 | for (int i= 0 ;i < count ; i++) { 104 | Ivar var = ivar[i]; 105 | const char *keyName = ivar_getName(var); 106 | NSString *key = [NSString stringWithUTF8String:keyName]; 107 | id value = [aDecoder decodeObjectForKey:key]; 108 | [self setValue:value forKey:key]; 109 | } 110 | free(ivar); 111 | } 112 | 113 | return self; 114 | } 115 | 116 | @end 117 | 118 | @implementation TURLSessionProtocol 119 | 120 | + (void)initialize 121 | { 122 | if (self == [TURLSessionProtocol class]) 123 | { 124 | static dispatch_once_t onceToken; 125 | dispatch_once(&onceToken, ^{ 126 | TURLSessionFilterUrlPreObject = [[NSObject alloc] init]; 127 | }); 128 | 129 | [self setFilterUrlPres:[NSSet setWithObject:@"http://api.233.com"]]; 130 | } 131 | } 132 | - (NSURLSession *)session { 133 | 134 | if (!_session) { 135 | _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil]; 136 | } 137 | return _session; 138 | } 139 | 140 | #pragma mark - publicFunc 141 | + (NSSet *)filterUrlPres { 142 | 143 | NSSet *set; 144 | @synchronized(TURLSessionFilterUrlPreObject) 145 | { 146 | set = TURLSessionFilterUrlPre; 147 | } 148 | return set; 149 | } 150 | 151 | + (void)setFilterUrlPres:(NSSet *)filterUrlPre { 152 | @synchronized(TURLSessionFilterUrlPreObject) 153 | { 154 | TURLSessionFilterUrlPre = filterUrlPre; 155 | } 156 | } 157 | 158 | #pragma mark - privateFunc 159 | 160 | - (NSString *)p_filePathWithUrlString:(NSString *)urlString { 161 | 162 | NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 163 | NSString *fileName = [urlString md5String]; 164 | return [cachesPath stringByAppendingPathComponent:fileName]; 165 | } 166 | 167 | - (BOOL)p_isUseCahceWithCacheData:(TURLProtocolCacheData *)cacheData { 168 | 169 | if (cacheData == nil) { 170 | return NO; 171 | } 172 | 173 | NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:cacheData.addDate]; 174 | return timeInterval < KCacheTime; 175 | } 176 | 177 | + (BOOL)p_isFilterWithUrlString:(NSString *)urlString { 178 | 179 | BOOL state = NO; 180 | for (NSString *str in TURLSessionFilterUrlPre) { 181 | 182 | if ([urlString hasPrefix:str]) { 183 | state = YES; 184 | break; 185 | } 186 | } 187 | return state; 188 | } 189 | 190 | #pragma mark - override 191 | 192 | +(BOOL)canInitWithRequest:(NSURLRequest *)request { 193 | 194 | if ([request valueForHTTPHeaderField:KProtocolHttpHeadKey] == nil && ![self p_isFilterWithUrlString:request.URL.absoluteString]) { 195 | //拦截请求头中包含KProtocolHttpHeadKey的请求 196 | // NSLog(@"request url:%@",request.URL.absoluteString); 197 | return YES; 198 | } 199 | return NO; 200 | } 201 | 202 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { 203 | 204 | return request; 205 | } 206 | 207 | + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { 208 | 209 | return [super requestIsCacheEquivalent:a toRequest:b]; 210 | } 211 | 212 | - (void)startLoading { 213 | 214 | NSString *url = self.request.URL.absoluteString;//请求的链接 215 | TURLProtocolCacheData *cacheData = [NSKeyedUnarchiver unarchiveObjectWithFile:[self p_filePathWithUrlString:url]]; 216 | 217 | if ([self p_isUseCahceWithCacheData:cacheData]) { 218 | //有缓存并且缓存没过期 219 | 220 | if (cacheData.redirectRequest) { 221 | [self.client URLProtocol:self wasRedirectedToRequest:cacheData.redirectRequest redirectResponse:cacheData.response]; 222 | } else if (cacheData.response){ 223 | [self.client URLProtocol:self didReceiveResponse:cacheData.response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; 224 | [self.client URLProtocol:self didLoadData:cacheData.data]; 225 | [self.client URLProtocolDidFinishLoading:self]; 226 | } 227 | 228 | 229 | } else { 230 | NSMutableURLRequest *request = [self.request mutableCopyWorkaround]; 231 | request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; 232 | // NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.request.URL.absoluteString]]; 233 | [request setValue:@"test" forHTTPHeaderField:KProtocolHttpHeadKey]; 234 | self.downloadTask = [self.session dataTaskWithRequest:request]; 235 | [self.downloadTask resume]; 236 | 237 | } 238 | } 239 | 240 | - (void)stopLoading { 241 | [self.downloadTask cancel]; 242 | self.cacheData = nil; 243 | self.downloadTask = nil; 244 | self.response = nil; 245 | 246 | 247 | } 248 | 249 | #pragma mark - session delegate 250 | 251 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { 252 | 253 | //处理重定向问题 254 | if (response != nil) { 255 | NSMutableURLRequest *redirectableRequest = [request mutableCopyWorkaround]; 256 | TURLProtocolCacheData *cacheData = [[TURLProtocolCacheData alloc] init]; 257 | cacheData.data = self.cacheData; 258 | cacheData.response = response; 259 | cacheData.redirectRequest = redirectableRequest; 260 | [NSKeyedArchiver archiveRootObject:cacheData toFile:[self p_filePathWithUrlString:request.URL.absoluteString]]; 261 | 262 | [self.client URLProtocol:self wasRedirectedToRequest:redirectableRequest redirectResponse:response]; 263 | completionHandler(request); 264 | 265 | } else { 266 | 267 | completionHandler(request); 268 | } 269 | } 270 | 271 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { 272 | 273 | [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; 274 | // 允许处理服务器的响应,才会继续接收服务器返回的数据 275 | completionHandler(NSURLSessionResponseAllow); 276 | self.cacheData = [NSMutableData data]; 277 | self.response = response; 278 | } 279 | 280 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { 281 | //下载过程中 282 | [self.client URLProtocol:self didLoadData:data]; 283 | [self.cacheData appendData:data]; 284 | 285 | } 286 | 287 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { 288 | // 下载完成之后的处理 289 | 290 | if (error) { 291 | NSLog(@"error url = %@",task.currentRequest.URL.absoluteString); 292 | [self.client URLProtocol:self didFailWithError:error]; 293 | } else { 294 | //将数据的缓存归档存入到本地文件中 295 | NSLog(@"ok url = %@",task.currentRequest.URL.absoluteString); 296 | TURLProtocolCacheData *cacheData = [[TURLProtocolCacheData alloc] init]; 297 | cacheData.data = [self.cacheData copy]; 298 | cacheData.addDate = [NSDate date]; 299 | cacheData.response = self.response; 300 | [NSKeyedArchiver archiveRootObject:cacheData toFile:[self p_filePathWithUrlString:self.request.URL.absoluteString]]; 301 | [self.client URLProtocolDidFinishLoading:self]; 302 | } 303 | } 304 | 305 | @end 306 | -------------------------------------------------------------------------------- /NSURLSession-NSURLProtocol-WebviewCache.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4DD4CB231CEEADCE00C6364F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DD4CB221CEEADCE00C6364F /* main.m */; }; 11 | 4DD4CB261CEEADCE00C6364F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DD4CB251CEEADCE00C6364F /* AppDelegate.m */; }; 12 | 4DD4CB291CEEADCE00C6364F /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DD4CB281CEEADCE00C6364F /* ViewController.m */; }; 13 | 4DD4CB2C1CEEADCE00C6364F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4DD4CB2A1CEEADCE00C6364F /* Main.storyboard */; }; 14 | 4DD4CB2E1CEEADCE00C6364F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4DD4CB2D1CEEADCE00C6364F /* Assets.xcassets */; }; 15 | 4DD4CB311CEEADCE00C6364F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4DD4CB2F1CEEADCE00C6364F /* LaunchScreen.storyboard */; }; 16 | 4DD4CB3A1CEEAE6A00C6364F /* TURLSessionProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DD4CB391CEEAE6A00C6364F /* TURLSessionProtocol.m */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 4DD4CB1E1CEEADCE00C6364F /* NSURLSession-NSURLProtocol-WebviewCache.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "NSURLSession-NSURLProtocol-WebviewCache.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 4DD4CB221CEEADCE00C6364F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 22 | 4DD4CB241CEEADCE00C6364F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 23 | 4DD4CB251CEEADCE00C6364F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 24 | 4DD4CB271CEEADCE00C6364F /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 25 | 4DD4CB281CEEADCE00C6364F /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 26 | 4DD4CB2B1CEEADCE00C6364F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 27 | 4DD4CB2D1CEEADCE00C6364F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | 4DD4CB301CEEADCE00C6364F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 29 | 4DD4CB321CEEADCE00C6364F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 4DD4CB381CEEAE6A00C6364F /* TURLSessionProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TURLSessionProtocol.h; sourceTree = ""; }; 31 | 4DD4CB391CEEAE6A00C6364F /* TURLSessionProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TURLSessionProtocol.m; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | 4DD4CB1B1CEEADCE00C6364F /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 4DD4CB151CEEADCE00C6364F = { 46 | isa = PBXGroup; 47 | children = ( 48 | 4DD4CB201CEEADCE00C6364F /* NSURLSession-NSURLProtocol-WebviewCache */, 49 | 4DD4CB1F1CEEADCE00C6364F /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | 4DD4CB1F1CEEADCE00C6364F /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 4DD4CB1E1CEEADCE00C6364F /* NSURLSession-NSURLProtocol-WebviewCache.app */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | 4DD4CB201CEEADCE00C6364F /* NSURLSession-NSURLProtocol-WebviewCache */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 4DD4CB241CEEADCE00C6364F /* AppDelegate.h */, 65 | 4DD4CB251CEEADCE00C6364F /* AppDelegate.m */, 66 | 4DD4CB271CEEADCE00C6364F /* ViewController.h */, 67 | 4DD4CB281CEEADCE00C6364F /* ViewController.m */, 68 | 4DD4CB2A1CEEADCE00C6364F /* Main.storyboard */, 69 | 4DD4CB381CEEAE6A00C6364F /* TURLSessionProtocol.h */, 70 | 4DD4CB391CEEAE6A00C6364F /* TURLSessionProtocol.m */, 71 | 4DD4CB2D1CEEADCE00C6364F /* Assets.xcassets */, 72 | 4DD4CB2F1CEEADCE00C6364F /* LaunchScreen.storyboard */, 73 | 4DD4CB321CEEADCE00C6364F /* Info.plist */, 74 | 4DD4CB211CEEADCE00C6364F /* Supporting Files */, 75 | ); 76 | path = "NSURLSession-NSURLProtocol-WebviewCache"; 77 | sourceTree = ""; 78 | }; 79 | 4DD4CB211CEEADCE00C6364F /* Supporting Files */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 4DD4CB221CEEADCE00C6364F /* main.m */, 83 | ); 84 | name = "Supporting Files"; 85 | sourceTree = ""; 86 | }; 87 | /* End PBXGroup section */ 88 | 89 | /* Begin PBXNativeTarget section */ 90 | 4DD4CB1D1CEEADCE00C6364F /* NSURLSession-NSURLProtocol-WebviewCache */ = { 91 | isa = PBXNativeTarget; 92 | buildConfigurationList = 4DD4CB351CEEADCE00C6364F /* Build configuration list for PBXNativeTarget "NSURLSession-NSURLProtocol-WebviewCache" */; 93 | buildPhases = ( 94 | 4DD4CB1A1CEEADCE00C6364F /* Sources */, 95 | 4DD4CB1B1CEEADCE00C6364F /* Frameworks */, 96 | 4DD4CB1C1CEEADCE00C6364F /* Resources */, 97 | ); 98 | buildRules = ( 99 | ); 100 | dependencies = ( 101 | ); 102 | name = "NSURLSession-NSURLProtocol-WebviewCache"; 103 | productName = "NSURLSession-NSURLProtocol-WebviewCache"; 104 | productReference = 4DD4CB1E1CEEADCE00C6364F /* NSURLSession-NSURLProtocol-WebviewCache.app */; 105 | productType = "com.apple.product-type.application"; 106 | }; 107 | /* End PBXNativeTarget section */ 108 | 109 | /* Begin PBXProject section */ 110 | 4DD4CB161CEEADCE00C6364F /* Project object */ = { 111 | isa = PBXProject; 112 | attributes = { 113 | LastUpgradeCheck = 0730; 114 | ORGANIZATIONNAME = Mark; 115 | TargetAttributes = { 116 | 4DD4CB1D1CEEADCE00C6364F = { 117 | CreatedOnToolsVersion = 7.3.1; 118 | }; 119 | }; 120 | }; 121 | buildConfigurationList = 4DD4CB191CEEADCE00C6364F /* Build configuration list for PBXProject "NSURLSession-NSURLProtocol-WebviewCache" */; 122 | compatibilityVersion = "Xcode 3.2"; 123 | developmentRegion = English; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | Base, 128 | ); 129 | mainGroup = 4DD4CB151CEEADCE00C6364F; 130 | productRefGroup = 4DD4CB1F1CEEADCE00C6364F /* Products */; 131 | projectDirPath = ""; 132 | projectRoot = ""; 133 | targets = ( 134 | 4DD4CB1D1CEEADCE00C6364F /* NSURLSession-NSURLProtocol-WebviewCache */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXResourcesBuildPhase section */ 140 | 4DD4CB1C1CEEADCE00C6364F /* Resources */ = { 141 | isa = PBXResourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 4DD4CB311CEEADCE00C6364F /* LaunchScreen.storyboard in Resources */, 145 | 4DD4CB2E1CEEADCE00C6364F /* Assets.xcassets in Resources */, 146 | 4DD4CB2C1CEEADCE00C6364F /* Main.storyboard in Resources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXResourcesBuildPhase section */ 151 | 152 | /* Begin PBXSourcesBuildPhase section */ 153 | 4DD4CB1A1CEEADCE00C6364F /* Sources */ = { 154 | isa = PBXSourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 4DD4CB291CEEADCE00C6364F /* ViewController.m in Sources */, 158 | 4DD4CB3A1CEEAE6A00C6364F /* TURLSessionProtocol.m in Sources */, 159 | 4DD4CB261CEEADCE00C6364F /* AppDelegate.m in Sources */, 160 | 4DD4CB231CEEADCE00C6364F /* main.m in Sources */, 161 | ); 162 | runOnlyForDeploymentPostprocessing = 0; 163 | }; 164 | /* End PBXSourcesBuildPhase section */ 165 | 166 | /* Begin PBXVariantGroup section */ 167 | 4DD4CB2A1CEEADCE00C6364F /* Main.storyboard */ = { 168 | isa = PBXVariantGroup; 169 | children = ( 170 | 4DD4CB2B1CEEADCE00C6364F /* Base */, 171 | ); 172 | name = Main.storyboard; 173 | sourceTree = ""; 174 | }; 175 | 4DD4CB2F1CEEADCE00C6364F /* LaunchScreen.storyboard */ = { 176 | isa = PBXVariantGroup; 177 | children = ( 178 | 4DD4CB301CEEADCE00C6364F /* Base */, 179 | ); 180 | name = LaunchScreen.storyboard; 181 | sourceTree = ""; 182 | }; 183 | /* End PBXVariantGroup section */ 184 | 185 | /* Begin XCBuildConfiguration section */ 186 | 4DD4CB331CEEADCE00C6364F /* Debug */ = { 187 | isa = XCBuildConfiguration; 188 | buildSettings = { 189 | ALWAYS_SEARCH_USER_PATHS = NO; 190 | CLANG_ANALYZER_NONNULL = YES; 191 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 192 | CLANG_CXX_LIBRARY = "libc++"; 193 | CLANG_ENABLE_MODULES = YES; 194 | CLANG_ENABLE_OBJC_ARC = YES; 195 | CLANG_WARN_BOOL_CONVERSION = YES; 196 | CLANG_WARN_CONSTANT_CONVERSION = YES; 197 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 198 | CLANG_WARN_EMPTY_BODY = YES; 199 | CLANG_WARN_ENUM_CONVERSION = YES; 200 | CLANG_WARN_INT_CONVERSION = YES; 201 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 202 | CLANG_WARN_UNREACHABLE_CODE = YES; 203 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 204 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 205 | COPY_PHASE_STRIP = NO; 206 | DEBUG_INFORMATION_FORMAT = dwarf; 207 | ENABLE_STRICT_OBJC_MSGSEND = YES; 208 | ENABLE_TESTABILITY = YES; 209 | GCC_C_LANGUAGE_STANDARD = gnu99; 210 | GCC_DYNAMIC_NO_PIC = NO; 211 | GCC_NO_COMMON_BLOCKS = YES; 212 | GCC_OPTIMIZATION_LEVEL = 0; 213 | GCC_PREPROCESSOR_DEFINITIONS = ( 214 | "DEBUG=1", 215 | "$(inherited)", 216 | ); 217 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 218 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 219 | GCC_WARN_UNDECLARED_SELECTOR = YES; 220 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 221 | GCC_WARN_UNUSED_FUNCTION = YES; 222 | GCC_WARN_UNUSED_VARIABLE = YES; 223 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 224 | MTL_ENABLE_DEBUG_INFO = YES; 225 | ONLY_ACTIVE_ARCH = YES; 226 | SDKROOT = iphoneos; 227 | }; 228 | name = Debug; 229 | }; 230 | 4DD4CB341CEEADCE00C6364F /* Release */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_ANALYZER_NONNULL = YES; 235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 236 | CLANG_CXX_LIBRARY = "libc++"; 237 | CLANG_ENABLE_MODULES = YES; 238 | CLANG_ENABLE_OBJC_ARC = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_EMPTY_BODY = YES; 243 | CLANG_WARN_ENUM_CONVERSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_UNREACHABLE_CODE = YES; 247 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 248 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 249 | COPY_PHASE_STRIP = NO; 250 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 251 | ENABLE_NS_ASSERTIONS = NO; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | GCC_C_LANGUAGE_STANDARD = gnu99; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 256 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 257 | GCC_WARN_UNDECLARED_SELECTOR = YES; 258 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 259 | GCC_WARN_UNUSED_FUNCTION = YES; 260 | GCC_WARN_UNUSED_VARIABLE = YES; 261 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 262 | MTL_ENABLE_DEBUG_INFO = NO; 263 | SDKROOT = iphoneos; 264 | VALIDATE_PRODUCT = YES; 265 | }; 266 | name = Release; 267 | }; 268 | 4DD4CB361CEEADCE00C6364F /* Debug */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 272 | CODE_SIGN_IDENTITY = "iPhone Developer: Wen Luo (6P2MSBB3R6)"; 273 | INFOPLIST_FILE = "NSURLSession-NSURLProtocol-WebviewCache/Info.plist"; 274 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 275 | PRODUCT_BUNDLE_IDENTIFIER = com.233.test.ppppp; 276 | PRODUCT_NAME = "$(TARGET_NAME)"; 277 | PROVISIONING_PROFILE = "5eb87a29-826a-4da2-809c-3ed7299dd292"; 278 | }; 279 | name = Debug; 280 | }; 281 | 4DD4CB371CEEADCE00C6364F /* Release */ = { 282 | isa = XCBuildConfiguration; 283 | buildSettings = { 284 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 285 | CODE_SIGN_IDENTITY = "iPhone Developer: Wen Luo (6P2MSBB3R6)"; 286 | INFOPLIST_FILE = "NSURLSession-NSURLProtocol-WebviewCache/Info.plist"; 287 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 288 | PRODUCT_BUNDLE_IDENTIFIER = com.233.test.ppppp; 289 | PRODUCT_NAME = "$(TARGET_NAME)"; 290 | PROVISIONING_PROFILE = "5eb87a29-826a-4da2-809c-3ed7299dd292"; 291 | }; 292 | name = Release; 293 | }; 294 | /* End XCBuildConfiguration section */ 295 | 296 | /* Begin XCConfigurationList section */ 297 | 4DD4CB191CEEADCE00C6364F /* Build configuration list for PBXProject "NSURLSession-NSURLProtocol-WebviewCache" */ = { 298 | isa = XCConfigurationList; 299 | buildConfigurations = ( 300 | 4DD4CB331CEEADCE00C6364F /* Debug */, 301 | 4DD4CB341CEEADCE00C6364F /* Release */, 302 | ); 303 | defaultConfigurationIsVisible = 0; 304 | defaultConfigurationName = Release; 305 | }; 306 | 4DD4CB351CEEADCE00C6364F /* Build configuration list for PBXNativeTarget "NSURLSession-NSURLProtocol-WebviewCache" */ = { 307 | isa = XCConfigurationList; 308 | buildConfigurations = ( 309 | 4DD4CB361CEEADCE00C6364F /* Debug */, 310 | 4DD4CB371CEEADCE00C6364F /* Release */, 311 | ); 312 | defaultConfigurationIsVisible = 0; 313 | defaultConfigurationName = Release; 314 | }; 315 | /* End XCConfigurationList section */ 316 | }; 317 | rootObject = 4DD4CB161CEEADCE00C6364F /* Project object */; 318 | } 319 | --------------------------------------------------------------------------------