forked from eway2012/SampleCode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNetworkManager.m
More file actions
512 lines (398 loc) · 21.6 KB
/
NetworkManager.m
File metadata and controls
512 lines (398 loc) · 21.6 KB
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
/*
File: NetworkManager.m
Contains: A singleton to manage the core network interactions.
Written by: DTS
Copyright: Copyright (c) 2010 Apple Inc. All Rights Reserved.
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or
redistribution of this Apple software constitutes acceptance of
these terms. If you do not agree with these terms, please do
not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following
terms, and subject to these terms, Apple grants you a personal,
non-exclusive license, under Apple's copyrights in this
original Apple software (the "Apple Software"), to use,
reproduce, modify and redistribute the Apple Software, with or
without modifications, in source and/or binary forms; provided
that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the
following text and disclaimers in all such redistributions of
the Apple Software. Neither the name, trademarks, service marks
or logos of Apple Inc. may be used to endorse or promote
products derived from the Apple Software without specific prior
written permission from Apple. Except as expressly stated in
this notice, no other rights or licenses, express or implied,
are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or
by other works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis.
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
#import "NetworkManager.h"
#import "QHTTPOperation.h"
#import "Logging.h"
@interface NetworkManager ()
// private properties
@property (nonatomic, retain, readonly ) NSThread * networkRunLoopThread;
@property (nonatomic, retain, readonly ) NSOperationQueue * queueForNetworkTransfers;
@property (nonatomic, retain, readonly ) NSOperationQueue * queueForNetworkManagement;
@property (nonatomic, retain, readonly ) NSOperationQueue * queueForCPU;
@end
@implementation NetworkManager
+ (NetworkManager *)sharedManager
// See comment in header.
{
static NetworkManager * sNetworkManager;
// This can be called on any thread, so we synchronise. We only do this in
// the sNetworkManager case because, once sNetworkManager goes non-nil, it can
// never go nil again.
if (sNetworkManager == nil) {
@synchronized (self) {
sNetworkManager = [[NetworkManager alloc] init];
assert(sNetworkManager != nil);
}
}
return sNetworkManager;
}
- (id)init
{
// any thread, but serialised by +sharedManager
self = [super init];
if (self != nil) {
// Create the network management queue. We will run an unbounded number of these operations
// in parallel because each one consumes minimal resources.
self->_queueForNetworkManagement = [[NSOperationQueue alloc] init];
assert(self->_queueForNetworkManagement != nil);
[self->_queueForNetworkManagement setMaxConcurrentOperationCount:NSIntegerMax];
assert(self->_queueForNetworkManagement != nil);
// Create the network transfer queue. We will run up to 4 simultaneous network requests.
self->_queueForNetworkTransfers = [[NSOperationQueue alloc] init];
assert(self->_queueForNetworkTransfers != nil);
[self->_queueForNetworkTransfers setMaxConcurrentOperationCount:4];
assert(self->_queueForNetworkTransfers != nil);
// Create the CPU queue. In contrast to the network queues, we leave
// maxConcurrentOperationCount set to the default, which means on current iOS devices
// the CPU operations are serialised. There's no point bouncing a single CPU between
// threads for this stuff.
self->_queueForCPU = [[NSOperationQueue alloc] init];
assert(self->_queueForCPU != nil);
// Create two dictionaries to store the target and action for each queued operation.
// Note that we retain the operation and the target but there's no need to retain the
// action selector.
self->_runningOperationToTargetMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
assert(self->_runningOperationToTargetMap != NULL);
self->_runningOperationToActionMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
assert(self->_runningOperationToActionMap != NULL);
self->_runningOperationToThreadMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
assert(self->_runningOperationToThreadMap != NULL);
// We run all of our network callbacks on a secondary thread to ensure that they don't
// contribute to main thread latency. Create and configure that thread.
self->_networkRunLoopThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRunLoopThreadEntry) object:nil];
assert(self->_networkRunLoopThread != nil);
[self->_networkRunLoopThread setName:@"networkRunLoopThread"];
if ( [self->_networkRunLoopThread respondsToSelector:@selector(setThreadPriority)] ) {
[self->_networkRunLoopThread setThreadPriority:0.3];
}
[self->_networkRunLoopThread start];
}
return self;
}
- (void)dealloc
{
// This object lives for the entire life of the application. Getting it to support being
// deallocated would be quite tricky (particularly from a threading perspective), so we
// don't even try.
assert(NO);
[super dealloc];
}
- (NSMutableURLRequest *)requestToGetURL:(NSURL *)url
// See comment in header.
{
NSMutableURLRequest * result;
static NSString * sUserAgentString;
// any thread
assert(url != nil);
// Create the request.
result = [NSMutableURLRequest requestWithURL:url];
assert(result != nil);
// Set up the user agent string.
if (sUserAgentString == nil) {
@synchronized ([self class]) {
sUserAgentString = [[NSString alloc] initWithFormat:@"MVCNetworking/%@", [[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey]];
assert(sUserAgentString != nil);
}
}
[result setValue:sUserAgentString forHTTPHeaderField:@"User-Agent"];
return result;
}
#pragma mark * Operation dispatch
@synthesize networkRunLoopThread = _networkRunLoopThread;
- (void)networkRunLoopThreadEntry
// This thread runs all of our network operation run loop callbacks.
{
assert( ! [NSThread isMainThread] );
while (YES) {
NSAutoreleasePool * pool;
pool = [[NSAutoreleasePool alloc] init];
assert(pool != nil);
[[NSRunLoop currentRunLoop] run];
[pool drain];
}
assert(NO);
}
- (BOOL)networkInUse
// See comment in header.
{
assert([NSThread isMainThread]);
// I base -networkInUse off the number of running operations, not the number of running
// network operations. This is probably technically incorrect, but the reality is that
// changing it would be tricky (but not /that/ tricky) and there's some question as to
// whether it's the right thing to do anyway. In an application that did extensive CPU work
// that was unrelated to the network then, sure, you'd only want the network activity
// indicator running while you were hitting the network. But in this application
// all CPU activity is the direct result of networking, so leaving the network activity
// indicator running while this CPU activity is busy isn't too far from the mark.
return self->_runningNetworkTransferCount != 0;
}
- (void)incrementRunningNetworkTransferCount
{
BOOL movingToInUse;
assert([NSThread isMainThread]);
movingToInUse = (self->_runningNetworkTransferCount == 0);
if (movingToInUse) {
[self willChangeValueForKey:@"networkInUse"];
}
self->_runningNetworkTransferCount += 1;
if (movingToInUse) {
[self didChangeValueForKey:@"networkInUse"];
}
}
- (void)decrementRunningNetworkTransferCount
{
BOOL movingToNotInUse;
assert([NSThread isMainThread]);
assert(self->_runningNetworkTransferCount != 0);
movingToNotInUse = (self->_runningNetworkTransferCount == 1);
if (movingToNotInUse) {
[self willChangeValueForKey:@"networkInUse"];
}
self->_runningNetworkTransferCount -= 1;
if (movingToNotInUse) {
[self didChangeValueForKey:@"networkInUse"];
}
}
@synthesize queueForNetworkTransfers = _queueForNetworkTransfers;
@synthesize queueForNetworkManagement = _queueForNetworkManagement;
@synthesize queueForCPU = _queueForCPU;
- (void)addOperation:(NSOperation *)operation toQueue:(NSOperationQueue *)queue finishedTarget:(id)target action:(SEL)action
// Core code to enqueue an operation on a queue.
{
// any thread
assert(operation != nil);
assert(target != nil);
assert(action != nil);
// In the debug build, apply our debugging preferences to any operations
// we enqueue.
#if ! defined(NDEBUG)
// While, in theory, networkErrorRate should only apply to network operations, we
// apply it to all operations if they support the -setDebugError: method.
if ( [operation respondsToSelector:@selector(setDebugError:)] ) {
static NSInteger sOperationCount;
NSInteger networkErrorRate;
networkErrorRate = [[NSUserDefaults standardUserDefaults] integerForKey:@"networkErrorRate"];
if (networkErrorRate != 0) {
sOperationCount += 1;
if ( (sOperationCount % networkErrorRate) == 0) {
[(id)operation setDebugError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]];
}
}
}
if ( [operation respondsToSelector:@selector(setDebugDelay:)] ) {
NSTimeInterval operationDelay;
operationDelay = [[NSUserDefaults standardUserDefaults] doubleForKey:@"operationDelay"];
if (operationDelay > 0.0) {
[(id)operation setDebugDelay:operationDelay];
}
}
#endif
// Update our networkInUse property; because we can be running on any thread, we
// do this update on the main thread.
if (queue == self.queueForNetworkTransfers) {
[self performSelectorOnMainThread:@selector(incrementRunningNetworkTransferCount) withObject:nil waitUntilDone:NO];
}
// Atomically enter the operation into our target and action maps.
@synchronized (self) {
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToActionMap) );
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToThreadMap) );
assert( CFDictionaryGetValue(self->_runningOperationToTargetMap, operation) == NULL ); // shouldn't already be in our map
assert( CFDictionaryGetValue(self->_runningOperationToActionMap, operation) == NULL ); // shouldn't already be in our map
assert( CFDictionaryGetValue(self->_runningOperationToThreadMap, operation) == NULL ); // shouldn't already be in our map
// Add the operations to , triggering a KVO notification
// of networkInUse if required.
CFDictionarySetValue(self->_runningOperationToTargetMap, operation, target);
CFDictionarySetValue(self->_runningOperationToActionMap, operation, action);
CFDictionarySetValue(self->_runningOperationToThreadMap, operation, [NSThread currentThread]);
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToActionMap) );
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToThreadMap) );
}
// Observe the isFinished property of the operation. We pass the queue parameter as the
// context so that, in the completion routine, we know what queue the operation was sent
// to (necessary to decide what thread to run the target/action on).
[operation addObserver:self forKeyPath:@"isFinished" options:0 context:queue];
// Queue the operation. When the operation completes, -operationDone: is called.
[queue addOperation:operation];
}
- (void)addNetworkManagementOperation:(NSOperation *)operation finishedTarget:(id)target action:(SEL)action
// See comment in header.
{
if ([operation respondsToSelector:@selector(setRunLoopThread:)]) {
if ( [(id)operation runLoopThread] == nil ) {
[ (id)operation setRunLoopThread:self.networkRunLoopThread];
}
}
[self addOperation:operation toQueue:self.queueForNetworkManagement finishedTarget:target action:action];
}
- (void)addNetworkTransferOperation:(NSOperation *)operation finishedTarget:(id)target action:(SEL)action
// See comment in header.
{
if ([operation respondsToSelector:@selector(setRunLoopThread:)]) {
if ( [(id)operation runLoopThread] == nil ) {
[ (id)operation setRunLoopThread:self.networkRunLoopThread];
}
}
[self addOperation:operation toQueue:self.queueForNetworkTransfers finishedTarget:target action:action];
}
- (void)addCPUOperation:(NSOperation *)operation finishedTarget:(id)target action:(SEL)action
// See comment in header.
{
[self addOperation:operation toQueue:self.queueForCPU finishedTarget:target action:action];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// any thread
if ( [keyPath isEqual:@"isFinished"] ) {
NSOperation * operation;
NSOperationQueue * queue;
NSThread * thread;
operation = (NSOperation *) object;
assert([operation isKindOfClass:[NSOperation class]]);
assert([operation isFinished]);
queue = (NSOperationQueue *) context;
assert([queue isKindOfClass:[NSOperationQueue class]]);
[operation removeObserver:self forKeyPath:@"isFinished"];
@synchronized (self) {
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToActionMap) );
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToThreadMap) );
thread = (NSThread *) CFDictionaryGetValue(self->_runningOperationToThreadMap, operation);
if (thread != nil) {
[thread retain];
}
}
if (thread != nil) {
[self performSelector:@selector(operationDone:) onThread:thread withObject:operation waitUntilDone:NO];
[thread release];
if (queue == self.queueForNetworkTransfers) {
[self performSelectorOnMainThread:@selector(decrementRunningNetworkTransferCount) withObject:nil waitUntilDone:NO];
}
}
} else if (NO) { // Disabled because the super class does nothing useful with it.
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)operationDone:(NSOperation *)operation
// Called by the operation queue when the operation is done. We find the corresponding
// target/action and call it on this thread.
{
id target;
SEL action;
NSThread * thread;
// any thread
assert(operation != nil);
// Find the target/action, if any, in the map and then remove it.
@synchronized (self) {
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToActionMap) );
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToThreadMap) );
target = (id) CFDictionaryGetValue(self->_runningOperationToTargetMap, operation);
action = (SEL) CFDictionaryGetValue(self->_runningOperationToActionMap, operation);
thread = (NSThread *) CFDictionaryGetValue(self->_runningOperationToThreadMap, operation);
assert( (target != nil) == (action != nil) );
assert( (target != nil) == (thread != nil) );
// We need target to persist across the remove /and/ after we leave the @synchronized
// block, so we retain it here. We need to test target for nil because -cancelOperation:
// might have pulled it out from underneath us.
if (target != nil) {
[target retain];
assert( thread == [NSThread currentThread] );
CFDictionaryRemoveValue(self->_runningOperationToTargetMap, operation);
CFDictionaryRemoveValue(self->_runningOperationToActionMap, operation);
CFDictionaryRemoveValue(self->_runningOperationToThreadMap, operation);
}
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToActionMap) );
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToThreadMap) );
}
// If we removed the operation, call the target/action. However, we still have to
// test isCancelled here because -cancelOperation: might have cancelled it but
// not yet pulled it out of the map.
//
// Note that there's no race condition testing isCancelled here. We know that the
// operation is out of the map at this point (specifically, at the point we leave
// the @synchronized block), so no one can call -cancelOperation: on the operation.
// So, the final fate of the operation, cancelled or not, is determined before
// we enter the @synchronized block.
if (target != nil) {
if ( ! [operation isCancelled] ) {
[target performSelector:action withObject:operation];
}
[target release];
}
}
- (void)cancelOperation:(NSOperation *)operation
// See comment in header.
{
id target;
SEL action;
NSThread * thread;
// any thread
// To simplify the client's clean up code, we specifically allow the operation to be nil
// and the operation to not be queued.
if (operation != nil) {
// We do the cancellation outside of the @synchronized block because it might take
// some time.
[operation cancel];
// Now we pull the target/action out of the map.
@synchronized (self) {
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToActionMap) );
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToThreadMap) );
target = (id) CFDictionaryGetValue(self->_runningOperationToTargetMap, operation);
action = (SEL) CFDictionaryGetValue(self->_runningOperationToActionMap, operation);
thread = (NSThread *) CFDictionaryGetValue(self->_runningOperationToThreadMap, operation);
assert( (target != nil) == (action != nil) );
assert( (target != nil) == (thread != nil) );
// We don't need to retain target here because we never actually call it, we just
// test it for nil. We need to test for target for nil because -operationDone:
// might have won the race to pull it out.
if (target != nil) {
CFDictionaryRemoveValue(self->_runningOperationToTargetMap, operation);
CFDictionaryRemoveValue(self->_runningOperationToActionMap, operation);
CFDictionaryRemoveValue(self->_runningOperationToThreadMap, operation);
}
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToActionMap) );
assert( CFDictionaryGetCount(self->_runningOperationToTargetMap) == CFDictionaryGetCount(self->_runningOperationToThreadMap) );
}
}
}
@end