参考: http://blog.mugunthkumar.com/coding/ios-tutorial-advanced-networking-with-mknetworkkit/
Couple of weeks ago, I wrote a clean, fast networking toolkit for iOS and Mac written for the LLVM Compiler 3.0 with ARC.
Reception was very good that it was the “most-watched” repository on Github last week. Early adopters have sent me innumerable emails on how fast their network operations are, and how responsive their app is after integrating MKNetworkKit.
Contents
MKNetworkKit is faster (and it makes your app feel smoother and faster with seamless transparent caching) in reality. There are two things that makes MKNetworkKit faster.
First, it’s ARC based and the awesome LLVM 3.0 compiler brings in a ton of performance benefits. Using the @autoreleasepool block instead of NSAutoReleasePool in MKNetworkOperation alone should improve the performance by a huge factor (Apple claims it to be 6 times faster). If you still haven’t migrated to ARC because you want to support those iOS 3.x users, read this post by Matt Gemmell first.
Second important performance benefit is seamless and transparent caching. By transparent caching, I mean, caching that involves zero overhead from the developer (That’s you).
Let me, in this post, brief you about how this seamless caching might help you. First, an example. You are refreshing a twitter stream. A GET request for that looks similar to
GET http://twitter.com/mugunthkumar/statuses
Caching your responses, the wrong way
Some developers cache responses using Core Data. STOP IT
Using Core Data for caching is like using the military to kill bedbugs in your bedroom. DON’T DO IT.
The problem when you implement Core Data is, you should program your view controllers to work with two kinds of data structures. One, JSON/XML from server and the other, Core Data NSManagedObjects. Some “clever” developers “take it to the next level” by just programming their view controllers to work with only Core Data. The design goes like this. The network layer talks to the server and updates Core Data Store. Once the store is updated, send a NSNotification to “refresh” view controllers.
This may sound clever to many. But it’s completely WRONG. I REPEAT. COMPLETELY WRONG. Core Data (or even structured SQL storage) is not meant for that. Core Data is a structured storage for persisting/serializing Object graphs in your application. Don’t use it for storing objects that have a low life time. Thumbnail images, list of tweets etc., just don’t belong there. Secondly, this would require you to read and write to flash memory on the iDevice which have a limited read/write cycle. Finally, reading and writing to disk (in this case, Core Data) is a very ugly way to transfer data from one class to another (in this case, from your Network Layer to View Controllers)
Say hello to MKNetworkKit
MKNetworkKit calls the SAME completion handler with cached data if you are making the call for the second time. When the network connectivity is proper, MKNetworkKit calls your completion handler twice. First with the cached data and again after fetching the latest data from server. As such, you can design your view controllers to work with just ONE kind of data, data from server. If you have an app that doesn’t cache and doesn’t work offline, just replace your networking library with MKNetworkKit and you get caching for free. Not even a single line of code change is required on your view controllers. Caching is transparent. Your view controllers needs to work with the same data structure, no matter whether the data is from cache or server.
Behind the scenes
MKNetworkKit caching is super light-weight and it caches your responses in a NSMutableDictionary. Doesn’t that lead to high memory usage? Yes. of-course, but MKNetworkKit is clever. It observes the UIApplicationDidReceiveMemoryWarningNotification and flushes the dictionary to disk. The next time you make a request to the cached URL, the cached data is brought back to memory. So in-effect, it has a in-memory cache and disk cache and uses a least recently used algorithm to flush items to disk. The MKNetworkEngine class has methods that can be over-ridden to control the cache cost.
Just override the method
-(int) cacheMemoryCost; |
and return a higher/lower value based on your application’s requirements.
MKNetworkKit caching works like a intermediate proxy server by intercepting the response headers. So, when a second GET request to the same URL is made, MKNetworkKit behaves the following way.
1) For GET requests that sent a ETag in the header previously (for example, requests to Amazon S3 servers), MKNetworkKit sends a second request as a HEAD request (even if you specify as GET) with IF-NONE-MATCHheader. Most servers that send ETag (like Amazon S3) responds to IF-NONE-MATCH by sending the real data only when it has been changed. Otherwise, they return a 304 Not Modified which MKNetworkKit safely ignores. So data transfer is really not huge. Even this request is honored only after 1 day. So, repeated requests to the same thumbnail image doesn’t even hit the server.
2) For any GET request that contained a Cache-Control:max-age=in the header, MKNetworkKit honors that and makes the second request only after the expiry date. For dynamically generated requests, you should set Cache-Control on server. This not only helps MKNetworkKit, but most proxy servers along the way.
3) For requests that contained a Last-Modified field in the header, MKNetworkKit sends a “IF-MODIFIED-SINCE” HEAD request. This again works like the IF-NONE-MATCH. The server, either sends the complete data or a 304 Not Modified.
4) For servers that implement Cache-Control: no-cache, MKNetworkKit goes one step ahead and makes a second request only after a minute (60 sec). So repeated refreshes to often refreshed page will not exactly hit the server. For example, refreshing foursquare check-ins. It’s rare for these information to change within the next 60 sec.
5) For performance reasons, when the response type is an image, and the response headers doesn’t have any cache control information, MKNetworkKit assumes an expiry date of 7 days.
The last two are not an RFC standard. But these optimizations make network operations fast on a mobile devices even if the server isn’t implemented in an optimized way. All POST, PUT, HEAD and DELETE requests are ignored and not cached.
And, of course, you can customize all these behaviors by editing the values in MKNetworkKit.h
Freezing Operations
Another very important feature of MKNetworkKit is operation freezing. Imagine that, your customer take an awesome photo with your own “Instagram-killer” app, but since he is at a remote exotic place, 3G connectivity is poor and uploading the photo fails. Without MKNetworkKit, you should remember the photo related meta data, store the photo’s NSData in a file and upload the operation the next time the app is launched. Painful!
With MKNetworkKit, you mark the photo upload operation is “freezable”. This means, in the event of network failure, your operations are automatically serialized (frozen beneath snow) and restored the next time the app launches, all for free.
MKNetworkOperation *op = [myEngine operationWithPath:@"/imageUpload" params:paramsDict httpMethod:@"POST"]; [op setFreezable:YES]; // ONE EXTRA LINE [myEngine enqueOperation:op]; |
You can mark any POST/PUT/DELETE operation as freezable. GET operations are not freezable and MKNetworkKit ignores your call to setFreezable: if your operation is a “GET” operation.
Advanced Tips
Friction-free authentication
MKNetworkOperation *op = [myEngine operationWithPath:@"/letmein"]; [op setUserName:@"mugunth" password:@"mYsEkReTpAsSwOrD"]; // ONE EXTRA LINE [op setUserName:@"mugunth" password:@"mYsEkReTpAsSwOrD" basicAuth:YES]; // OR THIS LINE [myEngine enqueOperation:op]; |
When your server uses HTTP Basic auth or Digest auth or even Windows NTLM authentication, all you need to do is to set your username and password and MKNetworkKit auto-magically authenticates your request!. For NTLM authentication, just ensure your username is “domain\username”. There is no separate method setDomain: or something.
You can also set basicAuth:YES to send the credentials even before receiving the authentication challenge. However, this works only if your server uses HTTP Basic Authentication.
Custom Authentication
If your server uses HTML Form authentication, you can override the authHandler block and provide custom auth mechanisms.
MKNetworkOperation *op = [myEngine operationWithPath:@"/letmein"]; op.authHandler = ^(NSURLAuthenticationChallenge *cred) { // show a web view controller or do whatever you want and finally, when you have a credentials ready, just call [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; }; [myEngine enqueOperation:op]; |
Overriding MKNetworkEngine
Tweaking the URL building methods with MKNetworkEngine is easy. Let me start by giving you an example. Imagine that you have a server which authenticates a logged in user and after authentication, it expects a Authorization header to be set with an access token.
You need to have a factory method that creates URLs customized to your web service. This could include adding a authorization header in our case. You can do this by overriding the method,
-(void) enqueueOperation:(MKNetworkOperation*) operation; |
For example, in the above case, you can check your engine subclass for an access token and add it to the operation’s header in this method (if you have one).
Overriding MKNetworkOperation
Sometimes, you might do custom error handling based on the response from your server. For example, your server might send a valid HTTP response (status code 200) which could be an internal error, like “Invalid User”. Business logic errors are better handled with your own application level error codes. You overriding MKNetworkOperation to customize the success/failure reporting. I would highly recommend doing this along with designing your server to send error codes for error conditions.
The following methods are to be overridden for customizing error handling.
-(void) operationSucceeded; -(void) operationFailedWithError:(NSError*) error; |
For example, in the previous case, you can override operationSucceeded, inspect the response and if the response was indeed a business logic error (“Invalid User”), you can call the [super operationFailedWithError:[Your custom error class for "Invalid User"]];
Otherwise, call [super operationSucceeded].
You can also override operationFailedWithError to introspect the actual cause of the error, if your server sends any error related information in the dictionary.
The reason for overriding at this level is to minimize error handling at view controller layer. So your presentation layer, UIViewController subclasses will be cleaner to read.
If you have subclassed the MKNetworkOperation and want the factory method in your engine subclass, operationWithURLString:params:httpMethod to prepare an operation of your subclass. To register your operation subclass with the engine, you can call the registerOperationSubclass: method.
-(void) registerOperationSubclass:(Class) aClass; |
Your code fragment might look like,
[self.myEngine registerOperationSubclass:[MyAppNetworkOperation class]]; |
Image Cache with MKNetworkEngine
MKNetworkEngine has a handy method to load images from a server. It’s built-in cache mechanism ensures, your images are cached without using any third-party image cache libraries. That means your code base is even leaner.
You can use the following method of MKNetworkEngine to load images.
-(void) imageAtURL:(NSURL *)url onCompletion:(MKNKImageBlock) imageFetchedBlock; |
MKNKImageBlock is defined like this.
typedef void (^MKNKImageBlock) (UIImage* fetchedImage, NSURL* url, BOOL isInCache); |
So your calling code will look like,
[ApplicationDelegate.myAppEngine imageAtURL:[NSURL URLWithString:@"http://example.com/image.png"] onCompletion:^(UIImage *fetchedImage, NSURL *fetchedURL, BOOL isInCache) { self.imageView.image = fetchedImage; }]; |
If you are loading images onto a imageView inside a tableview cell, you can check if the completed URL is same as the passed URL
[ApplicationDelegate.myAppEngine imageAtURL:[NSURL URLWithString:@"http://example.com/image.png"] onCompletion:^(UIImage *fetchedImage, NSURL *fetchedURL, BOOL isInCache) { if(originalURL == fetchedURL) cell.imageView.image = fetchedImage; }]; |
This check is necessary as tableview cells might be recycled and a cell might end up getting images from multiple network operations.
You might also want to read my other elaborate post on Image Caching with MKNetworkKit
Lastly, if you are fading in your images like those fancy apps, add your fade-in logic if the image is loaded from server and not from cache.
Source Code
MKNetworkKit on Github
MKNetworkKit might look simple, but it’s incredibly powerful. Use it in your apps and tell me how it scores. My next blog post would be on how to write a better RESTful server that serves a mobile device. Watch this space, subscribe to my feed or follow me on twitter.
Interested in learning in-depth features of Objective-C and iOS? You should get my book.
Amazon - http://mk.sg/ios5book
iBooks - http://mk.sg/ibook
Wiley – http://mk.sg/book
Book Depository – http://mk.sg/bdbook
–
Mugunth
Follow me on Twitter
欢迎关注微信公众号——计算机视觉:
相关推荐
graph slam tutorial :从推导到应用3(g2o版程序),包含文档读取,及后端优化
《Java EE 6 Tutorial: Basic Concepts, Fourth Edition》是一本面向新手及中级Java开发者的指南书籍,旨在帮助他们深入理解Java平台企业版6(Java EE 6)的各项特性与技术。本书由Oracle公司Java EE 6文档团队成员...
本书教您如何使用Ruby on Rails开发和部署真正的,具有工业实力的Web应用程序,Ruby on Rails是为诸如Twitter,Hulu,GitHub和Yellow Pages等顶级网站提供支持的开源Web框架。
Tutorial 1: Setting up DirectX 11 with Visual Studio Tutorial 2: Creating a Framework and Window Tutorial 3: Initializing DirectX 11 Tutorial 4: Buffers, Shaders, and HLSL Tutorial 5: ...
**OpenCV 指南:学习 OpenCV 的...综上所述,"opencv-tutorial"这个压缩包文件很可能包含了关于OpenCV的基础教程和实践案例,通过学习和实践,你将能够熟练掌握OpenCV的核心功能,为你的计算机视觉项目打下坚实基础。
R for Everyone: Advanced Analytics and Graphics (Addison-Wesley Data & Analytics Series) by Jared P. Lander English | 14 Jun. 2017 | ASIN: B071X9KT1D | 560 Pages | AZW3 | 58.72 MB Statistical ...
NIPS 2016 Tutorial: Generative Adversarial Networks(生成对抗网络) , by Ian Goodfellow
《iOS教程》是一份面向需要使用Objective-C语言进行iOS平台下iPhone和iPad应用开发的软件程序员的指南。iOS是苹果公司(Apple Inc.)开发和分发的一款移动操作系统,最初于2007年发布,用于iPhone、iPod Touch和...
This Book has been prepared for ...Title: Objective C Tutorial: Simply Easy Learning Author: Virender Singh Length: 309 pages Edition: 1 Language: English Publication Date: 2015-07-05 ISBN-10: B0112YNTDC
《iOS学徒教程1:入门》是为初学者准备的,旨在通过实践一个iOS游戏开发示例来教授iOS开发的基础知识。在这一教程中,你将学习如何使用Objective-C编程语言和Xcode集成开发环境,这是Apple公司开发iOS应用时必备的...
《EDEM教程1 - 传送带》是一份官方的学习资料,专为仿真爱好者设计,旨在帮助他们理解和掌握EDEM软件的使用。EDEM(Eulerian Discrete Element Method)是一种基于离散元方法的颗粒流体动力学软件,广泛应用于各种...
XDP-Tutorial 旨在帮助开发者和网络工程师学习并掌握eXpress Data Path(简称XDP),这是一种由Linux内核提供的高效、低延迟的数据包处理框架。该项目通过一系列教程和示例代码,引导用户深入了解XDP的原理与实践...
Learn Web Development with Rails Clear EPUB version in English, Second Edition “The author is clearly an expert at the Ruby language and the Rails framework, but more than that, he is a working ...
In this tutorial, we aim to present to researchers and industry practitioners a broad overview of imitation learning techniques and recent applications. Imitation learning is a powerful and practical ...
• Overview of proposed p802.3bv project • Technical / Economic Feasibility • Home networking market • Automo>ve market • Industrial market • Support • Summary and ques>ons
Doing Bayesian Data Analysis: A Tutorial with R, JAGS, and Stan, Second Edition provides an accessible approach for conducting Bayesian data analysis, as material is explained clearly with concrete ...
NVIDIA Vulkan Ray跟踪教程基于KHR的较新的教程版本本教程有一个基于VK_KHR_ray_tracing扩展的较新版本。 GitHub: : 该项目和所提供的代码的重点是展示使用扩展在现有Vulkan样本中进行光线跟踪的基本集成。...