commit dffe2570634267414d7079bf175abb6f1ec7ffc1 Author: Mattt Thompson Date: Tue May 31 16:27:34 2011 -0500 Initial import diff --git a/AFCallback.h b/AFCallback.h new file mode 100644 index 0000000..0385ea9 --- /dev/null +++ b/AFCallback.h @@ -0,0 +1,36 @@ +// AFCallback.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@protocol AFCallback ++ (id)callbackWithSuccess:(id)success; ++ (id)callbackWithSuccess:(id)success error:(id)error; +@end + +@interface AFCallback : NSObject { +@private + id _successBlock; + id _errorBlock; +} + +@end \ No newline at end of file diff --git a/AFCallback.m b/AFCallback.m new file mode 100644 index 0000000..db7f1a9 --- /dev/null +++ b/AFCallback.m @@ -0,0 +1,60 @@ +// AFCallback.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFCallback.h" + +@interface AFCallback () +@property (readwrite, nonatomic, copy) id successBlock; +@property (readwrite, nonatomic, copy) id errorBlock; +@end + +@implementation AFCallback +@synthesize successBlock = _successBlock; +@synthesize errorBlock = _errorBlock; + ++ (id)callbackWithSuccess:(id)success { + return [self callbackWithSuccess:success error:nil]; +} + ++ (id)callbackWithSuccess:(id)success error:(id)error { + id callback = [[[self alloc] init] autorelease]; + [callback setSuccessBlock:success]; + [callback setErrorBlock:error]; + + return callback; +} + +- (id)init { + if ([self class] == [AFCallback class]) { + [NSException raise:NSInternalInconsistencyException format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]; + } + + return [super init]; +} + +- (void)dealloc { + [_successBlock release]; + [_errorBlock release]; + [super dealloc]; +} + +@end \ No newline at end of file diff --git a/AFGowallaAPI.h b/AFGowallaAPI.h new file mode 100644 index 0000000..41bdd45 --- /dev/null +++ b/AFGowallaAPI.h @@ -0,0 +1,52 @@ +// AFGowallaAPI.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFHTTPOperation.h" + +extern NSString * const kAFGowallaClientID; +extern NSString * const kAFGowallaClientSecret; + +@interface AFGowallaAPI : NSObject + ++ (NSString *)defaultValueForHeader:(NSString *)header; ++ (void)setDefaultHeader:(NSString *)header value:(NSString *)value; ++ (void)setAuthorizationHeaderWithToken:(NSString *)token; ++ (void)clearAuthorizationHeader; + ++ (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters; ++ (void)enqueueHTTPOperationWithRequest:(NSURLRequest *)request callback:(AFHTTPOperationCallback *)callback; ++ (void)enqueueHTTPOperation:(AFHTTPOperation *)operation; + ++ (void)getPath:(NSString *)path parameters:(NSDictionary *)parameters callback:(AFHTTPOperationCallback *)callback; ++ (void)postPath:(NSString *)path parameters:(NSDictionary *)parameters callback:(AFHTTPOperationCallback *)callback; ++ (void)putPath:(NSString *)path parameters:(NSDictionary *)parameters callback:(AFHTTPOperationCallback *)callback; ++ (void)deletePath:(NSString *)path parameters:(NSDictionary *)parameters callback:(AFHTTPOperationCallback *)callback; + +@end + +#pragma mark - NSString + AFGowallaAPI + +@interface NSString (AFGowallaAPI) +- (NSString *)urlEncodedString; +- (NSString *)urlEncodedStringWithEncoding:(NSStringEncoding)encoding; +@end \ No newline at end of file diff --git a/AFGowallaAPI.m b/AFGowallaAPI.m new file mode 100644 index 0000000..de92864 --- /dev/null +++ b/AFGowallaAPI.m @@ -0,0 +1,171 @@ +// AFGowallaAPI.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFGowallaAPI.h" +#import "AFHTTPOperation.h" + +// Replace this with your own API Key, available at http://api.gowalla.com/api/keys/ +NSString * const kAFGowallaClientID = @"e7ccb7d3d2414eb2af4663fc91eb2793"; + +static NSString * const kAFGowallaBaseURLString = @"https://api.gowalla.com/"; + +static NSStringEncoding const kAFStringEncoding = NSUTF8StringEncoding; + +static NSMutableDictionary *_defaultHeaders = nil; +static NSOperationQueue *_operationQueue = nil; + +@implementation AFGowallaAPI + ++ (void)initialize { + _operationQueue = [[NSOperationQueue alloc] init]; + [_operationQueue setMaxConcurrentOperationCount:2]; + + _defaultHeaders = [[NSMutableDictionary alloc] init]; + + // Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 + [self setDefaultHeader:@"Accept" value:@"application/json"]; + + // Accept-Encoding HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 + [self setDefaultHeader:@"Accept-Encoding" value:@"gzip"]; + + // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 + NSString *preferredLanguageCodes = [[NSLocale preferredLanguages] componentsJoinedByString:@", "]; + [self setDefaultHeader:@"Accept-Language" value:[NSString stringWithFormat:@"%@, en-us;q=0.8", preferredLanguageCodes]]; + + // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 + [self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"AFNetworkingExample/%@ (%@, %@ %@, %@, Scale/%f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"], @"unknown", [[UIDevice currentDevice] systemName], [[UIDevice currentDevice] systemVersion], [[UIDevice currentDevice] model], ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0)]]; + + // X-Gowalla-API-Key HTTP Header; see http://api.gowalla.com/api/docs + [self setDefaultHeader:@"X-Gowalla-API-Key" value:kAFGowallaClientID]; + + // X-Gowalla-API-Version HTTP Header; see http://api.gowalla.com/api/docs + [self setDefaultHeader:@"X-Gowalla-API-Version" value:@"1"]; + + // X-UDID HTTP Header + [self setDefaultHeader:@"X-UDID" value:[[UIDevice currentDevice] uniqueIdentifier]]; +} + ++ (NSString *)defaultValueForHeader:(NSString *)header { + return [_defaultHeaders valueForKey:header]; +} + ++ (void)setDefaultHeader:(NSString *)header value:(NSString *)value { + [_defaultHeaders setObject:value forKey:header]; +} + ++ (void)setAuthorizationHeaderWithToken:(NSString *)token { + [self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Token token=\"%@\"", token]]; +} + ++ (void)clearAuthorizationHeader { + [_defaultHeaders removeObjectForKey:@"Authorization"]; +} + ++ (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters { + NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease]; + NSMutableDictionary *headers = [_defaultHeaders mutableCopy]; + NSURL *url = nil; + + NSMutableArray *mutableParameterComponents = [NSMutableArray array]; + for (id key in [parameters allKeys]) { + NSString *component = [NSString stringWithFormat:@"%@=%@", [key urlEncodedStringWithEncoding:kAFStringEncoding], [[parameters valueForKey:key] urlEncodedStringWithEncoding:kAFStringEncoding]]; + [mutableParameterComponents addObject:component]; + } + NSString *queryString = [mutableParameterComponents componentsJoinedByString:@"&"]; + + if ([method isEqualToString:@"GET"]) { + path = [path stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", queryString]; + url = [NSURL URLWithString:path relativeToURL:[NSURL URLWithString:kAFGowallaBaseURLString]]; + } else { + url = [NSURL URLWithString:path relativeToURL:[NSURL URLWithString:kAFGowallaBaseURLString]]; + NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(kAFStringEncoding)); + [headers setObject:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset] forKey:@"Content-Type"]; + [request setHTTPBody:[queryString dataUsingEncoding:NSUTF8StringEncoding]]; + } + + [request setURL:url]; + [request setHTTPMethod:method]; + [request setHTTPShouldHandleCookies:NO]; + [request setAllHTTPHeaderFields:headers]; + + return request; +} + ++ (void)enqueueHTTPOperationWithRequest:(NSURLRequest *)request callback:(AFHTTPOperationCallback *)callback { + if ([request URL] == nil || [[request URL] isEqual:[NSNull null]]) { + return; + } + + AFHTTPOperation *operation = [[[AFHTTPOperation alloc] initWithRequest:request callback:callback] autorelease]; + [self enqueueHTTPOperation:operation]; +} + ++ (void)enqueueHTTPOperation:(AFHTTPOperation *)operation { + [_operationQueue addOperation:operation]; +} + +#pragma mark - +#pragma mark HTTP Methods + ++ (void)getPath:(NSString *)path parameters:(NSDictionary *)parameters callback:(AFHTTPOperationCallback *)callback { + NSURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:parameters]; + [self enqueueHTTPOperationWithRequest:request callback:callback]; +} + ++ (void)postPath:(NSString *)path parameters:(NSDictionary *)parameters callback:(AFHTTPOperationCallback *)callback { + NSURLRequest *request = [self requestWithMethod:@"POST" path:path parameters:parameters]; + [self enqueueHTTPOperationWithRequest:request callback:callback]; +} + ++ (void)putPath:(NSString *)path parameters:(NSDictionary *)parameters callback:(AFHTTPOperationCallback *)callback { + NSURLRequest *request = [self requestWithMethod:@"PUT" path:path parameters:parameters]; + [self enqueueHTTPOperationWithRequest:request callback:callback]; +} + ++ (void)deletePath:(NSString *)path parameters:(NSDictionary *)parameters callback:(AFHTTPOperationCallback *)callback { + NSURLRequest *request = [self requestWithMethod:@"DELETE" path:path parameters:parameters]; + [self enqueueHTTPOperationWithRequest:request callback:callback]; +} + +@end + +#pragma mark - +#pragma mark - + +@implementation NSString (AFGowallaAPI) + +// See http://github.com/pokeb/asi-http-request/raw/master/Classes/ASIFormDataRequest.m +- (NSString*)urlEncodedString { + return [self urlEncodedStringWithEncoding:NSUTF8StringEncoding]; +} + +- (NSString *)urlEncodedStringWithEncoding:(NSStringEncoding)encoding { + NSString *newString = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)self, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding(encoding)) autorelease]; + + if (newString) { + return newString; + } + + return @""; +} + +@end diff --git a/AFHTTPOperation.h b/AFHTTPOperation.h new file mode 100644 index 0000000..6eb977a --- /dev/null +++ b/AFHTTPOperation.h @@ -0,0 +1,69 @@ +// AFHTTPOperation.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "QHTTPOperation.h" +#import "AFCallback.h" + +extern NSString * const AFHTTPOperationDidStartNotification; +extern NSString * const AFHTTPOperationDidSucceedNotification; +extern NSString * const AFHTTPOperationDidFailNotification; + +extern NSString * const AFHTTPOperationParsedDataErrorKey; + +@class AFHTTPOperationCallback; + +// QHTTPOperation/NSOperation subclass that provides JSON parsing and started/finished notifications. +// +// Usage: +// NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://apple.com"]]; +// AFHTTPOperation *op = [[[AFHTTPOperation alloc] initWithRequest:request] autorelease]; +// [[NSOperationQueue mainQueue] addOperation:op]; +// +@interface AFHTTPOperation : QHTTPOperation { +@private + AFHTTPOperationCallback *_callback; +} + +@property (nonatomic, retain) AFHTTPOperationCallback *callback; +@property (readonly) NSString *responseString; + ++ (id)operationWithRequest:(NSURLRequest *)urlRequest callback:(AFHTTPOperationCallback *)callback; +- (id)initWithRequest:(NSURLRequest *)urlRequest callback:(AFHTTPOperationCallback *)callback; + +@end + +#pragma mark - AFHTTPOperationCallback + +typedef void (^AFHTTPOperationSuccessBlock)(NSURLRequest *request, NSHTTPURLResponse *response, NSDictionary *data); +typedef void (^AFHTTPOperationErrorBlock)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error); + +@protocol AFHTTPOperationCallback +@optional ++ (id)callbackWithSuccess:(AFHTTPOperationSuccessBlock)success; ++ (id)callbackWithSuccess:(AFHTTPOperationSuccessBlock)success error:(AFHTTPOperationErrorBlock)error; +@end + +@interface AFHTTPOperationCallback : AFCallback +@property (readwrite, nonatomic, copy) AFHTTPOperationSuccessBlock successBlock; +@property (readwrite, nonatomic, copy) AFHTTPOperationErrorBlock errorBlock; +@end \ No newline at end of file diff --git a/AFHTTPOperation.m b/AFHTTPOperation.m new file mode 100644 index 0000000..37286bb --- /dev/null +++ b/AFHTTPOperation.m @@ -0,0 +1,106 @@ +// AFHTTPOperation.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFHTTPOperation.h" +#import "JSONKit.h" + +NSString * const AFHTTPOperationDidStartNotification = @"com.alamofire.http-operation.start"; +NSString * const AFHTTPOperationDidSucceedNotification = @"com.alamofire.http-operation.success"; +NSString * const AFHTTPOperationDidFailNotification = @"com.alamofire.http-operation.failure"; + +NSString * const AFHTTPOperationParsedDataErrorKey = @"com.alamofire.http-operation.error.parsed-data"; + +@implementation AFHTTPOperation +@synthesize callback = _callback; + ++ (id)operationWithRequest:(NSURLRequest *)urlRequest callback:(AFHTTPOperationCallback *)callback { + return [[[self alloc] initWithRequest:urlRequest callback:callback] autorelease]; +} + +- (id)initWithRequest:(NSURLRequest *)urlRequest callback:(AFHTTPOperationCallback *)callback { + self = [super initWithRequest:urlRequest]; + if (!self) { + return nil; + } + + self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/plain", nil]; + self.callback = callback; + + return self; +} + +- (void)dealloc { + [_callback release]; + [super dealloc]; +} + +- (NSString *)responseString { + return [[[NSString alloc] initWithData:self.responseBody encoding:NSUTF8StringEncoding] autorelease]; +} + +#pragma mark - QRunLoopOperation + +- (void)operationDidStart { + [super operationDidStart]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFHTTPOperationDidStartNotification object:self]; +} + +- (void)finishWithError:(NSError *)error { + [super finishWithError:error]; + + NSDictionary *data = nil; + if (self.contentTypeAcceptable) { + if ([[self.lastResponse MIMEType] isEqualToString:@"application/json"]) { + NSError *jsonError = nil; + data = [[JSONDecoder decoder] parseJSONData:self.responseBody error:&jsonError]; + } + } + + if (self.statusCodeAcceptable) { + [[NSNotificationCenter defaultCenter] postNotificationName:AFHTTPOperationDidSucceedNotification object:self]; + + if(self.callback.successBlock) { + self.callback.successBlock(self.lastRequest, self.lastResponse, data); + } + } else { + [[NSNotificationCenter defaultCenter] postNotificationName:AFHTTPOperationDidFailNotification object:self]; + + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[error userInfo]]; + [userInfo setValue:[NSHTTPURLResponse localizedStringForStatusCode:[self.lastResponse statusCode]] forKey:NSLocalizedDescriptionKey]; + [userInfo setValue:[[self.lastRequest URL] absoluteString] forKey:NSURLErrorFailingURLStringErrorKey]; + [userInfo setValue:data forKey:AFHTTPOperationParsedDataErrorKey]; + + error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:[self.lastResponse statusCode] userInfo:userInfo]; + + if (self.callback.errorBlock) { + self.callback.errorBlock(self.lastRequest, self.lastResponse, error); + } + } +} + +@end + +#pragma mark - AFHTTPOperationCallback + +@implementation AFHTTPOperationCallback +@dynamic successBlock, errorBlock; +@end \ No newline at end of file diff --git a/AFImageRequest.h b/AFImageRequest.h new file mode 100644 index 0000000..a43da5f --- /dev/null +++ b/AFImageRequest.h @@ -0,0 +1,38 @@ +// AFImageRequest.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFImageRequestOperation.h" + +@protocol AFImageRequester +@required +- (void)setImageURLString:(NSString *)urlString; +- (void)setImageURLString:(NSString *)urlString options:(AFImageRequestOptions)options; +@optional +@property (nonatomic, copy) NSString *imageURLString; +@end + +@interface AFImageRequest : NSObject + ++ (void)requestImageWithURLString:(NSString *)urlString options:(AFImageRequestOptions)options block:(void (^)(UIImage *image))block; ++ (void)requestImageWithURLString:(NSString *)urlString size:(CGSize)imageSize options:(AFImageRequestOptions)options block:(void (^)(UIImage *image))block; + +@end diff --git a/AFImageRequest.m b/AFImageRequest.m new file mode 100644 index 0000000..7e20e4a --- /dev/null +++ b/AFImageRequest.m @@ -0,0 +1,73 @@ +// AFImageRequest.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFImageRequest.h" +#import "AFImageRequestOperation.h" +#import "AFURLCache.h" + +static NSOperationQueue *_operationQueue = nil; +static NSMutableSet *_cachedRequests = nil; + +@implementation AFImageRequest + ++ (void)initialize { + _operationQueue = [[NSOperationQueue alloc] init]; + [_operationQueue setMaxConcurrentOperationCount:6]; + + _cachedRequests = [[NSMutableSet alloc] init]; +} + ++ (void)requestImageWithURLString:(NSString *)urlString options:(AFImageRequestOptions)options block:(void (^)(UIImage *image))block { + [self requestImageWithURLString:urlString size:CGSizeZero options:options block:block]; +} + ++ (void)requestImageWithURLString:(NSString *)urlString size:(CGSize)imageSize options:(AFImageRequestOptions)options block:(void (^)(UIImage *image))block { + // Append a hash to the image URL so that unique image options get cached separately + NSString *cacheKey = [NSString stringWithFormat:@"%fx%f:%d", imageSize.width, imageSize.height, options]; + NSURL *url = [NSURL URLWithString:[urlString stringByAppendingString:[NSString stringWithFormat:@"#%@", cacheKey]]]; + if (!url) { + if (block) { + block(nil); + } + return; + } + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLCacheStorageAllowed timeoutInterval:30.0]; + [request setHTTPShouldHandleCookies:NO]; + + AFImageRequestOperationCallback *callback = [AFImageRequestOperationCallback callbackWithSuccess:block]; + callback.options = options; + callback.imageSize = imageSize; + AFImageRequestOperation *operation = [[[AFImageRequestOperation alloc] initWithRequest:request callback:callback] autorelease]; + + NSCachedURLResponse *cachedResponse = [[[[AFURLCache sharedURLCache] cachedResponseForRequest:request] retain] autorelease]; + if (cachedResponse) { + if (block) { + block([UIImage imageWithData:[cachedResponse data]]); + return; + } + } + + [_operationQueue addOperation:operation]; +} + +@end diff --git a/AFImageRequestOperation.h b/AFImageRequestOperation.h new file mode 100644 index 0000000..a02e7ba --- /dev/null +++ b/AFImageRequestOperation.h @@ -0,0 +1,72 @@ +// AFImageRequestOperation.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import +#import "QHTTPOperation.h" +#import "AFCallback.h" + +typedef enum { + AFImageRequestResize = 1 << 1, + AFImageRequestRoundCorners = 1 << 2, + AFImageCacheProcessedImage = 1 << 0xA, + AFImageRequestDefaultOptions = AFImageRequestResize, +} AFImageRequestOptions; + +@class AFImageRequestOperationCallback; + +@interface AFImageRequestOperation : QHTTPOperation { +@private + AFImageRequestOperationCallback *_callback; +} + +@property (nonatomic, retain) AFImageRequestOperationCallback *callback; + +- (id)initWithRequest:(NSURLRequest *)someRequest callback:(AFImageRequestOperationCallback *)someCallback; + +@end + +#pragma mark - AFHTTPOperationCallback + +typedef void (^AFImageRequestOperationSuccessBlock)(UIImage *image); +typedef void (^AFImageRequestOperationErrorBlock)(NSError *error); + +@protocol AFImageRequestOperationCallback +@optional ++ (id)callbackWithSuccess:(AFImageRequestOperationSuccessBlock)success; ++ (id)callbackWithSuccess:(AFImageRequestOperationSuccessBlock)success error:(AFImageRequestOperationErrorBlock)error; +@end + +@interface AFImageRequestOperationCallback : AFCallback { +@private + CGSize _imageSize; + AFImageRequestOptions _options; +} + +@property (readwrite, nonatomic, assign) CGSize imageSize; +@property (readwrite, nonatomic, assign) AFImageRequestOptions options; + +@property (readwrite, nonatomic, copy) AFImageRequestOperationSuccessBlock successBlock; +@property (readwrite, nonatomic, copy) AFImageRequestOperationErrorBlock errorBlock; + ++ (id)callbackWithSuccess:(AFImageRequestOperationSuccessBlock)success imageSize:(CGSize)imageSize options:(AFImageRequestOptions)options; +@end \ No newline at end of file diff --git a/AFImageRequestOperation.m b/AFImageRequestOperation.m new file mode 100644 index 0000000..55772f5 --- /dev/null +++ b/AFImageRequestOperation.m @@ -0,0 +1,140 @@ +// AFImageRequestOperation.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFImageRequestOperation.h" +#import "AFURLCache.h" + +#import "UIImage+AFNetworking.h" + +const CGFloat kAFImageRequestJPEGQuality = 0.8; +const NSUInteger kAFImageRequestMaximumResponseSize = 8 * 1024 * 1024; +static inline CGSize kAFImageRequestRoundedCornerRadii(CGSize imageSize) { + CGFloat dimension = fmaxf(imageSize.width, imageSize.height); + return CGSizeMake(dimension, dimension); +} + +@implementation AFImageRequestOperation +@synthesize callback; + +- (id)initWithRequest:(NSURLRequest *)someRequest callback:(AFImageRequestOperationCallback *)someCallback { + self = [super initWithRequest:someRequest]; + if (!self) { + return nil; + } + + self.maximumResponseSize = kAFImageRequestMaximumResponseSize; + + NSMutableIndexSet *statusCodes = [NSMutableIndexSet indexSetWithIndex:0]; + [statusCodes addIndexesInRange:NSMakeRange(200, 100)]; + self.acceptableStatusCodes = statusCodes; + self.acceptableContentTypes = [NSSet setWithObjects:@"image/png", @"image/jpeg", @"image/pjpeg", @"image/gif", @"application/x-0", nil]; + self.callback = someCallback; + + if (self.callback) { + self.runLoopModes = [NSSet setWithObjects:NSRunLoopCommonModes, NSDefaultRunLoopMode, nil]; + } + + return self; +} + +#pragma mark - QHTTPRequestOperation + +// QHTTPRequestOperation requires this to return an NSHTTPURLResponse, but in certain circumstances, +// this method would otherwise return an instance of its superclass, NSURLResponse +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + if([response isKindOfClass:[NSHTTPURLResponse class]]) { + [super connection:connection didReceiveResponse:response]; + } else { + [super connection:connection didReceiveResponse:[[[NSHTTPURLResponse alloc] initWithURL:[response URL] MIMEType:[response MIMEType] expectedContentLength:[response expectedContentLength] textEncodingName:[response textEncodingName]] autorelease]]; + } +} + +#pragma mark - QRunLoopOperation + +- (void)finishWithError:(NSError *)error { + [super finishWithError:error]; + + if (error) { + if (callback.errorBlock) { + callback.errorBlock(error); + } + + return; + } + + UIImage *image = nil; + if ([[UIScreen mainScreen] scale] == 2.0) { + CGImageRef imageRef = [UIImage imageWithData:self.responseBody].CGImage; + image = [UIImage imageWithCGImage:imageRef scale:2.0 orientation:UIImageOrientationUp]; + } else { + image = [UIImage imageWithData:self.responseBody]; + } + + BOOL didProcessingOnImage = NO; + + if ((self.callback.options & AFImageRequestResize) && !(CGSizeEqualToSize(image.size, self.callback.imageSize) || CGSizeEqualToSize(self.callback.imageSize, CGSizeZero))) { + image = [UIImage imageByScalingAndCroppingImage:image size:self.callback.imageSize]; + didProcessingOnImage = YES; + } + if ((self.callback.options & AFImageRequestRoundCorners)) { + image = [UIImage imageByRoundingCornersOfImage:image corners:UIRectCornerAllCorners cornerRadii:kAFImageRequestRoundedCornerRadii(image.size)]; + didProcessingOnImage = YES; + } + + + if (self.callback.successBlock) { + self.callback.successBlock(image); + } + + if ((self.callback.options & AFImageCacheProcessedImage) && didProcessingOnImage) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSData *processedImageData = nil; + if ((self.callback.options & AFImageRequestRoundCorners) || [[[[self.lastRequest URL] path] pathExtension] isEqualToString:@"png"]) { + processedImageData = UIImagePNGRepresentation(image); + } else { + processedImageData = UIImageJPEGRepresentation(image, kAFImageRequestJPEGQuality); + } + NSURLResponse *response = [[[NSURLResponse alloc] initWithURL:[self.lastRequest URL] MIMEType:[self.lastResponse MIMEType] expectedContentLength:[processedImageData length] textEncodingName:[self.lastResponse textEncodingName]] autorelease]; + NSCachedURLResponse *cachedResponse = [[[NSCachedURLResponse alloc] initWithResponse:response data:processedImageData] autorelease]; + [[AFURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:self.lastRequest]; + [pool drain]; + } +} + +@end + +#pragma mark - AFHTTPOperationCallback + +@implementation AFImageRequestOperationCallback : AFCallback +@synthesize options = _options; +@synthesize imageSize = _imageSize; +@dynamic successBlock, errorBlock; + ++ (id)callbackWithSuccess:(AFImageRequestOperationSuccessBlock)success imageSize:(CGSize)imageSize options:(AFImageRequestOptions)options { + id callback = [self callbackWithSuccess:success]; + [callback setImageSize:imageSize]; + [callback setOptions:options]; + + return callback; +} + +@end \ No newline at end of file diff --git a/AFNetworkingExample.xcodeproj/project.pbxproj b/AFNetworkingExample.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b4f1455 --- /dev/null +++ b/AFNetworkingExample.xcodeproj/project.pbxproj @@ -0,0 +1,440 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + F8E469651395739D00DB05C8 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469641395739D00DB05C8 /* UIKit.framework */; }; + F8E469671395739D00DB05C8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469661395739D00DB05C8 /* Foundation.framework */; }; + F8E469691395739D00DB05C8 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469681395739D00DB05C8 /* CoreGraphics.framework */; }; + F8E469721395739D00DB05C8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469711395739D00DB05C8 /* main.m */; }; + F8E469751395739D00DB05C8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469741395739D00DB05C8 /* AppDelegate.m */; }; + F8E4698E1395742500DB05C8 /* AFCallback.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469851395742500DB05C8 /* AFCallback.m */; }; + F8E4698F1395742500DB05C8 /* AFHTTPOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469871395742500DB05C8 /* AFHTTPOperation.m */; }; + F8E469901395742500DB05C8 /* AFImageRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469891395742500DB05C8 /* AFImageRequestOperation.m */; }; + F8E469911395742500DB05C8 /* AFURLCache.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E4698B1395742500DB05C8 /* AFURLCache.m */; }; + F8E469921395742500DB05C8 /* UIImage+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E4698D1395742500DB05C8 /* UIImage+AFNetworking.m */; }; + F8E469A9139574DA00DB05C8 /* QHTTPOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469A4139574DA00DB05C8 /* QHTTPOperation.m */; }; + F8E469AA139574DA00DB05C8 /* QReachabilityOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469A6139574DA00DB05C8 /* QReachabilityOperation.m */; }; + F8E469AB139574DA00DB05C8 /* QRunLoopOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469A8139574DA00DB05C8 /* QRunLoopOperation.m */; }; + F8E469B31395752400DB05C8 /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469B21395752400DB05C8 /* JSONKit.m */; }; + F8E469BC139575D000DB05C8 /* AFGowallaAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469B9139575D000DB05C8 /* AFGowallaAPI.m */; }; + F8E469BD139575D000DB05C8 /* AFImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469BB139575D000DB05C8 /* AFImageRequest.m */; }; + F8E469CB139577E000DB05C8 /* NearbySpotsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469C8139577E000DB05C8 /* NearbySpotsViewController.m */; }; + F8E469CE1395780600DB05C8 /* SpotTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469CD1395780600DB05C8 /* SpotTableViewCell.m */; }; + F8E469D11395781500DB05C8 /* Spot.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469D01395781500DB05C8 /* Spot.m */; }; + F8E469DF13957DD500DB05C8 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469DE13957DD500DB05C8 /* CoreLocation.framework */; }; + F8E469E113957DF100DB05C8 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E013957DF100DB05C8 /* Security.framework */; }; + F8E469E513957E0400DB05C8 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E413957E0400DB05C8 /* SystemConfiguration.framework */; }; + F8E469EC13957FC500DB05C8 /* TTTLocationFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E469EB13957FC500DB05C8 /* TTTLocationFormatter.m */; }; + F8E469F01395812A00DB05C8 /* placeholder-stamp.png in Resources */ = {isa = PBXBuildFile; fileRef = F8E469EE1395812A00DB05C8 /* placeholder-stamp.png */; }; + F8E469F11395812A00DB05C8 /* placeholder-stamp@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F8E469EF1395812A00DB05C8 /* placeholder-stamp@2x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + F8E469601395739C00DB05C8 /* AFNetworkingExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AFNetworkingExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F8E469641395739D00DB05C8 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + F8E469661395739D00DB05C8 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + F8E469681395739D00DB05C8 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + F8E4696C1395739D00DB05C8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F8E469701395739D00DB05C8 /* Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = ""; }; + F8E469711395739D00DB05C8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + F8E469731395739D00DB05C8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + F8E469741395739D00DB05C8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + F8E469841395742500DB05C8 /* AFCallback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFCallback.h; sourceTree = ""; }; + F8E469851395742500DB05C8 /* AFCallback.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFCallback.m; sourceTree = ""; }; + F8E469861395742500DB05C8 /* AFHTTPOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPOperation.h; sourceTree = ""; }; + F8E469871395742500DB05C8 /* AFHTTPOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPOperation.m; sourceTree = ""; }; + F8E469881395742500DB05C8 /* AFImageRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFImageRequestOperation.h; sourceTree = ""; }; + F8E469891395742500DB05C8 /* AFImageRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFImageRequestOperation.m; sourceTree = ""; }; + F8E4698A1395742500DB05C8 /* AFURLCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFURLCache.h; sourceTree = ""; }; + F8E4698B1395742500DB05C8 /* AFURLCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLCache.m; sourceTree = ""; }; + F8E4698C1395742500DB05C8 /* UIImage+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+AFNetworking.h"; sourceTree = ""; }; + F8E4698D1395742500DB05C8 /* UIImage+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+AFNetworking.m"; sourceTree = ""; }; + F8E469A3139574DA00DB05C8 /* QHTTPOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QHTTPOperation.h; sourceTree = ""; }; + F8E469A4139574DA00DB05C8 /* QHTTPOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QHTTPOperation.m; sourceTree = ""; }; + F8E469A5139574DA00DB05C8 /* QReachabilityOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QReachabilityOperation.h; sourceTree = ""; }; + F8E469A6139574DA00DB05C8 /* QReachabilityOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QReachabilityOperation.m; sourceTree = ""; }; + F8E469A7139574DA00DB05C8 /* QRunLoopOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QRunLoopOperation.h; sourceTree = ""; }; + F8E469A8139574DA00DB05C8 /* QRunLoopOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QRunLoopOperation.m; sourceTree = ""; }; + F8E469B11395752400DB05C8 /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONKit.h; sourceTree = ""; }; + F8E469B21395752400DB05C8 /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = ""; }; + F8E469B8139575D000DB05C8 /* AFGowallaAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFGowallaAPI.h; sourceTree = ""; }; + F8E469B9139575D000DB05C8 /* AFGowallaAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFGowallaAPI.m; sourceTree = ""; }; + F8E469BA139575D000DB05C8 /* AFImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFImageRequest.h; sourceTree = ""; }; + F8E469BB139575D000DB05C8 /* AFImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFImageRequest.m; sourceTree = ""; }; + F8E469C7139577E000DB05C8 /* NearbySpotsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NearbySpotsViewController.h; sourceTree = ""; }; + F8E469C8139577E000DB05C8 /* NearbySpotsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NearbySpotsViewController.m; sourceTree = ""; }; + F8E469CC1395780600DB05C8 /* SpotTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpotTableViewCell.h; sourceTree = ""; }; + F8E469CD1395780600DB05C8 /* SpotTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpotTableViewCell.m; sourceTree = ""; }; + F8E469CF1395781500DB05C8 /* Spot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Spot.h; sourceTree = ""; }; + F8E469D01395781500DB05C8 /* Spot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Spot.m; sourceTree = ""; }; + F8E469DE13957DD500DB05C8 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; + F8E469E013957DF100DB05C8 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + F8E469E213957DF700DB05C8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + F8E469E413957E0400DB05C8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + F8E469EA13957FC500DB05C8 /* TTTLocationFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTTLocationFormatter.h; sourceTree = ""; }; + F8E469EB13957FC500DB05C8 /* TTTLocationFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTTLocationFormatter.m; sourceTree = ""; }; + F8E469EE1395812A00DB05C8 /* placeholder-stamp.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "placeholder-stamp.png"; sourceTree = ""; }; + F8E469EF1395812A00DB05C8 /* placeholder-stamp@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "placeholder-stamp@2x.png"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + F8E4695D1395739C00DB05C8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F8E469651395739D00DB05C8 /* UIKit.framework in Frameworks */, + F8E469671395739D00DB05C8 /* Foundation.framework in Frameworks */, + F8E469691395739D00DB05C8 /* CoreGraphics.framework in Frameworks */, + F8E469DF13957DD500DB05C8 /* CoreLocation.framework in Frameworks */, + F8E469E513957E0400DB05C8 /* SystemConfiguration.framework in Frameworks */, + F8E469E113957DF100DB05C8 /* Security.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + F8E469551395739C00DB05C8 = { + isa = PBXGroup; + children = ( + F8E469B71395759C00DB05C8 /* Networking Extensions */, + F8E4696A1395739D00DB05C8 /* Classes */, + F8E469ED1395812A00DB05C8 /* Images */, + F8E469931395743A00DB05C8 /* Vendor */, + F8E469631395739D00DB05C8 /* Frameworks */, + F8E469611395739C00DB05C8 /* Products */, + ); + sourceTree = ""; + }; + F8E469611395739C00DB05C8 /* Products */ = { + isa = PBXGroup; + children = ( + F8E469601395739C00DB05C8 /* AFNetworkingExample.app */, + ); + name = Products; + sourceTree = ""; + }; + F8E469631395739D00DB05C8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F8E469E413957E0400DB05C8 /* SystemConfiguration.framework */, + F8E469E213957DF700DB05C8 /* SystemConfiguration.framework */, + F8E469E013957DF100DB05C8 /* Security.framework */, + F8E469DE13957DD500DB05C8 /* CoreLocation.framework */, + F8E469641395739D00DB05C8 /* UIKit.framework */, + F8E469661395739D00DB05C8 /* Foundation.framework */, + F8E469681395739D00DB05C8 /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + F8E4696A1395739D00DB05C8 /* Classes */ = { + isa = PBXGroup; + children = ( + F8E469C9139577E000DB05C8 /* Models */, + F8E469CA139577E000DB05C8 /* Views */, + F8E469C6139577E000DB05C8 /* Controllers */, + F8E4696B1395739D00DB05C8 /* Supporting Files */, + ); + name = Classes; + path = AFNetworkingExample; + sourceTree = ""; + }; + F8E4696B1395739D00DB05C8 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + F8E469711395739D00DB05C8 /* main.m */, + F8E469701395739D00DB05C8 /* Prefix.pch */, + F8E469731395739D00DB05C8 /* AppDelegate.h */, + F8E469741395739D00DB05C8 /* AppDelegate.m */, + F8E4696C1395739D00DB05C8 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + F8E469931395743A00DB05C8 /* Vendor */ = { + isa = PBXGroup; + children = ( + F8E469941395744600DB05C8 /* Alamofire */, + F8E469A2139574DA00DB05C8 /* QHTTPOperation */, + F8E469B01395752400DB05C8 /* JSONKit */, + F8E469E613957F8900DB05C8 /* TTT */, + ); + name = Vendor; + sourceTree = ""; + }; + F8E469941395744600DB05C8 /* Alamofire */ = { + isa = PBXGroup; + children = ( + F8E469841395742500DB05C8 /* AFCallback.h */, + F8E469851395742500DB05C8 /* AFCallback.m */, + F8E469861395742500DB05C8 /* AFHTTPOperation.h */, + F8E469871395742500DB05C8 /* AFHTTPOperation.m */, + F8E469881395742500DB05C8 /* AFImageRequestOperation.h */, + F8E469891395742500DB05C8 /* AFImageRequestOperation.m */, + F8E4698A1395742500DB05C8 /* AFURLCache.h */, + F8E4698B1395742500DB05C8 /* AFURLCache.m */, + F8E4698C1395742500DB05C8 /* UIImage+AFNetworking.h */, + F8E4698D1395742500DB05C8 /* UIImage+AFNetworking.m */, + ); + name = Alamofire; + sourceTree = ""; + }; + F8E469A2139574DA00DB05C8 /* QHTTPOperation */ = { + isa = PBXGroup; + children = ( + F8E469A3139574DA00DB05C8 /* QHTTPOperation.h */, + F8E469A4139574DA00DB05C8 /* QHTTPOperation.m */, + F8E469A5139574DA00DB05C8 /* QReachabilityOperation.h */, + F8E469A6139574DA00DB05C8 /* QReachabilityOperation.m */, + F8E469A7139574DA00DB05C8 /* QRunLoopOperation.h */, + F8E469A8139574DA00DB05C8 /* QRunLoopOperation.m */, + ); + path = QHTTPOperation; + sourceTree = ""; + }; + F8E469B01395752400DB05C8 /* JSONKit */ = { + isa = PBXGroup; + children = ( + F8E469B11395752400DB05C8 /* JSONKit.h */, + F8E469B21395752400DB05C8 /* JSONKit.m */, + ); + name = JSONKit; + path = AFNetworkingExample/Vendor/JSONKit; + sourceTree = ""; + }; + F8E469B71395759C00DB05C8 /* Networking Extensions */ = { + isa = PBXGroup; + children = ( + F8E469B8139575D000DB05C8 /* AFGowallaAPI.h */, + F8E469B9139575D000DB05C8 /* AFGowallaAPI.m */, + F8E469BA139575D000DB05C8 /* AFImageRequest.h */, + F8E469BB139575D000DB05C8 /* AFImageRequest.m */, + ); + name = "Networking Extensions"; + sourceTree = ""; + }; + F8E469C6139577E000DB05C8 /* Controllers */ = { + isa = PBXGroup; + children = ( + F8E469C7139577E000DB05C8 /* NearbySpotsViewController.h */, + F8E469C8139577E000DB05C8 /* NearbySpotsViewController.m */, + ); + name = Controllers; + path = Classes/Controllers; + sourceTree = ""; + }; + F8E469C9139577E000DB05C8 /* Models */ = { + isa = PBXGroup; + children = ( + F8E469CF1395781500DB05C8 /* Spot.h */, + F8E469D01395781500DB05C8 /* Spot.m */, + ); + name = Models; + path = Classes/Models; + sourceTree = ""; + }; + F8E469CA139577E000DB05C8 /* Views */ = { + isa = PBXGroup; + children = ( + F8E469CC1395780600DB05C8 /* SpotTableViewCell.h */, + F8E469CD1395780600DB05C8 /* SpotTableViewCell.m */, + ); + name = Views; + path = Classes/Views; + sourceTree = ""; + }; + F8E469E613957F8900DB05C8 /* TTT */ = { + isa = PBXGroup; + children = ( + F8E469EA13957FC500DB05C8 /* TTTLocationFormatter.h */, + F8E469EB13957FC500DB05C8 /* TTTLocationFormatter.m */, + ); + name = TTT; + path = AFNetworkingExample/Vendor/TTT; + sourceTree = ""; + }; + F8E469ED1395812A00DB05C8 /* Images */ = { + isa = PBXGroup; + children = ( + F8E469EE1395812A00DB05C8 /* placeholder-stamp.png */, + F8E469EF1395812A00DB05C8 /* placeholder-stamp@2x.png */, + ); + name = Images; + path = AFNetworkingExample/Images; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F8E4695F1395739C00DB05C8 /* AFNetworkingExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = F8E469811395739D00DB05C8 /* Build configuration list for PBXNativeTarget "AFNetworkingExample" */; + buildPhases = ( + F8E4695C1395739C00DB05C8 /* Sources */, + F8E4695D1395739C00DB05C8 /* Frameworks */, + F8E4695E1395739C00DB05C8 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AFNetworkingExample; + productName = AFNetworkingExample; + productReference = F8E469601395739C00DB05C8 /* AFNetworkingExample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F8E469571395739C00DB05C8 /* Project object */ = { + isa = PBXProject; + attributes = { + ORGANIZATIONNAME = Gowalla; + }; + buildConfigurationList = F8E4695A1395739C00DB05C8 /* Build configuration list for PBXProject "AFNetworkingExample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = F8E469551395739C00DB05C8; + productRefGroup = F8E469611395739C00DB05C8 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F8E4695F1395739C00DB05C8 /* AFNetworkingExample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F8E4695E1395739C00DB05C8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F8E469F01395812A00DB05C8 /* placeholder-stamp.png in Resources */, + F8E469F11395812A00DB05C8 /* placeholder-stamp@2x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + F8E4695C1395739C00DB05C8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F8E469721395739D00DB05C8 /* main.m in Sources */, + F8E469751395739D00DB05C8 /* AppDelegate.m in Sources */, + F8E4698E1395742500DB05C8 /* AFCallback.m in Sources */, + F8E4698F1395742500DB05C8 /* AFHTTPOperation.m in Sources */, + F8E469901395742500DB05C8 /* AFImageRequestOperation.m in Sources */, + F8E469911395742500DB05C8 /* AFURLCache.m in Sources */, + F8E469921395742500DB05C8 /* UIImage+AFNetworking.m in Sources */, + F8E469A9139574DA00DB05C8 /* QHTTPOperation.m in Sources */, + F8E469AA139574DA00DB05C8 /* QReachabilityOperation.m in Sources */, + F8E469AB139574DA00DB05C8 /* QRunLoopOperation.m in Sources */, + F8E469B31395752400DB05C8 /* JSONKit.m in Sources */, + F8E469BC139575D000DB05C8 /* AFGowallaAPI.m in Sources */, + F8E469BD139575D000DB05C8 /* AFImageRequest.m in Sources */, + F8E469CB139577E000DB05C8 /* NearbySpotsViewController.m in Sources */, + F8E469CE1395780600DB05C8 /* SpotTableViewCell.m in Sources */, + F8E469D11395781500DB05C8 /* Spot.m in Sources */, + F8E469EC13957FC500DB05C8 /* TTTLocationFormatter.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + F8E4697F1395739D00DB05C8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = DEBUG; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_VERSION = com.apple.compilers.llvmgcc42; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 4.3; + SDKROOT = iphoneos; + }; + name = Debug; + }; + F8E469801395739D00DB05C8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_VERSION = com.apple.compilers.llvmgcc42; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 4.3; + OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + SDKROOT = iphoneos; + }; + name = Release; + }; + F8E469821395739D00DB05C8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AFNetworkingExample/Prefix.pch; + INFOPLIST_FILE = AFNetworkingExample/Info.plist; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + F8E469831395739D00DB05C8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AFNetworkingExample/Prefix.pch; + INFOPLIST_FILE = AFNetworkingExample/Info.plist; + PRODUCT_NAME = "$(TARGET_NAME)"; + VALIDATE_PRODUCT = YES; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F8E4695A1395739C00DB05C8 /* Build configuration list for PBXProject "AFNetworkingExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F8E4697F1395739D00DB05C8 /* Debug */, + F8E469801395739D00DB05C8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F8E469811395739D00DB05C8 /* Build configuration list for PBXNativeTarget "AFNetworkingExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F8E469821395739D00DB05C8 /* Debug */, + F8E469831395739D00DB05C8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; +/* End XCConfigurationList section */ + }; + rootObject = F8E469571395739C00DB05C8 /* Project object */; +} diff --git a/AFNetworkingExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/AFNetworkingExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..98d1e4f --- /dev/null +++ b/AFNetworkingExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/AFNetworkingExample.xcodeproj/project.xcworkspace/xcuserdata/mattt.xcuserdatad/UserInterfaceState.xcuserstate b/AFNetworkingExample.xcodeproj/project.xcworkspace/xcuserdata/mattt.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..be2eaef --- /dev/null +++ b/AFNetworkingExample.xcodeproj/project.xcworkspace/xcuserdata/mattt.xcuserdatad/UserInterfaceState.xcuserstate @@ -0,0 +1,8039 @@ + + + + + $archiver + NSKeyedArchiver + $objects + + $null + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 2 + + + CF$UID + 3 + + + NS.objects + + + CF$UID + 4 + + + CF$UID + 286 + + + + 38A1FB4D-CFF6-414D-81B8-5577D135BD6D + IDEWorkspaceDocument + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 5 + + + CF$UID + 6 + + + CF$UID + 7 + + + CF$UID + 8 + + + CF$UID + 9 + + + CF$UID + 10 + + + NS.objects + + + CF$UID + 11 + + + CF$UID + 284 + + + CF$UID + 21 + + + CF$UID + 5 + + + CF$UID + 285 + + + CF$UID + 2 + + + + IDEWorkspaceTabController_4477BFCD-C087-4B94-AEA0-FFB3C20C7F0F + IDEOrderedWorkspaceTabControllers + IDEWindowToolbarIsVisible + IDEActiveWorkspaceTabController + IDEWindowFrame + IDEWorkspaceWindowControllerUniqueIdentifier + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 12 + + + CF$UID + 13 + + + CF$UID + 14 + + + CF$UID + 15 + + + CF$UID + 16 + + + CF$UID + 17 + + + CF$UID + 18 + + + CF$UID + 19 + + + NS.objects + + + CF$UID + 20 + + + CF$UID + 21 + + + CF$UID + 22 + + + CF$UID + 23 + + + CF$UID + 36 + + + CF$UID + 95 + + + CF$UID + 51 + + + CF$UID + 104 + + + + AssistantEditorsLayout + IDEShowNavigator + IDETabLabel + IDEWorkspaceTabControllerUtilityAreaSplitView + IDENavigatorArea + IDEWorkspaceTabControllerDesignAreaSplitView + IDEShowUtilities + IDEEditorArea + 0 + + AFCallback.h + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 24 + + + NS.objects + + + CF$UID + 25 + + + + DVTSplitViewItems + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 26 + + + CF$UID + 32 + + + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 29 + + + CF$UID + 30 + + + + DVTIdentifier + DVTViewMagnitude + + 1140 + + $classes + + NSDictionary + NSObject + + $classname + NSDictionary + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 29 + + + CF$UID + 33 + + + + 224 + + $classes + + NSMutableArray + NSArray + NSObject + + $classname + NSMutableArray + + + $classes + + NSMutableDictionary + NSDictionary + NSObject + + $classname + NSMutableDictionary + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 37 + + + CF$UID + 38 + + + CF$UID + 39 + + + NS.objects + + + CF$UID + 40 + + + CF$UID + 39 + + + CF$UID + 59 + + + + Xcode.IDEKit.Navigator.Issues + SelectedNavigator + Xcode.IDEKit.Navigator.Structure + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 41 + + + CF$UID + 42 + + + CF$UID + 43 + + + CF$UID + 44 + + + CF$UID + 45 + + + CF$UID + 46 + + + CF$UID + 47 + + + CF$UID + 48 + + + CF$UID + 49 + + + CF$UID + 50 + + + NS.objects + + + CF$UID + 51 + + + CF$UID + 52 + + + CF$UID + 53 + + + CF$UID + 55 + + + CF$UID + 56 + + + CF$UID + 51 + + + CF$UID + 51 + + + CF$UID + 57 + + + CF$UID + 51 + + + CF$UID + 58 + + + + IDEErrorFilteringEnabled + IDEVisibleRect + IDECollapsedFiles + IDEExpandedIssues + IDESelectedNavigables + IDEShowsByType + IDESchemeFilteringEnabled + IDECollapsedTypes + IDERecentFilteringEnabled + IDECollapsedGroups + + {{0, 0}, {339, 1298}} + + $class + + CF$UID + 54 + + NS.objects + + + + $classes + + NSMutableSet + NSSet + NSObject + + $classname + NSMutableSet + + + $class + + CF$UID + 54 + + NS.objects + + + + $class + + CF$UID + 34 + + NS.objects + + + + $class + + CF$UID + 54 + + NS.objects + + + + $class + + CF$UID + 54 + + NS.objects + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 60 + + + CF$UID + 61 + + + CF$UID + 62 + + + CF$UID + 63 + + + CF$UID + 64 + + + CF$UID + 65 + + + CF$UID + 66 + + + NS.objects + + + CF$UID + 67 + + + CF$UID + 51 + + + CF$UID + 68 + + + CF$UID + 51 + + + CF$UID + 51 + + + CF$UID + 70 + + + CF$UID + 77 + + + + IDEVisibleRect + IDEUnsavedDocumentFilteringEnabled + IDENavigatorExpandedItemsBeforeFilteringSet + IDERecentDocumentFilteringEnabled + IDESCMStatusFilteringEnabled + IDESelectedObjects + IDEExpandedItemsSet + {{0, 0}, {339, 1320}} + + $class + + CF$UID + 69 + + NS.objects + + + + $classes + + NSSet + NSObject + + $classname + NSSet + + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 71 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 73 + + + CF$UID + 74 + + + CF$UID + 75 + + + + AFNetworkingExample + Vendor + Alamofire + AFCallback.h + + $classes + + NSArray + NSObject + + $classname + NSArray + + + $class + + CF$UID + 69 + + NS.objects + + + CF$UID + 78 + + + CF$UID + 79 + + + CF$UID + 81 + + + CF$UID + 82 + + + CF$UID + 84 + + + CF$UID + 86 + + + CF$UID + 88 + + + CF$UID + 89 + + + CF$UID + 91 + + + CF$UID + 93 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 80 + + + + Classes + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 73 + + + CF$UID + 74 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 80 + + + CF$UID + 83 + + + + Supporting Files + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 80 + + + CF$UID + 85 + + + + Views + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 87 + + + + Networking Extensions + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 73 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 80 + + + CF$UID + 90 + + + + Models + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 80 + + + CF$UID + 92 + + + + Controllers + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 72 + + + CF$UID + 94 + + + + Images + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 24 + + + NS.objects + + + CF$UID + 96 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 97 + + + CF$UID + 99 + + + CF$UID + 101 + + + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 16 + + + CF$UID + 98 + + + + 340 + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 19 + + + CF$UID + 100 + + + + 2220 + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 102 + + + CF$UID + 103 + + + + IDEUtilitiesArea + 260 + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 105 + + + CF$UID + 106 + + + CF$UID + 107 + + + CF$UID + 108 + + + CF$UID + 109 + + + CF$UID + 110 + + + CF$UID + 111 + + + CF$UID + 112 + + + CF$UID + 113 + + + NS.objects + + + CF$UID + 114 + + + CF$UID + 134 + + + CF$UID + 161 + + + CF$UID + 21 + + + CF$UID + 20 + + + CF$UID + 200 + + + CF$UID + 208 + + + CF$UID + 209 + + + CF$UID + 21 + + + + layoutTree + IDEEDitorArea_DebugArea + IDEEditorMode_Standard + IDEShowEditor + EditorMode + DebuggerSplitView + DefaultPersistentRepresentations + IDEEditorMode_Genius + ShowDebuggerArea + + $class + + CF$UID + 133 + + geniusEditorContextNode + + CF$UID + 0 + + primaryEditorContextNode + + CF$UID + 115 + + rootLayoutTreeNode + + CF$UID + 130 + + + + $class + + CF$UID + 132 + + children + + CF$UID + 0 + + contentType + 1 + documentArchivableRepresentation + + CF$UID + 116 + + orientation + 0 + parent + + CF$UID + 130 + + + + $class + + CF$UID + 129 + + DocumentLocation + + CF$UID + 125 + + DomainIdentifier + + CF$UID + 117 + + IdentifierPath + + CF$UID + 118 + + IndexOfDocumentIdentifier + + CF$UID + 20 + + + Xcode.IDENavigableItemDomain.WorkspaceStructure + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 119 + + + CF$UID + 121 + + + CF$UID + 122 + + + CF$UID + 123 + + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 75 + + + + $classes + + IDEArchivableStringIndexPair + NSObject + + $classname + IDEArchivableStringIndexPair + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 74 + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 73 + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 124 + + + AFNetworkingExample + + $class + + CF$UID + 128 + + documentURL + + CF$UID + 126 + + timestamp + + CF$UID + 0 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFCallback.h + + + $classes + + NSMutableString + NSString + NSObject + + $classname + NSMutableString + + + $classes + + DVTDocumentLocation + NSObject + + $classname + DVTDocumentLocation + + + $classes + + IDENavigableItemArchivableRepresentation + NSObject + + $classname + IDENavigableItemArchivableRepresentation + + + $class + + CF$UID + 132 + + children + + CF$UID + 131 + + contentType + 0 + documentArchivableRepresentation + + CF$UID + 0 + + orientation + 0 + parent + + CF$UID + 0 + + + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 115 + + + + + $classes + + IDEWorkspaceTabControllerLayoutTreeNode + NSObject + + $classname + IDEWorkspaceTabControllerLayoutTreeNode + + + $classes + + IDEWorkspaceTabControllerLayoutTree + NSObject + + $classname + IDEWorkspaceTabControllerLayoutTree + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 135 + + + CF$UID + 136 + + + CF$UID + 137 + + + CF$UID + 138 + + + CF$UID + 139 + + + CF$UID + 140 + + + NS.objects + + + CF$UID + 141 + + + CF$UID + 142 + + + CF$UID + 144 + + + CF$UID + 141 + + + CF$UID + 147 + + + CF$UID + 155 + + + + LayoutFocusMode + console + variables + LayoutMode + IDEDebugArea_SplitView + IDEDebuggerAreaSplitView + 1 + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 143 + + + NS.objects + + + CF$UID + 20 + + + + ConsoleFilterMode + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 145 + + + NS.objects + + + CF$UID + 146 + + + + DBGVariablesViewFilterMode + 2 + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 24 + + + NS.objects + + + CF$UID + 148 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 149 + + + CF$UID + 152 + + + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 150 + + + CF$UID + 151 + + + + VariablesView + 320 + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 153 + + + CF$UID + 154 + + + + ConsoleArea + 1899 + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 24 + + + NS.objects + + + CF$UID + 156 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 157 + + + CF$UID + 159 + + + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 150 + + + CF$UID + 158 + + + + 320 + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 153 + + + CF$UID + 160 + + + + 1899 + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 162 + + + NS.objects + + + CF$UID + 163 + + + + EditorLayout_PersistentRepresentation + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 164 + + + NS.objects + + + CF$UID + 165 + + + + Main + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 166 + + + CF$UID + 167 + + + CF$UID + 168 + + + NS.objects + + + CF$UID + 169 + + + CF$UID + 20 + + + CF$UID + 198 + + + + EditorLayout_StateSavingStateDictionaries + EditorLayout_Selected + EditorLayout_Geometry + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 170 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 171 + + + CF$UID + 172 + + + CF$UID + 173 + + + CF$UID + 174 + + + CF$UID + 175 + + + CF$UID + 176 + + + CF$UID + 177 + + + NS.objects + + + CF$UID + 178 + + + CF$UID + 179 + + + CF$UID + 186 + + + CF$UID + 75 + + + CF$UID + 75 + + + CF$UID + 194 + + + CF$UID + 195 + + + + FileDataType + ArchivableRepresentation + EditorState + NavigableItemName + DocumentNavigableItemName + DocumentExtensionIdentifier + DocumentURL + public.c-header + + $class + + CF$UID + 129 + + DocumentLocation + + CF$UID + 125 + + DomainIdentifier + + CF$UID + 117 + + IdentifierPath + + CF$UID + 180 + + IndexOfDocumentIdentifier + + CF$UID + 20 + + + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 181 + + + CF$UID + 182 + + + CF$UID + 183 + + + CF$UID + 184 + + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 75 + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 74 + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 73 + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 185 + + + AFNetworkingExample + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 191 + + + CF$UID + 192 + + + CF$UID + 51 + + + CF$UID + 193 + + + + PrimaryDocumentTimestamp + PrimaryDocumentVisibleCharacterRange + HideAllIssues + PrimaryDocumentSelectedCharacterRange + 328569621.79426199 + {0, 1417} + {16, 1132} + Xcode.IDEKit.EditorDocument.SourceCode + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 196 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFCallback.h + + $classes + + NSURL + NSObject + + $classname + NSURL + + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 199 + + + + {{0, 0}, {2220, 1227}} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 24 + + + NS.objects + + + CF$UID + 201 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 202 + + + CF$UID + 205 + + + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 203 + + + CF$UID + 204 + + + + IDEEditor + 1249 + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 206 + + + CF$UID + 207 + + + + IDEDebuggerArea + 115 + + $class + + CF$UID + 35 + + NS.keys + + NS.objects + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 210 + + + CF$UID + 162 + + + NS.objects + + + CF$UID + 211 + + + CF$UID + 212 + + + + SplitPosition + 0.5 + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 213 + + + CF$UID + 164 + + + NS.objects + + + CF$UID + 214 + + + CF$UID + 242 + + + + Alternate + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 166 + + + CF$UID + 167 + + + CF$UID + 168 + + + NS.objects + + + CF$UID + 215 + + + CF$UID + 20 + + + CF$UID + 240 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 216 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 171 + + + CF$UID + 172 + + + CF$UID + 173 + + + CF$UID + 174 + + + CF$UID + 175 + + + CF$UID + 176 + + + CF$UID + 177 + + + NS.objects + + + CF$UID + 217 + + + CF$UID + 218 + + + CF$UID + 234 + + + CF$UID + 221 + + + CF$UID + 221 + + + CF$UID + 194 + + + CF$UID + 238 + + + + public.precompiled-c-header + + $class + + CF$UID + 129 + + DocumentLocation + + CF$UID + 232 + + DomainIdentifier + + CF$UID + 0 + + IdentifierPath + + CF$UID + 219 + + IndexOfDocumentIdentifier + + CF$UID + 20 + + + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 220 + + + CF$UID + 222 + + + CF$UID + 223 + + + CF$UID + 224 + + + CF$UID + 229 + + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 221 + + + Prefix.pch + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 83 + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 80 + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 225 + + + CF$UID + 226 + + + NS.objects + + + CF$UID + 227 + + + CF$UID + 228 + + + + manualDomainIdentifier + navigableItem_name + Xcode.IDENavigableItemDomain.WorkspaceStructure + AFNetworkingExample + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 230 + + + NS.objects + + + CF$UID + 231 + + + + identifier + Xcode.IDEKit.GeniusCategory.Manual + + $class + + CF$UID + 128 + + documentURL + + CF$UID + 233 + + timestamp + + CF$UID + 0 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Prefix.pch + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 235 + + + CF$UID + 236 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328564090.93231797 + {0, 344} + {0, 0} + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 239 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Prefix.pch + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 241 + + + + {{0, 0}, {1109, 1364}} + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 166 + + + CF$UID + 167 + + + CF$UID + 168 + + + NS.objects + + + CF$UID + 243 + + + CF$UID + 20 + + + CF$UID + 282 + + + + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 244 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 171 + + + CF$UID + 172 + + + CF$UID + 173 + + + CF$UID + 174 + + + CF$UID + 175 + + + CF$UID + 176 + + + CF$UID + 177 + + + NS.objects + + + CF$UID + 245 + + + CF$UID + 246 + + + CF$UID + 253 + + + CF$UID + 278 + + + CF$UID + 278 + + + CF$UID + 279 + + + CF$UID + 280 + + + + com.apple.xcode.project + + $class + + CF$UID + 129 + + DocumentLocation + + CF$UID + 251 + + DomainIdentifier + + CF$UID + 117 + + IdentifierPath + + CF$UID + 247 + + IndexOfDocumentIdentifier + + CF$UID + 250 + + + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 248 + + + + + $class + + CF$UID + 120 + + Identifier + + CF$UID + 249 + + + AFNetworkingExample + 9223372036854775807 + + $class + + CF$UID + 128 + + documentURL + + CF$UID + 252 + + timestamp + + CF$UID + 0 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample.xcodeproj/ + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 254 + + + CF$UID + 255 + + + CF$UID + 256 + + + CF$UID + 257 + + + CF$UID + 258 + + + NS.objects + + + CF$UID + 259 + + + CF$UID + 260 + + + CF$UID + 266 + + + CF$UID + 267 + + + CF$UID + 277 + + + + Xcode3ProjectEditorPreviousProjectEditorClass + Xcode3ProjectEditor.sourceList.splitview + Xcode3ProjectEditorPreviousTargetEditorClass + Xcode3ProjectEditorSelectedDocumentLocations + Xcode3ProjectEditor_Xcode3TargetEditor + Xcode3ProjectInfoEditor + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 24 + + + NS.objects + + + CF$UID + 261 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 262 + + + CF$UID + 264 + + + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 29 + + + CF$UID + 263 + + + + 170 + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 29 + + + CF$UID + 265 + + + + 940 + Xcode3TargetEditor + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 268 + + + + + $class + + CF$UID + 276 + + documentURL + + CF$UID + 269 + + selection + + CF$UID + 271 + + timestamp + + CF$UID + 270 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample.xcodeproj/ + 328564090.93203801 + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 272 + + + CF$UID + 273 + + + NS.objects + + + CF$UID + 274 + + + CF$UID + 275 + + + + Editor + Target + Xcode3TargetEditor + AFNetworkingExample + + $classes + + Xcode3ProjectDocumentLocation + DVTDocumentLocation + NSObject + + $classname + Xcode3ProjectDocumentLocation + + + $class + + CF$UID + 35 + + NS.keys + + NS.objects + + + AFNetworkingExample + Xcode.Xcode3ProjectSupport.EditorDocument.Xcode3Project + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 281 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample.xcodeproj/ + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 283 + + + + {{0, 0}, {2220, 1364}} + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 5 + + + + {{1920, -98}, {2560, 1440}} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 287 + + + CF$UID + 288 + + + CF$UID + 289 + + + CF$UID + 290 + + + CF$UID + 291 + + + CF$UID + 292 + + + CF$UID + 293 + + + CF$UID + 294 + + + CF$UID + 295 + + + CF$UID + 296 + + + NS.objects + + + CF$UID + 51 + + + CF$UID + 297 + + + CF$UID + 20 + + + CF$UID + 594 + + + CF$UID + 599 + + + CF$UID + 602 + + + CF$UID + 632 + + + CF$UID + 633 + + + CF$UID + 51 + + + CF$UID + 51 + + + + BreakpointsActivated + DefaultEditorStatesForURLs + DebuggingWindowBehavior + ActiveRunDestination + ActiveScheme + LastCompletedPersistentSchemeBasedActivityReport + DocumentWindows + RecentEditorDocumentURLs + AppFocusInMiniDebugging + MiniDebuggingConsole + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 298 + + + CF$UID + 279 + + + CF$UID + 194 + + + CF$UID + 299 + + + CF$UID + 300 + + + NS.objects + + + CF$UID + 301 + + + CF$UID + 311 + + + CF$UID + 351 + + + CF$UID + 551 + + + CF$UID + 568 + + + + Xcode.IDEKit.EditorDocument.PlistEditor + Xcode.IDEKit.EditorDocument.LogDocument + Xcode.IDEKit.CocoaTouchIntegration.EditorDocument.CocoaTouch + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 302 + + + NS.objects + + + CF$UID + 304 + + + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 303 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Info.plist + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 305 + + + CF$UID + 306 + + + CF$UID + 307 + + + NS.objects + + + CF$UID + 308 + + + CF$UID + 309 + + + CF$UID + 310 + + + + IDE_PLIST_EDITOR_SELECTION_KEY + IDE_PLIST_EDITOR_VISIBLERECT_KEY + IDE_PLIST_EDITOR_EXPANSION_KEY + + $class + + CF$UID + 76 + + NS.objects + + + {{0, 0}, {2220, 1188}} + + $class + + CF$UID + 54 + + NS.objects + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 312 + + + NS.objects + + + CF$UID + 314 + + + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 313 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample.xcodeproj/ + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 254 + + + CF$UID + 255 + + + CF$UID + 256 + + + CF$UID + 257 + + + CF$UID + 315 + + + NS.objects + + + CF$UID + 316 + + + CF$UID + 317 + + + CF$UID + 323 + + + CF$UID + 324 + + + CF$UID + 337 + + + + Xcode3ProjectEditor_Xcode3BuildPhasesEditor + Xcode3BuildSettingsEditor + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 24 + + + NS.objects + + + CF$UID + 318 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 319 + + + CF$UID + 321 + + + + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 29 + + + CF$UID + 320 + + + + 170 + + $class + + CF$UID + 31 + + NS.keys + + + CF$UID + 27 + + + CF$UID + 28 + + + NS.objects + + + CF$UID + 29 + + + CF$UID + 322 + + + + 2050 + Xcode3BuildPhasesEditor + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 325 + + + + + $class + + CF$UID + 276 + + documentURL + + CF$UID + 326 + + selection + + CF$UID + 328 + + timestamp + + CF$UID + 327 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample.xcodeproj/ + 328564319.53072202 + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 272 + + + CF$UID + 273 + + + CF$UID + 329 + + + NS.objects + + + CF$UID + 330 + + + CF$UID + 275 + + + CF$UID + 331 + + + + Xcode3BuildPhasesEditorLocations + Xcode3BuildPhasesEditor + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 332 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 333 + + + NS.objects + + + CF$UID + 334 + + + + Link Binary With Libraries + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 335 + + + + + $class + + CF$UID + 336 + + NSLength + 2 + NSLocation + 4 + NSRangeCount + 1 + + + $classes + + NSIndexSet + NSObject + + $classname + NSIndexSet + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 338 + + + CF$UID + 339 + + + CF$UID + 340 + + + CF$UID + 341 + + + CF$UID + 342 + + + CF$UID + 343 + + + CF$UID + 344 + + + NS.objects + + + CF$UID + 345 + + + CF$UID + 346 + + + CF$UID + 347 + + + CF$UID + 348 + + + CF$UID + 349 + + + CF$UID + 237 + + + CF$UID + 350 + + + + F8E4695F1395739C00DB05C8 + Xcode3BuildPhasesEditorFilterKey + F8E4695E1395739C00DB05C8 + F8E4695C1395739C00DB05C8 + Xcode3BuildPhasesEditorDisclosedNamesKey + kXcode3BuildPhasesEditorScrollPointKey + F8E4695D1395739C00DB05C8 + + $class + + CF$UID + 35 + + NS.keys + + NS.objects + + + + + $class + + CF$UID + 35 + + NS.keys + + NS.objects + + + + $class + + CF$UID + 35 + + NS.keys + + NS.objects + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 333 + + + CF$UID + 333 + + + CF$UID + 333 + + + CF$UID + 333 + + + CF$UID + 333 + + + CF$UID + 333 + + + CF$UID + 333 + + + CF$UID + 333 + + + CF$UID + 333 + + + CF$UID + 333 + + + CF$UID + 333 + + + + + $class + + CF$UID + 35 + + NS.keys + + NS.objects + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 352 + + + CF$UID + 354 + + + CF$UID + 356 + + + CF$UID + 358 + + + CF$UID + 360 + + + CF$UID + 362 + + + CF$UID + 364 + + + CF$UID + 366 + + + CF$UID + 368 + + + CF$UID + 370 + + + CF$UID + 371 + + + CF$UID + 373 + + + CF$UID + 375 + + + CF$UID + 377 + + + CF$UID + 379 + + + CF$UID + 381 + + + CF$UID + 383 + + + CF$UID + 385 + + + CF$UID + 387 + + + CF$UID + 389 + + + CF$UID + 391 + + + CF$UID + 393 + + + CF$UID + 395 + + + CF$UID + 397 + + + CF$UID + 399 + + + CF$UID + 401 + + + CF$UID + 403 + + + CF$UID + 405 + + + CF$UID + 407 + + + CF$UID + 409 + + + CF$UID + 411 + + + CF$UID + 413 + + + CF$UID + 415 + + + CF$UID + 417 + + + CF$UID + 419 + + + NS.objects + + + CF$UID + 421 + + + CF$UID + 425 + + + CF$UID + 428 + + + CF$UID + 432 + + + CF$UID + 435 + + + CF$UID + 439 + + + CF$UID + 442 + + + CF$UID + 445 + + + CF$UID + 448 + + + CF$UID + 452 + + + CF$UID + 456 + + + CF$UID + 460 + + + CF$UID + 464 + + + CF$UID + 468 + + + CF$UID + 472 + + + CF$UID + 476 + + + CF$UID + 480 + + + CF$UID + 484 + + + CF$UID + 488 + + + CF$UID + 492 + + + CF$UID + 496 + + + CF$UID + 499 + + + CF$UID + 503 + + + CF$UID + 506 + + + CF$UID + 509 + + + CF$UID + 513 + + + CF$UID + 517 + + + CF$UID + 521 + + + CF$UID + 525 + + + CF$UID + 529 + + + CF$UID + 533 + + + CF$UID + 536 + + + CF$UID + 540 + + + CF$UID + 544 + + + CF$UID + 547 + + + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 353 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Controllers/NearbySpotsViewController.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 355 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/AppDelegate.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 357 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFURLCache.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 359 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFCallback.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 361 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Vendor/TTT/TTTArrayFormatter.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 363 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/AFNetworkingExample-Prefix.pch + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 365 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/en.lproj/InfoPlist.strings + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 367 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/main.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 369 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Vendor/TTT/TTTLocationFormatter.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 126 + + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 372 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFImageRequestOperation.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 374 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Controllers/NearbySpotsViewController.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 376 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/AFNetworkingExampleAppDelegate.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 378 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Controllers/SpotsViewController.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 380 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFHTTPOperation.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 382 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFImageRequestOperation.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 384 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Views/SpotTableViewCell.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 386 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Models/Spot.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 388 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Models/Spot.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 390 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Controllers/RootViewController.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 392 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/RootViewController.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 394 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFHTTPOperation.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 396 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/AppDelegate.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 398 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Prefix.pch + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 400 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/UIImage+AFNetworking.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 402 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Controllers/SpotsViewController.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 404 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/AFNetworkingExampleAppDelegate.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 406 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFImageRequest.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 408 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFURLCache.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 410 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFGowallaAPI.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 412 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Vendor/TTT/TTTArrayFormatter.m + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 414 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFImageRequest.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 416 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Views/SpotTableViewCell.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 418 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/UIImage+AFNetworking.h + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 420 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFGowallaAPI.h + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 422 + + + CF$UID + 423 + + + CF$UID + 51 + + + CF$UID + 424 + + + + 328569441.889938 + {1898, 1930} + {3529, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 426 + + + CF$UID + 427 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328568925.573183 + {0, 1389} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 429 + + + CF$UID + 430 + + + CF$UID + 51 + + + CF$UID + 431 + + + + 328569616.782251 + {0, 1572} + {1320, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 433 + + + CF$UID + 434 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328569462.49987203 + {0, 2022} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 436 + + + CF$UID + 437 + + + CF$UID + 51 + + + CF$UID + 438 + + + + 328564633.34488499 + {0, 2360} + {539, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 440 + + + CF$UID + 441 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328564080.43520099 + {0, 344} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 443 + + + CF$UID + 444 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328564054.60792202 + {0, 45} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 446 + + + CF$UID + 447 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328568938.20444399 + {0, 1391} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 449 + + + CF$UID + 450 + + + CF$UID + 51 + + + CF$UID + 451 + + + + 328564689.54627103 + {0, 2259} + {1952, 20} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 453 + + + CF$UID + 454 + + + CF$UID + 51 + + + CF$UID + 455 + + + + 328569621.79128498 + {0, 1417} + {16, 1132} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 457 + + + CF$UID + 458 + + + CF$UID + 51 + + + CF$UID + 459 + + + + 328568900.79838699 + {0, 2291} + {2093, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 461 + + + CF$UID + 462 + + + CF$UID + 51 + + + CF$UID + 463 + + + + 328568942.82172298 + {0, 1519} + {1375, 138} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 465 + + + CF$UID + 466 + + + CF$UID + 51 + + + CF$UID + 467 + + + + 328562134.45606798 + {0, 1661} + {472, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 469 + + + CF$UID + 470 + + + CF$UID + 51 + + + CF$UID + 471 + + + + 328562784.29455298 + {0, 284} + {284, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 473 + + + CF$UID + 474 + + + CF$UID + 51 + + + CF$UID + 475 + + + + 328569464.60332 + {2028, 2097} + {3133, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 477 + + + CF$UID + 478 + + + CF$UID + 51 + + + CF$UID + 479 + + + + 328569472.30021399 + {3002, 2370} + {4212, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 481 + + + CF$UID + 482 + + + CF$UID + 51 + + + CF$UID + 483 + + + + 328568945.21344799 + {0, 2160} + {1894, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 485 + + + CF$UID + 486 + + + CF$UID + 51 + + + CF$UID + 487 + + + + 328569459.30297899 + {1143, 2029} + {2083, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 489 + + + CF$UID + 490 + + + CF$UID + 51 + + + CF$UID + 491 + + + + 328569452.326361 + {0, 1866} + {1143, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 493 + + + CF$UID + 494 + + + CF$UID + 51 + + + CF$UID + 495 + + + + 328562716.033566 + {0, 242} + {187, 18} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 497 + + + CF$UID + 498 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328562654.93471301 + {0, 242} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 500 + + + CF$UID + 501 + + + CF$UID + 51 + + + CF$UID + 502 + + + + 328568901.80959499 + {160, 2627} + {2619, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 504 + + + CF$UID + 505 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328568917.82865 + {0, 2305} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 507 + + + CF$UID + 508 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328568931.12116998 + {0, 226} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 510 + + + CF$UID + 511 + + + CF$UID + 51 + + + CF$UID + 512 + + + + 328569476.86600798 + {1160, 2169} + {2087, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 514 + + + CF$UID + 515 + + + CF$UID + 51 + + + CF$UID + 516 + + + + 328562932.42907399 + {0, 1192} + {587, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 518 + + + CF$UID + 519 + + + CF$UID + 51 + + + CF$UID + 520 + + + + 328563929.35566801 + {0, 420} + {199, 30} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 522 + + + CF$UID + 523 + + + CF$UID + 51 + + + CF$UID + 524 + + + + 328569433.09656501 + {470, 2550} + {1150, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 526 + + + CF$UID + 527 + + + CF$UID + 51 + + + CF$UID + 528 + + + + 328569504.05768299 + {0, 2770} + {1491, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 530 + + + CF$UID + 531 + + + CF$UID + 51 + + + CF$UID + 532 + + + + 328568956.32660103 + {0, 2996} + {17, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 534 + + + CF$UID + 535 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328564665.45652199 + {0, 2412} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 537 + + + CF$UID + 538 + + + CF$UID + 51 + + + CF$UID + 539 + + + + 328568954.44763798 + {0, 1770} + {17, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 541 + + + CF$UID + 542 + + + CF$UID + 51 + + + CF$UID + 543 + + + + 328568946.85565799 + {0, 1316} + {0, 1155} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 545 + + + CF$UID + 546 + + + CF$UID + 51 + + + CF$UID + 237 + + + + 328568899.47802299 + {0, 1398} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 187 + + + CF$UID + 188 + + + CF$UID + 189 + + + CF$UID + 190 + + + NS.objects + + + CF$UID + 548 + + + CF$UID + 549 + + + CF$UID + 51 + + + CF$UID + 550 + + + + 328568958.07031602 + {0, 2517} + {15, 0} + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 552 + + + CF$UID + 554 + + + NS.objects + + + CF$UID + 556 + + + CF$UID + 565 + + + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 553 + + + x-xcode-log://03813013-719D-479A-8136-7272BFDF4A69 + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 555 + + + x-xcode-log://172F2581-B75E-46AE-977B-15FBF67D56DF + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 557 + + + NS.objects + + + CF$UID + 558 + + + + SelectedDocumentLocations + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 559 + + + + + $class + + CF$UID + 564 + + documentURL + + CF$UID + 553 + + expandTranscript + + indexPath + + CF$UID + 560 + + timestamp + + CF$UID + 0 + + + + $class + + CF$UID + 563 + + NSIndexPathData + + CF$UID + 561 + + NSIndexPathLength + 2 + + + $class + + CF$UID + 562 + + NS.data + + ABM= + + + + $classes + + NSMutableData + NSData + NSObject + + $classname + NSMutableData + + + $classes + + NSIndexPath + NSObject + + $classname + NSIndexPath + + + $classes + + IDELogDocumentLocation + DVTDocumentLocation + NSObject + + $classname + IDELogDocumentLocation + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 557 + + + NS.objects + + + CF$UID + 566 + + + + + $class + + CF$UID + 76 + + NS.objects + + + CF$UID + 567 + + + + + $class + + CF$UID + 564 + + documentURL + + CF$UID + 555 + + expandTranscript + + indexPath + + CF$UID + 560 + + timestamp + + CF$UID + 0 + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 569 + + + CF$UID + 571 + + + NS.objects + + + CF$UID + 573 + + + CF$UID + 587 + + + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 570 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/en.lproj/MainWindow.xib + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 572 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/en.lproj/RootViewController.xib + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 574 + + + CF$UID + 575 + + + CF$UID + 576 + + + CF$UID + 577 + + + NS.objects + + + CF$UID + 578 + + + CF$UID + 581 + + + CF$UID + 577 + + + CF$UID + 582 + + + + IBDockViewController + SelectedObjectIDs + SelectionProvider + IBCanvasViewController + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 579 + + + NS.objects + + + CF$UID + 580 + + + + LastKnownOutlineViewWidth + 270 + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 146 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 583 + + + CF$UID + 584 + + + NS.objects + + + CF$UID + 585 + + + CF$UID + 586 + + + + ObjectIDToLastKnownCanvasPositionMap + EditedTopLevelObjectIDs + + $class + + CF$UID + 35 + + NS.keys + + NS.objects + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 146 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 574 + + + CF$UID + 575 + + + CF$UID + 576 + + + CF$UID + 577 + + + NS.objects + + + CF$UID + 588 + + + CF$UID + 590 + + + CF$UID + 577 + + + CF$UID + 591 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 579 + + + NS.objects + + + CF$UID + 589 + + + + 270 + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 146 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 583 + + + CF$UID + 584 + + + NS.objects + + + CF$UID + 592 + + + CF$UID + 593 + + + + + $class + + CF$UID + 35 + + NS.keys + + NS.objects + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 146 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 595 + + + CF$UID + 596 + + + NS.objects + + + CF$UID + 597 + + + CF$UID + 598 + + + + IDEDeviceLocation + IDEDeviceArchitecture + dvtdevice-iphonesimulator:/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.3.sdk-iPhone + i386 + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 600 + + + NS.objects + + + CF$UID + 601 + + + + IDENameString + AFNetworkingExample + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 603 + + + CF$UID + 604 + + + CF$UID + 605 + + + NS.objects + + + CF$UID + 606 + + + CF$UID + 631 + + + CF$UID + 613 + + + + IDEActivityReportCompletionSummaryStringSegments + IDEActivityReportOptions + IDEActivityReportTitle + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 607 + + + CF$UID + 614 + + + CF$UID + 618 + + + CF$UID + 622 + + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 608 + + + CF$UID + 609 + + + CF$UID + 610 + + + NS.objects + + + CF$UID + 611 + + + CF$UID + 612 + + + CF$UID + 613 + + + + IDEActivityReportStringSegmentPriority + IDEActivityReportStringSegmentBackSeparator + IDEActivityReportStringSegmentStringValue + 2 + + Build + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 608 + + + CF$UID + 609 + + + CF$UID + 610 + + + NS.objects + + + CF$UID + 615 + + + CF$UID + 616 + + + CF$UID + 617 + + + + 4 + : + AFNetworkingExample + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 608 + + + CF$UID + 609 + + + CF$UID + 610 + + + NS.objects + + + CF$UID + 619 + + + CF$UID + 620 + + + CF$UID + 621 + + + + 1 + │ + + $class + + CF$UID + 562 + + NS.data + + YnBsaXN0MDDUAQIDBAUGOzxYJHZlcnNpb25YJG9iamVjdHNZJGFy + Y2hpdmVyVCR0b3ASAAGGoK0HCA8QGhscJCUrMTQ3VSRudWxs0wkK + CwwNDlxOU0F0dHJpYnV0ZXNWJGNsYXNzWE5TU3RyaW5ngAOADIAC + WVN1Y2NlZWRlZNMKERITFBdXTlMua2V5c1pOUy5vYmplY3RzgAui + FRaABIAFohgZgAaACVZOU0ZvbnRXTlNDb2xvctQKHR4fICEiI1ZO + U05hbWVWTlNTaXplWE5TZkZsYWdzgAiAByNAJgAAAAAAABENEF8Q + EUx1Y2lkYUdyYW5kZS1Cb2xk0iYnKClaJGNsYXNzbmFtZVgkY2xh + c3Nlc1ZOU0ZvbnSiKCpYTlNPYmplY3TTCiwtLi8wXE5TQ29sb3JT + cGFjZVdOU1doaXRlgAoQA0IwANImJzIzV05TQ29sb3KiMirSJic1 + NlxOU0RpY3Rpb25hcnmiNSrSJic4OV8QEk5TQXR0cmlidXRlZFN0 + cmluZ6I6Kl8QEk5TQXR0cmlidXRlZFN0cmluZ18QD05TS2V5ZWRB + cmNoaXZlctE9PlRyb290gAEACAARABoAIwAtADIANwBFAEsAUgBf + AGYAbwBxAHMAdQB/AIYAjgCZAJsAngCgAKIApQCnAKkAsAC4AMEA + yADPANgA2gDcAOUA6AD8AQEBDAEVARwBHwEoAS8BPAFEAUYBSAFL + AVABWAFbAWABbQFwAXUBigGNAaIBtAG3AbwAAAAAAAACAQAAAAAA + AAA/AAAAAAAAAAAAAAAAAAABvg== + + + + $class + + CF$UID + 35 + + NS.keys + + + CF$UID + 608 + + + CF$UID + 623 + + + CF$UID + 624 + + + CF$UID + 610 + + + CF$UID + 625 + + + CF$UID + 626 + + + NS.objects + + + CF$UID + 627 + + + CF$UID + 141 + + + CF$UID + 628 + + + CF$UID + 630 + + + CF$UID + 141 + + + CF$UID + 141 + + + + IDEActivityReportStringSegmentType + IDEActivityReportStringSegmentDate + IDEActivityReportStringSegmentDateStyle + IDEActivityReportStringSegmentTimeStyle + 3 + + $class + + CF$UID + 629 + + NS.time + 328569410.72342497 + + + $classes + + NSDate + NSObject + + $classname + NSDate + + Today at 16:16 + 106 + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 2 + + + + + $class + + CF$UID + 34 + + NS.objects + + + CF$UID + 634 + + + CF$UID + 636 + + + CF$UID + 638 + + + CF$UID + 640 + + + CF$UID + 642 + + + CF$UID + 644 + + + CF$UID + 646 + + + CF$UID + 648 + + + CF$UID + 650 + + + CF$UID + 652 + + + + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 635 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFCallback.h + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 637 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFURLCache.h + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 639 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFURLCache.m + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 641 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/UIImage+AFNetworking.m + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 643 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFImageRequestOperation.m + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 645 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFHTTPOperation.m + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 647 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFCallback.m + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 649 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Models/Spot.m + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 651 + + + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Models/Spot.h + + $class + + CF$UID + 197 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 653 + + + + $class + + CF$UID + 127 + + NS.string + file://localhost/Users/mattt/Code/Objective-C/AFNetworking/AFNetworkingExample/AFNetworkingExample/Classes/Controllers/NearbySpotsViewController.m + + + $top + + State + + CF$UID + 1 + + + $version + 100000 + + diff --git a/AFNetworkingExample.xcodeproj/project.xcworkspace/xcuserdata/mattt.xcuserdatad/WorkspaceSettings.xcsettings b/AFNetworkingExample.xcodeproj/project.xcworkspace/xcuserdata/mattt.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..06c7d50 --- /dev/null +++ b/AFNetworkingExample.xcodeproj/project.xcworkspace/xcuserdata/mattt.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + IDEWorkspaceUserSettings_HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges + + IDEWorkspaceUserSettings_SnapshotAutomaticallyBeforeSignificantChanges + + + diff --git a/AFNetworkingExample.xcodeproj/xcuserdata/mattt.xcuserdatad/xcschemes/AFNetworkingExample.xcscheme b/AFNetworkingExample.xcodeproj/xcuserdata/mattt.xcuserdatad/xcschemes/AFNetworkingExample.xcscheme new file mode 100644 index 0000000..955ce4b --- /dev/null +++ b/AFNetworkingExample.xcodeproj/xcuserdata/mattt.xcuserdatad/xcschemes/AFNetworkingExample.xcscheme @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AFNetworkingExample.xcodeproj/xcuserdata/mattt.xcuserdatad/xcschemes/xcschememanagement.plist b/AFNetworkingExample.xcodeproj/xcuserdata/mattt.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..0f45e0f --- /dev/null +++ b/AFNetworkingExample.xcodeproj/xcuserdata/mattt.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + AFNetworkingExample.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + F8E4695F1395739C00DB05C8 + + primary + + + + + diff --git a/AFNetworkingExample/AppDelegate.h b/AFNetworkingExample/AppDelegate.h new file mode 100644 index 0000000..d1d3588 --- /dev/null +++ b/AFNetworkingExample/AppDelegate.h @@ -0,0 +1,32 @@ +// AFNetworkingExampleAppDelegate.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@interface AppDelegate : NSObject { + +} + +@property (nonatomic, retain) UIWindow *window; +@property (nonatomic, retain) UINavigationController *navigationController; + +@end diff --git a/AFNetworkingExample/AppDelegate.m b/AFNetworkingExample/AppDelegate.m new file mode 100644 index 0000000..dff50ea --- /dev/null +++ b/AFNetworkingExample/AppDelegate.m @@ -0,0 +1,50 @@ +// AFNetworkingExampleAppDelegate.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +#import "AppDelegate.h" +#import "NearbySpotsViewController.h" +#import "AFURLCache.h" + +@implementation AppDelegate +@synthesize window = _window; +@synthesize navigationController = _navigationController; + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + AFURLCache *URLCache = [[[AFURLCache alloc] initWithMemoryCapacity:1024 * 1024 diskCapacity:1024 * 1024 * 5 diskPath:[AFURLCache defaultCachePath]] autorelease]; + [NSURLCache setSharedURLCache:URLCache]; + + UITableViewController *viewController = [[[NearbySpotsViewController alloc] init] autorelease]; + self.navigationController = [[[UINavigationController alloc] initWithRootViewController:viewController] autorelease]; + + self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = self.navigationController; + [self.window makeKeyAndVisible]; + return YES; +} + +- (void)dealloc { + [_window release]; + [_navigationController release]; + [super dealloc]; +} + +@end diff --git a/AFNetworkingExample/Classes/Controllers/NearbySpotsViewController.h b/AFNetworkingExample/Classes/Controllers/NearbySpotsViewController.h new file mode 100644 index 0000000..63609ac --- /dev/null +++ b/AFNetworkingExample/Classes/Controllers/NearbySpotsViewController.h @@ -0,0 +1,33 @@ +// NearbySpotsViewController.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import + +@interface NearbySpotsViewController : UITableViewController { + UIActivityIndicatorView *_activityIndicatorView; +} + +@property (readonly, nonatomic, retain) NSArray *nearbySpots; +@property (readonly, nonatomic, retain) CLLocationManager *locationManager; + +@end diff --git a/AFNetworkingExample/Classes/Controllers/NearbySpotsViewController.m b/AFNetworkingExample/Classes/Controllers/NearbySpotsViewController.m new file mode 100644 index 0000000..2d27750 --- /dev/null +++ b/AFNetworkingExample/Classes/Controllers/NearbySpotsViewController.m @@ -0,0 +1,174 @@ +// NearbySpotsViewController.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "NearbySpotsViewController.h" +#import "Spot.h" +#import "SpotTableViewCell.h" +#import "TTTLocationFormatter.h" + +@interface NearbySpotsViewController () +@property (readwrite, nonatomic, retain) NSArray *nearbySpots; +@property (readwrite, nonatomic, retain) CLLocationManager *locationManager; +@property (readwrite, nonatomic, retain) UIActivityIndicatorView *activityIndicatorView; + +- (void)loadSpotsForLocation:(CLLocation *)location; +- (void)refresh:(id)sender; +@end + +static TTTLocationFormatter *__locationFormatter; + +@implementation NearbySpotsViewController +@synthesize nearbySpots = _spots; +@synthesize locationManager = _locationManager; +@synthesize activityIndicatorView = _activityIndicatorView; + ++ (void)initialize { + __locationFormatter = [[TTTLocationFormatter alloc] init]; + [__locationFormatter setUnitSystem:TTTImperialSystem]; +} + +- (id)init { + self = [super init]; + if (!self) { + return nil; + } + + self.nearbySpots = [NSArray array]; + + self.locationManager = [[[CLLocationManager alloc] init] autorelease]; + self.locationManager.delegate = self; + self.locationManager.distanceFilter = 80.0; + + return self; +} + +- (void)dealloc { + [_spots release]; + [_locationManager release]; + [_activityIndicatorView release]; + [super dealloc]; +} + +- (void)loadSpotsForLocation:(CLLocation *)location { + [self.activityIndicatorView startAnimating]; + self.navigationItem.rightBarButtonItem.enabled = NO; + + [Spot spotsWithURLString:@"/spots/advanced_search" near:location parameters:[NSDictionary dictionaryWithObject:@"128" forKey:@"per_page"] withBlock:^(NSArray *records) { + self.nearbySpots = [records sortedArrayUsingComparator:^ NSComparisonResult(id obj1, id obj2) { + CLLocationDistance d1 = [[(Spot *)obj1 location] distanceFromLocation:location]; + CLLocationDistance d2 = [[(Spot *)obj2 location] distanceFromLocation:location]; + + if (d1 < d2) { + return NSOrderedAscending; + } else if (d1 > d2) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }]; + + [self.tableView reloadData]; + + [self.activityIndicatorView stopAnimating]; + self.navigationItem.rightBarButtonItem.enabled = YES; + }]; +} + +#pragma mark - UIViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = NSLocalizedString(@"Nearby Spots", nil); + + self.activityIndicatorView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] autorelease]; + self.activityIndicatorView.hidesWhenStopped = YES; + self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:self.activityIndicatorView] autorelease]; + + self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(refresh:)] autorelease]; + self.navigationItem.rightBarButtonItem.enabled = NO; + + [self.navigationController.navigationBar setTintColor:[UIColor darkGrayColor]]; + + [self.locationManager startUpdatingLocation]; +} + +- (void)viewDidUnload { + [super viewDidUnload]; + [self.locationManager stopUpdatingLocation]; +} + +#pragma mark - CLLocationManagerDelegate + +- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { + [self loadSpotsForLocation:newLocation]; +} + +#pragma mark - Actions + +- (void)refresh:(id)sender { + self.nearbySpots = [NSArray array]; + [self.tableView reloadData]; + [[NSURLCache sharedURLCache] removeAllCachedResponses]; + + if (self.locationManager.location) { + [self loadSpotsForLocation:self.locationManager.location]; + } +} + +#pragma mark - UITableViewDelegate + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [self.nearbySpots count]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 70.0f; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *CellIdentifier = @"Cell"; + + SpotTableViewCell *cell = (SpotTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[SpotTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; + } + + Spot *spot = [self.nearbySpots objectAtIndex:indexPath.row]; + cell.textLabel.text = spot.name; + if (self.locationManager.location) { + cell.detailTextLabel.text = [__locationFormatter stringFromDistanceAndBearingFromLocation:self.locationManager.location toLocation:spot.location]; + } + cell.imageURLString = spot.imageURLString; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +@end diff --git a/AFNetworkingExample/Classes/Models/Spot.h b/AFNetworkingExample/Classes/Models/Spot.h new file mode 100644 index 0000000..2fa5e01 --- /dev/null +++ b/AFNetworkingExample/Classes/Models/Spot.h @@ -0,0 +1,45 @@ +// Spot.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import + +typedef void (^AFRecordsBlock)(NSArray *records); + +@interface Spot : NSObject { +@private + NSString *_name; + NSString *_imageURLString; + NSNumber *_latitude; + NSNumber *_longitude; +} + +@property (nonatomic, retain) NSString *name; +@property (nonatomic, retain) NSString *imageURLString; +@property (nonatomic, retain) NSNumber *latitude; +@property (nonatomic, retain) NSNumber *longitude; +@property (readonly) CLLocation *location; + +- (id)initWithAttributes:(NSDictionary *)attributes; ++ (void)spotsWithURLString:(NSString *)urlString near:(CLLocation *)location parameters:(NSDictionary *)parameters withBlock:(AFRecordsBlock)block; + +@end diff --git a/AFNetworkingExample/Classes/Models/Spot.m b/AFNetworkingExample/Classes/Models/Spot.m new file mode 100644 index 0000000..53426e1 --- /dev/null +++ b/AFNetworkingExample/Classes/Models/Spot.m @@ -0,0 +1,79 @@ +// Spot.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +#import "Spot.h" + +#import "AFGowallaAPI.h" + +@implementation Spot +@synthesize name = _name; +@synthesize imageURLString = _imageURLString; +@synthesize latitude = _latitude; +@synthesize longitude = _longitude; +@dynamic location; + +- (id)initWithAttributes:(NSDictionary *)attributes { + self = [super init]; + if (!self) { + return nil; + } + + self.name = [attributes valueForKeyPath:@"name"]; + self.imageURLString = [attributes valueForKeyPath:@"image_url"]; + self.latitude = [attributes valueForKeyPath:@"lat"]; + self.longitude = [attributes valueForKeyPath:@"lng"]; + + return self; +} + +- (void)dealloc { + [_name release]; + [_imageURLString release]; + [_latitude release]; + [_longitude release]; + [super dealloc]; +} + +- (CLLocation *)location { + return [[[CLLocation alloc] initWithLatitude:[self.latitude doubleValue] longitude:[self.longitude doubleValue]] autorelease]; +} + ++ (void)spotsWithURLString:(NSString *)urlString near:(CLLocation *)location parameters:(NSDictionary *)parameters withBlock:(AFRecordsBlock)block { + NSDictionary *mutableParameters = [NSMutableDictionary dictionaryWithDictionary:parameters]; + if (location) { + [mutableParameters setValue:[NSString stringWithFormat:@"%1.7f", location.coordinate.latitude] forKey:@"lat"]; + [mutableParameters setValue:[NSString stringWithFormat:@"%1.7f", location.coordinate.longitude] forKey:@"lng"]; + } + + [AFGowallaAPI getPath:urlString parameters:mutableParameters callback:[AFHTTPOperationCallback callbackWithSuccess:^(NSURLRequest *request, NSHTTPURLResponse *response, NSDictionary *data) { + if (block) { + NSMutableArray *mutableRecords = [NSMutableArray array]; + for (NSDictionary *attributes in [data valueForKeyPath:@"spots"]) { + Spot *spot = [[[Spot alloc] initWithAttributes:attributes] autorelease]; + [mutableRecords addObject:spot]; + } + + block([NSArray arrayWithArray:mutableRecords]); + } + }]]; +} + +@end diff --git a/AFNetworkingExample/Classes/Views/SpotTableViewCell.h b/AFNetworkingExample/Classes/Views/SpotTableViewCell.h new file mode 100644 index 0000000..375f8b4 --- /dev/null +++ b/AFNetworkingExample/Classes/Views/SpotTableViewCell.h @@ -0,0 +1,30 @@ +// SpotTableViewCell.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFImageRequest.h" + +@interface SpotTableViewCell : UITableViewCell { + NSString *_imageURLString; +} + +@end diff --git a/AFNetworkingExample/Classes/Views/SpotTableViewCell.m b/AFNetworkingExample/Classes/Views/SpotTableViewCell.m new file mode 100644 index 0000000..73a5a39 --- /dev/null +++ b/AFNetworkingExample/Classes/Views/SpotTableViewCell.m @@ -0,0 +1,106 @@ +// SpotTableViewCell.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "SpotTableViewCell.h" + +@implementation SpotTableViewCell +@synthesize imageURLString = _imageURLString; + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (!self) { + return nil; + } + + self.textLabel.textColor = [UIColor darkGrayColor]; + self.textLabel.numberOfLines = 2; + + self.detailTextLabel.textColor = [UIColor grayColor]; + + return self; +} + +- (void)dealloc { + [_imageURLString release]; + [super dealloc]; +} + +- (void)setImageURLString:(NSString *)imageURLString { + [self setImageURLString:imageURLString options:AFImageRequestResize | AFImageCacheProcessedImage]; +} + +- (void)setImageURLString:(NSString *)imageURLString options:(AFImageRequestOptions)options { + if ([_imageURLString isEqual:imageURLString]) { + return; + } + + if (_imageURLString) { + self.imageView.image = [UIImage imageNamed:@"placeholder-stamp.png"]; + } + + [self willChangeValueForKey:@"imageURLString"]; + [_imageURLString release]; + _imageURLString = [imageURLString copy]; + [self didChangeValueForKey:@"imageURLString"]; + + if (imageURLString) { + [AFImageRequest requestImageWithURLString:self.imageURLString size:CGSizeMake(50.0f, 50.0f) options:options block:^(UIImage *image) { + if ([self.imageURLString isEqualToString:imageURLString]) { + BOOL needsLayout = self.imageView.image == nil; + self.imageView.image = image; + + if (needsLayout) { + [self setNeedsLayout]; + } + } + }]; + } +} + +#pragma mark - UITableViewCell + + +#pragma mark - UIView + +- (void)layoutSubviews { + [super layoutSubviews]; + + CGRect imageViewFrame = self.imageView.frame; + CGRect textLabelFrame = self.textLabel.frame; + CGRect detailTextLabelFrame = self.detailTextLabel.frame; + + imageViewFrame.origin.x = 10.0f; + imageViewFrame.origin.y = 10.0f; + imageViewFrame.size = CGSizeMake(50.0f, 50.0f); + + textLabelFrame.origin.x = imageViewFrame.size.width + 30.0f; + detailTextLabelFrame.origin.x = textLabelFrame.origin.x; + + textLabelFrame.size.width = 240.0f; + detailTextLabelFrame.size.width = textLabelFrame.size.width; + + self.textLabel.frame = textLabelFrame; + self.detailTextLabel.frame = detailTextLabelFrame; + self.imageView.frame = imageViewFrame; +} + +@end diff --git a/AFNetworkingExample/Images/placeholder-stamp.png b/AFNetworkingExample/Images/placeholder-stamp.png new file mode 100644 index 0000000..7aafe3d Binary files /dev/null and b/AFNetworkingExample/Images/placeholder-stamp.png differ diff --git a/AFNetworkingExample/Images/placeholder-stamp@2x.png b/AFNetworkingExample/Images/placeholder-stamp@2x.png new file mode 100644 index 0000000..68591f3 Binary files /dev/null and b/AFNetworkingExample/Images/placeholder-stamp@2x.png differ diff --git a/AFNetworkingExample/Info.plist b/AFNetworkingExample/Info.plist new file mode 100644 index 0000000..8a8f9d8 --- /dev/null +++ b/AFNetworkingExample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + AFNetworking + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.alamofire.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/AFNetworkingExample/Prefix.pch b/AFNetworkingExample/Prefix.pch new file mode 100644 index 0000000..47d76c1 --- /dev/null +++ b/AFNetworkingExample/Prefix.pch @@ -0,0 +1,10 @@ +#import + +#ifndef __IPHONE_3_0 +#warning "This project uses features only available in iPhone SDK 3.0 and later." +#endif + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/AFNetworkingExample/Vendor/JSONKit/JSONKit.h b/AFNetworkingExample/Vendor/JSONKit/JSONKit.h new file mode 100644 index 0000000..bdd5380 --- /dev/null +++ b/AFNetworkingExample/Vendor/JSONKit/JSONKit.h @@ -0,0 +1,251 @@ +// +// JSONKit.h +// http://github.com/johnezang/JSONKit +// Dual licensed under either the terms of the BSD License, or alternatively +// under the terms of the Apache License, Version 2.0, as specified below. +// + +/* + Copyright (c) 2011, John Engelhart + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the Zang Industries nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + Copyright 2011 John Engelhart + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +#import +#import +#import +#import +#import +#import +#endif // __OBJC__ + +#ifdef __cplusplus +extern "C" { +#endif + + + // For Mac OS X < 10.5. +#ifndef NSINTEGER_DEFINED +#define NSINTEGER_DEFINED +#if defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) + typedef long NSInteger; + typedef unsigned long NSUInteger; +#define NSIntegerMin LONG_MIN +#define NSIntegerMax LONG_MAX +#define NSUIntegerMax ULONG_MAX +#else // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) + typedef int NSInteger; + typedef unsigned int NSUInteger; +#define NSIntegerMin INT_MIN +#define NSIntegerMax INT_MAX +#define NSUIntegerMax UINT_MAX +#endif // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) +#endif // NSINTEGER_DEFINED + + +#ifndef _JSONKIT_H_ +#define _JSONKIT_H_ + +#if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465) +#define JK_DEPRECATED_ATTRIBUTE __attribute__((deprecated)) +#else +#define JK_DEPRECATED_ATTRIBUTE +#endif + +#define JSONKIT_VERSION_MAJOR 1 +#define JSONKIT_VERSION_MINOR 4 + + typedef NSUInteger JKFlags; + + /* + JKParseOptionComments : Allow C style // and /_* ... *_/ (without a _, obviously) comments in JSON. + JKParseOptionUnicodeNewlines : Allow Unicode recommended (?:\r\n|[\n\v\f\r\x85\p{Zl}\p{Zp}]) newlines. + JKParseOptionLooseUnicode : Normally the decoder will stop with an error at any malformed Unicode. + This option allows JSON with malformed Unicode to be parsed without reporting an error. + Any malformed Unicode is replaced with \uFFFD, or "REPLACEMENT CHARACTER". + */ + + enum { + JKParseOptionNone = 0, + JKParseOptionStrict = 0, + JKParseOptionComments = (1 << 0), + JKParseOptionUnicodeNewlines = (1 << 1), + JKParseOptionLooseUnicode = (1 << 2), + JKParseOptionPermitTextAfterValidJSON = (1 << 3), + JKParseOptionValidFlags = (JKParseOptionComments | JKParseOptionUnicodeNewlines | JKParseOptionLooseUnicode | JKParseOptionPermitTextAfterValidJSON), + }; + typedef JKFlags JKParseOptionFlags; + + enum { + JKSerializeOptionNone = 0, + JKSerializeOptionPretty = (1 << 0), + JKSerializeOptionEscapeUnicode = (1 << 1), + JKSerializeOptionEscapeForwardSlashes = (1 << 4), + JKSerializeOptionValidFlags = (JKSerializeOptionPretty | JKSerializeOptionEscapeUnicode | JKSerializeOptionEscapeForwardSlashes), + }; + typedef JKFlags JKSerializeOptionFlags; + +#ifdef __OBJC__ + + typedef struct JKParseState JKParseState; // Opaque internal, private type. + + // As a general rule of thumb, if you use a method that doesn't accept a JKParseOptionFlags argument, it defaults to JKParseOptionStrict + + @interface JSONDecoder : NSObject { + JKParseState *parseState; + } ++ (id)decoder; ++ (id)decoderWithParseOptions:(JKParseOptionFlags)parseOptionFlags; +- (id)initWithParseOptions:(JKParseOptionFlags)parseOptionFlags; +- (void)clearCache; + +// The parse... methods were deprecated in v1.4 in favor of the v1.4 objectWith... methods. +- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length: instead. +- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length:error: instead. +// The NSData MUST be UTF8 encoded JSON. +- (id)parseJSONData:(NSData *)jsonData JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData: instead. +- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData:error: instead. + +// Methods that return immutable collection objects. +- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length; +- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error; +// The NSData MUST be UTF8 encoded JSON. +- (id)objectWithData:(NSData *)jsonData; +- (id)objectWithData:(NSData *)jsonData error:(NSError **)error; + +// Methods that return mutable collection objects. +- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length; +- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error; +// The NSData MUST be UTF8 encoded JSON. +- (id)mutableObjectWithData:(NSData *)jsonData; +- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error; + +@end + + //////////// +#pragma mark Deserializing methods + //////////// + + @interface NSString (JSONKitDeserializing) +- (id)objectFromJSONString; +- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags; +- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error; +- (id)mutableObjectFromJSONString; +- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags; +- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error; +@end + + @interface NSData (JSONKitDeserializing) +// The NSData MUST be UTF8 encoded JSON. +- (id)objectFromJSONData; +- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags; +- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error; +- (id)mutableObjectFromJSONData; +- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags; +- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error; +@end + + //////////// +#pragma mark Serializing methods + //////////// + + @interface NSString (JSONKitSerializing) +// Convenience methods for those that need to serialize the receiving NSString (i.e., instead of having to serialize a NSArray with a single NSString, you can "serialize to JSON" just the NSString). +// Normally, a string that is serialized to JSON has quotation marks surrounding it, which you may or may not want when serializing a single string, and can be controlled with includeQuotes: +// includeQuotes:YES `a "test"...` -> `"a \"test\"..."` +// includeQuotes:NO `a "test"...` -> `a \"test\"...` +- (NSData *)JSONData; // Invokes JSONDataWithOptions:JKSerializeOptionNone includeQuotes:YES +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error; +- (NSString *)JSONString; // Invokes JSONStringWithOptions:JKSerializeOptionNone includeQuotes:YES +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error; +@end + + @interface NSArray (JSONKitSerializing) +- (NSData *)JSONData; +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error; +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +- (NSString *)JSONString; +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error; +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +@end + + @interface NSDictionary (JSONKitSerializing) +- (NSData *)JSONData; +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error; +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +- (NSString *)JSONString; +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error; +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +@end + +#ifdef __BLOCKS__ + + @interface NSArray (JSONKitSerializingBlockAdditions) +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; +@end + + @interface NSDictionary (JSONKitSerializingBlockAdditions) +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; +@end + +#endif + + +#endif // __OBJC__ + +#endif // _JSONKIT_H_ + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/AFNetworkingExample/Vendor/JSONKit/JSONKit.m b/AFNetworkingExample/Vendor/JSONKit/JSONKit.m new file mode 100644 index 0000000..3977849 --- /dev/null +++ b/AFNetworkingExample/Vendor/JSONKit/JSONKit.m @@ -0,0 +1,3011 @@ +// +// JSONKit.m +// http://github.com/johnezang/JSONKit +// Dual licensed under either the terms of the BSD License, or alternatively +// under the terms of the Apache License, Version 2.0, as specified below. +// + +/* + Copyright (c) 2011, John Engelhart + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the Zang Industries nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + Copyright 2011 John Engelhart + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + +/* + Acknowledgments: + + The bulk of the UTF8 / UTF32 conversion and verification comes + from ConvertUTF.[hc]. It has been modified from the original sources. + + The original sources were obtained from http://www.unicode.org/. + However, the web site no longer seems to host the files. Instead, + the Unicode FAQ http://www.unicode.org/faq//utf_bom.html#gen4 + points to International Components for Unicode (ICU) + http://site.icu-project.org/ as an example of how to write a UTF + converter. + + The decision to use the ConvertUTF.[ch] code was made to leverage + "proven" code. Hopefully the local modifications are bug free. + + The code in isValidCodePoint() is derived from the ICU code in + utf.h for the macros U_IS_UNICODE_NONCHAR and U_IS_UNICODE_CHAR. + + From the original ConvertUTF.[ch]: + + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#import "JSONKit.h" + +//#include +#include +#include +#include +#include + +//#import +#import +#import +#import +#import +#import +#import +#import + +#ifdef JK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS +#warning As of JSONKit v1.4, JK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS is no longer required. It is no longer a valid option. +#endif + +#ifdef __OBJC_GC__ +#error JSONKit does not support Objective-C Garbage Collection +#endif + +// The following checks are really nothing more than sanity checks. +// JSONKit technically has a few problems from a "strictly C99 conforming" standpoint, though they are of the pedantic nitpicking variety. +// In practice, though, for the compilers and architectures we can reasonably expect this code to be compiled for, these pedantic nitpicks aren't really a problem. +// Since we're limited as to what we can do with pre-processor #if checks, these checks are not nearly as through as they should be. + +#if (UINT_MAX != 0xffffffffU) || (INT_MIN != (-0x7fffffff-1)) || (ULLONG_MAX != 0xffffffffffffffffULL) || (LLONG_MIN != (-0x7fffffffffffffffLL-1LL)) +#error JSONKit requires the C 'int' and 'long long' types to be 32 and 64 bits respectively. +#endif + +#if !defined(__LP64__) && ((UINT_MAX != ULONG_MAX) || (INT_MAX != LONG_MAX) || (INT_MIN != LONG_MIN) || (WORD_BIT != LONG_BIT)) +#error JSONKit requires the C 'int' and 'long' types to be the same on 32-bit architectures. +#endif + +// Cocoa / Foundation uses NS*Integer as the type for a lot of arguments. We make sure that NS*Integer is something we are expecting and is reasonably compatible with size_t / ssize_t + +#if (NSUIntegerMax != ULONG_MAX) || (NSIntegerMax != LONG_MAX) || (NSIntegerMin != LONG_MIN) +#error JSONKit requires NSInteger and NSUInteger to be the same size as the C 'long' type. +#endif + +#if (NSUIntegerMax != SIZE_MAX) || (NSIntegerMax != SSIZE_MAX) +#error JSONKit requires NSInteger and NSUInteger to be the same size as the C 'size_t' type. +#endif + + +// For DJB hash. +#define JK_HASH_INIT (1402737925UL) + +// Use __builtin_clz() instead of trailingBytesForUTF8[] table lookup. +#define JK_FAST_TRAILING_BYTES + +// JK_CACHE_SLOTS must be a power of 2. Default size is 1024 slots. +#define JK_CACHE_SLOTS_BITS (10) +#define JK_CACHE_SLOTS (1UL << JK_CACHE_SLOTS_BITS) +// JK_CACHE_PROBES is the number of probe attempts. +#define JK_CACHE_PROBES (4UL) +// JK_INIT_CACHE_AGE must be (1 << AGE) - 1 +#define JK_INIT_CACHE_AGE (0) + +// JK_TOKENBUFFER_SIZE is the default stack size for the temporary buffer used to hold "non-simple" strings (i.e., contains \ escapes) +#define JK_TOKENBUFFER_SIZE (1024UL * 2UL) + +// JK_STACK_OBJS is the default number of spaces reserved on the stack for temporarily storing pointers to Obj-C objects before they can be transferred to a NSArray / NSDictionary. +#define JK_STACK_OBJS (1024UL * 1UL) + +#define JK_JSONBUFFER_SIZE (1024UL * 4UL) +#define JK_UTF8BUFFER_SIZE (1024UL * 16UL) + +#define JK_ENCODE_CACHE_SLOTS (1024UL) + + +#if defined (__GNUC__) && (__GNUC__ >= 4) +#define JK_ATTRIBUTES(attr, ...) __attribute__((attr, ##__VA_ARGS__)) +#define JK_EXPECTED(cond, expect) __builtin_expect((long)(cond), (expect)) +#define JK_EXPECT_T(cond) JK_EXPECTED(cond, 1U) +#define JK_EXPECT_F(cond) JK_EXPECTED(cond, 0U) +#define JK_PREFETCH(ptr) __builtin_prefetch(ptr) +#else // defined (__GNUC__) && (__GNUC__ >= 4) +#define JK_ATTRIBUTES(attr, ...) +#define JK_EXPECTED(cond, expect) (cond) +#define JK_EXPECT_T(cond) (cond) +#define JK_EXPECT_F(cond) (cond) +#define JK_PREFETCH(ptr) +#endif // defined (__GNUC__) && (__GNUC__ >= 4) + +#define JK_STATIC_INLINE static __inline__ JK_ATTRIBUTES(always_inline) +#define JK_ALIGNED(arg) JK_ATTRIBUTES(aligned(arg)) +#define JK_UNUSED_ARG JK_ATTRIBUTES(unused) +#define JK_WARN_UNUSED JK_ATTRIBUTES(warn_unused_result) +#define JK_WARN_UNUSED_CONST JK_ATTRIBUTES(warn_unused_result, const) +#define JK_WARN_UNUSED_PURE JK_ATTRIBUTES(warn_unused_result, pure) +#define JK_WARN_UNUSED_SENTINEL JK_ATTRIBUTES(warn_unused_result, sentinel) +#define JK_NONNULL_ARGS(arg, ...) JK_ATTRIBUTES(nonnull(arg, ##__VA_ARGS__)) +#define JK_WARN_UNUSED_NONNULL_ARGS(arg, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(arg, ##__VA_ARGS__)) +#define JK_WARN_UNUSED_CONST_NONNULL_ARGS(arg, ...) JK_ATTRIBUTES(warn_unused_result, const, nonnull(arg, ##__VA_ARGS__)) +#define JK_WARN_UNUSED_PURE_NONNULL_ARGS(arg, ...) JK_ATTRIBUTES(warn_unused_result, pure, nonnull(arg, ##__VA_ARGS__)) + +#if defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) +#define JK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__), alloc_size(as)) +#else // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) +#define JK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__)) +#endif // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) + + +@class JKArray, JKDictionaryEnumerator, JKDictionary; + +enum { + JSONNumberStateStart = 0, + JSONNumberStateFinished = 1, + JSONNumberStateError = 2, + JSONNumberStateWholeNumberStart = 3, + JSONNumberStateWholeNumberMinus = 4, + JSONNumberStateWholeNumberZero = 5, + JSONNumberStateWholeNumber = 6, + JSONNumberStatePeriod = 7, + JSONNumberStateFractionalNumberStart = 8, + JSONNumberStateFractionalNumber = 9, + JSONNumberStateExponentStart = 10, + JSONNumberStateExponentPlusMinus = 11, + JSONNumberStateExponent = 12, +}; + +enum { + JSONStringStateStart = 0, + JSONStringStateParsing = 1, + JSONStringStateFinished = 2, + JSONStringStateError = 3, + JSONStringStateEscape = 4, + JSONStringStateEscapedUnicode1 = 5, + JSONStringStateEscapedUnicode2 = 6, + JSONStringStateEscapedUnicode3 = 7, + JSONStringStateEscapedUnicode4 = 8, + JSONStringStateEscapedUnicodeSurrogate1 = 9, + JSONStringStateEscapedUnicodeSurrogate2 = 10, + JSONStringStateEscapedUnicodeSurrogate3 = 11, + JSONStringStateEscapedUnicodeSurrogate4 = 12, + JSONStringStateEscapedNeedEscapeForSurrogate = 13, + JSONStringStateEscapedNeedEscapedUForSurrogate = 14, +}; + +enum { + JKParseAcceptValue = (1 << 0), + JKParseAcceptComma = (1 << 1), + JKParseAcceptEnd = (1 << 2), + JKParseAcceptValueOrEnd = (JKParseAcceptValue | JKParseAcceptEnd), + JKParseAcceptCommaOrEnd = (JKParseAcceptComma | JKParseAcceptEnd), +}; + +enum { + JKClassUnknown = 0, + JKClassString = 1, + JKClassNumber = 2, + JKClassArray = 3, + JKClassDictionary = 4, + JKClassNull = 5, +}; + +enum { + JKManagedBufferOnStack = 1, + JKManagedBufferOnHeap = 2, + JKManagedBufferLocationMask = (0x3), + JKManagedBufferLocationShift = (0), + + JKManagedBufferMustFree = (1 << 2), +}; +typedef JKFlags JKManagedBufferFlags; + +enum { + JKObjectStackOnStack = 1, + JKObjectStackOnHeap = 2, + JKObjectStackLocationMask = (0x3), + JKObjectStackLocationShift = (0), + + JKObjectStackMustFree = (1 << 2), +}; +typedef JKFlags JKObjectStackFlags; + +enum { + JKTokenTypeInvalid = 0, + JKTokenTypeNumber = 1, + JKTokenTypeString = 2, + JKTokenTypeObjectBegin = 3, + JKTokenTypeObjectEnd = 4, + JKTokenTypeArrayBegin = 5, + JKTokenTypeArrayEnd = 6, + JKTokenTypeSeparator = 7, + JKTokenTypeComma = 8, + JKTokenTypeTrue = 9, + JKTokenTypeFalse = 10, + JKTokenTypeNull = 11, + JKTokenTypeWhiteSpace = 12, +}; +typedef NSUInteger JKTokenType; + +// These are prime numbers to assist with hash slot probing. +enum { + JKValueTypeNone = 0, + JKValueTypeString = 5, + JKValueTypeLongLong = 7, + JKValueTypeUnsignedLongLong = 11, + JKValueTypeDouble = 13, +}; +typedef NSUInteger JKValueType; + +enum { + JKEncodeOptionAsData = 1, + JKEncodeOptionAsString = 2, + JKEncodeOptionAsTypeMask = 0x7, + JKEncodeOptionCollectionObj = (1 << 3), + JKEncodeOptionStringObj = (1 << 4), + JKEncodeOptionStringObjTrimQuotes = (1 << 5), + +}; +typedef NSUInteger JKEncodeOptionType; + +typedef NSUInteger JKHash; + +typedef struct JKTokenCacheItem JKTokenCacheItem; +typedef struct JKTokenCache JKTokenCache; +typedef struct JKTokenValue JKTokenValue; +typedef struct JKParseToken JKParseToken; +typedef struct JKPtrRange JKPtrRange; +typedef struct JKObjectStack JKObjectStack; +typedef struct JKBuffer JKBuffer; +typedef struct JKConstBuffer JKConstBuffer; +typedef struct JKConstPtrRange JKConstPtrRange; +typedef struct JKRange JKRange; +typedef struct JKManagedBuffer JKManagedBuffer; +typedef struct JKFastClassLookup JKFastClassLookup; +typedef struct JKEncodeCache JKEncodeCache; +typedef struct JKEncodeState JKEncodeState; +typedef struct JKObjCImpCache JKObjCImpCache; +typedef struct JKHashTableEntry JKHashTableEntry; + +typedef id (*NSNumberAllocImp)(id receiver, SEL selector); +typedef id (*NSNumberInitWithUnsignedLongLongImp)(id receiver, SEL selector, unsigned long long value); +typedef id (*JKClassFormatterIMP)(id receiver, SEL selector, id object); +#ifdef __BLOCKS__ +typedef id (^JKClassFormatterBlock)(id formatObject); +#endif + + +struct JKPtrRange { + unsigned char *ptr; + size_t length; +}; + +struct JKConstPtrRange { + const unsigned char *ptr; + size_t length; +}; + +struct JKRange { + size_t location, length; +}; + +struct JKManagedBuffer { + JKPtrRange bytes; + JKManagedBufferFlags flags; + size_t roundSizeUpToMultipleOf; +}; + +struct JKObjectStack { + void **objects, **keys; + CFHashCode *cfHashes; + size_t count, index, roundSizeUpToMultipleOf; + JKObjectStackFlags flags; +}; + +struct JKBuffer { + JKPtrRange bytes; +}; + +struct JKConstBuffer { + JKConstPtrRange bytes; +}; + +struct JKTokenValue { + JKConstPtrRange ptrRange; + JKValueType type; + JKHash hash; + union { + long long longLongValue; + unsigned long long unsignedLongLongValue; + double doubleValue; + } number; + JKTokenCacheItem *cacheItem; +}; + +struct JKParseToken { + JKConstPtrRange tokenPtrRange; + JKTokenType type; + JKTokenValue value; + JKManagedBuffer tokenBuffer; +}; + +struct JKTokenCacheItem { + void *object; + JKHash hash; + CFHashCode cfHash; + size_t size; + unsigned char *bytes; + JKValueType type; +}; + +struct JKTokenCache { + JKTokenCacheItem *items; + size_t count; + unsigned int prng_lfsr; + unsigned char age[JK_CACHE_SLOTS]; +}; + +struct JKObjCImpCache { + Class NSNumberClass; + NSNumberAllocImp NSNumberAlloc; + NSNumberInitWithUnsignedLongLongImp NSNumberInitWithUnsignedLongLong; +}; + +struct JKParseState { + JKParseOptionFlags parseOptionFlags; + JKConstBuffer stringBuffer; + size_t atIndex, lineNumber, lineStartIndex; + size_t prev_atIndex, prev_lineNumber, prev_lineStartIndex; + JKParseToken token; + JKObjectStack objectStack; + JKTokenCache cache; + JKObjCImpCache objCImpCache; + NSError *error; + int errorIsPrev; + BOOL mutableCollections; +}; + +struct JKFastClassLookup { + void *stringClass; + void *numberClass; + void *arrayClass; + void *dictionaryClass; + void *nullClass; +}; + +struct JKEncodeCache { + id object; + size_t offset; + size_t length; +}; + +struct JKEncodeState { + JKManagedBuffer utf8ConversionBuffer; + JKManagedBuffer stringBuffer; + size_t atIndex; + JKFastClassLookup fastClassLookup; + JKEncodeCache cache[JK_ENCODE_CACHE_SLOTS]; + JKSerializeOptionFlags serializeOptionFlags; + JKEncodeOptionType encodeOption; + size_t depth; + NSError *error; + id classFormatterDelegate; + SEL classFormatterSelector; + JKClassFormatterIMP classFormatterIMP; +#ifdef __BLOCKS__ + JKClassFormatterBlock classFormatterBlock; +#endif +}; + +// This is a JSONKit private class. +@interface JKSerializer : NSObject { + JKEncodeState *encodeState; +} + +#ifdef __BLOCKS__ +#define JKSERIALIZER_BLOCKS_PROTO id(^)(id object) +#else +#define JKSERIALIZER_BLOCKS_PROTO id +#endif + ++ (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +- (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error; +- (void)releaseState; + +@end + +struct JKHashTableEntry { + NSUInteger keyHash; + id key, object; +}; + + +typedef uint32_t UTF32; /* at least 32 bits */ +typedef uint16_t UTF16; /* at least 16 bits */ +typedef uint8_t UTF8; /* typically 8 bits */ + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} ConversionResult; + +#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD +#define UNI_MAX_BMP (UTF32)0x0000FFFF +#define UNI_MAX_UTF16 (UTF32)0x0010FFFF +#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF +#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF +#define UNI_SUR_HIGH_START (UTF32)0xD800 +#define UNI_SUR_HIGH_END (UTF32)0xDBFF +#define UNI_SUR_LOW_START (UTF32)0xDC00 +#define UNI_SUR_LOW_END (UTF32)0xDFFF + + +#if !defined(JK_FAST_TRAILING_BYTES) +static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; +#endif + +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +#define JK_AT_STRING_PTR(x) (&((x)->stringBuffer.bytes.ptr[(x)->atIndex])) +#define JK_END_STRING_PTR(x) (&((x)->stringBuffer.bytes.ptr[(x)->stringBuffer.bytes.length])) + + +static JKArray *_JKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection); +static void _JKArrayInsertObjectAtIndex(JKArray *array, id newObject, NSUInteger objectIndex); +static void _JKArrayReplaceObjectAtIndexWithObject(JKArray *array, NSUInteger objectIndex, id newObject); +static void _JKArrayRemoveObjectAtIndex(JKArray *array, NSUInteger objectIndex); + + +static NSUInteger _JKDictionaryCapacityForCount(NSUInteger count); +static JKDictionary *_JKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection); +static JKHashTableEntry *_JKDictionaryHashEntry(JKDictionary *dictionary); +static NSUInteger _JKDictionaryCapacity(JKDictionary *dictionary); +static void _JKDictionaryResizeIfNeccessary(JKDictionary *dictionary); +static void _JKDictionaryRemoveObjectWithEntry(JKDictionary *dictionary, JKHashTableEntry *entry); +static void _JKDictionaryAddObject(JKDictionary *dictionary, NSUInteger keyHash, id key, id object); +static JKHashTableEntry *_JKDictionaryHashTableEntryForKey(JKDictionary *dictionary, id aKey); + + +static void _JSONDecoderCleanup(JSONDecoder *decoder); + +static id _NSStringObjectFromJSONString(NSString *jsonString, JKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection); + + +static void jk_managedBuffer_release(JKManagedBuffer *managedBuffer); +static void jk_managedBuffer_setToStackBuffer(JKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length); +static unsigned char *jk_managedBuffer_resize(JKManagedBuffer *managedBuffer, size_t newSize); +static void jk_objectStack_release(JKObjectStack *objectStack); +static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count); +static int jk_objectStack_resize(JKObjectStack *objectStack, size_t newCount); + +static void jk_error(JKParseState *parseState, NSString *format, ...); +static int jk_parse_string(JKParseState *parseState); +static int jk_parse_number(JKParseState *parseState); +static size_t jk_parse_is_newline(JKParseState *parseState, const unsigned char *atCharacterPtr); +JK_STATIC_INLINE int jk_parse_skip_newline(JKParseState *parseState); +JK_STATIC_INLINE void jk_parse_skip_whitespace(JKParseState *parseState); +static int jk_parse_next_token(JKParseState *parseState); +static void jk_error_parse_accept_or3(JKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String); +static void *jk_create_dictionary(JKParseState *parseState, size_t startingObjectIndex); +static void *jk_parse_dictionary(JKParseState *parseState); +static void *jk_parse_array(JKParseState *parseState); +static void *jk_object_for_token(JKParseState *parseState); +static void *jk_cachedObjects(JKParseState *parseState); +JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState); +JK_STATIC_INLINE void jk_set_parsed_token(JKParseState *parseState, const unsigned char *ptr, size_t length, JKTokenType type, size_t advanceBy); + + +static void jk_encode_error(JKEncodeState *encodeState, NSString *format, ...); +static int jk_encode_printf(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...); +static int jk_encode_write(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format); +static int jk_encode_writePrettyPrintWhiteSpace(JKEncodeState *encodeState); +static int jk_encode_write1slow(JKEncodeState *encodeState, ssize_t depthChange, const char *format); +static int jk_encode_write1fast(JKEncodeState *encodeState, ssize_t depthChange JK_UNUSED_ARG, const char *format); +static int jk_encode_writen(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length); +JK_STATIC_INLINE JKHash jk_encode_object_hash(void *objectPtr); +JK_STATIC_INLINE void jk_encode_updateCache(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object); +static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *objectPtr); + +#define jk_encode_write1(es, dc, f) (JK_EXPECT_F(_jk_encode_prettyPrint) ? jk_encode_write1slow(es, dc, f) : jk_encode_write1fast(es, dc, f)) + + +JK_STATIC_INLINE size_t jk_min(size_t a, size_t b); +JK_STATIC_INLINE size_t jk_max(size_t a, size_t b); +JK_STATIC_INLINE JKHash calculateHash(JKHash currentHash, unsigned char c); + +// JSONKit v1.4 used both a JKArray : NSArray and JKMutableArray : NSMutableArray, and the same for the dictionary collection type. +// However, Louis Gerbarg (via cocoa-dev) pointed out that Cocoa / Core Foundation actually implements only a single class that inherits from the +// mutable version, and keeps an ivar bit for whether or not that instance is mutable. This means that the immutable versions of the collection +// classes receive the mutating methods, but this is handled by having those methods throw an exception when the ivar bit is set to immutable. +// We adopt the same strategy here. It's both cleaner and gets rid of the method swizzling hackery used in JSONKit v1.4. + +#pragma mark - +@interface JKArray : NSMutableArray { + id *objects; + NSUInteger count, capacity, mutations; +} +@end + +@implementation JKArray + +static Class _JKArrayClass = NULL; +static size_t _JKArrayInstanceSize = 0UL; + ++ (void)load +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Though technically not required, the run time environment at +load time may be less than ideal. + + _JKArrayClass = objc_getClass("JKArray"); + _JKArrayInstanceSize = jk_max(16UL, class_getInstanceSize(_JKArrayClass)); + + [pool release]; pool = NULL; +} + ++ (id)allocWithZone:(NSZone *)zone +{ +#pragma unused(zone) + [NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to JSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])]; + return(NULL); +} + +static JKArray *_JKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection) { + NSCParameterAssert((objects != NULL) && (_JKArrayClass != NULL) && (_JKArrayInstanceSize > 0UL)); + JKArray *array = NULL; + if(JK_EXPECT_T((array = (JKArray *)calloc(1UL, _JKArrayInstanceSize)) != NULL)) { // Directly allocate the JKArray instance via calloc. + array->isa = _JKArrayClass; + if((array = [array init]) == NULL) { return(NULL); } + array->capacity = count; + array->count = count; + if(JK_EXPECT_F((array->objects = (id *)malloc(sizeof(id) * array->capacity)) == NULL)) { [array autorelease]; return(NULL); } + memcpy(array->objects, objects, array->capacity * sizeof(id)); + array->mutations = (mutableCollection == NO) ? 0UL : 1UL; + } + return(array); +} + +// Note: The caller is responsible for -retaining the object that is to be added. +static void _JKArrayInsertObjectAtIndex(JKArray *array, id newObject, NSUInteger objectIndex) { + NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex <= array->count) && (newObject != NULL)); + if(!((array != NULL) && (array->objects != NULL) && (objectIndex <= array->count) && (newObject != NULL))) { [newObject autorelease]; return; } + array->count++; + if(array->count >= array->capacity) { + array->capacity += 16UL; + id *newObjects = NULL; + if((newObjects = (id *)realloc(array->objects, sizeof(id) * array->capacity)) == NULL) { [NSException raise:NSMallocException format:@"Unable to resize objects array."]; } + array->objects = newObjects; + memset(&array->objects[array->count], 0, sizeof(id) * (array->capacity - array->count)); + } + if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex + 1UL], &array->objects[objectIndex], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[objectIndex] = NULL; } + array->objects[objectIndex] = newObject; +} + +// Note: The caller is responsible for -retaining the object that is to be added. +static void _JKArrayReplaceObjectAtIndexWithObject(JKArray *array, NSUInteger objectIndex, id newObject) { + NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL)); + if(!((array != NULL) && (array->objects != NULL) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL))) { [newObject autorelease]; return; } + CFRelease(array->objects[objectIndex]); + array->objects[objectIndex] = NULL; + array->objects[objectIndex] = newObject; +} + +static void _JKArrayRemoveObjectAtIndex(JKArray *array, NSUInteger objectIndex) { + NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL)); + if(!((array != NULL) && (array->objects != NULL) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL))) { return; } + CFRelease(array->objects[objectIndex]); + array->objects[objectIndex] = NULL; + if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex], &array->objects[objectIndex + 1UL], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[array->count] = NULL; } + array->count--; +} + +- (void)dealloc +{ + if(JK_EXPECT_T(objects != NULL)) { + NSUInteger atObject = 0UL; + for(atObject = 0UL; atObject < count; atObject++) { if(JK_EXPECT_T(objects[atObject] != NULL)) { CFRelease(objects[atObject]); objects[atObject] = NULL; } } + free(objects); objects = NULL; + } + + [super dealloc]; +} + +- (NSUInteger)count +{ + NSParameterAssert((objects != NULL) && (count <= capacity)); + return(count); +} + +- (void)getObjects:(id *)objectsPtr range:(NSRange)range +{ + NSParameterAssert((objects != NULL) && (count <= capacity)); + if((objectsPtr == NULL) && (NSMaxRange(range) > 0UL)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: pointer to objects array is NULL but range length is %lu", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range)]; } + if((range.location > count) || (NSMaxRange(range) > count)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range), count]; } + memcpy(objectsPtr, objects + range.location, range.length * sizeof(id)); +} + +- (id)objectAtIndex:(NSUInteger)objectIndex +{ + if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } + NSParameterAssert((objects != NULL) && (count <= capacity) && (objects[objectIndex] != NULL)); + return(objects[objectIndex]); +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len +{ + NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (objects != NULL) && (count <= capacity)); + if(JK_EXPECT_F(state->state == 0UL)) { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; } + if(JK_EXPECT_F(state->state >= count)) { return(0UL); } + + NSUInteger enumeratedCount = 0UL; + while(JK_EXPECT_T(enumeratedCount < len) && JK_EXPECT_T(state->state < count)) { NSParameterAssert(objects[state->state] != NULL); stackbuf[enumeratedCount++] = objects[state->state++]; } + + return(enumeratedCount); +} + +- (void)insertObject:(id)anObject atIndex:(NSUInteger)objectIndex +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(objectIndex > count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count + 1UL]; } + anObject = [anObject retain]; + _JKArrayInsertObjectAtIndex(self, anObject, objectIndex); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; +} + +- (void)removeObjectAtIndex:(NSUInteger)objectIndex +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } + _JKArrayRemoveObjectAtIndex(self, objectIndex); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; +} + +- (void)replaceObjectAtIndex:(NSUInteger)objectIndex withObject:(id)anObject +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } + anObject = [anObject retain]; + _JKArrayReplaceObjectAtIndexWithObject(self, objectIndex, anObject); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; +} + +- (id)copyWithZone:(NSZone *)zone +{ + NSParameterAssert((objects != NULL) && (count <= capacity)); + return((mutations == 0UL) ? [self retain] : [[NSArray allocWithZone:zone] initWithObjects:objects count:count]); +} + +- (id)mutableCopyWithZone:(NSZone *)zone +{ + NSParameterAssert((objects != NULL) && (count <= capacity)); + return([[NSMutableArray allocWithZone:zone] initWithObjects:objects count:count]); +} + +@end + + +#pragma mark - +@interface JKDictionaryEnumerator : NSEnumerator { + id collection; + NSUInteger nextObject; +} + +- (id)initWithJKDictionary:(JKDictionary *)initDictionary; +- (NSArray *)allObjects; +- (id)nextObject; + +@end + +@implementation JKDictionaryEnumerator + +- (id)initWithJKDictionary:(JKDictionary *)initDictionary +{ + NSParameterAssert(initDictionary != NULL); + if((self = [super init]) == NULL) { return(NULL); } + if((collection = (id)CFRetain(initDictionary)) == NULL) { [self autorelease]; return(NULL); } + return(self); +} + +- (void)dealloc +{ + if(collection != NULL) { CFRelease(collection); collection = NULL; } + [super dealloc]; +} + +- (NSArray *)allObjects +{ + NSParameterAssert(collection != NULL); + NSUInteger count = [collection count], atObject = 0UL; + id objects[count]; + + while((objects[atObject] = [self nextObject]) != NULL) { NSParameterAssert(atObject < count); atObject++; } + + return([NSArray arrayWithObjects:objects count:atObject]); +} + +- (id)nextObject +{ + NSParameterAssert((collection != NULL) && (_JKDictionaryHashEntry(collection) != NULL)); + JKHashTableEntry *entry = _JKDictionaryHashEntry(collection); + NSUInteger capacity = _JKDictionaryCapacity(collection); + id returnObject = NULL; + + if(entry != NULL) { while((nextObject < capacity) && ((returnObject = entry[nextObject++].key) == NULL)) { /* ... */ } } + + return(returnObject); +} + +@end + +#pragma mark - +@interface JKDictionary : NSMutableDictionary { + NSUInteger count, capacity, mutations; + JKHashTableEntry *entry; +} +@end + +@implementation JKDictionary + +static Class _JKDictionaryClass = NULL; +static size_t _JKDictionaryInstanceSize = 0UL; + ++ (void)load +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Though technically not required, the run time environment at +load time may be less than ideal. + + _JKDictionaryClass = objc_getClass("JKDictionary"); + _JKDictionaryInstanceSize = jk_max(16UL, class_getInstanceSize(_JKDictionaryClass)); + + [pool release]; pool = NULL; +} + ++ (id)allocWithZone:(NSZone *)zone +{ +#pragma unused(zone) + [NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to JSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])]; + return(NULL); +} + +// These values are taken from Core Foundation CF-550 CFBasicHash.m. As a bonus, they align very well with our JKHashTableEntry struct too. +static const NSUInteger jk_dictionaryCapacities[] = { + 0UL, 3UL, 7UL, 13UL, 23UL, 41UL, 71UL, 127UL, 191UL, 251UL, 383UL, 631UL, 1087UL, 1723UL, + 2803UL, 4523UL, 7351UL, 11959UL, 19447UL, 31231UL, 50683UL, 81919UL, 132607UL, + 214519UL, 346607UL, 561109UL, 907759UL, 1468927UL, 2376191UL, 3845119UL, + 6221311UL, 10066421UL, 16287743UL, 26354171UL, 42641881UL, 68996069UL, + 111638519UL, 180634607UL, 292272623UL, 472907251UL +}; + +static NSUInteger _JKDictionaryCapacityForCount(NSUInteger count) { + NSUInteger bottom = 0UL, top = sizeof(jk_dictionaryCapacities) / sizeof(NSUInteger), mid = 0UL, tableSize = lround(floor((count) * 1.33)); + while(top > bottom) { mid = (top + bottom) / 2UL; if(jk_dictionaryCapacities[mid] < tableSize) { bottom = mid + 1UL; } else { top = mid; } } + return(jk_dictionaryCapacities[bottom]); +} + +static void _JKDictionaryResizeIfNeccessary(JKDictionary *dictionary) { + NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity)); + + NSUInteger capacityForCount = 0UL; + if(dictionary->capacity < (capacityForCount = _JKDictionaryCapacityForCount(dictionary->count + 1UL))) { // resize + NSUInteger oldCapacity = dictionary->capacity; +#ifndef NS_BLOCK_ASSERTIONS + NSUInteger oldCount = dictionary->count; +#endif + JKHashTableEntry *oldEntry = dictionary->entry; + if(JK_EXPECT_F((dictionary->entry = (JKHashTableEntry *)calloc(1UL, sizeof(JKHashTableEntry) * capacityForCount)) == NULL)) { [NSException raise:NSMallocException format:@"Unable to allocate memory for hash table."]; } + dictionary->capacity = capacityForCount; + dictionary->count = 0UL; + + NSUInteger idx = 0UL; + for(idx = 0UL; idx < oldCapacity; idx++) { if(oldEntry[idx].key != NULL) { _JKDictionaryAddObject(dictionary, oldEntry[idx].keyHash, oldEntry[idx].key, oldEntry[idx].object); oldEntry[idx].keyHash = 0UL; oldEntry[idx].key = NULL; oldEntry[idx].object = NULL; } } + NSCParameterAssert((oldCount == dictionary->count)); + free(oldEntry); oldEntry = NULL; + } +} + +static JKDictionary *_JKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection) { + NSCParameterAssert((keys != NULL) && (keyHashes != NULL) && (objects != NULL) && (_JKDictionaryClass != NULL) && (_JKDictionaryInstanceSize > 0UL)); + JKDictionary *dictionary = NULL; + if(JK_EXPECT_T((dictionary = (JKDictionary *)calloc(1UL, _JKDictionaryInstanceSize)) != NULL)) { // Directly allocate the JKDictionary instance via calloc. + dictionary->isa = _JKDictionaryClass; + if((dictionary = [dictionary init]) == NULL) { return(NULL); } + dictionary->capacity = _JKDictionaryCapacityForCount(count); + dictionary->count = 0UL; + + if(JK_EXPECT_F((dictionary->entry = (JKHashTableEntry *)calloc(1UL, sizeof(JKHashTableEntry) * dictionary->capacity)) == NULL)) { [dictionary autorelease]; return(NULL); } + + NSUInteger idx = 0UL; + for(idx = 0UL; idx < count; idx++) { _JKDictionaryAddObject(dictionary, keyHashes[idx], keys[idx], objects[idx]); } + + dictionary->mutations = (mutableCollection == NO) ? 0UL : 1UL; + } + return(dictionary); +} + +- (void)dealloc +{ + if(JK_EXPECT_T(entry != NULL)) { + NSUInteger atEntry = 0UL; + for(atEntry = 0UL; atEntry < capacity; atEntry++) { + if(JK_EXPECT_T(entry[atEntry].key != NULL)) { CFRelease(entry[atEntry].key); entry[atEntry].key = NULL; } + if(JK_EXPECT_T(entry[atEntry].object != NULL)) { CFRelease(entry[atEntry].object); entry[atEntry].object = NULL; } + } + + free(entry); entry = NULL; + } + + [super dealloc]; +} + +static JKHashTableEntry *_JKDictionaryHashEntry(JKDictionary *dictionary) { + NSCParameterAssert(dictionary != NULL); + return(dictionary->entry); +} + +static NSUInteger _JKDictionaryCapacity(JKDictionary *dictionary) { + NSCParameterAssert(dictionary != NULL); + return(dictionary->capacity); +} + +static void _JKDictionaryRemoveObjectWithEntry(JKDictionary *dictionary, JKHashTableEntry *entry) { + NSCParameterAssert((dictionary != NULL) && (entry != NULL) && (entry->key != NULL) && (entry->object != NULL) && (dictionary->count > 0UL) && (dictionary->count <= dictionary->capacity)); + CFRelease(entry->key); entry->key = NULL; + CFRelease(entry->object); entry->object = NULL; + entry->keyHash = 0UL; + dictionary->count--; + // In order for certain invariants that are used to speed up the search for a particular key, we need to "re-add" all the entries in the hash table following this entry until we hit a NULL entry. + NSUInteger removeIdx = entry - dictionary->entry, idx = 0UL; + NSCParameterAssert((removeIdx < dictionary->capacity)); + for(idx = 0UL; idx < dictionary->capacity; idx++) { + NSUInteger entryIdx = (removeIdx + idx + 1UL) % dictionary->capacity; + JKHashTableEntry *atEntry = &dictionary->entry[entryIdx]; + if(atEntry->key == NULL) { break; } + NSUInteger keyHash = atEntry->keyHash; + id key = atEntry->key, object = atEntry->object; + NSCParameterAssert(object != NULL); + atEntry->keyHash = 0UL; + atEntry->key = NULL; + atEntry->object = NULL; + NSUInteger addKeyEntry = keyHash % dictionary->capacity, addIdx = 0UL; + for(addIdx = 0UL; addIdx < dictionary->capacity; addIdx++) { + JKHashTableEntry *atAddEntry = &dictionary->entry[((addKeyEntry + addIdx) % dictionary->capacity)]; + if(JK_EXPECT_T(atAddEntry->key == NULL)) { NSCParameterAssert((atAddEntry->keyHash == 0UL) && (atAddEntry->object == NULL)); atAddEntry->key = key; atAddEntry->object = object; atAddEntry->keyHash = keyHash; break; } + } + } +} + +static void _JKDictionaryAddObject(JKDictionary *dictionary, NSUInteger keyHash, id key, id object) { + NSCParameterAssert((dictionary != NULL) && (key != NULL) && (object != NULL) && (dictionary->count < dictionary->capacity) && (dictionary->entry != NULL)); + NSUInteger keyEntry = keyHash % dictionary->capacity, idx = 0UL; + for(idx = 0UL; idx < dictionary->capacity; idx++) { + NSUInteger entryIdx = (keyEntry + idx) % dictionary->capacity; + JKHashTableEntry *atEntry = &dictionary->entry[entryIdx]; + if(JK_EXPECT_F(atEntry->keyHash == keyHash) && JK_EXPECT_T(atEntry->key != NULL) && (JK_EXPECT_F(key == atEntry->key) || JK_EXPECT_F(CFEqual(atEntry->key, key)))) { _JKDictionaryRemoveObjectWithEntry(dictionary, atEntry); } + if(JK_EXPECT_T(atEntry->key == NULL)) { NSCParameterAssert((atEntry->keyHash == 0UL) && (atEntry->object == NULL)); atEntry->key = key; atEntry->object = object; atEntry->keyHash = keyHash; dictionary->count++; return; } + } + + // We should never get here. If we do, we -release the key / object because it's our responsibility. + CFRelease(key); + CFRelease(object); +} + +- (NSUInteger)count +{ + return(count); +} + +static JKHashTableEntry *_JKDictionaryHashTableEntryForKey(JKDictionary *dictionary, id aKey) { + NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity)); + if((aKey == NULL) || (dictionary->capacity == 0UL)) { return(NULL); } + NSUInteger keyHash = CFHash(aKey), keyEntry = (keyHash % dictionary->capacity), idx = 0UL; + JKHashTableEntry *atEntry = NULL; + for(idx = 0UL; idx < dictionary->capacity; idx++) { + atEntry = &dictionary->entry[(keyEntry + idx) % dictionary->capacity]; + if(JK_EXPECT_T(atEntry->keyHash == keyHash) && JK_EXPECT_T(atEntry->key != NULL) && ((atEntry->key == aKey) || CFEqual(atEntry->key, aKey))) { NSCParameterAssert(atEntry->object != NULL); return(atEntry); break; } + if(JK_EXPECT_F(atEntry->key == NULL)) { NSCParameterAssert(atEntry->object == NULL); return(NULL); break; } // If the key was in the table, we would have found it by now. + } + return(NULL); +} + +- (id)objectForKey:(id)aKey +{ + NSParameterAssert((entry != NULL) && (count <= capacity)); + JKHashTableEntry *entryForKey = _JKDictionaryHashTableEntryForKey(self, aKey); + return((entryForKey != NULL) ? entryForKey->object : NULL); +} + +- (void)getObjects:(id *)objects andKeys:(id *)keys +{ + NSParameterAssert((entry != NULL) && (count <= capacity)); + NSUInteger atEntry = 0UL; NSUInteger arrayIdx = 0UL; + for(atEntry = 0UL; atEntry < capacity; atEntry++) { + if(JK_EXPECT_T(entry[atEntry].key != NULL)) { + NSCParameterAssert((entry[atEntry].object != NULL) && (arrayIdx < count)); + if(JK_EXPECT_T(keys != NULL)) { keys[arrayIdx] = entry[atEntry].key; } + if(JK_EXPECT_T(objects != NULL)) { objects[arrayIdx] = entry[atEntry].object; } + arrayIdx++; + } + } +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len +{ + NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (entry != NULL) && (count <= capacity)); + if(JK_EXPECT_F(state->state == 0UL)) { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; } + if(JK_EXPECT_F(state->state >= capacity)) { return(0UL); } + + NSUInteger enumeratedCount = 0UL; + while(JK_EXPECT_T(enumeratedCount < len) && JK_EXPECT_T(state->state < capacity)) { if(JK_EXPECT_T(entry[state->state].key != NULL)) { stackbuf[enumeratedCount++] = entry[state->state].key; } state->state++; } + + return(enumeratedCount); +} + +- (NSEnumerator *)keyEnumerator +{ + return([[[JKDictionaryEnumerator alloc] initWithJKDictionary:self] autorelease]); +} + +- (void)setObject:(id)anObject forKey:(id)aKey +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(aKey == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil key", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil value (key: %@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey]; } + + _JKDictionaryResizeIfNeccessary(self); +#ifndef __clang_analyzer__ + aKey = [aKey copy]; // Why on earth would clang complain that this -copy "might leak", + anObject = [anObject retain]; // but this -retain doesn't!? +#endif // __clang_analyzer__ + _JKDictionaryAddObject(self, CFHash(aKey), aKey, anObject); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; +} + +- (void)removeObjectForKey:(id)aKey +{ + if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if(aKey == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to remove nil key", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + JKHashTableEntry *entryForKey = _JKDictionaryHashTableEntryForKey(self, aKey); + if(entryForKey != NULL) { + _JKDictionaryRemoveObjectWithEntry(self, entryForKey); + mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; + } +} + +- (id)copyWithZone:(NSZone *)zone +{ + NSParameterAssert((entry != NULL) && (count <= capacity)); + return((mutations == 0UL) ? [self retain] : [[NSDictionary allocWithZone:zone] initWithDictionary:self]); +} + +- (id)mutableCopyWithZone:(NSZone *)zone +{ + NSParameterAssert((entry != NULL) && (count <= capacity)); + return([[NSMutableDictionary allocWithZone:zone] initWithDictionary:self]); +} + +@end + + + +#pragma mark - + +JK_STATIC_INLINE size_t jk_min(size_t a, size_t b) { return((a < b) ? a : b); } +JK_STATIC_INLINE size_t jk_max(size_t a, size_t b) { return((a > b) ? a : b); } + +JK_STATIC_INLINE JKHash calculateHash(JKHash currentHash, unsigned char c) { return(((currentHash << 5) + currentHash) + c); } + +static void jk_error(JKParseState *parseState, NSString *format, ...) { + NSCParameterAssert((parseState != NULL) && (format != NULL)); + + va_list varArgsList; + va_start(varArgsList, format); + NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; + va_end(varArgsList); + +#if 0 + const unsigned char *lineStart = parseState->stringBuffer.bytes.ptr + parseState->lineStartIndex; + const unsigned char *lineEnd = lineStart; + const unsigned char *atCharacterPtr = NULL; + + for(atCharacterPtr = lineStart; atCharacterPtr < JK_END_STRING_PTR(parseState); atCharacterPtr++) { lineEnd = atCharacterPtr; if(jk_parse_is_newline(parseState, atCharacterPtr)) { break; } } + + NSString *lineString = @"", *carretString = @""; + if(lineStart < JK_END_STRING_PTR(parseState)) { + lineString = [[[NSString alloc] initWithBytes:lineStart length:(lineEnd - lineStart) encoding:NSUTF8StringEncoding] autorelease]; + carretString = [NSString stringWithFormat:@"%*.*s^", (int)(parseState->atIndex - parseState->lineStartIndex), (int)(parseState->atIndex - parseState->lineStartIndex), " "]; + } +#endif + + if(parseState->error == NULL) { + parseState->error = [NSError errorWithDomain:@"JKErrorDomain" code:-1L userInfo: + [NSDictionary dictionaryWithObjectsAndKeys: + formatString, NSLocalizedDescriptionKey, + [NSNumber numberWithUnsignedLong:parseState->atIndex], @"JKAtIndexKey", + [NSNumber numberWithUnsignedLong:parseState->lineNumber], @"JKLineNumberKey", + //lineString, @"JKErrorLine0Key", + //carretString, @"JKErrorLine1Key", + NULL]]; + } +} + +#pragma mark - +#pragma mark Buffer and Object Stack management functions + +static void jk_managedBuffer_release(JKManagedBuffer *managedBuffer) { + if((managedBuffer->flags & JKManagedBufferMustFree)) { + if(managedBuffer->bytes.ptr != NULL) { free(managedBuffer->bytes.ptr); managedBuffer->bytes.ptr = NULL; } + managedBuffer->flags &= ~JKManagedBufferMustFree; + } + + managedBuffer->bytes.ptr = NULL; + managedBuffer->bytes.length = 0UL; + managedBuffer->flags &= ~JKManagedBufferLocationMask; +} + +static void jk_managedBuffer_setToStackBuffer(JKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length) { + jk_managedBuffer_release(managedBuffer); + managedBuffer->bytes.ptr = ptr; + managedBuffer->bytes.length = length; + managedBuffer->flags = (managedBuffer->flags & ~JKManagedBufferLocationMask) | JKManagedBufferOnStack; +} + +static unsigned char *jk_managedBuffer_resize(JKManagedBuffer *managedBuffer, size_t newSize) { + size_t roundedUpNewSize = newSize; + + if(managedBuffer->roundSizeUpToMultipleOf > 0UL) { roundedUpNewSize = newSize + ((managedBuffer->roundSizeUpToMultipleOf - (newSize % managedBuffer->roundSizeUpToMultipleOf)) % managedBuffer->roundSizeUpToMultipleOf); } + + if((roundedUpNewSize != managedBuffer->bytes.length) && (roundedUpNewSize > managedBuffer->bytes.length)) { + if((managedBuffer->flags & JKManagedBufferLocationMask) == JKManagedBufferOnStack) { + NSCParameterAssert((managedBuffer->flags & JKManagedBufferMustFree) == 0); + unsigned char *newBuffer = NULL, *oldBuffer = managedBuffer->bytes.ptr; + + if((newBuffer = (unsigned char *)malloc(roundedUpNewSize)) == NULL) { return(NULL); } + memcpy(newBuffer, oldBuffer, jk_min(managedBuffer->bytes.length, roundedUpNewSize)); + managedBuffer->flags = (managedBuffer->flags & ~JKManagedBufferLocationMask) | (JKManagedBufferOnHeap | JKManagedBufferMustFree); + managedBuffer->bytes.ptr = newBuffer; + managedBuffer->bytes.length = roundedUpNewSize; + } else { + NSCParameterAssert(((managedBuffer->flags & JKManagedBufferMustFree) != 0) && ((managedBuffer->flags & JKManagedBufferLocationMask) == JKManagedBufferOnHeap)); + if((managedBuffer->bytes.ptr = (unsigned char *)reallocf(managedBuffer->bytes.ptr, roundedUpNewSize)) == NULL) { return(NULL); } + managedBuffer->bytes.length = roundedUpNewSize; + } + } + + return(managedBuffer->bytes.ptr); +} + + + +static void jk_objectStack_release(JKObjectStack *objectStack) { + NSCParameterAssert(objectStack != NULL); + + NSCParameterAssert(objectStack->index <= objectStack->count); + size_t atIndex = 0UL; + for(atIndex = 0UL; atIndex < objectStack->index; atIndex++) { + if(objectStack->objects[atIndex] != NULL) { CFRelease(objectStack->objects[atIndex]); objectStack->objects[atIndex] = NULL; } + if(objectStack->keys[atIndex] != NULL) { CFRelease(objectStack->keys[atIndex]); objectStack->keys[atIndex] = NULL; } + } + objectStack->index = 0UL; + + if(objectStack->flags & JKObjectStackMustFree) { + NSCParameterAssert((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnHeap); + if(objectStack->objects != NULL) { free(objectStack->objects); objectStack->objects = NULL; } + if(objectStack->keys != NULL) { free(objectStack->keys); objectStack->keys = NULL; } + if(objectStack->cfHashes != NULL) { free(objectStack->cfHashes); objectStack->cfHashes = NULL; } + objectStack->flags &= ~JKObjectStackMustFree; + } + + objectStack->objects = NULL; + objectStack->keys = NULL; + objectStack->cfHashes = NULL; + + objectStack->count = 0UL; + objectStack->flags &= ~JKObjectStackLocationMask; +} + +static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count) { + NSCParameterAssert((objectStack != NULL) && (objects != NULL) && (keys != NULL) && (cfHashes != NULL) && (count > 0UL)); + jk_objectStack_release(objectStack); + objectStack->objects = objects; + objectStack->keys = keys; + objectStack->cfHashes = cfHashes; + objectStack->count = count; + objectStack->flags = (objectStack->flags & ~JKObjectStackLocationMask) | JKObjectStackOnStack; +#ifndef NS_BLOCK_ASSERTIONS + size_t idx; + for(idx = 0UL; idx < objectStack->count; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; } +#endif +} + +static int jk_objectStack_resize(JKObjectStack *objectStack, size_t newCount) { + size_t roundedUpNewCount = newCount; + int returnCode = 0; + + void **newObjects = NULL, **newKeys = NULL; + CFHashCode *newCFHashes = NULL; + + if(objectStack->roundSizeUpToMultipleOf > 0UL) { roundedUpNewCount = newCount + ((objectStack->roundSizeUpToMultipleOf - (newCount % objectStack->roundSizeUpToMultipleOf)) % objectStack->roundSizeUpToMultipleOf); } + + if((roundedUpNewCount != objectStack->count) && (roundedUpNewCount > objectStack->count)) { + if((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnStack) { + NSCParameterAssert((objectStack->flags & JKObjectStackMustFree) == 0); + + if((newObjects = (void ** )calloc(1UL, roundedUpNewCount * sizeof(void * ))) == NULL) { returnCode = 1; goto errorExit; } + memcpy(newObjects, objectStack->objects, jk_min(objectStack->count, roundedUpNewCount) * sizeof(void *)); + if((newKeys = (void ** )calloc(1UL, roundedUpNewCount * sizeof(void * ))) == NULL) { returnCode = 1; goto errorExit; } + memcpy(newKeys, objectStack->keys, jk_min(objectStack->count, roundedUpNewCount) * sizeof(void *)); + + if((newCFHashes = (CFHashCode *)calloc(1UL, roundedUpNewCount * sizeof(CFHashCode))) == NULL) { returnCode = 1; goto errorExit; } + memcpy(newCFHashes, objectStack->cfHashes, jk_min(objectStack->count, roundedUpNewCount) * sizeof(CFHashCode)); + + objectStack->flags = (objectStack->flags & ~JKObjectStackLocationMask) | (JKObjectStackOnHeap | JKObjectStackMustFree); + objectStack->objects = newObjects; newObjects = NULL; + objectStack->keys = newKeys; newKeys = NULL; + objectStack->cfHashes = newCFHashes; newCFHashes = NULL; + objectStack->count = roundedUpNewCount; + } else { + NSCParameterAssert(((objectStack->flags & JKObjectStackMustFree) != 0) && ((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnHeap)); + if((newObjects = (void ** )realloc(objectStack->objects, roundedUpNewCount * sizeof(void * ))) != NULL) { objectStack->objects = newObjects; newObjects = NULL; } else { returnCode = 1; goto errorExit; } + if((newKeys = (void ** )realloc(objectStack->keys, roundedUpNewCount * sizeof(void * ))) != NULL) { objectStack->keys = newKeys; newKeys = NULL; } else { returnCode = 1; goto errorExit; } + if((newCFHashes = (CFHashCode *)realloc(objectStack->cfHashes, roundedUpNewCount * sizeof(CFHashCode))) != NULL) { objectStack->cfHashes = newCFHashes; newCFHashes = NULL; } else { returnCode = 1; goto errorExit; } + +#ifndef NS_BLOCK_ASSERTIONS + size_t idx; + for(idx = objectStack->count; idx < roundedUpNewCount; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; } +#endif + objectStack->count = roundedUpNewCount; + } + } + +errorExit: + if(newObjects != NULL) { free(newObjects); newObjects = NULL; } + if(newKeys != NULL) { free(newKeys); newKeys = NULL; } + if(newCFHashes != NULL) { free(newCFHashes); newCFHashes = NULL; } + + return(returnCode); +} + +//////////// +#pragma mark - +#pragma mark Unicode related functions + +JK_STATIC_INLINE ConversionResult isValidCodePoint(UTF32 *u32CodePoint) { + ConversionResult result = conversionOK; + UTF32 ch = *u32CodePoint; + + if(JK_EXPECT_F(ch >= UNI_SUR_HIGH_START) && (JK_EXPECT_T(ch <= UNI_SUR_LOW_END))) { result = sourceIllegal; ch = UNI_REPLACEMENT_CHAR; goto finished; } + if(JK_EXPECT_F(ch >= 0xFDD0U) && (JK_EXPECT_F(ch <= 0xFDEFU) || JK_EXPECT_F((ch & 0xFFFEU) == 0xFFFEU)) && JK_EXPECT_T(ch <= 0x10FFFFU)) { result = sourceIllegal; ch = UNI_REPLACEMENT_CHAR; goto finished; } + if(JK_EXPECT_F(ch == 0U)) { result = sourceIllegal; ch = UNI_REPLACEMENT_CHAR; goto finished; } + +finished: + *u32CodePoint = ch; + return(result); +} + + +static int isLegalUTF8(const UTF8 *source, size_t length) { + const UTF8 *srcptr = source + length; + UTF8 a; + + switch(length) { + default: return(0); // Everything else falls through when "true"... + case 4: if(JK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); } + case 3: if(JK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); } + case 2: if(JK_EXPECT_F( (a = (*--srcptr)) > 0xBF )) { return(0); } + + switch(*source) { // no fall-through in this inner switch + case 0xE0: if(JK_EXPECT_F(a < 0xA0)) { return(0); } break; + case 0xED: if(JK_EXPECT_F(a > 0x9F)) { return(0); } break; + case 0xF0: if(JK_EXPECT_F(a < 0x90)) { return(0); } break; + case 0xF4: if(JK_EXPECT_F(a > 0x8F)) { return(0); } break; + default: if(JK_EXPECT_F(a < 0x80)) { return(0); } + } + + case 1: if(JK_EXPECT_F((JK_EXPECT_T(*source < 0xC2)) && JK_EXPECT_F(*source >= 0x80))) { return(0); } + } + + if(JK_EXPECT_F(*source > 0xF4)) { return(0); } + + return(1); +} + +static ConversionResult ConvertSingleCodePointInUTF8(const UTF8 *sourceStart, const UTF8 *sourceEnd, UTF8 const **nextUTF8, UTF32 *convertedUTF32) { + ConversionResult result = conversionOK; + const UTF8 *source = sourceStart; + UTF32 ch = 0UL; + +#if !defined(JK_FAST_TRAILING_BYTES) + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; +#else + unsigned short extraBytesToRead = __builtin_clz(((*source)^0xff) << 25); +#endif + + if(JK_EXPECT_F((source + extraBytesToRead + 1) > sourceEnd) || JK_EXPECT_F(!isLegalUTF8(source, extraBytesToRead + 1))) { + source++; + while((source < sourceEnd) && (((*source) & 0xc0) == 0x80) && ((source - sourceStart) < (extraBytesToRead + 1))) { source++; } + NSCParameterAssert(source <= sourceEnd); + result = ((source < sourceEnd) && (((*source) & 0xc0) != 0x80)) ? sourceIllegal : ((sourceStart + extraBytesToRead + 1) > sourceEnd) ? sourceExhausted : sourceIllegal; + ch = UNI_REPLACEMENT_CHAR; + goto finished; + } + + switch(extraBytesToRead) { // The cases all fall through. + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + result = isValidCodePoint(&ch); + +finished: + *nextUTF8 = source; + *convertedUTF32 = ch; + + return(result); +} + + +static ConversionResult ConvertUTF32toUTF8 (UTF32 u32CodePoint, UTF8 **targetStart, UTF8 *targetEnd) { + const UTF32 byteMask = 0xBF, byteMark = 0x80; + ConversionResult result = conversionOK; + UTF8 *target = *targetStart; + UTF32 ch = u32CodePoint; + unsigned short bytesToWrite = 0; + + result = isValidCodePoint(&ch); + + // Figure out how many bytes the result will require. Turn any illegally large UTF32 things (> Plane 17) into replacement chars. + if(ch < (UTF32)0x80) { bytesToWrite = 1; } + else if(ch < (UTF32)0x800) { bytesToWrite = 2; } + else if(ch < (UTF32)0x10000) { bytesToWrite = 3; } + else if(ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; } + else { bytesToWrite = 3; ch = UNI_REPLACEMENT_CHAR; result = sourceIllegal; } + + target += bytesToWrite; + if (target > targetEnd) { target -= bytesToWrite; result = targetExhausted; goto finished; } + + switch (bytesToWrite) { // note: everything falls through. + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + + target += bytesToWrite; + +finished: + *targetStart = target; + return(result); +} + +JK_STATIC_INLINE int jk_string_add_unicodeCodePoint(JKParseState *parseState, uint32_t unicodeCodePoint, size_t *tokenBufferIdx, JKHash *stringHash) { + UTF8 *u8s = &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx]; + ConversionResult result; + + if((result = ConvertUTF32toUTF8(unicodeCodePoint, &u8s, (parseState->token.tokenBuffer.bytes.ptr + parseState->token.tokenBuffer.bytes.length))) != conversionOK) { if(result == targetExhausted) { return(1); } } + size_t utf8len = u8s - &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx], nextIdx = (*tokenBufferIdx) + utf8len; + + while(*tokenBufferIdx < nextIdx) { *stringHash = calculateHash(*stringHash, parseState->token.tokenBuffer.bytes.ptr[(*tokenBufferIdx)++]); } + + return(0); +} + +//////////// +#pragma mark - +#pragma mark Decoding / parsing / deserializing functions + +static int jk_parse_string(JKParseState *parseState) { + NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState))); + const unsigned char *stringStart = JK_AT_STRING_PTR(parseState) + 1; + const unsigned char *endOfBuffer = JK_END_STRING_PTR(parseState); + const unsigned char *atStringCharacter = stringStart; + unsigned char *tokenBuffer = parseState->token.tokenBuffer.bytes.ptr; + size_t tokenStartIndex = parseState->atIndex; + size_t tokenBufferIdx = 0UL; + + int onlySimpleString = 1, stringState = JSONStringStateStart; + uint16_t escapedUnicode1 = 0U, escapedUnicode2 = 0U; + uint32_t escapedUnicodeCodePoint = 0U; + JKHash stringHash = JK_HASH_INIT; + + while(1) { + unsigned long currentChar; + + if(JK_EXPECT_F(atStringCharacter == endOfBuffer)) { /* XXX Add error message */ stringState = JSONStringStateError; goto finishedParsing; } + + if(JK_EXPECT_F((currentChar = *atStringCharacter++) >= 0x80UL)) { + const unsigned char *nextValidCharacter = NULL; + UTF32 u32ch = 0U; + ConversionResult result; + + if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(atStringCharacter - 1, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { goto switchToSlowPath; } + stringHash = calculateHash(stringHash, currentChar); + while(atStringCharacter < nextValidCharacter) { NSCParameterAssert(JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)); stringHash = calculateHash(stringHash, *atStringCharacter++); } + continue; + } else { + if(JK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = JSONStringStateFinished; goto finishedParsing; } + + if(JK_EXPECT_F(currentChar == (unsigned long)'\\')) { + switchToSlowPath: + onlySimpleString = 0; + stringState = JSONStringStateParsing; + tokenBufferIdx = (atStringCharacter - stringStart) - 1L; + if(JK_EXPECT_F((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length)) { if((tokenBuffer = jk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { jk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } } + memcpy(tokenBuffer, stringStart, tokenBufferIdx); + goto slowMatch; + } + + if(JK_EXPECT_F(currentChar < 0x20UL)) { jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing; } + + stringHash = calculateHash(stringHash, currentChar); + } + } + +slowMatch: + + for(atStringCharacter = (stringStart + ((atStringCharacter - stringStart) - 1L)); (atStringCharacter < endOfBuffer) && (tokenBufferIdx < parseState->token.tokenBuffer.bytes.length); atStringCharacter++) { + if((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length) { if((tokenBuffer = jk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { jk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } } + + NSCParameterAssert(tokenBufferIdx < parseState->token.tokenBuffer.bytes.length); + + unsigned long currentChar = (*atStringCharacter), escapedChar; + + if(JK_EXPECT_T(stringState == JSONStringStateParsing)) { + if(JK_EXPECT_T(currentChar >= 0x20UL)) { + if(JK_EXPECT_T(currentChar < (unsigned long)0x80)) { // Not a UTF8 sequence + if(JK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = JSONStringStateFinished; atStringCharacter++; goto finishedParsing; } + if(JK_EXPECT_F(currentChar == (unsigned long)'\\')) { stringState = JSONStringStateEscape; continue; } + stringHash = calculateHash(stringHash, currentChar); + tokenBuffer[tokenBufferIdx++] = currentChar; + continue; + } else { // UTF8 sequence + const unsigned char *nextValidCharacter = NULL; + UTF32 u32ch = 0U; + ConversionResult result; + + if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(atStringCharacter, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { + if((result == sourceIllegal) && ((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0)) { jk_error(parseState, @"Illegal UTF8 sequence found in \"\" string."); stringState = JSONStringStateError; goto finishedParsing; } + if(result == sourceExhausted) { jk_error(parseState, @"End of buffer reached while parsing UTF8 in \"\" string."); stringState = JSONStringStateError; goto finishedParsing; } + if(jk_string_add_unicodeCodePoint(parseState, u32ch, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } + atStringCharacter = nextValidCharacter - 1; + continue; + } else { + while(atStringCharacter < nextValidCharacter) { tokenBuffer[tokenBufferIdx++] = *atStringCharacter; stringHash = calculateHash(stringHash, *atStringCharacter++); } + atStringCharacter--; + continue; + } + } + } else { // currentChar < 0x20 + jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing; + } + + } else { // stringState != JSONStringStateParsing + int isSurrogate = 1; + + switch(stringState) { + case JSONStringStateEscape: + switch(currentChar) { + case 'u': escapedUnicode1 = 0U; escapedUnicode2 = 0U; escapedUnicodeCodePoint = 0U; stringState = JSONStringStateEscapedUnicode1; break; + + case 'b': escapedChar = '\b'; goto parsedEscapedChar; + case 'f': escapedChar = '\f'; goto parsedEscapedChar; + case 'n': escapedChar = '\n'; goto parsedEscapedChar; + case 'r': escapedChar = '\r'; goto parsedEscapedChar; + case 't': escapedChar = '\t'; goto parsedEscapedChar; + case '\\': escapedChar = '\\'; goto parsedEscapedChar; + case '/': escapedChar = '/'; goto parsedEscapedChar; + case '"': escapedChar = '"'; goto parsedEscapedChar; + + parsedEscapedChar: + stringState = JSONStringStateParsing; + stringHash = calculateHash(stringHash, escapedChar); + tokenBuffer[tokenBufferIdx++] = escapedChar; + break; + + default: jk_error(parseState, @"Invalid escape sequence found in \"\" string."); stringState = JSONStringStateError; goto finishedParsing; break; + } + break; + + case JSONStringStateEscapedUnicode1: + case JSONStringStateEscapedUnicode2: + case JSONStringStateEscapedUnicode3: + case JSONStringStateEscapedUnicode4: isSurrogate = 0; + case JSONStringStateEscapedUnicodeSurrogate1: + case JSONStringStateEscapedUnicodeSurrogate2: + case JSONStringStateEscapedUnicodeSurrogate3: + case JSONStringStateEscapedUnicodeSurrogate4: + { + uint16_t hexValue = 0U; + + switch(currentChar) { + case '0' ... '9': hexValue = currentChar - '0'; goto parsedHex; + case 'a' ... 'f': hexValue = (currentChar - 'a') + 10U; goto parsedHex; + case 'A' ... 'F': hexValue = (currentChar - 'A') + 10U; goto parsedHex; + + parsedHex: + if(!isSurrogate) { escapedUnicode1 = (escapedUnicode1 << 4) | hexValue; } else { escapedUnicode2 = (escapedUnicode2 << 4) | hexValue; } + + if(stringState == JSONStringStateEscapedUnicode4) { + if(((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xE000U))) { + if((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xDC00U)) { stringState = JSONStringStateEscapedNeedEscapeForSurrogate; } + else if((escapedUnicode1 >= 0xDC00U) && (escapedUnicode1 < 0xE000U)) { + if((parseState->parseOptionFlags & JKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = UNI_REPLACEMENT_CHAR; } + else { jk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; } + } + } + else { escapedUnicodeCodePoint = escapedUnicode1; } + } + + if(stringState == JSONStringStateEscapedUnicodeSurrogate4) { + if((escapedUnicode2 < 0xdc00) || (escapedUnicode2 > 0xdfff)) { + if((parseState->parseOptionFlags & JKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = UNI_REPLACEMENT_CHAR; } + else { jk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; } + } + else { escapedUnicodeCodePoint = ((escapedUnicode1 - 0xd800) * 0x400) + (escapedUnicode2 - 0xdc00) + 0x10000; } + } + + if((stringState == JSONStringStateEscapedUnicode4) || (stringState == JSONStringStateEscapedUnicodeSurrogate4)) { + if((isValidCodePoint(&escapedUnicodeCodePoint) == sourceIllegal) && ((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0)) { jk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; } + stringState = JSONStringStateParsing; + if(jk_string_add_unicodeCodePoint(parseState, escapedUnicodeCodePoint, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } + } + else if((stringState >= JSONStringStateEscapedUnicode1) && (stringState <= JSONStringStateEscapedUnicodeSurrogate4)) { stringState++; } + break; + + default: jk_error(parseState, @"Unexpected character found in \\u Unicode escape sequence. Found '%c', expected [0-9a-fA-F].", currentChar); stringState = JSONStringStateError; goto finishedParsing; break; + } + } + break; + + case JSONStringStateEscapedNeedEscapeForSurrogate: + if((currentChar == '\\')) { stringState = JSONStringStateEscapedNeedEscapedUForSurrogate; } + else { + if((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0) { jk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; } + else { stringState = JSONStringStateParsing; atStringCharacter--; if(jk_string_add_unicodeCodePoint(parseState, UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } } + } + break; + + case JSONStringStateEscapedNeedEscapedUForSurrogate: + if(currentChar == 'u') { stringState = JSONStringStateEscapedUnicodeSurrogate1; } + else { + if((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0) { jk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; } + else { stringState = JSONStringStateParsing; atStringCharacter -= 2; if(jk_string_add_unicodeCodePoint(parseState, UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } } + } + break; + + default: jk_error(parseState, @"Internal error: Unknown stringState. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; break; + } + } + } + +finishedParsing: + + if(JK_EXPECT_T(stringState == JSONStringStateFinished)) { + NSCParameterAssert((parseState->stringBuffer.bytes.ptr + tokenStartIndex) < atStringCharacter); + + parseState->token.tokenPtrRange.ptr = parseState->stringBuffer.bytes.ptr + tokenStartIndex; + parseState->token.tokenPtrRange.length = (atStringCharacter - parseState->token.tokenPtrRange.ptr); + + if(JK_EXPECT_T(onlySimpleString)) { + NSCParameterAssert(((parseState->token.tokenPtrRange.ptr + 1) < endOfBuffer) && (parseState->token.tokenPtrRange.length >= 2UL) && (((parseState->token.tokenPtrRange.ptr + 1) + (parseState->token.tokenPtrRange.length - 2)) < endOfBuffer)); + parseState->token.value.ptrRange.ptr = parseState->token.tokenPtrRange.ptr + 1; + parseState->token.value.ptrRange.length = parseState->token.tokenPtrRange.length - 2UL; + } else { + parseState->token.value.ptrRange.ptr = parseState->token.tokenBuffer.bytes.ptr; + parseState->token.value.ptrRange.length = tokenBufferIdx; + } + + parseState->token.value.hash = stringHash; + parseState->token.value.type = JKValueTypeString; + parseState->atIndex = (atStringCharacter - parseState->stringBuffer.bytes.ptr); + } + + if(JK_EXPECT_F(stringState != JSONStringStateFinished)) { jk_error(parseState, @"Invalid string."); } + return(JK_EXPECT_T(stringState == JSONStringStateFinished) ? 0 : 1); +} + +static int jk_parse_number(JKParseState *parseState) { + NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState))); + const unsigned char *numberStart = JK_AT_STRING_PTR(parseState); + const unsigned char *endOfBuffer = JK_END_STRING_PTR(parseState); + const unsigned char *atNumberCharacter = NULL; + int numberState = JSONNumberStateWholeNumberStart, isFloatingPoint = 0, isNegative = 0, backup = 0; + size_t startingIndex = parseState->atIndex; + + for(atNumberCharacter = numberStart; (JK_EXPECT_T(atNumberCharacter < endOfBuffer)) && (JK_EXPECT_T(!(JK_EXPECT_F(numberState == JSONNumberStateFinished) || JK_EXPECT_F(numberState == JSONNumberStateError)))); atNumberCharacter++) { + unsigned long currentChar = (unsigned long)(*atNumberCharacter), lowerCaseCC = currentChar | 0x20UL; + + switch(numberState) { + case JSONNumberStateWholeNumberStart: if (currentChar == '-') { numberState = JSONNumberStateWholeNumberMinus; isNegative = 1; break; } + case JSONNumberStateWholeNumberMinus: if (currentChar == '0') { numberState = JSONNumberStateWholeNumberZero; break; } + else if( (currentChar >= '1') && (currentChar <= '9')) { numberState = JSONNumberStateWholeNumber; break; } + else { /* XXX Add error message */ numberState = JSONNumberStateError; break; } + case JSONNumberStateExponentStart: if( (currentChar == '+') || (currentChar == '-')) { numberState = JSONNumberStateExponentPlusMinus; break; } + case JSONNumberStateFractionalNumberStart: + case JSONNumberStateExponentPlusMinus:if(!((currentChar >= '0') && (currentChar <= '9'))) { /* XXX Add error message */ numberState = JSONNumberStateError; break; } + else { if(numberState == JSONNumberStateFractionalNumberStart) { numberState = JSONNumberStateFractionalNumber; } + else { numberState = JSONNumberStateExponent; } break; } + case JSONNumberStateWholeNumberZero: + case JSONNumberStateWholeNumber: if (currentChar == '.') { numberState = JSONNumberStateFractionalNumberStart; isFloatingPoint = 1; break; } + case JSONNumberStateFractionalNumber: if (lowerCaseCC == 'e') { numberState = JSONNumberStateExponentStart; isFloatingPoint = 1; break; } + case JSONNumberStateExponent: if(!((currentChar >= '0') && (currentChar <= '9')) || (numberState == JSONNumberStateWholeNumberZero)) { numberState = JSONNumberStateFinished; backup = 1; break; } + break; + default: /* XXX Add error message */ numberState = JSONNumberStateError; break; + } + } + + parseState->token.tokenPtrRange.ptr = parseState->stringBuffer.bytes.ptr + startingIndex; + parseState->token.tokenPtrRange.length = (atNumberCharacter - parseState->token.tokenPtrRange.ptr) - backup; + parseState->atIndex = (parseState->token.tokenPtrRange.ptr + parseState->token.tokenPtrRange.length) - parseState->stringBuffer.bytes.ptr; + + if(JK_EXPECT_T(numberState == JSONNumberStateFinished)) { + unsigned char numberTempBuf[parseState->token.tokenPtrRange.length + 4UL]; + unsigned char *endOfNumber = NULL; + + memcpy(numberTempBuf, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length); + numberTempBuf[parseState->token.tokenPtrRange.length] = 0; + + errno = 0; + + // Treat "-0" as a floating point number, which is capable of representing negative zeros. + if(JK_EXPECT_F(parseState->token.tokenPtrRange.length == 2UL) && JK_EXPECT_F(numberTempBuf[1] == '0') && JK_EXPECT_F(isNegative)) { isFloatingPoint = 1; } + + if(isFloatingPoint) { + parseState->token.value.number.doubleValue = strtod((const char *)numberTempBuf, (char **)&endOfNumber); // strtod is documented to return U+2261 (identical to) 0.0 on an underflow error (along with setting errno to ERANGE). + parseState->token.value.type = JKValueTypeDouble; + parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.doubleValue; + parseState->token.value.ptrRange.length = sizeof(double); + parseState->token.value.hash = (JK_HASH_INIT + parseState->token.value.type); + } else { + if(isNegative) { + parseState->token.value.number.longLongValue = strtoll((const char *)numberTempBuf, (char **)&endOfNumber, 10); + parseState->token.value.type = JKValueTypeLongLong; + parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.longLongValue; + parseState->token.value.ptrRange.length = sizeof(long long); + parseState->token.value.hash = (JK_HASH_INIT + parseState->token.value.type) + (JKHash)parseState->token.value.number.longLongValue; + } else { + parseState->token.value.number.unsignedLongLongValue = strtoull((const char *)numberTempBuf, (char **)&endOfNumber, 10); + parseState->token.value.type = JKValueTypeUnsignedLongLong; + parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.unsignedLongLongValue; + parseState->token.value.ptrRange.length = sizeof(unsigned long long); + parseState->token.value.hash = (JK_HASH_INIT + parseState->token.value.type) + (JKHash)parseState->token.value.number.unsignedLongLongValue; + } + } + + if(JK_EXPECT_F(errno != 0)) { + numberState = JSONNumberStateError; + if(errno == ERANGE) { + switch(parseState->token.value.type) { + case JKValueTypeDouble: jk_error(parseState, @"The value '%s' could not be represented as a 'double' due to %s.", numberTempBuf, (parseState->token.value.number.doubleValue == 0.0) ? "underflow" : "overflow"); break; // see above for == 0.0. + case JKValueTypeLongLong: jk_error(parseState, @"The value '%s' exceeded the minimum value that could be represented: %lld.", numberTempBuf, parseState->token.value.number.longLongValue); break; + case JKValueTypeUnsignedLongLong: jk_error(parseState, @"The value '%s' exceeded the maximum value that could be represented: %llu.", numberTempBuf, parseState->token.value.number.unsignedLongLongValue); break; + default: jk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; + } + } + } + if(JK_EXPECT_F(endOfNumber != &numberTempBuf[parseState->token.tokenPtrRange.length]) && JK_EXPECT_F(numberState != JSONNumberStateError)) { numberState = JSONNumberStateError; jk_error(parseState, @"The conversion function did not consume all of the number tokens characters."); } + + size_t hashIndex = 0UL; + for(hashIndex = 0UL; hashIndex < parseState->token.value.ptrRange.length; hashIndex++) { parseState->token.value.hash = calculateHash(parseState->token.value.hash, parseState->token.value.ptrRange.ptr[hashIndex]); } + } + + if(JK_EXPECT_F(numberState != JSONNumberStateFinished)) { jk_error(parseState, @"Invalid number."); } + return(JK_EXPECT_T((numberState == JSONNumberStateFinished)) ? 0 : 1); +} + +JK_STATIC_INLINE void jk_set_parsed_token(JKParseState *parseState, const unsigned char *ptr, size_t length, JKTokenType type, size_t advanceBy) { + parseState->token.tokenPtrRange.ptr = ptr; + parseState->token.tokenPtrRange.length = length; + parseState->token.type = type; + parseState->atIndex += advanceBy; +} + +static size_t jk_parse_is_newline(JKParseState *parseState, const unsigned char *atCharacterPtr) { + NSCParameterAssert((parseState != NULL) && (atCharacterPtr != NULL) && (atCharacterPtr >= parseState->stringBuffer.bytes.ptr) && (atCharacterPtr < JK_END_STRING_PTR(parseState))); + const unsigned char *endOfStringPtr = JK_END_STRING_PTR(parseState); + + if(JK_EXPECT_F(atCharacterPtr >= endOfStringPtr)) { return(0UL); } + + if(JK_EXPECT_F((*(atCharacterPtr + 0)) == '\n')) { return(1UL); } + if(JK_EXPECT_F((*(atCharacterPtr + 0)) == '\r')) { if((JK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr)) && ((*(atCharacterPtr + 1)) == '\n')) { return(2UL); } return(1UL); } + if(parseState->parseOptionFlags & JKParseOptionUnicodeNewlines) { + if((JK_EXPECT_F((*(atCharacterPtr + 0)) == 0xc2)) && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x85))) { return(2UL); } + if((JK_EXPECT_F((*(atCharacterPtr + 0)) == 0xe2)) && (((atCharacterPtr + 2) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x80) && (((*(atCharacterPtr + 2)) == 0xa8) || ((*(atCharacterPtr + 2)) == 0xa9)))) { return(3UL); } + } + + return(0UL); +} + +JK_STATIC_INLINE int jk_parse_skip_newline(JKParseState *parseState) { + size_t newlineAdvanceAtIndex = 0UL; + if(JK_EXPECT_F((newlineAdvanceAtIndex = jk_parse_is_newline(parseState, JK_AT_STRING_PTR(parseState))) > 0UL)) { parseState->lineNumber++; parseState->atIndex += (newlineAdvanceAtIndex - 1UL); parseState->lineStartIndex = parseState->atIndex + 1UL; return(1); } + return(0); +} + +JK_STATIC_INLINE void jk_parse_skip_whitespace(JKParseState *parseState) { +#ifndef __clang_analyzer__ + NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState))); + const unsigned char *atCharacterPtr = NULL; + const unsigned char *endOfStringPtr = JK_END_STRING_PTR(parseState); + + for(atCharacterPtr = JK_AT_STRING_PTR(parseState); (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { + if(((*(atCharacterPtr + 0)) == ' ') || ((*(atCharacterPtr + 0)) == '\t')) { continue; } + if(jk_parse_skip_newline(parseState)) { continue; } + if(parseState->parseOptionFlags & JKParseOptionComments) { + if((JK_EXPECT_F((*(atCharacterPtr + 0)) == '/')) && (JK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr))) { + if((*(atCharacterPtr + 1)) == '/') { + parseState->atIndex++; + for(atCharacterPtr = JK_AT_STRING_PTR(parseState); (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { if(jk_parse_skip_newline(parseState)) { break; } } + continue; + } + if((*(atCharacterPtr + 1)) == '*') { + parseState->atIndex++; + for(atCharacterPtr = JK_AT_STRING_PTR(parseState); (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { + if(jk_parse_skip_newline(parseState)) { continue; } + if(((*(atCharacterPtr + 0)) == '*') && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == '/'))) { parseState->atIndex++; break; } + } + continue; + } + } + } + break; + } +#endif +} + +static int jk_parse_next_token(JKParseState *parseState) { + NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState))); + const unsigned char *atCharacterPtr = NULL; + const unsigned char *endOfStringPtr = JK_END_STRING_PTR(parseState); + unsigned char currentCharacter = 0U; + int stopParsing = 0; + + parseState->prev_atIndex = parseState->atIndex; + parseState->prev_lineNumber = parseState->lineNumber; + parseState->prev_lineStartIndex = parseState->lineStartIndex; + + jk_parse_skip_whitespace(parseState); + + if((JK_AT_STRING_PTR(parseState) == endOfStringPtr)) { stopParsing = 1; } + + if((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr))) { + currentCharacter = *atCharacterPtr; + + if(JK_EXPECT_T(currentCharacter == '"')) { if(JK_EXPECT_T((stopParsing = jk_parse_string(parseState)) == 0)) { jk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, JKTokenTypeString, 0UL); } } + else if(JK_EXPECT_T(currentCharacter == ':')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeSeparator, 1UL); } + else if(JK_EXPECT_T(currentCharacter == ',')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeComma, 1UL); } + else if((JK_EXPECT_T(currentCharacter >= '0') && JK_EXPECT_T(currentCharacter <= '9')) || JK_EXPECT_T(currentCharacter == '-')) { if(JK_EXPECT_T((stopParsing = jk_parse_number(parseState)) == 0)) { jk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, JKTokenTypeNumber, 0UL); } } + else if(JK_EXPECT_T(currentCharacter == '{')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeObjectBegin, 1UL); } + else if(JK_EXPECT_T(currentCharacter == '}')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeObjectEnd, 1UL); } + else if(JK_EXPECT_T(currentCharacter == '[')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeArrayBegin, 1UL); } + else if(JK_EXPECT_T(currentCharacter == ']')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeArrayEnd, 1UL); } + + else if(JK_EXPECT_T(currentCharacter == 't')) { if(!((JK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (JK_EXPECT_T(atCharacterPtr[1] == 'r')) && (JK_EXPECT_T(atCharacterPtr[2] == 'u')) && (JK_EXPECT_T(atCharacterPtr[3] == 'e')))) { stopParsing = 1; /* XXX Add error message */ } else { jk_set_parsed_token(parseState, atCharacterPtr, 4UL, JKTokenTypeTrue, 4UL); } } + else if(JK_EXPECT_T(currentCharacter == 'f')) { if(!((JK_EXPECT_T((atCharacterPtr + 5UL) < endOfStringPtr)) && (JK_EXPECT_T(atCharacterPtr[1] == 'a')) && (JK_EXPECT_T(atCharacterPtr[2] == 'l')) && (JK_EXPECT_T(atCharacterPtr[3] == 's')) && (JK_EXPECT_T(atCharacterPtr[4] == 'e')))) { stopParsing = 1; /* XXX Add error message */ } else { jk_set_parsed_token(parseState, atCharacterPtr, 5UL, JKTokenTypeFalse, 5UL); } } + else if(JK_EXPECT_T(currentCharacter == 'n')) { if(!((JK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (JK_EXPECT_T(atCharacterPtr[1] == 'u')) && (JK_EXPECT_T(atCharacterPtr[2] == 'l')) && (JK_EXPECT_T(atCharacterPtr[3] == 'l')))) { stopParsing = 1; /* XXX Add error message */ } else { jk_set_parsed_token(parseState, atCharacterPtr, 4UL, JKTokenTypeNull, 4UL); } } + else { stopParsing = 1; /* XXX Add error message */ } + } + + if(JK_EXPECT_F(stopParsing)) { jk_error(parseState, @"Unexpected token, wanted '{', '}', '[', ']', ',', ':', 'true', 'false', 'null', '\"STRING\"', 'NUMBER'."); } + return(stopParsing); +} + +static void jk_error_parse_accept_or3(JKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String) { + NSString *acceptStrings[16]; + int acceptIdx = 0; + if(state & JKParseAcceptValue) { acceptStrings[acceptIdx++] = or1String; } + if(state & JKParseAcceptComma) { acceptStrings[acceptIdx++] = or2String; } + if(state & JKParseAcceptEnd) { acceptStrings[acceptIdx++] = or3String; } + if(acceptIdx == 1) { jk_error(parseState, @"Expected %@, not '%*.*s'", acceptStrings[0], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); } + else if(acceptIdx == 2) { jk_error(parseState, @"Expected %@ or %@, not '%*.*s'", acceptStrings[0], acceptStrings[1], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); } + else if(acceptIdx == 3) { jk_error(parseState, @"Expected %@, %@, or %@, not '%*.*s", acceptStrings[0], acceptStrings[1], acceptStrings[2], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); } +} + +static void *jk_parse_array(JKParseState *parseState) { + size_t startingObjectIndex = parseState->objectStack.index; + int arrayState = JKParseAcceptValueOrEnd, stopParsing = 0; + void *parsedArray = NULL; + + while(JK_EXPECT_T((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) { + if(JK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(jk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { jk_error(parseState, @"Internal error: [array] objectsIndex > %zu, resize failed? %@ line %#ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } } + + if(JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0)) { + void *object = NULL; +#ifndef NS_BLOCK_ASSERTIONS + parseState->objectStack.objects[parseState->objectStack.index] = NULL; + parseState->objectStack.keys [parseState->objectStack.index] = NULL; +#endif + switch(parseState->token.type) { + case JKTokenTypeNumber: + case JKTokenTypeString: + case JKTokenTypeTrue: + case JKTokenTypeFalse: + case JKTokenTypeNull: + case JKTokenTypeArrayBegin: + case JKTokenTypeObjectBegin: + if(JK_EXPECT_F((arrayState & JKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected value."); stopParsing = 1; break; } + if(JK_EXPECT_F((object = jk_object_for_token(parseState)) == NULL)) { jk_error(parseState, @"Internal error: Object == NULL"); stopParsing = 1; break; } else { parseState->objectStack.objects[parseState->objectStack.index++] = object; arrayState = JKParseAcceptCommaOrEnd; } + break; + case JKTokenTypeArrayEnd: if(JK_EXPECT_T(arrayState & JKParseAcceptEnd)) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedArray = (void *)_JKArrayCreate((id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections); } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ']'."); } stopParsing = 1; break; + case JKTokenTypeComma: if(JK_EXPECT_T(arrayState & JKParseAcceptComma)) { arrayState = JKParseAcceptValue; } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break; + default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, arrayState, @"a value", @"a comma", @"a ']'"); stopParsing = 1; break; + } + } + } + + if(JK_EXPECT_F(parsedArray == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } } +#if !defined(NS_BLOCK_ASSERTIONS) + else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } } +#endif + + parseState->objectStack.index = startingObjectIndex; + return(parsedArray); +} + +static void *jk_create_dictionary(JKParseState *parseState, size_t startingObjectIndex) { + void *parsedDictionary = NULL; + + parseState->objectStack.index--; + + parsedDictionary = _JKDictionaryCreate((id *)&parseState->objectStack.keys[startingObjectIndex], (NSUInteger *)&parseState->objectStack.cfHashes[startingObjectIndex], (id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections); + + return(parsedDictionary); +} + +static void *jk_parse_dictionary(JKParseState *parseState) { + size_t startingObjectIndex = parseState->objectStack.index; + int dictState = JKParseAcceptValueOrEnd, stopParsing = 0; + void *parsedDictionary = NULL; + + while(JK_EXPECT_T((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) { + if(JK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(jk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { jk_error(parseState, @"Internal error: [dictionary] objectsIndex > %zu, resize failed? %@ line #%ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } } + + size_t objectStackIndex = parseState->objectStack.index++; + parseState->objectStack.keys[objectStackIndex] = NULL; + parseState->objectStack.objects[objectStackIndex] = NULL; + void *key = NULL, *object = NULL; + + if(JK_EXPECT_T((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0)))) { + switch(parseState->token.type) { + case JKTokenTypeString: + if(JK_EXPECT_F((dictState & JKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected string."); stopParsing = 1; break; } + if(JK_EXPECT_F((key = jk_object_for_token(parseState)) == NULL)) { jk_error(parseState, @"Internal error: Key == NULL."); stopParsing = 1; break; } + else { + parseState->objectStack.keys[objectStackIndex] = key; + if(JK_EXPECT_T(parseState->token.value.cacheItem != NULL)) { if(JK_EXPECT_F(parseState->token.value.cacheItem->cfHash == 0UL)) { parseState->token.value.cacheItem->cfHash = CFHash(key); } parseState->objectStack.cfHashes[objectStackIndex] = parseState->token.value.cacheItem->cfHash; } + else { parseState->objectStack.cfHashes[objectStackIndex] = CFHash(key); } + } + break; + + case JKTokenTypeObjectEnd: if((JK_EXPECT_T(dictState & JKParseAcceptEnd))) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedDictionary = jk_create_dictionary(parseState, startingObjectIndex); } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected '}'."); } stopParsing = 1; break; + case JKTokenTypeComma: if((JK_EXPECT_T(dictState & JKParseAcceptComma))) { dictState = JKParseAcceptValue; parseState->objectStack.index--; continue; } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break; + + default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, dictState, @"a \"STRING\"", @"a comma", @"a '}'"); stopParsing = 1; break; + } + } + + if(JK_EXPECT_T(stopParsing == 0)) { + if(JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0)) { if(JK_EXPECT_F(parseState->token.type != JKTokenTypeSeparator)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Expected ':'."); stopParsing = 1; } } + } + + if((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0))) { + switch(parseState->token.type) { + case JKTokenTypeNumber: + case JKTokenTypeString: + case JKTokenTypeTrue: + case JKTokenTypeFalse: + case JKTokenTypeNull: + case JKTokenTypeArrayBegin: + case JKTokenTypeObjectBegin: + if(JK_EXPECT_F((dictState & JKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected value."); stopParsing = 1; break; } + if(JK_EXPECT_F((object = jk_object_for_token(parseState)) == NULL)) { jk_error(parseState, @"Internal error: Object == NULL."); stopParsing = 1; break; } else { parseState->objectStack.objects[objectStackIndex] = object; dictState = JKParseAcceptCommaOrEnd; } + break; + default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, dictState, @"a value", @"a comma", @"a '}'"); stopParsing = 1; break; + } + } + } + + if(JK_EXPECT_F(parsedDictionary == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.keys[idx] != NULL) { CFRelease(parseState->objectStack.keys[idx]); parseState->objectStack.keys[idx] = NULL; } if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } } +#if !defined(NS_BLOCK_ASSERTIONS) + else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } } +#endif + + parseState->objectStack.index = startingObjectIndex; + return(parsedDictionary); +} + +static id json_parse_it(JKParseState *parseState) { + id parsedObject = NULL; + int stopParsing = 0; + + while((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length))) { + if((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0))) { + switch(parseState->token.type) { + case JKTokenTypeArrayBegin: + case JKTokenTypeObjectBegin: parsedObject = [(id)jk_object_for_token(parseState) autorelease]; stopParsing = 1; break; + default: jk_error(parseState, @"Expected either '[' or '{'."); stopParsing = 1; break; + } + } + } + + NSCParameterAssert((parseState->objectStack.index == 0) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState))); + + if((parsedObject == NULL) && (JK_AT_STRING_PTR(parseState) == JK_END_STRING_PTR(parseState))) { jk_error(parseState, @"Reached the end of the buffer."); } + if(parsedObject == NULL) { jk_error(parseState, @"Unable to parse JSON."); } + + if((parsedObject != NULL) && (JK_AT_STRING_PTR(parseState) < JK_END_STRING_PTR(parseState))) { + jk_parse_skip_whitespace(parseState); + if((parsedObject != NULL) && ((parseState->parseOptionFlags & JKParseOptionPermitTextAfterValidJSON) == 0) && (JK_AT_STRING_PTR(parseState) < JK_END_STRING_PTR(parseState))) { + jk_error(parseState, @"A valid JSON object was parsed but there were additional non-white-space characters remaining."); + parsedObject = NULL; + } + } + + return(parsedObject); +} + +//////////// +#pragma mark - +#pragma mark Object cache + +// This uses a Galois Linear Feedback Shift Register (LFSR) PRNG to pick which item in the cache to age. It has a period of (2^32)-1. +// NOTE: A LFSR *MUST* be initialized to a non-zero value and must always have a non-zero value. +JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) { + NSCParameterAssert((parseState != NULL) && (parseState->cache.prng_lfsr != 0U)); + parseState->cache.prng_lfsr = (parseState->cache.prng_lfsr >> 1) ^ ((0U - (parseState->cache.prng_lfsr & 1U)) & 0x80200003U); + parseState->cache.age[parseState->cache.prng_lfsr & (parseState->cache.count - 1UL)] >>= 1; +} + +// The object cache is nothing more than a hash table with open addressing collision resolution that is bounded by JK_CACHE_PROBES attempts. +// +// The hash table is a linear C array of JKTokenCacheItem. The terms "item" and "bucket" are synonymous with the index in to the cache array, i.e. cache.items[bucket]. +// +// Items in the cache have an age associated with them. The age is the number of rightmost 1 bits, i.e. 0000 = 0, 0001 = 1, 0011 = 2, 0111 = 3, 1111 = 4. +// This allows us to use left and right shifts to add or subtract from an items age. Add = (age << 1) | 1. Subtract = age >> 0. Subtract is synonymous with "age" (i.e., age an item). +// The reason for this is it allows us to perform saturated adds and subtractions and is branchless. +// The primitive C type MUST be unsigned. It is currently a "char", which allows (at a minimum and in practice) 8 bits. +// +// A "useable bucket" is a bucket that is not in use (never populated), or has an age == 0. +// +// When an item is found in the cache, it's age is incremented. +// If a useable bucket hasn't been found, the current item (bucket) is aged along with two random items. +// +// If a value is not found in the cache, and no useable bucket has been found, that value is not added to the cache. + +static void *jk_cachedObjects(JKParseState *parseState) { + unsigned long bucket = parseState->token.value.hash & (parseState->cache.count - 1UL), setBucket = 0UL, useableBucket = 0UL, x = 0UL; + void *parsedAtom = NULL; + + if(JK_EXPECT_F(parseState->token.value.ptrRange.length == 0UL) && JK_EXPECT_T(parseState->token.value.type == JKValueTypeString)) { return(@""); } + + for(x = 0UL; x < JK_CACHE_PROBES; x++) { + if(JK_EXPECT_F(parseState->cache.items[bucket].object == NULL)) { setBucket = 1UL; useableBucket = bucket; break; } + + if((JK_EXPECT_T(parseState->cache.items[bucket].hash == parseState->token.value.hash)) && (JK_EXPECT_T(parseState->cache.items[bucket].size == parseState->token.value.ptrRange.length)) && (JK_EXPECT_T(parseState->cache.items[bucket].type == parseState->token.value.type)) && (JK_EXPECT_T(parseState->cache.items[bucket].bytes != NULL)) && (JK_EXPECT_T(strncmp((const char *)parseState->cache.items[bucket].bytes, (const char *)parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length) == 0U))) { + parseState->cache.age[bucket] = (parseState->cache.age[bucket] << 1) | 1U; + parseState->token.value.cacheItem = &parseState->cache.items[bucket]; + NSCParameterAssert(parseState->cache.items[bucket].object != NULL); + return((void *)CFRetain(parseState->cache.items[bucket].object)); + } else { + if(JK_EXPECT_F(setBucket == 0UL) && JK_EXPECT_F(parseState->cache.age[bucket] == 0U)) { setBucket = 1UL; useableBucket = bucket; } + if(JK_EXPECT_F(setBucket == 0UL)) { parseState->cache.age[bucket] >>= 1; jk_cache_age(parseState); jk_cache_age(parseState); } + // This is the open addressing function. The values length and type are used as a form of "double hashing" to distribute values with the same effective value hash across different object cache buckets. + // The values type is a prime number that is relatively coprime to the other primes in the set of value types and the number of hash table buckets. + bucket = (parseState->token.value.hash + (parseState->token.value.ptrRange.length * (x + 1UL)) + (parseState->token.value.type * (x + 1UL)) + (3UL * (x + 1UL))) & (parseState->cache.count - 1UL); + } + } + + switch(parseState->token.value.type) { + case JKValueTypeString: parsedAtom = (void *)CFStringCreateWithBytes(NULL, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length, kCFStringEncodingUTF8, 0); break; + case JKValueTypeLongLong: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.longLongValue); break; + case JKValueTypeUnsignedLongLong: + if(parseState->token.value.number.unsignedLongLongValue <= LLONG_MAX) { parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.unsignedLongLongValue); } + else { parsedAtom = (void *)parseState->objCImpCache.NSNumberInitWithUnsignedLongLong(parseState->objCImpCache.NSNumberAlloc(parseState->objCImpCache.NSNumberClass, @selector(alloc)), @selector(initWithUnsignedLongLong:), parseState->token.value.number.unsignedLongLongValue); } + break; + case JKValueTypeDouble: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberDoubleType, &parseState->token.value.number.doubleValue); break; + default: jk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; + } + + if(JK_EXPECT_T(setBucket) && (JK_EXPECT_T(parsedAtom != NULL))) { + bucket = useableBucket; + if(JK_EXPECT_T((parseState->cache.items[bucket].object != NULL))) { CFRelease(parseState->cache.items[bucket].object); parseState->cache.items[bucket].object = NULL; } + + if(JK_EXPECT_T((parseState->cache.items[bucket].bytes = (unsigned char *)reallocf(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.length)) != NULL)) { + memcpy(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length); + parseState->cache.items[bucket].object = (void *)CFRetain(parsedAtom); + parseState->cache.items[bucket].hash = parseState->token.value.hash; + parseState->cache.items[bucket].cfHash = 0UL; + parseState->cache.items[bucket].size = parseState->token.value.ptrRange.length; + parseState->cache.items[bucket].type = parseState->token.value.type; + parseState->token.value.cacheItem = &parseState->cache.items[bucket]; + parseState->cache.age[bucket] = JK_INIT_CACHE_AGE; + } else { // The realloc failed, so clear the appropriate fields. + parseState->cache.items[bucket].hash = 0UL; + parseState->cache.items[bucket].cfHash = 0UL; + parseState->cache.items[bucket].size = 0UL; + parseState->cache.items[bucket].type = 0UL; + } + } + + return(parsedAtom); +} + + +static void *jk_object_for_token(JKParseState *parseState) { + void *parsedAtom = NULL; + + parseState->token.value.cacheItem = NULL; + switch(parseState->token.type) { + case JKTokenTypeString: parsedAtom = jk_cachedObjects(parseState); break; + case JKTokenTypeNumber: parsedAtom = jk_cachedObjects(parseState); break; + case JKTokenTypeObjectBegin: parsedAtom = jk_parse_dictionary(parseState); break; + case JKTokenTypeArrayBegin: parsedAtom = jk_parse_array(parseState); break; + case JKTokenTypeTrue: parsedAtom = (void *)kCFBooleanTrue; break; + case JKTokenTypeFalse: parsedAtom = (void *)kCFBooleanFalse; break; + case JKTokenTypeNull: parsedAtom = (void *)kCFNull; break; + default: jk_error(parseState, @"Internal error: Unknown token type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; + } + + return(parsedAtom); +} + +#pragma mark - +@implementation JSONDecoder + +static Class _jk_NSNumberClass; +static NSNumberAllocImp _jk_NSNumberAllocImp; +static NSNumberInitWithUnsignedLongLongImp _jk_NSNumberInitWithUnsignedLongLongImp; + ++ (void)load +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Though technically not required, the run time environment at +load time may be less than ideal. + + _jk_NSNumberClass = [NSNumber class]; + _jk_NSNumberAllocImp = (NSNumberAllocImp)[NSNumber methodForSelector:@selector(alloc)]; + + // Hacktacular. Need to do it this way due to the nature of class clusters. + id temp_NSNumber = [NSNumber alloc]; + _jk_NSNumberInitWithUnsignedLongLongImp = (NSNumberInitWithUnsignedLongLongImp)[temp_NSNumber methodForSelector:@selector(initWithUnsignedLongLong:)]; + [[temp_NSNumber init] release]; + temp_NSNumber = NULL; + + [pool release]; pool = NULL; +} + ++ (id)decoder +{ + return([self decoderWithParseOptions:JKParseOptionStrict]); +} + ++ (id)decoderWithParseOptions:(JKParseOptionFlags)parseOptionFlags +{ + return([[[self alloc] initWithParseOptions:parseOptionFlags] autorelease]); +} + +- (id)init +{ + return([self initWithParseOptions:JKParseOptionStrict]); +} + +- (id)initWithParseOptions:(JKParseOptionFlags)parseOptionFlags +{ + if((self = [super init]) == NULL) { return(NULL); } + + if(parseOptionFlags & ~JKParseOptionValidFlags) { [self autorelease]; [NSException raise:NSInvalidArgumentException format:@"Invalid parse options."]; } + + if((parseState = (JKParseState *)calloc(1UL, sizeof(JKParseState))) == NULL) { goto errorExit; } + + parseState->parseOptionFlags = parseOptionFlags; + + parseState->token.tokenBuffer.roundSizeUpToMultipleOf = 4096UL; + parseState->objectStack.roundSizeUpToMultipleOf = 2048UL; + + parseState->objCImpCache.NSNumberClass = _jk_NSNumberClass; + parseState->objCImpCache.NSNumberAlloc = _jk_NSNumberAllocImp; + parseState->objCImpCache.NSNumberInitWithUnsignedLongLong = _jk_NSNumberInitWithUnsignedLongLongImp; + + parseState->cache.prng_lfsr = 1U; + parseState->cache.count = JK_CACHE_SLOTS; + if((parseState->cache.items = (JKTokenCacheItem *)calloc(1UL, sizeof(JKTokenCacheItem) * parseState->cache.count)) == NULL) { goto errorExit; } + + return(self); + +errorExit: + if(self) { [self autorelease]; self = NULL; } + return(NULL); +} + +// This is here primarily to support the NSString and NSData convenience functions so the autoreleased JSONDecoder can release most of its resources before the pool pops. +static void _JSONDecoderCleanup(JSONDecoder *decoder) { + if((decoder != NULL) && (decoder->parseState != NULL)) { + jk_managedBuffer_release(&decoder->parseState->token.tokenBuffer); + jk_objectStack_release(&decoder->parseState->objectStack); + + [decoder clearCache]; + if(decoder->parseState->cache.items != NULL) { free(decoder->parseState->cache.items); decoder->parseState->cache.items = NULL; } + + free(decoder->parseState); decoder->parseState = NULL; + } +} + +- (void)dealloc +{ + _JSONDecoderCleanup(self); + [super dealloc]; +} + +- (void)clearCache +{ + if(JK_EXPECT_T(parseState != NULL)) { + if(JK_EXPECT_T(parseState->cache.items != NULL)) { + size_t idx = 0UL; + for(idx = 0UL; idx < parseState->cache.count; idx++) { + if(JK_EXPECT_T(parseState->cache.items[idx].object != NULL)) { CFRelease(parseState->cache.items[idx].object); parseState->cache.items[idx].object = NULL; } + if(JK_EXPECT_T(parseState->cache.items[idx].bytes != NULL)) { free(parseState->cache.items[idx].bytes); parseState->cache.items[idx].bytes = NULL; } + memset(&parseState->cache.items[idx], 0, sizeof(JKTokenCacheItem)); + parseState->cache.age[idx] = 0U; + } + } + } +} + +// This needs to be completely rewritten. +static id _JKParseUTF8String(JKParseState *parseState, BOOL mutableCollections, const unsigned char *string, size_t length, NSError **error) { + NSCParameterAssert((parseState != NULL) && (string != NULL) && (parseState->cache.prng_lfsr != 0U)); + parseState->stringBuffer.bytes.ptr = string; + parseState->stringBuffer.bytes.length = length; + parseState->atIndex = 0UL; + parseState->lineNumber = 1UL; + parseState->lineStartIndex = 0UL; + parseState->prev_atIndex = 0UL; + parseState->prev_lineNumber = 1UL; + parseState->prev_lineStartIndex = 0UL; + parseState->error = NULL; + parseState->errorIsPrev = 0; + parseState->mutableCollections = (mutableCollections == NO) ? NO : YES; + + unsigned char stackTokenBuffer[JK_TOKENBUFFER_SIZE] JK_ALIGNED(64); + jk_managedBuffer_setToStackBuffer(&parseState->token.tokenBuffer, stackTokenBuffer, sizeof(stackTokenBuffer)); + + void *stackObjects [JK_STACK_OBJS] JK_ALIGNED(64); + void *stackKeys [JK_STACK_OBJS] JK_ALIGNED(64); + CFHashCode stackCFHashes[JK_STACK_OBJS] JK_ALIGNED(64); + jk_objectStack_setToStackBuffer(&parseState->objectStack, stackObjects, stackKeys, stackCFHashes, JK_STACK_OBJS); + + id parsedJSON = json_parse_it(parseState); + + if((error != NULL) && (parseState->error != NULL)) { *error = parseState->error; } + + jk_managedBuffer_release(&parseState->token.tokenBuffer); + jk_objectStack_release(&parseState->objectStack); + + parseState->stringBuffer.bytes.ptr = NULL; + parseState->stringBuffer.bytes.length = 0UL; + parseState->atIndex = 0UL; + parseState->lineNumber = 1UL; + parseState->lineStartIndex = 0UL; + parseState->prev_atIndex = 0UL; + parseState->prev_lineNumber = 1UL; + parseState->prev_lineStartIndex = 0UL; + parseState->error = NULL; + parseState->errorIsPrev = 0; + parseState->mutableCollections = NO; + + return(parsedJSON); +} + +//////////// +#pragma mark Deprecated as of v1.4 +//////////// + +// Deprecated in JSONKit v1.4. Use objectWithUTF8String:length: instead. +- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length +{ + return([self objectWithUTF8String:string length:length error:NULL]); +} + +// Deprecated in JSONKit v1.4. Use objectWithUTF8String:length:error: instead. +- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error +{ + return([self objectWithUTF8String:string length:length error:error]); +} + +// Deprecated in JSONKit v1.4. Use objectWithData: instead. +- (id)parseJSONData:(NSData *)jsonData +{ + return([self objectWithData:jsonData error:NULL]); +} + +// Deprecated in JSONKit v1.4. Use objectWithData:error: instead. +- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error +{ + return([self objectWithData:jsonData error:error]); +} + +//////////// +#pragma mark Methods that return immutable collection objects +//////////// + +- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length +{ + return([self objectWithUTF8String:string length:length error:NULL]); +} + +- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error +{ + if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."]; } + if(string == NULL) { [NSException raise:NSInvalidArgumentException format:@"The string argument is NULL."]; } + + return(_JKParseUTF8String(parseState, NO, string, (size_t)length, error)); +} + +- (id)objectWithData:(NSData *)jsonData +{ + return([self objectWithData:jsonData error:NULL]); +} + +- (id)objectWithData:(NSData *)jsonData error:(NSError **)error +{ + if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; } + return([self objectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]); +} + +//////////// +#pragma mark Methods that return mutable collection objects +//////////// + +- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length +{ + return([self mutableObjectWithUTF8String:string length:length error:NULL]); +} + +- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error +{ + if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."]; } + if(string == NULL) { [NSException raise:NSInvalidArgumentException format:@"The string argument is NULL."]; } + + return(_JKParseUTF8String(parseState, YES, string, (size_t)length, error)); +} + +- (id)mutableObjectWithData:(NSData *)jsonData +{ + return([self mutableObjectWithData:jsonData error:NULL]); +} + +- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error +{ + if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; } + return([self mutableObjectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]); +} + +@end + +/* + The NSString and NSData convenience methods need a little bit of explanation. + + Prior to JSONKit v1.4, the NSString -objectFromJSONStringWithParseOptions:error: method looked like + + const unsigned char *utf8String = (const unsigned char *)[self UTF8String]; + if(utf8String == NULL) { return(NULL); } + size_t utf8Length = strlen((const char *)utf8String); + return([[JSONDecoder decoderWithParseOptions:parseOptionFlags] parseUTF8String:utf8String length:utf8Length error:error]); + + This changed with v1.4 to a more complicated method. The reason for this is to keep the amount of memory that is + allocated, but not yet freed because it is dependent on the autorelease pool to pop before it can be reclaimed. + + In the simpler v1.3 code, this included all the bytes used to store the -UTF8String along with the JSONDecoder and all its overhead. + + Now we use an autoreleased CFMutableData that is sized to the UTF8 length of the NSString in question and is used to hold the UTF8 + conversion of said string. + + Once parsed, the CFMutableData has its length set to 0. This should, hopefully, allow the CFMutableData to realloc and/or free + the buffer. + + Another change made was a slight modification to JSONDecoder so that most of the cleanup work that was done in -dealloc was moved + to a private, internal function. These convenience routines keep the pointer to the autoreleased JSONDecoder and calls + _JSONDecoderCleanup() to early release the decoders resources since we already know that particular decoder is not going to be used + again. + + If everything goes smoothly, this will most likely result in perhaps a few hundred bytes that are allocated but waiting for the + autorelease pool to pop. This is compared to the thousands and easily hundreds of thousands of bytes that would have been in + autorelease limbo. It's more complicated for us, but a win for the user. + + Autorelease objects are used in case things don't go smoothly. By having them autoreleased, we effectively guarantee that our + requirement to -release the object is always met, not matter what goes wrong. The downside is having a an object or two in + autorelease limbo, but we've done our best to minimize that impact, so it all balances out. + */ + +@implementation NSString (JSONKitDeserializing) + +static id _NSStringObjectFromJSONString(NSString *jsonString, JKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection) { + id returnObject = NULL; + CFMutableDataRef mutableData = NULL; + JSONDecoder *decoder = NULL; + + CFIndex stringLength = CFStringGetLength((CFStringRef)jsonString); + NSUInteger stringUTF8Length = [jsonString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + if((mutableData = (CFMutableDataRef)[(id)CFDataCreateMutable(NULL, (NSUInteger)stringUTF8Length) autorelease]) != NULL) { + UInt8 *utf8String = CFDataGetMutableBytePtr(mutableData); + CFIndex usedBytes = 0L, convertedCount = 0L; + + convertedCount = CFStringGetBytes((CFStringRef)jsonString, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, utf8String, (NSUInteger)stringUTF8Length, &usedBytes); + if(JK_EXPECT_F(convertedCount != stringLength) || JK_EXPECT_F(usedBytes < 0L)) { if(error != NULL) { *error = [NSError errorWithDomain:@"JKErrorDomain" code:-1L userInfo:[NSDictionary dictionaryWithObject:@"An error occurred converting the contents of a NSString to UTF8." forKey:NSLocalizedDescriptionKey]]; } goto exitNow; } + + if(mutableCollection == NO) { returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) objectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; } + else { returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; } + } + +exitNow: + if(mutableData != NULL) { CFDataSetLength(mutableData, 0L); } + if(decoder != NULL) { _JSONDecoderCleanup(decoder); } + return(returnObject); +} + +- (id)objectFromJSONString +{ + return([self objectFromJSONStringWithParseOptions:JKParseOptionStrict error:NULL]); +} + +- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags +{ + return([self objectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]); +} + +- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error +{ + return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, NO)); +} + + +- (id)mutableObjectFromJSONString +{ + return([self mutableObjectFromJSONStringWithParseOptions:JKParseOptionStrict error:NULL]); +} + +- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags +{ + return([self mutableObjectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]); +} + +- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error +{ + return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, YES)); +} + +@end + +@implementation NSData (JSONKitDeserializing) + +- (id)objectFromJSONData +{ + return([self objectFromJSONDataWithParseOptions:JKParseOptionStrict error:NULL]); +} + +- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags +{ + return([self objectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]); +} + +- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error +{ + JSONDecoder *decoder = NULL; + id returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) objectWithData:self error:error]; + if(decoder != NULL) { _JSONDecoderCleanup(decoder); } + return(returnObject); +} + +- (id)mutableObjectFromJSONData +{ + return([self mutableObjectFromJSONDataWithParseOptions:JKParseOptionStrict error:NULL]); +} + +- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags +{ + return([self mutableObjectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]); +} + +- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error +{ + JSONDecoder *decoder = NULL; + id returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithData:self error:error]; + if(decoder != NULL) { _JSONDecoderCleanup(decoder); } + return(returnObject); +} + + +@end + +//////////// +#pragma mark - +#pragma mark Encoding / deserializing functions + +static void jk_encode_error(JKEncodeState *encodeState, NSString *format, ...) { + NSCParameterAssert((encodeState != NULL) && (format != NULL)); + + va_list varArgsList; + va_start(varArgsList, format); + NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; + va_end(varArgsList); + + if(encodeState->error == NULL) { + encodeState->error = [NSError errorWithDomain:@"JKErrorDomain" code:-1L userInfo: + [NSDictionary dictionaryWithObjectsAndKeys: + formatString, NSLocalizedDescriptionKey, + NULL]]; + } +} + +JK_STATIC_INLINE void jk_encode_updateCache(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object) { + NSCParameterAssert(encodeState != NULL); + if(JK_EXPECT_T(cacheSlot != NULL)) { + NSCParameterAssert((object != NULL) && (startingAtIndex <= encodeState->atIndex)); + cacheSlot->object = object; + cacheSlot->offset = startingAtIndex; + cacheSlot->length = (size_t)(encodeState->atIndex - startingAtIndex); + } +} + +static int jk_encode_printf(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...) { + va_list varArgsList, varArgsListCopy; + va_start(varArgsList, format); + va_copy(varArgsListCopy, varArgsList); + + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL)); + + ssize_t formattedStringLength = 0L; + int returnValue = 0; + + if(JK_EXPECT_T((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsList)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) { + NSCParameterAssert(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)); + if(JK_EXPECT_F(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (formattedStringLength * 2UL)+ 4096UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); returnValue = 1; goto exitNow; } + if(JK_EXPECT_F((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsListCopy)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) { jk_encode_error(encodeState, @"vsnprintf failed unexpectedly."); returnValue = 1; goto exitNow; } + } + +exitNow: + va_end(varArgsList); + va_end(varArgsListCopy); + if(JK_EXPECT_T(returnValue == 0)) { encodeState->atIndex += formattedStringLength; jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); } + return(returnValue); +} + +static int jk_encode_write(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL)); + if(JK_EXPECT_F(((encodeState->atIndex + strlen(format) + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + strlen(format) + 1024UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + + size_t formatIdx = 0UL; + for(formatIdx = 0UL; format[formatIdx] != 0; formatIdx++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[formatIdx]; } + jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); + return(0); +} + +static int jk_encode_writePrettyPrintWhiteSpace(JKEncodeState *encodeState) { + NSCParameterAssert((encodeState != NULL) && ((encodeState->serializeOptionFlags & JKSerializeOptionPretty) != 0UL)); + if(JK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && JK_EXPECT_T(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\n'; + size_t depthWhiteSpace = 0UL; + for(depthWhiteSpace = 0UL; depthWhiteSpace < (encodeState->depth * 2UL); depthWhiteSpace++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; } + return(0); +} + +static int jk_encode_write1slow(JKEncodeState *encodeState, ssize_t depthChange, const char *format) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (format != NULL) && ((depthChange >= -1L) && (depthChange <= 1L)) && ((encodeState->depth == 0UL) ? (depthChange >= 0L) : 1) && ((encodeState->serializeOptionFlags & JKSerializeOptionPretty) != 0UL)); + if(JK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && JK_EXPECT_F(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + encodeState->depth += depthChange; + if(JK_EXPECT_T(format[0] == ':')) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; } + else { + if(JK_EXPECT_F(depthChange == -1L)) { if(JK_EXPECT_F(jk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } } + encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; + if(JK_EXPECT_T(depthChange != -1L)) { if(JK_EXPECT_F(jk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } } + } + NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); + return(0); +} + +static int jk_encode_write1fast(JKEncodeState *encodeState, ssize_t depthChange JK_UNUSED_ARG, const char *format) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && ((encodeState->serializeOptionFlags & JKSerializeOptionPretty) == 0UL)); + if(JK_EXPECT_T((encodeState->atIndex + 4UL) < encodeState->stringBuffer.bytes.length)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; } + else { return(jk_encode_write(encodeState, NULL, 0UL, NULL, format)); } + return(0); +} + +static int jk_encode_writen(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex)); + if(JK_EXPECT_F((encodeState->stringBuffer.bytes.length - encodeState->atIndex) < (length + 4UL))) { if(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + 4096UL + length) == NULL) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } } + memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, format, length); + encodeState->atIndex += length; + jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); + return(0); +} + +JK_STATIC_INLINE JKHash jk_encode_object_hash(void *objectPtr) { + return( ( (((JKHash)objectPtr) >> 21) ^ (((JKHash)objectPtr) >> 9) ) + (((JKHash)objectPtr) >> 4) ); +} + +static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *objectPtr) { + NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (objectPtr != NULL)); + + id object = (id)objectPtr, encodeCacheObject = object; + int isClass = JKClassUnknown; + size_t startingAtIndex = encodeState->atIndex; + + JKHash objectHash = jk_encode_object_hash(objectPtr); + JKEncodeCache *cacheSlot = &encodeState->cache[objectHash % JK_ENCODE_CACHE_SLOTS]; + + if(JK_EXPECT_T(cacheSlot->object == object)) { + NSCParameterAssert((cacheSlot->object != NULL) && + (cacheSlot->offset < encodeState->atIndex) && ((cacheSlot->offset + cacheSlot->length) < encodeState->atIndex) && + (cacheSlot->offset < encodeState->stringBuffer.bytes.length) && ((cacheSlot->offset + cacheSlot->length) < encodeState->stringBuffer.bytes.length) && + ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length))); + if(JK_EXPECT_F(((encodeState->atIndex + cacheSlot->length + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + cacheSlot->length + 1024UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + NSCParameterAssert(((encodeState->atIndex + cacheSlot->length) < encodeState->stringBuffer.bytes.length) && + ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && + ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->atIndex))); + memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, encodeState->stringBuffer.bytes.ptr + cacheSlot->offset, cacheSlot->length); + encodeState->atIndex += cacheSlot->length; + return(0); + } + + // When we encounter a class that we do not handle, and we have either a delegate or block that the user supplied to format unsupported classes, + // we "re-run" the object check. However, we re-run the object check exactly ONCE. If the user supplies an object that isn't one of the + // supported classes, we fail the second type (i.e., double fault error). + BOOL rerunningAfterClassFormatter = NO; +rerunAfterClassFormatter: + if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.stringClass)) { isClass = JKClassString; } + else if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.numberClass)) { isClass = JKClassNumber; } + else if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.dictionaryClass)) { isClass = JKClassDictionary; } + else if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.arrayClass)) { isClass = JKClassArray; } + else if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.nullClass)) { isClass = JKClassNull; } + else { + if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastClassLookup.stringClass = object->isa; isClass = JKClassString; } + else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { encodeState->fastClassLookup.numberClass = object->isa; isClass = JKClassNumber; } + else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { encodeState->fastClassLookup.dictionaryClass = object->isa; isClass = JKClassDictionary; } + else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { encodeState->fastClassLookup.arrayClass = object->isa; isClass = JKClassArray; } + else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { encodeState->fastClassLookup.nullClass = object->isa; isClass = JKClassNull; } + else { + if((rerunningAfterClassFormatter == NO) && ( +#ifdef __BLOCKS__ + ((encodeState->classFormatterBlock) && ((object = encodeState->classFormatterBlock(object)) != NULL)) || +#endif + ((encodeState->classFormatterIMP) && ((object = encodeState->classFormatterIMP(encodeState->classFormatterDelegate, encodeState->classFormatterSelector, object)) != NULL)) )) { rerunningAfterClassFormatter = YES; goto rerunAfterClassFormatter; } + + if(rerunningAfterClassFormatter == NO) { jk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([encodeCacheObject class])); return(1); } + else { jk_encode_error(encodeState, @"Unable to serialize object class %@ that was returned by the unsupported class formatter. Original object class was %@.", (object == NULL) ? @"NULL" : NSStringFromClass([object class]), NSStringFromClass([encodeCacheObject class])); return(1); } + } + } + + // This is here for the benefit of the optimizer. It allows the optimizer to do loop invariant code motion for the JKClassArray + // and JKClassDictionary cases when printing simple, single characters via jk_encode_write(), which is actually a macro: + // #define jk_encode_write1(es, dc, f) (_jk_encode_prettyPrint ? jk_encode_write1slow(es, dc, f) : jk_encode_write1fast(es, dc, f)) + int _jk_encode_prettyPrint = JK_EXPECT_T((encodeState->serializeOptionFlags & JKSerializeOptionPretty) == 0) ? 0 : 1; + + switch(isClass) { + case JKClassString: + { + { + const unsigned char *cStringPtr = (const unsigned char *)CFStringGetCStringPtr((CFStringRef)object, kCFStringEncodingMacRoman); + if(cStringPtr != NULL) { + const unsigned char *utf8String = cStringPtr; + size_t utf8Idx = 0UL; + + CFIndex stringLength = CFStringGetLength((CFStringRef)object); + if(JK_EXPECT_F(((encodeState->atIndex + (stringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (stringLength * 2UL) + 1024UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + + if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } + for(utf8Idx = 0UL; utf8String[utf8Idx] != 0U; utf8Idx++) { + NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length); + NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); + if(JK_EXPECT_F(utf8String[utf8Idx] >= 0x80U)) { encodeState->atIndex = startingAtIndex; goto slowUTF8Path; } + if(JK_EXPECT_F(utf8String[utf8Idx] < 0x20U)) { + switch(utf8String[utf8Idx]) { + case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break; + case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break; + case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break; + case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break; + case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break; + default: if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break; + } + } else { + if(JK_EXPECT_F(utf8String[utf8Idx] == '\"') || JK_EXPECT_F(utf8String[utf8Idx] == '\\') || (JK_EXPECT_F(encodeState->serializeOptionFlags & JKSerializeOptionEscapeForwardSlashes) && JK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; } + encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx]; + } + } + NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length); + if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } + jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject); + return(0); + } + } + + slowUTF8Path: + { + CFIndex stringLength = CFStringGetLength((CFStringRef)object); + CFIndex maxStringUTF8Length = CFStringGetMaximumSizeForEncoding(stringLength, kCFStringEncodingUTF8) + 32L; + + if(JK_EXPECT_F((size_t)maxStringUTF8Length > encodeState->utf8ConversionBuffer.bytes.length) && JK_EXPECT_F(jk_managedBuffer_resize(&encodeState->utf8ConversionBuffer, maxStringUTF8Length + 1024UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + + CFIndex usedBytes = 0L, convertedCount = 0L; + convertedCount = CFStringGetBytes((CFStringRef)object, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, encodeState->utf8ConversionBuffer.bytes.ptr, encodeState->utf8ConversionBuffer.bytes.length - 16L, &usedBytes); + if(JK_EXPECT_F(convertedCount != stringLength) || JK_EXPECT_F(usedBytes < 0L)) { jk_encode_error(encodeState, @"An error occurred converting the contents of a NSString to UTF8."); return(1); } + + if(JK_EXPECT_F((encodeState->atIndex + (maxStringUTF8Length * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length) && JK_EXPECT_F(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (maxStringUTF8Length * 2UL) + 1024UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } + + const unsigned char *utf8String = encodeState->utf8ConversionBuffer.bytes.ptr; + + size_t utf8Idx = 0UL; + if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } + for(utf8Idx = 0UL; utf8Idx < (size_t)usedBytes; utf8Idx++) { + NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length); + NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); + NSCParameterAssert((CFIndex)utf8Idx < usedBytes); + if(JK_EXPECT_F(utf8String[utf8Idx] < 0x20U)) { + switch(utf8String[utf8Idx]) { + case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break; + case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break; + case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break; + case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break; + case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break; + default: if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break; + } + } else { + if(JK_EXPECT_F(utf8String[utf8Idx] >= 0x80U) && (encodeState->serializeOptionFlags & JKSerializeOptionEscapeUnicode)) { + const unsigned char *nextValidCharacter = NULL; + UTF32 u32ch = 0U; + ConversionResult result; + + if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(&utf8String[utf8Idx], &utf8String[usedBytes], (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { jk_encode_error(encodeState, @"Error converting UTF8."); return(1); } + else { + utf8Idx = (nextValidCharacter - utf8String) - 1UL; + if(JK_EXPECT_T(u32ch <= 0xffffU)) { if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", u32ch))) { return(1); } } + else { if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x\\u%4.4x", (0xd7c0U + (u32ch >> 10)), (0xdc00U + (u32ch & 0x3ffU))))) { return(1); } } + } + } else { + if(JK_EXPECT_F(utf8String[utf8Idx] == '\"') || JK_EXPECT_F(utf8String[utf8Idx] == '\\') || (JK_EXPECT_F(encodeState->serializeOptionFlags & JKSerializeOptionEscapeForwardSlashes) && JK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; } + encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx]; + } + } + } + NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length); + if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } + jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject); + return(0); + } + } + break; + + case JKClassNumber: + { + if(object == (id)kCFBooleanTrue) { return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "true", 4UL)); } + else if(object == (id)kCFBooleanFalse) { return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "false", 5UL)); } + + const char *objCType = [object objCType]; + char anum[256], *aptr = &anum[255]; + int isNegative = 0; + unsigned long long ullv; + long long llv; + + if(JK_EXPECT_F(objCType == NULL) || JK_EXPECT_F(objCType[0] == 0) || JK_EXPECT_F(objCType[1] != 0)) { jk_encode_error(encodeState, @"NSNumber conversion error, unknown type. Type: '%s'", (objCType == NULL) ? "" : objCType); return(1); } + + switch(objCType[0]) { + case 'c': case 'i': case 's': case 'l': case 'q': + if(JK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &llv))) { + if(llv < 0LL) { ullv = -llv; isNegative = 1; } else { ullv = llv; isNegative = 0; } + goto convertNumber; + } else { jk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); } + break; + case 'C': case 'I': case 'S': case 'L': case 'Q': case 'B': + if(JK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &ullv))) { + convertNumber: + if(JK_EXPECT_F(ullv < 10ULL)) { *--aptr = ullv + '0'; } else { while(JK_EXPECT_T(ullv > 0ULL)) { *--aptr = (ullv % 10ULL) + '0'; ullv /= 10ULL; NSCParameterAssert(aptr > anum); } } + if(isNegative) { *--aptr = '-'; } + NSCParameterAssert(aptr > anum); + return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, aptr, &anum[255] - aptr)); + } else { jk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); } + break; + case 'f': case 'd': + { + double dv; + if(JK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberDoubleType, &dv))) { + if(JK_EXPECT_F(!isfinite(dv))) { jk_encode_error(encodeState, @"Floating point values must be finite. JSON does not support NaN or Infinity."); return(1); } + return(jk_encode_printf(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "%.17g", dv)); + } else { jk_encode_error(encodeState, @"Unable to get floating point value from number object."); return(1); } + } + break; + default: jk_encode_error(encodeState, @"NSNumber conversion error, unknown type. Type: '%c' / 0x%2.2x", objCType[0], objCType[0]); return(1); break; + } + } + break; + + case JKClassArray: + { + int printComma = 0; + CFIndex arrayCount = CFArrayGetCount((CFArrayRef)object), idx = 0L; + if(JK_EXPECT_F(jk_encode_write1(encodeState, 1L, "["))) { return(1); } + if(JK_EXPECT_F(arrayCount > 1020L)) { + for(id arrayObject in object) { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, arrayObject))) { return(1); } } + } else { + void *objects[1024]; + CFArrayGetValues((CFArrayRef)object, CFRangeMake(0L, arrayCount), (const void **)objects); + for(idx = 0L; idx < arrayCount; idx++) { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } } + } + return(jk_encode_write1(encodeState, -1L, "]")); + } + break; + + case JKClassDictionary: + { + int printComma = 0; + CFIndex dictionaryCount = CFDictionaryGetCount((CFDictionaryRef)object), idx = 0L; + id enumerateObject = JK_EXPECT_F(_jk_encode_prettyPrint) ? [[object allKeys] sortedArrayUsingSelector:@selector(compare:)] : object; + + if(JK_EXPECT_F(jk_encode_write1(encodeState, 1L, "{"))) { return(1); } + if(JK_EXPECT_F(_jk_encode_prettyPrint) || JK_EXPECT_F(dictionaryCount > 1020L)) { + for(id keyObject in enumerateObject) { + if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } + printComma = 1; + if(JK_EXPECT_F((keyObject->isa != encodeState->fastClassLookup.stringClass)) && JK_EXPECT_F(([keyObject isKindOfClass:[NSString class]] == NO))) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } + if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keyObject))) { return(1); } + if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); } + if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, (void *)CFDictionaryGetValue((CFDictionaryRef)object, keyObject)))) { return(1); } + } + } else { + void *keys[1024], *objects[1024]; + CFDictionaryGetKeysAndValues((CFDictionaryRef)object, (const void **)keys, (const void **)objects); + for(idx = 0L; idx < dictionaryCount; idx++) { + if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } + printComma = 1; + if(JK_EXPECT_F(((id)keys[idx])->isa != encodeState->fastClassLookup.stringClass) && JK_EXPECT_F([(id)keys[idx] isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } + if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keys[idx]))) { return(1); } + if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); } + if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } + } + } + return(jk_encode_write1(encodeState, -1L, "}")); + } + break; + + case JKClassNull: return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "null", 4UL)); break; + + default: jk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([object class])); return(1); break; + } + + return(0); +} + + +@implementation JKSerializer + ++ (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([[[[self alloc] init] autorelease] serializeObject:object options:optionFlags encodeOption:encodeOption block:block delegate:delegate selector:selector error:error]); +} + +- (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ +#ifndef __BLOCKS__ +#pragma unused(block) +#endif + NSParameterAssert((object != NULL) && (encodeState == NULL) && ((delegate != NULL) ? (block == NULL) : 1) && ((block != NULL) ? (delegate == NULL) : 1) && + (((encodeOption & JKEncodeOptionCollectionObj) != 0UL) ? (((encodeOption & JKEncodeOptionStringObj) == 0UL) && ((encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) : 1) && + (((encodeOption & JKEncodeOptionStringObj) != 0UL) ? ((encodeOption & JKEncodeOptionCollectionObj) == 0UL) : 1)); + + id returnObject = NULL; + + if(encodeState != NULL) { [self releaseState]; } + if((encodeState = (struct JKEncodeState *)calloc(1UL, sizeof(JKEncodeState))) == NULL) { [NSException raise:NSMallocException format:@"Unable to allocate state structure."]; return(NULL); } + + if((error != NULL) && (*error != NULL)) { *error = NULL; } + + if(delegate != NULL) { + if(selector == NULL) { [NSException raise:NSInvalidArgumentException format:@"The delegate argument is not NULL, but the selector argument is NULL."]; } + if([delegate respondsToSelector:selector] == NO) { [NSException raise:NSInvalidArgumentException format:@"The serializeUnsupportedClassesUsingDelegate: delegate does not respond to the selector argument."]; } + encodeState->classFormatterDelegate = delegate; + encodeState->classFormatterSelector = selector; + encodeState->classFormatterIMP = (JKClassFormatterIMP)[delegate methodForSelector:selector]; + NSCParameterAssert(encodeState->classFormatterIMP != NULL); + } + +#ifdef __BLOCKS__ + encodeState->classFormatterBlock = block; +#endif + encodeState->serializeOptionFlags = optionFlags; + encodeState->encodeOption = encodeOption; + encodeState->stringBuffer.roundSizeUpToMultipleOf = (1024UL * 32UL); + encodeState->utf8ConversionBuffer.roundSizeUpToMultipleOf = 4096UL; + + unsigned char stackJSONBuffer[JK_JSONBUFFER_SIZE] JK_ALIGNED(64); + jk_managedBuffer_setToStackBuffer(&encodeState->stringBuffer, stackJSONBuffer, sizeof(stackJSONBuffer)); + + unsigned char stackUTF8Buffer[JK_UTF8BUFFER_SIZE] JK_ALIGNED(64); + jk_managedBuffer_setToStackBuffer(&encodeState->utf8ConversionBuffer, stackUTF8Buffer, sizeof(stackUTF8Buffer)); + + if(((encodeOption & JKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:[NSArray class]] == NO) && ([object isKindOfClass:[NSDictionary class]] == NO))) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; } + if(((encodeOption & JKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; } + + if(jk_encode_add_atom_to_buffer(encodeState, object) == 0) { + BOOL stackBuffer = ((encodeState->stringBuffer.flags & JKManagedBufferMustFree) == 0UL) ? YES : NO; + + if((encodeState->atIndex < 2UL)) + if((stackBuffer == NO) && ((encodeState->stringBuffer.bytes.ptr = (unsigned char *)reallocf(encodeState->stringBuffer.bytes.ptr, encodeState->atIndex + 16UL)) == NULL)) { jk_encode_error(encodeState, @"Unable to realloc buffer"); goto errorExit; } + + switch((encodeOption & JKEncodeOptionAsTypeMask)) { + case JKEncodeOptionAsData: + if(stackBuffer == YES) { if((returnObject = [(id)CFDataCreate( NULL, encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex) autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSData object"); } } + else { if((returnObject = [(id)CFDataCreateWithBytesNoCopy( NULL, encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, NULL) autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSData object"); } } + break; + + case JKEncodeOptionAsString: + if(stackBuffer == YES) { if((returnObject = [(id)CFStringCreateWithBytes( NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO) autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSString object"); } } + else { if((returnObject = [(id)CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO, NULL) autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSString object"); } } + break; + + default: jk_encode_error(encodeState, @"Unknown encode as type."); break; + } + + if((returnObject != NULL) && (stackBuffer == NO)) { encodeState->stringBuffer.flags &= ~JKManagedBufferMustFree; encodeState->stringBuffer.bytes.ptr = NULL; encodeState->stringBuffer.bytes.length = 0UL; } + } + +errorExit: + if((encodeState != NULL) && (error != NULL) && (encodeState->error != NULL)) { *error = encodeState->error; encodeState->error = NULL; } + [self releaseState]; + + return(returnObject); +} + +- (void)releaseState +{ + if(encodeState != NULL) { + jk_managedBuffer_release(&encodeState->stringBuffer); + jk_managedBuffer_release(&encodeState->utf8ConversionBuffer); + free(encodeState); encodeState = NULL; + } +} + +- (void)dealloc +{ + [self releaseState]; + [super dealloc]; +} + +@end + +@implementation NSString (JSONKitSerializing) + +//////////// +#pragma mark Methods for serializing a single NSString. +//////////// + +// Useful for those who need to serialize just a NSString. Otherwise you would have to do something like [NSArray arrayWithObject:stringToBeJSONSerialized], serializing the array, and then chopping of the extra ^\[.*\]$ square brackets. + +// NSData returning methods... + +- (NSData *)JSONData +{ + return([self JSONDataWithOptions:JKSerializeOptionNone includeQuotes:YES error:NULL]); +} + +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | ((includeQuotes == NO) ? JKEncodeOptionStringObjTrimQuotes : 0UL) | JKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +// NSString returning methods... + +- (NSString *)JSONString +{ + return([self JSONStringWithOptions:JKSerializeOptionNone includeQuotes:YES error:NULL]); +} + +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | ((includeQuotes == NO) ? JKEncodeOptionStringObjTrimQuotes : 0UL) | JKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +@end + +@implementation NSArray (JSONKitSerializing) + +// NSData returning methods... + +- (NSData *)JSONData +{ + return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); +} + +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); +} + +// NSString returning methods... + +- (NSString *)JSONString +{ + return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); +} + +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); +} + +@end + +@implementation NSDictionary (JSONKitSerializing) + +// NSData returning methods... + +- (NSData *)JSONData +{ + return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); +} + +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); +} + +// NSString returning methods... + +- (NSString *)JSONString +{ + return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); +} + +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); +} + +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); +} + +@end + + +#ifdef __BLOCKS__ + +@implementation NSArray (JSONKitSerializingBlockAdditions) + +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); +} + +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); +} + +@end + +@implementation NSDictionary (JSONKitSerializingBlockAdditions) + +- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); +} + +- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error +{ + return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); +} + +@end + +#endif // __BLOCKS__ diff --git a/AFNetworkingExample/Vendor/TTT/TTTLocationFormatter.h b/AFNetworkingExample/Vendor/TTT/TTTLocationFormatter.h new file mode 100644 index 0000000..be7a374 --- /dev/null +++ b/AFNetworkingExample/Vendor/TTT/TTTLocationFormatter.h @@ -0,0 +1,83 @@ +// TTTLocationFormatter.h +// +// Copyright (c) 2011 Mattt Thompson (http://mattt.me) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import + +typedef enum { + TTTNorthDirection, + TTTNortheastDirection, + TTTEastDirection, + TTTSoutheastDirection, + TTTSouthDirection, + TTTSouthwestDirection, + TTTWestDirection, + TTTNorthwestDirection, +} TTTLocationCardinalDirection; + +extern TTTLocationCardinalDirection TTTLocationCardinalDirectionFromBearing(CLLocationDegrees bearing); + +typedef enum { + TTTCoordinateLatLngOrder = 0, + TTTCoordinateLngLatOrder, +} TTTLocationFormatterCoordinateOrder; + +typedef enum { + TTTBearingWordStyle = 0, + TTTBearingAbbreviationWordStyle, + TTTBearingNumericStyle, +} TTTLocationFormatterBearingStyle; + +typedef enum { + TTTMetricSystem = 0, + TTTImperialSystem, +} TTTLocationUnitSystem; + +@interface TTTLocationFormatter : NSFormatter { + TTTLocationFormatterCoordinateOrder _coordinateOrder; + TTTLocationFormatterBearingStyle _bearingStyle; + TTTLocationUnitSystem _unitSystem; + NSNumberFormatter *_numberFormatter; +} + +@property (readonly, nonatomic, retain) NSNumberFormatter *numberFormatter; + +- (NSString *)stringFromCoordinate:(CLLocationCoordinate2D)coordinate; +- (NSString *)stringFromLocation:(CLLocation *)location; +- (NSString *)stringFromDistance:(CLLocationDistance)distance; +- (NSString *)stringFromBearing:(CLLocationDegrees)bearing; +- (NSString *)stringFromSpeed:(CLLocationSpeed)speed; +- (NSString *)stringFromDistanceFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation; +- (NSString *)stringFromBearingFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation; +- (NSString *)stringFromDistanceAndBearingFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation; +- (NSString *)stringFromVelocityFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation atSpeed:(CLLocationSpeed)speed; + +- (TTTLocationFormatterCoordinateOrder)coordinateOrder; +- (void)setCoordinateOrder:(TTTLocationFormatterCoordinateOrder)coordinateOrder; + +- (TTTLocationFormatterBearingStyle)bearingStyle; +- (void)setBearingStyle:(TTTLocationFormatterBearingStyle)bearingStyle; + +- (TTTLocationUnitSystem)unitSystem; +- (void)setUnitSystem:(TTTLocationUnitSystem)unitSystem; + +@end diff --git a/AFNetworkingExample/Vendor/TTT/TTTLocationFormatter.m b/AFNetworkingExample/Vendor/TTT/TTTLocationFormatter.m new file mode 100644 index 0000000..88089df --- /dev/null +++ b/AFNetworkingExample/Vendor/TTT/TTTLocationFormatter.m @@ -0,0 +1,299 @@ +// TTTLocationFormatter.m +// +// Copyright (c) 2011 Mattt Thompson (http://mattt.me) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "TTTLocationFormatter.h" + +static double const kTTTMetersToKilometersCoefficient = 0.001; +static double const kTTTMetersToFeetCoefficient = 3.2808399; +static double const kTTTMetersToYardsCoefficient = 1.0936133; +static double const kTTTMetersToMilesCoefficient = 0.000621371192; + +static inline double CLLocationDistanceToKilometers(CLLocationDistance distance) { + return distance * kTTTMetersToKilometersCoefficient; +} + +static inline double CLLocationDistanceToFeet(CLLocationDistance distance) { + return distance * kTTTMetersToFeetCoefficient; +} + +static inline double CLLocationDistanceToYards(CLLocationDistance distance) { + return distance * kTTTMetersToYardsCoefficient; +} + +static inline double CLLocationDistanceToMiles(CLLocationDistance distance) { + return distance * kTTTMetersToMilesCoefficient; +} + +#pragma mark - + +static inline double DEG2RAD(double degrees) { + return degrees * M_PI / 180; +}; + +static inline double RAD2DEG(double radians) { + return radians * 180 / M_PI; +}; + +static inline CLLocationDegrees CLLocationDegreesBearingBetweenCoordinates(CLLocationCoordinate2D originCoordinate, CLLocationCoordinate2D destinationCoordinate) { + double lat1 = DEG2RAD(originCoordinate.latitude); + double lon1 = DEG2RAD(originCoordinate.longitude); + double lat2 = DEG2RAD(destinationCoordinate.latitude); + double lon2 = DEG2RAD(destinationCoordinate.longitude); + + double dLon = lon2 - lon1; + double y = sin(dLon) * cos(lat2); + double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon); + double bearing = atan2(y, x) + (2 * M_PI); + + // `atan2` works on a range of -π to 0 to π, so add on 2π and perform a modulo check + if (bearing > (2 * M_PI)) { + bearing = bearing - (2 * M_PI); + } + + return RAD2DEG(bearing); +} + +TTTLocationCardinalDirection TTTLocationCardinalDirectionFromBearing(CLLocationDegrees bearing) { + if(bearing > 337.5) { + return TTTNorthDirection; + } else if(bearing > 292.5) { + return TTTNorthwestDirection; + } else if(bearing > 247.5) { + return TTTWestDirection; + } else if(bearing > 202.5) { + return TTTSouthwestDirection; + } else if(bearing > 157.5) { + return TTTSouthDirection; + } else if(bearing > 112.5) { + return TTTSoutheastDirection; + } else if(bearing > 67.5) { + return TTTEastDirection; + } else if(bearing > 22.5) { + return TTTNortheastDirection; + } else { + return TTTNorthDirection; + } +} + +#pragma mark - + +static double const kTTTMetersPerSecondToKilometersPerHourCoefficient = 3.6; +static double const kTTTMetersPerSecondToFeetPerSecondCoefficient = 3.2808399; +static double const kTTTMetersPerSecondToMilesPerHourCoefficient = 2.23693629; + +static inline double CLLocationSpeedToKilometersPerHour(CLLocationSpeed speed) { + return speed * kTTTMetersPerSecondToKilometersPerHourCoefficient; +} + +static inline double CLLocationSpeedToFeetPerSecond(CLLocationSpeed speed) { + return speed * kTTTMetersPerSecondToFeetPerSecondCoefficient; +} + +static inline double CLLocationSpeedToMilesPerHour(CLLocationSpeed speed) { + return speed * kTTTMetersPerSecondToMilesPerHourCoefficient; +} + + +@interface TTTLocationFormatter () +@property (readwrite, nonatomic, assign) TTTLocationFormatterCoordinateOrder coordinateOrder; +@property (readwrite, nonatomic, assign) TTTLocationFormatterBearingStyle bearingStyle; +@property (readwrite, nonatomic, assign) TTTLocationUnitSystem unitSystem; +@property (readwrite, nonatomic, retain) NSNumberFormatter *numberFormatter; +@end + +@implementation TTTLocationFormatter +@synthesize coordinateOrder = _coordinateOrder; +@synthesize bearingStyle = _bearingStyle; +@synthesize unitSystem = _unitSystem; +@synthesize numberFormatter = _numberFormatter; + +- (id)init { + self = [super init]; + if (!self) { + return nil; + } + + self.coordinateOrder = TTTCoordinateLatLngOrder; + self.bearingStyle = TTTBearingWordStyle; + self.unitSystem = TTTMetricSystem; + + self.numberFormatter = [[[NSNumberFormatter alloc] init] autorelease]; + [self.numberFormatter setLocale:[NSLocale currentLocale]]; + [self.numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; + [self.numberFormatter setMaximumSignificantDigits:2]; + [self.numberFormatter setUsesSignificantDigits:YES]; + + return self; +} + +- (NSString *)stringFromCoordinate:(CLLocationCoordinate2D)coordinate { + return [NSString stringWithFormat:NSLocalizedString(@"(%@, %@)", @"Coordinate format"), [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:coordinate.latitude]], [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:coordinate.longitude]], nil]; +} + +- (NSString *)stringFromLocation:(CLLocation *)location { + return [self stringFromCoordinate:location.coordinate]; +} + +- (NSString *)stringFromDistance:(CLLocationDistance)distance { + NSString *distanceString = nil; + NSString *unitString = nil; + + switch (self.unitSystem) { + case TTTMetricSystem: { + double meterDistance = distance; + double kilometerDistance = CLLocationDistanceToKilometers(distance); + + if (kilometerDistance > 1) { + distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:kilometerDistance]]; + unitString = NSLocalizedString(@"km", @"Kilometer Unit"); + } else { + distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:meterDistance]]; + unitString = NSLocalizedString(@"m", @"Meter Unit"); + } + break; + } + + case TTTImperialSystem: { + double feetDistance = CLLocationDistanceToFeet(distance); + double yardDistance = CLLocationDistanceToYards(distance); + double milesDistance = CLLocationDistanceToMiles(distance); + + if (feetDistance < 300) { + distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:feetDistance]]; + unitString = NSLocalizedString(@"ft", @"Feet Unit"); + } else if (yardDistance < 500) { + distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:yardDistance]]; + unitString = NSLocalizedString(@"yds", @"Yard Unit"); + } else { + distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:milesDistance]]; + unitString = (milesDistance > 1.0 && milesDistance < 1.1) ? NSLocalizedString(@"mile", @"Mile Unit (Singular)") : NSLocalizedString(@"miles", @"Mile Unit (Plural)"); + } + break; + } + } + + return [NSString stringWithFormat:NSLocalizedString(@"%@ %@", @"#{Distance} #{Unit}"), distanceString, unitString]; +} + +- (NSString *)stringFromBearing:(CLLocationDegrees)bearing { + switch (self.bearingStyle) { + case TTTBearingWordStyle: + switch (TTTLocationCardinalDirectionFromBearing(bearing)) { + case TTTNorthDirection: + return NSLocalizedString(@"North", @"North Direction"); + case TTTNortheastDirection: + return NSLocalizedString(@"Northeast", @"Northeast Direction"); + case TTTEastDirection: + return NSLocalizedString(@"East", @"East Direction"); + case TTTSoutheastDirection: + return NSLocalizedString(@"Southeast", @"Southeast Direction"); + case TTTSouthDirection: + return NSLocalizedString(@"South", @"South Direction"); + case TTTSouthwestDirection: + return NSLocalizedString(@"Southwest", @"Southwest Direction"); + case TTTWestDirection: + return NSLocalizedString(@"West", @"West Direction"); + case TTTNorthwestDirection: + return NSLocalizedString(@"Northwest", @"Northwest Direction"); + } + break; + case TTTBearingAbbreviationWordStyle: + switch (TTTLocationCardinalDirectionFromBearing(bearing)) { + case TTTNorthDirection: + return NSLocalizedString(@"N", @"North Direction Abbreviation"); + case TTTNortheastDirection: + return NSLocalizedString(@"NE", @"Northeast Direction Abbreviation"); + case TTTEastDirection: + return NSLocalizedString(@"E", @"East Direction Abbreviation"); + case TTTSoutheastDirection: + return NSLocalizedString(@"SE", @"Southeast Direction Abbreviation"); + case TTTSouthDirection: + return NSLocalizedString(@"S", @"South Direction Abbreviation"); + case TTTSouthwestDirection: + return NSLocalizedString(@"SW", @"Southwest Direction Abbreviation"); + case TTTWestDirection: + return NSLocalizedString(@"W", @"West Direction Abbreviation"); + case TTTNorthwestDirection: + return NSLocalizedString(@"NW", @"Northwest Direction Abbreviation");; + } + break; + case TTTBearingNumericStyle: + return [[self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:bearing]] stringByAppendingString:NSLocalizedString(@"°", @"Degrees Symbol")]; + } + + return nil; +} + +- (NSString *)stringFromSpeed:(CLLocationSpeed)speed { + NSString *speedString = nil; + NSString *unitString = nil; + + switch (self.unitSystem) { + case TTTMetricSystem: { + double metersPerSecondSpeed = speed; + double kilometersPerHourSpeed = CLLocationSpeedToKilometersPerHour(speed); + + if (kilometersPerHourSpeed > 1) { + speedString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:kilometersPerHourSpeed]]; + unitString = NSLocalizedString(@"km/h", @"Kilometers Per Hour Unit"); + } else { + speedString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:metersPerSecondSpeed]]; + unitString = NSLocalizedString(@"m/s", @"Meters Per Second Unit"); + } + break; + } + + case TTTImperialSystem: { + double feetPerSecondSpeed = CLLocationSpeedToFeetPerSecond(speed); + double milesPerHourSpeed = CLLocationSpeedToMilesPerHour(speed); + + if (milesPerHourSpeed > 1) { + speedString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:milesPerHourSpeed]]; + unitString = NSLocalizedString(@"mph", @"Miles Per Hour Unit"); + } else { + speedString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:feetPerSecondSpeed]]; + unitString = NSLocalizedString(@"ft/s", @"Feet Per Second Unit"); + } + break; + } + } + + return [NSString stringWithFormat:NSLocalizedString(@"%@ %@", @"#{Speed} #{Unit}"), speedString, unitString]; +} + +- (NSString *)stringFromDistanceFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation { + return [self stringFromDistance:[destinationLocation distanceFromLocation:originLocation]]; +} + +- (NSString *)stringFromBearingFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation { + return [self stringFromBearing:CLLocationDegreesBearingBetweenCoordinates(originLocation.coordinate, destinationLocation.coordinate)]; +} + +- (NSString *)stringFromDistanceAndBearingFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation { + return [NSString stringWithFormat:NSLocalizedString(@"%@ %@", @"#{Dimensional Quantity} #{Direction}"), [self stringFromDistanceFromLocation:originLocation toLocation:destinationLocation], [self stringFromBearingFromLocation:originLocation toLocation:destinationLocation]]; +} + +- (NSString *)stringFromVelocityFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation atSpeed:(CLLocationSpeed)speed { + return [NSString stringWithFormat:NSLocalizedString(@"%@ %@", @"#{Dimensional Quantity} #{Direction}"), [self stringFromSpeed:speed], [self stringFromBearingFromLocation:originLocation toLocation:destinationLocation]]; +} + +@end diff --git a/AFNetworkingExample/main.m b/AFNetworkingExample/main.m new file mode 100644 index 0000000..e8f8501 --- /dev/null +++ b/AFNetworkingExample/main.m @@ -0,0 +1,31 @@ +// main.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +int main(int argc, char *argv[]) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, @"UIApplication", @"AppDelegate"); + [pool release]; + return retVal; +} diff --git a/AFURLCache.h b/AFURLCache.h new file mode 100644 index 0000000..5313301 --- /dev/null +++ b/AFURLCache.h @@ -0,0 +1,39 @@ +// AFURLCache.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +extern NSString * AFURLCacheKeyForNSURLRequest(NSURLRequest *request); + +@interface AFURLCache : NSURLCache { +@private + NSCountedSet *_cachedRequests; + NSMutableDictionary *_keyedCachedResponsesByRequest; + + NSOperationQueue *_periodicMaintenanceOperationQueue; + NSOperation *_periodicMaintenanceOperation; + NSTimer *_periodicMaintenanceTimer; +} + ++ (NSString *)defaultCachePath; + +@end diff --git a/AFURLCache.m b/AFURLCache.m new file mode 100644 index 0000000..117d555 --- /dev/null +++ b/AFURLCache.m @@ -0,0 +1,159 @@ +// AFURLCache.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "AFURLCache.h" + +static const NSTimeInterval kAFURLCacheMaintenanceTimeInterval = 30.0; + +NSString * AFURLCacheKeyForNSURLRequest(NSURLRequest *request) { + const char *str = [[[request URL] absoluteString] UTF8String]; + unsigned char r[CC_MD5_DIGEST_LENGTH]; + CC_MD5(str, strlen(str), r); + return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]]; +} + +@interface AFURLCache () +@property (nonatomic, retain) NSCountedSet *cachedRequests; +@property (nonatomic, retain) NSMutableDictionary *keyedCachedResponsesByRequest; + +@property (nonatomic, retain) NSOperationQueue *periodicMaintenanceOperationQueue; +@property (nonatomic, retain) NSOperation *periodicMaintenanceOperation; +@property (nonatomic, retain) NSTimer *periodicMaintenanceTimer; +@end + +@implementation AFURLCache +@synthesize cachedRequests = _cachedRequests; +@synthesize keyedCachedResponsesByRequest = _keyedCachedResponsesByRequest; +@synthesize periodicMaintenanceOperationQueue = _periodicMaintenanceOperationQueue; +@synthesize periodicMaintenanceOperation = _periodicMaintenanceOperation; +@synthesize periodicMaintenanceTimer = _periodicMaintenanceTimer; + ++ (NSString *)defaultCachePath { + return nil; +} + +- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path { + self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path]; + if (!self) { + return nil; + } + + self.cachedRequests = [NSCountedSet setWithCapacity:200]; + self.keyedCachedResponsesByRequest = [NSMutableDictionary dictionaryWithCapacity:200]; + + self.periodicMaintenanceOperationQueue = [[[NSOperationQueue alloc] init] autorelease]; + [self.periodicMaintenanceOperationQueue setMaxConcurrentOperationCount:1]; + self.periodicMaintenanceTimer = [[NSTimer scheduledTimerWithTimeInterval:kAFURLCacheMaintenanceTimeInterval target:self selector:@selector(periodicMaintenance) userInfo:nil repeats:YES] retain]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sweepMemoryCache) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; + + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [_cachedRequests release]; + [_keyedCachedResponsesByRequest release]; + + [_periodicMaintenanceOperationQueue cancelAllOperations]; + [_periodicMaintenanceOperationQueue release]; + [_periodicMaintenanceOperation release]; + + [_periodicMaintenanceTimer invalidate]; + _periodicMaintenanceTimer = nil; + [super dealloc]; +} + +#pragma mark - NSURLCache + +- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request { + NSString *cacheKey = AFURLCacheKeyForNSURLRequest(request); + NSCachedURLResponse *cachedResponse = [self.keyedCachedResponsesByRequest valueForKey:cacheKey]; + if (cachedResponse) { + return cachedResponse; + } + + return nil; +} + +- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request { + NSString *cacheKey = AFURLCacheKeyForNSURLRequest(request); + [self.keyedCachedResponsesByRequest setObject:cachedResponse forKey:cacheKey]; + [self.cachedRequests addObject:cacheKey]; +} + +- (void)removeCachedResponseForRequest:(NSURLRequest *)request { + NSString *cacheKey = AFURLCacheKeyForNSURLRequest(request); + [self.keyedCachedResponsesByRequest removeObjectForKey:cacheKey]; + [self.cachedRequests removeObject:cacheKey]; +} + +- (void)removeAllCachedResponses { + [self.keyedCachedResponsesByRequest removeAllObjects]; + [self.cachedRequests removeAllObjects]; +} + +- (NSUInteger)currentMemoryUsage { + return [[[self.keyedCachedResponsesByRequest allValues] valueForKeyPath:@"@sum.data.length"] integerValue]; +} + +#pragma mark - Maintenance + +- (void)periodicMaintenance { + [self.periodicMaintenanceOperation cancel]; + self.periodicMaintenanceOperation = nil; + if ([self currentMemoryUsage] > [self memoryCapacity]) { + self.periodicMaintenanceOperation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sweepMemoryCache) object:nil] autorelease]; + [self.periodicMaintenanceOperationQueue addOperation:self.periodicMaintenanceOperation]; + } +} + +- (void)sweepMemoryCache { + NSArray *sortedCachedRequests = [[self.cachedRequests allObjects] sortedArrayUsingComparator:^(id obj1, id obj2) { + NSUInteger count1 = [self.cachedRequests countForObject:obj1]; + NSUInteger count2 = [self.cachedRequests countForObject:obj2]; + + if (count1 > count2) { + return NSOrderedDescending; + } else if (count1 < count2) { + return NSOrderedAscending; + } else { + return NSOrderedSame; + } + }]; + + NSUInteger memoryDeficit = [self currentMemoryUsage] - [self memoryCapacity]; + NSString *cacheKey = nil; + NSEnumerator *enumerator = [sortedCachedRequests reverseObjectEnumerator]; + while (memoryDeficit > 0 && (cacheKey = [enumerator nextObject])) { + NSCachedURLResponse *response = (NSCachedURLResponse *)[self.keyedCachedResponsesByRequest objectForKey:cacheKey]; + memoryDeficit -= [[response data] length]; + [self.keyedCachedResponsesByRequest removeObjectForKey:cacheKey]; + } + + self.cachedRequests = [NSCountedSet setWithArray:[self.keyedCachedResponsesByRequest allKeys]]; +} + +@end diff --git a/QHTTPOperation/QHTTPOperation.h b/QHTTPOperation/QHTTPOperation.h new file mode 100644 index 0000000..62332da --- /dev/null +++ b/QHTTPOperation/QHTTPOperation.h @@ -0,0 +1,245 @@ +/* + File: QHTTPOperation.h + + Contains: An NSOperation that runs an HTTP request. + + 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 "QRunLoopOperation.h" + +/* + QHTTPOperation is a general purpose NSOperation that runs an HTTP request. + You initialise it with an HTTP request and then, when you run the operation, + it sends the request and gathers the response. It is quite a complex + object because it handles a wide variety of edge cases, but it's very + easy to use in simple cases: + + 1. create the operation with the URL you want to get + + op = [[[QHTTPOperation alloc] initWithURL:url] autorelease]; + + 2. set up any non-default parameters, for example, set which HTTP + content types are acceptable + + op.acceptableContentTypes = [NSSet setWithObject:@"text/html"]; + + 3. enqueue the operation + + [queue addOperation:op]; + + 4. finally, when the operation is done, use the lastResponse and + error properties to find out how things went + + As mentioned above, QHTTPOperation is very general purpose. There are a + large number of configuration and result options available to you. + + o You can specify a NSURLRequest rather than just a URL. + + o You can configure the run loop and modes on which the NSURLConnection is + scheduled. + + o You can specify what HTTP status codes and content types are OK. + + o You can set an authentication delegate to handle authentication challenges. + + o You can accumulate responses in memory or in an NSOutputStream. + + o For in-memory responses, you can specify a default response size + (used to size the response buffer) and a maximum response size + (to prevent unbounded memory use). + + o You can get at the last request and the last response, to track + redirects. + + o There are a variety of funky debugging options to simulator errors + and delays. + + Finally, it's perfectly reasonable to subclass QHTTPOperation to meet you + own specific needs. Specifically, it's common for the subclass to + override -connection:didReceiveResponse: in order to setup the output + stream based on the specific details of the response. +*/ + +@protocol QHTTPOperationAuthenticationDelegate; + +@interface QHTTPOperation : QRunLoopOperation /* */ +{ + NSURLRequest * _request; + NSIndexSet * _acceptableStatusCodes; + NSSet * _acceptableContentTypes; + id _authenticationDelegate; + NSOutputStream * _responseOutputStream; + NSUInteger _defaultResponseSize; + NSUInteger _maximumResponseSize; + NSURLConnection * _connection; + BOOL _firstData; + NSMutableData * _dataAccumulator; + NSURLRequest * _lastRequest; + NSHTTPURLResponse * _lastResponse; + NSData * _responseBody; +#if ! defined(NDEBUG) + NSError * _debugError; + NSTimeInterval _debugDelay; + NSTimer * _debugDelayTimer; +#endif +} + +- (id)initWithRequest:(NSURLRequest *)request; // designated +- (id)initWithURL:(NSURL *)url; // convenience, calls +[NSURLRequest requestWithURL:] + +// Things that are configured by the init method and can't be changed. + +@property (copy, readonly) NSURLRequest * request; +@property (copy, readonly) NSURL * URL; + +// Things you can configure before queuing the operation. + +// runLoopThread and runLoopModes inherited from QRunLoopOperation +@property (copy, readwrite) NSIndexSet * acceptableStatusCodes; // default is nil, implying 200..299 +@property (copy, readwrite) NSSet * acceptableContentTypes; // default is nil, implying anything is acceptable +@property (assign, readwrite) id authenticationDelegate; + +#if ! defined(NDEBUG) +@property (copy, readwrite) NSError * debugError; // default is nil +@property (assign, readwrite) NSTimeInterval debugDelay; // default is none +#endif + +// Things you can configure up to the point where you start receiving data. +// Typically you would change these in -connection:didReceiveResponse:, but +// it is possible to change them up to the point where -connection:didReceiveData: +// is called for the first time (that is, you could override -connection:didReceiveData: +// and change these before calling super). + +// IMPORTANT: If you set a response stream, QHTTPOperation calls the response +// stream synchronously. This is fine for file and memory streams, but it would +// not work well for other types of streams (like a bound pair). + +@property (retain, readwrite) NSOutputStream * responseOutputStream; // defaults to nil, which puts response into responseBody +@property (assign, readwrite) NSUInteger defaultResponseSize; // default is 1 MB, ignored if responseOutputStream is set +@property (assign, readwrite) NSUInteger maximumResponseSize; // default is 4 MB, ignored if responseOutputStream is set + // defaults are 1/4 of the above on embedded + +// Things that are only meaningful after a response has been received; + +@property (assign, readonly, getter=isStatusCodeAcceptable) BOOL statusCodeAcceptable; +@property (assign, readonly, getter=isContentTypeAcceptable) BOOL contentTypeAcceptable; + +// Things that are only meaningful after the operation is finished. + +// error property inherited from QRunLoopOperation +@property (copy, readonly) NSURLRequest * lastRequest; +@property (copy, readonly) NSHTTPURLResponse * lastResponse; + +@property (copy, readonly) NSData * responseBody; + +@end + +@interface QHTTPOperation (NSURLConnectionDelegate) + +// QHTTPOperation implements all of these methods, so if you override them +// you must consider whether or not to call super. +// +// These will be called on the operation's run loop thread. + +- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace; + // Routes the request to the authentication delegate if it exists, otherwise + // just returns NO. + +- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; + // Routes the request to the authentication delegate if it exists, otherwise + // just cancels the challenge. + +- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response; + // Latches the request and response in lastRequest and lastResponse. + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; + // Latches the response in lastResponse. + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data; + // If this is the first chunk of data, it decides whether the data is going to be + // routed to memory (responseBody) or a stream (responseOutputStream) and makes the + // appropriate preparations. For this and subsequent data it then actually shuffles + // the data to its destination. + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection; + // Completes the operation with either no error (if the response status code is acceptable) + // or an error (otherwise). + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; + // Completes the operation with the error. + +@end + +@protocol QHTTPOperationAuthenticationDelegate +@required + +// These are called on the operation's run loop thread and have the same semantics as their +// NSURLConnection equivalents. It's important to realise that there is no +// didCancelAuthenticationChallenge callback (because NSURLConnection doesn't issue one to us). +// Rather, an authentication delegate is expected to observe the operation and cancel itself +// if the operation completes while the challenge is running. + +- (BOOL)httpOperation:(QHTTPOperation *)operation canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace; +- (void)httpOperation:(QHTTPOperation *)operation didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; + +@end + +extern NSString * kQHTTPOperationErrorDomain; + +// positive error codes are HTML status codes (when they are not allowed via acceptableStatusCodes) +// +// 0 is, of course, not a valid error code +// +// negative error codes are errors from the module + +enum { + kQHTTPOperationErrorResponseTooLarge = -1, + kQHTTPOperationErrorOnOutputStream = -2, + kQHTTPOperationErrorBadContentType = -3 +}; diff --git a/QHTTPOperation/QHTTPOperation.m b/QHTTPOperation/QHTTPOperation.m new file mode 100644 index 0000000..fec3f19 --- /dev/null +++ b/QHTTPOperation/QHTTPOperation.m @@ -0,0 +1,653 @@ +/* + File: QHTTPOperation.m + + Contains: An NSOperation that runs an HTTP request. + + 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 "QHTTPOperation.h" + +@interface QHTTPOperation () + +// Read/write versions of public properties + +@property (copy, readwrite) NSURLRequest * lastRequest; +@property (copy, readwrite) NSHTTPURLResponse * lastResponse; + +// Internal properties + +@property (retain, readwrite) NSURLConnection * connection; +@property (assign, readwrite) BOOL firstData; +@property (retain, readwrite) NSMutableData * dataAccumulator; + +#if ! defined(NDEBUG) +@property (retain, readwrite) NSTimer * debugDelayTimer; +#endif + +@end + +@implementation QHTTPOperation + +#pragma mark * Initialise and finalise + +- (id)initWithRequest:(NSURLRequest *)request + // See comment in header. +{ + // any thread + assert(request != nil); + assert([request URL] != nil); + // Because we require an NSHTTPURLResponse, we only support HTTP and HTTPS URLs. + assert([[[[request URL] scheme] lowercaseString] isEqual:@"http"] || [[[[request URL] scheme] lowercaseString] isEqual:@"https"]); + self = [super init]; + if (self != nil) { + #if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR + static const NSUInteger kPlatformReductionFactor = 4; + #else + static const NSUInteger kPlatformReductionFactor = 1; + #endif + self->_request = [request copy]; + self->_defaultResponseSize = 1 * 1024 * 1024 / kPlatformReductionFactor; + self->_maximumResponseSize = 4 * 1024 * 1024 / kPlatformReductionFactor; + self->_firstData = YES; + } + return self; +} + +- (id)initWithURL:(NSURL *)url + // See comment in header. +{ + assert(url != nil); + return [self initWithRequest:[NSURLRequest requestWithURL:url]]; +} + +- (void)dealloc +{ + #if ! defined(NDEBUG) + [self->_debugError release]; + [self->_debugDelayTimer invalidate]; + [self->_debugDelayTimer release]; + #endif + // any thread + [self->_request release]; + [self->_acceptableStatusCodes release]; + [self->_acceptableContentTypes release]; + [self->_responseOutputStream release]; + assert(self->_connection == nil); // should have been shut down by now + [self->_dataAccumulator release]; + [self->_lastRequest release]; + [self->_lastResponse release]; + [self->_responseBody release]; + [super dealloc]; +} + +#pragma mark * Properties + +// We write our own settings for many properties because we want to bounce +// sets that occur in the wrong state. And, given that we've written the +// setter anyway, we also avoid KVO notifications when the value doesn't change. + +@synthesize request = _request; + +@synthesize authenticationDelegate = _authenticationDelegate; + ++ (BOOL)automaticallyNotifiesObserversOfAuthenticationDelegate +{ + return NO; +} + +- (id)authenticationDelegate +{ + return self->_authenticationDelegate; +} + +- (void)setAuthenticationDelegate:(id)newValue +{ + if (self.state != kQRunLoopOperationStateInited) { + assert(NO); + } else { + if (newValue != self->_authenticationDelegate) { + [self willChangeValueForKey:@"authenticationDelegate"]; + self->_authenticationDelegate = newValue; + [self didChangeValueForKey:@"authenticationDelegate"]; + } + } +} + +@synthesize acceptableStatusCodes = _acceptableStatusCodes; + ++ (BOOL)automaticallyNotifiesObserversOfAcceptableStatusCodes +{ + return NO; +} + +- (NSIndexSet *)acceptableStatusCodes +{ + return [[self->_acceptableStatusCodes retain] autorelease]; +} + +- (void)setAcceptableStatusCodes:(NSIndexSet *)newValue +{ + if (self.state != kQRunLoopOperationStateInited) { + assert(NO); + } else { + if (newValue != self->_acceptableStatusCodes) { + [self willChangeValueForKey:@"acceptableStatusCodes"]; + [self->_acceptableStatusCodes autorelease]; + self->_acceptableStatusCodes = [newValue copy]; + [self didChangeValueForKey:@"acceptableStatusCodes"]; + } + } +} + +@synthesize acceptableContentTypes = _acceptableContentTypes; + ++ (BOOL)automaticallyNotifiesObserversOfAcceptableContentTypes +{ + return NO; +} + +- (NSSet *)acceptableContentTypes +{ + return [[self->_acceptableContentTypes retain] autorelease]; +} + +- (void)setAcceptableContentTypes:(NSSet *)newValue +{ + if (self.state != kQRunLoopOperationStateInited) { + assert(NO); + } else { + if (newValue != self->_acceptableContentTypes) { + [self willChangeValueForKey:@"acceptableContentTypes"]; + [self->_acceptableContentTypes autorelease]; + self->_acceptableContentTypes = [newValue copy]; + [self didChangeValueForKey:@"acceptableContentTypes"]; + } + } +} + +@synthesize responseOutputStream = _responseOutputStream; + ++ (BOOL)automaticallyNotifiesObserversOfResponseOutputStream +{ + return NO; +} + +- (NSOutputStream *)responseOutputStream +{ + return [[self->_responseOutputStream retain] autorelease]; +} + +- (void)setResponseOutputStream:(NSOutputStream *)newValue +{ + if (self.dataAccumulator != nil) { + assert(NO); + } else { + if (newValue != self->_responseOutputStream) { + [self willChangeValueForKey:@"responseOutputStream"]; + [self->_responseOutputStream autorelease]; + self->_responseOutputStream = [newValue retain]; + [self didChangeValueForKey:@"responseOutputStream"]; + } + } +} + +@synthesize defaultResponseSize = _defaultResponseSize; + ++ (BOOL)automaticallyNotifiesObserversOfDefaultResponseSize +{ + return NO; +} + +- (NSUInteger)defaultResponseSize +{ + return self->_defaultResponseSize; +} + +- (void)setDefaultResponseSize:(NSUInteger)newValue +{ + if (self.dataAccumulator != nil) { + assert(NO); + } else { + if (newValue != self->_defaultResponseSize) { + [self willChangeValueForKey:@"defaultResponseSize"]; + self->_defaultResponseSize = newValue; + [self didChangeValueForKey:@"defaultResponseSize"]; + } + } +} + +@synthesize maximumResponseSize = _maximumResponseSize; + ++ (BOOL)automaticallyNotifiesObserversOfMaximumResponseSize +{ + return NO; +} + +- (NSUInteger)maximumResponseSize +{ + return self->_maximumResponseSize; +} + +- (void)setMaximumResponseSize:(NSUInteger)newValue +{ + if (self.dataAccumulator != nil) { + assert(NO); + } else { + if (newValue != self->_maximumResponseSize) { + [self willChangeValueForKey:@"maximumResponseSize"]; + self->_maximumResponseSize = newValue; + [self didChangeValueForKey:@"maximumResponseSize"]; + } + } +} + +@synthesize lastRequest = _lastRequest; +@synthesize lastResponse = _lastResponse; +@synthesize responseBody = _responseBody; + +@synthesize connection = _connection; +@synthesize firstData = _firstData; +@synthesize dataAccumulator = _dataAccumulator; + +- (NSURL *)URL +{ + return [self.request URL]; +} + +- (BOOL)isStatusCodeAcceptable +{ + NSIndexSet * acceptableStatusCodes; + NSInteger statusCode; + + assert(self.lastResponse != nil); + + acceptableStatusCodes = self.acceptableStatusCodes; + if (acceptableStatusCodes == nil) { + acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; + } + assert(acceptableStatusCodes != nil); + + statusCode = [self.lastResponse statusCode]; + return (statusCode >= 0) && [acceptableStatusCodes containsIndex: (NSUInteger) statusCode]; +} + +- (BOOL)isContentTypeAcceptable +{ + NSString * contentType; + + assert(self.lastResponse != nil); + contentType = [self.lastResponse MIMEType]; + return (self.acceptableContentTypes == nil) || ((contentType != nil) && [self.acceptableContentTypes containsObject:contentType]); +} + +#pragma mark * Start and finish overrides + +- (void)operationDidStart + // Called by QRunLoopOperation when the operation starts. This kicks of an + // asynchronous NSURLConnection. +{ + assert(self.isActualRunLoopThread); + assert(self.state == kQRunLoopOperationStateExecuting); + + assert(self.defaultResponseSize > 0); + assert(self.maximumResponseSize > 0); + assert(self.defaultResponseSize <= self.maximumResponseSize); + + assert(self.request != nil); + + // If a debug error is set, apply that error rather than running the connection. + + #if ! defined(NDEBUG) + if (self.debugError != nil) { + [self finishWithError:self.debugError]; + return; + } + #endif + + // Create a connection that's scheduled in the required run loop modes. + + assert(self.connection == nil); + self.connection = [[[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO] autorelease]; + assert(self.connection != nil); + + for (NSString * mode in self.actualRunLoopModes) { + [self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:mode]; + } + + [self.connection start]; +} + +- (void)operationWillFinish + // Called by QRunLoopOperation when the operation has finished. We + // do various bits of tidying up. +{ + assert(self.isActualRunLoopThread); + assert(self.state == kQRunLoopOperationStateExecuting); + + // It is possible to hit this state of the operation is cancelled while + // the debugDelayTimer is running. In that case, hey, we'll just accept + // the inevitable and finish rather than trying anything else clever. + + #if ! defined(NDEBUG) + if (self.debugDelayTimer != nil) { + [self.debugDelayTimer invalidate]; + self.debugDelayTimer = nil; + } + #endif + + [self.connection cancel]; + self.connection = nil; + + // If we have an output stream, close it at this point. We might never + // have actually opened this stream but, AFAICT, closing an unopened stream + // doesn't hurt. + + if (self.responseOutputStream != nil) { + [self.responseOutputStream close]; + } +} + +- (void)finishWithError:(NSError *)error + // We override -finishWithError: just so we can handle our debug delay. +{ + // If a debug delay was set, don't finish now but rather start the debug delay timer + // and have it do the actual finish. We clear self.debugDelay so that the next + // time this code runs its doesn't do this again. + // + // We only do this in the non-cancellation case. In the cancellation case, we + // just stop immediately. + + #if ! defined(NDEBUG) + if (self.debugDelay > 0.0) { + if ( (error != nil) && [[error domain] isEqual:NSCocoaErrorDomain] && ([error code] == NSUserCancelledError) ) { + self.debugDelay = 0.0; + } else { + assert(self.debugDelayTimer == nil); + self.debugDelayTimer = [NSTimer timerWithTimeInterval:self.debugDelay target:self selector:@selector(debugDelayTimerDone:) userInfo:error repeats:NO]; + assert(self.debugDelayTimer != nil); + for (NSString * mode in self.actualRunLoopModes) { + [[NSRunLoop currentRunLoop] addTimer:self.debugDelayTimer forMode:mode]; + } + self.debugDelay = 0.0; + return; + } + } + #endif + + [super finishWithError:error]; +} + +#if ! defined(NDEBUG) + +@synthesize debugError = _debugError; +@synthesize debugDelay = _debugDelay; +@synthesize debugDelayTimer = _debugDelayTimer; + +- (void)debugDelayTimerDone:(NSTimer *)timer +{ + NSError * error; + + assert(timer == self.debugDelayTimer); + + error = [[[timer userInfo] retain] autorelease]; + assert( (error == nil) || [error isKindOfClass:[NSError class]] ); + + [self.debugDelayTimer invalidate]; + self.debugDelayTimer = nil; + + [self finishWithError:error]; +} + +#endif + +#pragma mark * NSURLConnection delegate callbacks + +- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace + // See comment in header. +{ + BOOL result; + + assert(self.isActualRunLoopThread); + assert(connection == self.connection); + #pragma unused(connection) + assert(protectionSpace != nil); + #pragma unused(protectionSpace) + + result = NO; + if (self.authenticationDelegate != nil) { + result = [self.authenticationDelegate httpOperation:self canAuthenticateAgainstProtectionSpace:protectionSpace]; + } + return result; +} + +- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge + // See comment in header. +{ + assert(self.isActualRunLoopThread); + assert(connection == self.connection); + #pragma unused(connection) + assert(challenge != nil); + #pragma unused(challenge) + + if (self.authenticationDelegate != nil) { + [self.authenticationDelegate httpOperation:self didReceiveAuthenticationChallenge:challenge]; + } else { + if ( [challenge previousFailureCount] == 0 ) { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } else { + [[challenge sender] cancelAuthenticationChallenge:challenge]; + } + } +} + +- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response + // See comment in header. +{ + assert(self.isActualRunLoopThread); + assert(connection == self.connection); + #pragma unused(connection) + assert( (response == nil) || [response isKindOfClass:[NSHTTPURLResponse class]] ); + + self.lastRequest = request; + self.lastResponse = (NSHTTPURLResponse *) response; + return request; +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response + // See comment in header. +{ + assert(self.isActualRunLoopThread); + assert(connection == self.connection); + #pragma unused(connection) + assert([response isKindOfClass:[NSHTTPURLResponse class]]); + + self.lastResponse = (NSHTTPURLResponse *) response; + + // We don't check the status code here because we want to give the client an opportunity + // to get the data of the error message. Perhaps we /should/ check the content type + // here, but I'm not sure whether that's the right thing to do. +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data + // See comment in header. +{ + BOOL success; + + assert(self.isActualRunLoopThread); + assert(connection == self.connection); + #pragma unused(connection) + assert(data != nil); + + // If we don't yet have a destination for the data, calculate one. Note that, even + // if there is an output stream, we don't use it for error responses. + + success = YES; + if (self.firstData) { + assert(self.dataAccumulator == nil); + + if ( (self.responseOutputStream == nil) || ! self.isStatusCodeAcceptable ) { + long long length; + + assert(self.dataAccumulator == nil); + + length = [self.lastResponse expectedContentLength]; + if (length == NSURLResponseUnknownLength) { + length = self.defaultResponseSize; + } + if (length <= (long long) self.maximumResponseSize) { + self.dataAccumulator = [NSMutableData dataWithCapacity:(NSUInteger)length]; + } else { + [self finishWithError:[NSError errorWithDomain:kQHTTPOperationErrorDomain code:kQHTTPOperationErrorResponseTooLarge userInfo:nil]]; + success = NO; + } + } + + // If the data is going to an output stream, open it. + + if (success) { + if (self.dataAccumulator == nil) { + assert(self.responseOutputStream != nil); + [self.responseOutputStream open]; + } + } + + self.firstData = NO; + } + + // Write the data to its destination. + + if (success) { + if (self.dataAccumulator != nil) { + if ( ([self.dataAccumulator length] + [data length]) <= self.maximumResponseSize ) { + [self.dataAccumulator appendData:data]; + } else { + [self finishWithError:[NSError errorWithDomain:kQHTTPOperationErrorDomain code:kQHTTPOperationErrorResponseTooLarge userInfo:nil]]; + } + } else { + NSUInteger dataOffset; + NSUInteger dataLength; + const uint8_t * dataPtr; + NSError * error; + NSInteger bytesWritten; + + assert(self.responseOutputStream != nil); + + dataOffset = 0; + dataLength = [data length]; + dataPtr = [data bytes]; + error = nil; + do { + if (dataOffset == dataLength) { + break; + } + bytesWritten = [self.responseOutputStream write:&dataPtr[dataOffset] maxLength:dataLength - dataOffset]; + if (bytesWritten <= 0) { + error = [self.responseOutputStream streamError]; + if (error == nil) { + error = [NSError errorWithDomain:kQHTTPOperationErrorDomain code:kQHTTPOperationErrorOnOutputStream userInfo:nil]; + } + break; + } else { + dataOffset += bytesWritten; + } + } while (YES); + + if (error != nil) { + [self finishWithError:error]; + } + } + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection + // See comment in header. +{ + assert(self.isActualRunLoopThread); + assert(connection == self.connection); + #pragma unused(connection) + + assert(self.lastResponse != nil); + + // Swap the data accumulator over to the response data so that we don't trigger a copy. + + assert(self->_responseBody == nil); + self->_responseBody = self->_dataAccumulator; + self->_dataAccumulator = nil; + + // Because we fill out _dataAccumulator lazily, an empty body will leave _dataAccumulator + // set to nil. That's not what our clients expect, so we fix it here. + + if (self->_responseBody == nil) { + self->_responseBody = [[NSData alloc] init]; + assert(self->_responseBody != nil); + } + + if ( ! self.isStatusCodeAcceptable ) { + [self finishWithError:[NSError errorWithDomain:kQHTTPOperationErrorDomain code:self.lastResponse.statusCode userInfo:nil]]; + } else if ( ! self.isContentTypeAcceptable ) { + [self finishWithError:[NSError errorWithDomain:kQHTTPOperationErrorDomain code:kQHTTPOperationErrorBadContentType userInfo:nil]]; + } else { + [self finishWithError:nil]; + } +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error + // See comment in header. +{ + assert(self.isActualRunLoopThread); + assert(connection == self.connection); + #pragma unused(connection) + assert(error != nil); + + [self finishWithError:error]; +} + +@end + +NSString * kQHTTPOperationErrorDomain = @"kQHTTPOperationErrorDomain"; diff --git a/QHTTPOperation/QReachabilityOperation.h b/QHTTPOperation/QReachabilityOperation.h new file mode 100644 index 0000000..52d39b4 --- /dev/null +++ b/QHTTPOperation/QReachabilityOperation.h @@ -0,0 +1,30 @@ +#import "QRunLoopOperation.h" +#include + +@interface QReachabilityOperation : QRunLoopOperation { + NSString * _hostName; + NSUInteger _flagsTargetMask; + NSUInteger _flagsTargetValue; + NSUInteger _flags; + SCNetworkReachabilityRef _ref; +} + +// Initialises the operation to monitor the reachability of the specified +// host. The operation finishes when (flags & flagsTargetMask) == flagsTargetValue. +- (id)initWithHostName:(NSString *)hostName; + +// Things that are configured by the init method and can't be changed. +@property (copy, readonly ) NSString * hostName; + +// Things you can configure before queuing the operation. + +// runLoopThread and runLoopModes inherited from QRunLoopOperation +@property (assign, readwrite) NSUInteger flagsTargetMask; +@property (assign, readwrite) NSUInteger flagsTargetValue; + +// Things that change as part of the progress of the operation. + +// error property inherited from QRunLoopOperation +@property (assign, readonly ) NSUInteger flags; // observable, changes on the actual run loop thread + +@end diff --git a/QHTTPOperation/QReachabilityOperation.m b/QHTTPOperation/QReachabilityOperation.m new file mode 100644 index 0000000..276ce99 --- /dev/null +++ b/QHTTPOperation/QReachabilityOperation.m @@ -0,0 +1,111 @@ +#import "QReachabilityOperation.h" + +@interface QReachabilityOperation () + +@property (assign, readwrite) NSUInteger flags; + +static void ReachabilityCallback( + SCNetworkReachabilityRef target, + SCNetworkReachabilityFlags flags, + void * info +); + +- (void)reachabilitySetFlags:(NSUInteger)newValue; + +@end + +@implementation QReachabilityOperation + +- (id)initWithHostName:(NSString *)hostName { + assert(hostName != nil); + self = [super init]; + if (self != nil) { + self->_hostName = [hostName copy]; + self->_flagsTargetMask = kSCNetworkReachabilityFlagsReachable | kSCNetworkReachabilityFlagsInterventionRequired; + self->_flagsTargetValue = kSCNetworkReachabilityFlagsReachable; + } + return self; +} + +- (void)dealloc { + [self->_hostName release]; + assert(self->_ref == NULL); + [super dealloc]; +} + +@synthesize hostName = _hostName; +@synthesize flagsTargetMask = _flagsTargetMask; +@synthesize flagsTargetValue = _flagsTargetValue; +@synthesize flags = _flags; + +// Called by QRunLoopOperation when the operation starts. This is our opportunity +// to install our run loop callbacks, which is exactly what we do. The only tricky +// thing is that we have to schedule the reachability ref to run in all of the +// run loop modes specified by our client. +- (void)operationDidStart { + Boolean success; + SCNetworkReachabilityContext context = { 0, self, NULL, NULL, NULL }; + + assert(self->_ref == NULL); + self->_ref = SCNetworkReachabilityCreateWithName(NULL, [self.hostName UTF8String]); + assert(self->_ref != NULL); + + success = SCNetworkReachabilitySetCallback(self->_ref, ReachabilityCallback, &context); + assert(success); + + for (NSString * mode in self.actualRunLoopModes) { + success = SCNetworkReachabilityScheduleWithRunLoop(self->_ref, CFRunLoopGetCurrent(), (CFStringRef) mode); + assert(success); + } +} + +static void ReachabilityCallback( + SCNetworkReachabilityRef target, + SCNetworkReachabilityFlags flags, + void * info +) + // Called by the system when the reachability flags change. We just forward + // the flags to our Objective-C code. +{ + QReachabilityOperation * obj; + + obj = (QReachabilityOperation *) info; + assert([obj isKindOfClass:[QReachabilityOperation class]]); + assert(target == obj->_ref); + #pragma unused(target) + + [obj reachabilitySetFlags:flags]; +} + +// Called when the reachability flags change. We just store the flags and then +// check to see if the flags meet our target criteria, in which case we stop the +// operation. +- (void)reachabilitySetFlags:(NSUInteger)newValue { + assert( [NSThread currentThread] == self.actualRunLoopThread ); + + self.flags = newValue; + if ( (self.flags & self.flagsTargetMask) == self.flagsTargetValue ) { + [self finishWithError:nil]; + } +} + +// Called by QRunLoopOperation when the operation finishes. We just clean up +// our reachability ref. +- (void)operationWillFinish { + Boolean success; + + if (self->_ref != NULL) { + for (NSString * mode in self.actualRunLoopModes) { + success = SCNetworkReachabilityUnscheduleFromRunLoop(self->_ref, CFRunLoopGetCurrent(), (CFStringRef) mode); + assert(success); + } + + success = SCNetworkReachabilitySetCallback(self->_ref, NULL, NULL); + assert(success); + + CFRelease(self->_ref); + self->_ref = NULL; + } +} + +@end diff --git a/QHTTPOperation/QRunLoopOperation.h b/QHTTPOperation/QRunLoopOperation.h new file mode 100644 index 0000000..7c1f76f --- /dev/null +++ b/QHTTPOperation/QRunLoopOperation.h @@ -0,0 +1,119 @@ +/* + File: QRunLoopOperation.h + + Contains: An abstract subclass of NSOperation for async run loop based operations. + + 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 + +enum QRunLoopOperationState { + kQRunLoopOperationStateInited, + kQRunLoopOperationStateExecuting, + kQRunLoopOperationStateFinished +}; +typedef enum QRunLoopOperationState QRunLoopOperationState; + +@interface QRunLoopOperation : NSOperation +{ + QRunLoopOperationState _state; + NSThread * _runLoopThread; + NSSet * _runLoopModes; + NSError * _error; +} + +// Things you can configure before queuing the operation. + +// IMPORTANT: Do not change these after queuing the operation; it's very likely that +// bad things will happen if you do. + +@property (retain, readwrite) NSThread * runLoopThread; // default is nil, implying main thread +@property (copy, readwrite) NSSet * runLoopModes; // default is nil, implying set containing NSDefaultRunLoopMode + +// Things that are only meaningful after the operation is finished. + +@property (copy, readonly ) NSError * error; + +// Things you can only alter implicitly. + +@property (assign, readonly ) QRunLoopOperationState state; +@property (retain, readonly ) NSThread * actualRunLoopThread; // main thread if runLoopThread is nil, runLoopThread otherwise +@property (assign, readonly ) BOOL isActualRunLoopThread; // YES if the current thread is the actual run loop thread +@property (copy, readonly ) NSSet * actualRunLoopModes; // set containing NSDefaultRunLoopMode if runLoopModes is nil or empty, runLoopModes otherwise + +@end + +@interface QRunLoopOperation (SubClassSupport) + +// Override points + +// A subclass will probably need to override -operationDidStart and -operationWillFinish +// to set up and tear down its run loop sources, respectively. These are always called +// on the actual run loop thread. +// +// Note that -operationWillFinish will be called even if the operation is cancelled. +// +// -operationWillFinish can check the error property to see whether the operation was +// successful. error will be NSCocoaErrorDomain/NSUserCancelledError on cancellation. +// +// -operationDidStart is allowed to call -finishWithError:. + +- (void)operationDidStart; +- (void)operationWillFinish; + +// Support methods + +// A subclass should call finishWithError: when the operation is complete, passing nil +// for no error and an error otherwise. It must call this on the actual run loop thread. +// +// Note that this will call -operationWillFinish before returning. + +- (void)finishWithError:(NSError *)error; + +@end diff --git a/QHTTPOperation/QRunLoopOperation.m b/QHTTPOperation/QRunLoopOperation.m new file mode 100644 index 0000000..ab75aa2 --- /dev/null +++ b/QHTTPOperation/QRunLoopOperation.m @@ -0,0 +1,359 @@ +/* + File: QRunLoopOperation.m + + Contains: An abstract subclass of NSOperation for async run loop based operations. + + 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 "QRunLoopOperation.h" + +/* + Theory of Operation + ------------------- + Some critical points: + + 1. By the time we're running on the run loop thread, we know that all further state + transitions happen on the run loop thread. That's because there are only three + states (inited, executing, and finished) and run loop thread code can only run + in the last two states and the transition from executing to finished is + always done on the run loop thread. + + 2. -start can only be called once. So run loop thread code doesn't have to worry + about racing with -start because, by the time the run loop thread code runs, + -start has already been called. + + 3. -cancel can be called multiple times from any thread. Run loop thread code + must take a lot of care with do the right thing with cancellation. + + Some state transitions: + + 1. init -> dealloc + 2. init -> cancel -> dealloc +XXX 3. init -> cancel -> start -> finish -> dealloc + 4. init -> cancel -> start -> startOnRunLoopThreadThread -> finish dealloc +!!! 5. init -> start -> cancel -> startOnRunLoopThreadThread -> finish -> cancelOnRunLoopThreadThread -> dealloc +XXX 6. init -> start -> cancel -> cancelOnRunLoopThreadThread -> startOnRunLoopThreadThread -> finish -> dealloc +XXX 7. init -> start -> cancel -> startOnRunLoopThreadThread -> cancelOnRunLoopThreadThread -> finish -> dealloc + 8. init -> start -> startOnRunLoopThreadThread -> finish -> dealloc + 9. init -> start -> startOnRunLoopThreadThread -> cancel -> cancelOnRunLoopThreadThread -> finish -> dealloc +!!! 10. init -> start -> startOnRunLoopThreadThread -> cancel -> finish -> cancelOnRunLoopThreadThread -> dealloc + 11. init -> start -> startOnRunLoopThreadThread -> finish -> cancel -> dealloc + + Markup: + XXX means that the case doesn't happen. + !!! means that the case is interesting. + + Described: + + 1. It's valid to allocate an operation and never run it. + 2. It's also valid to allocate an operation, cancel it, and yet never run it. + 3. While it's valid to cancel an operation before it starting it, this case doesn't + happen because -start always bounces to the run loop thread to maintain the invariant + that the executing to finished transition always happens on the run loop thread. + 4. In this -startOnRunLoopThread detects the cancellation and finishes immediately. + 5. Because the -cancel can happen on any thread, it's possible for the -cancel + to come in between the -start and the -startOnRunLoop thread. In this case + -startOnRunLoopThread notices isCancelled and finishes straightaway. And + -cancelOnRunLoopThread detects that the operation is finished and does nothing. + 6. This case can never happen because -performSelecton:onThread:xxx + callbacks happen in order, -start is synchronised with -cancel, and -cancel + only schedules if -start has run. + 7. This case can never happen because -startOnRunLoopThread will finish immediately + if it detects isCancelled (see case 5). + 8. This is the standard run-to-completion case. + 9. This is the standard cancellation case. -cancelOnRunLoopThread wins the race + with finish, and it detects that the operation is executing and actually cancels. + 10. In this case the -cancelOnRunLoopThread loses the race with finish, but that's OK + because -cancelOnRunLoopThread already does nothing if the operation is already + finished. + 11. Cancellating after finishing still sets isCancelled but has no impact + on the RunLoop thread code. +*/ + +@interface QRunLoopOperation () + +// read/write versions of public properties + +@property (assign, readwrite) QRunLoopOperationState state; +@property (copy, readwrite) NSError * error; + +@end + +@implementation QRunLoopOperation + +- (id)init +{ + self = [super init]; + if (self != nil) { + assert(self->_state == kQRunLoopOperationStateInited); + } + return self; +} + +- (void)dealloc +{ + assert(self->_state != kQRunLoopOperationStateExecuting); + [self->_runLoopModes release]; + [self->_runLoopThread release]; + [self->_error release]; + [super dealloc]; +} + +#pragma mark * Properties + +@synthesize runLoopThread = _runLoopThread; +@synthesize runLoopModes = _runLoopModes; + +- (NSThread *)actualRunLoopThread + // Returns the effective run loop thread, that is, the one set by the user + // or, if that's not set, the main thread. +{ + NSThread * result; + + result = self.runLoopThread; + if (result == nil) { + result = [NSThread mainThread]; + } + return result; +} + +- (BOOL)isActualRunLoopThread + // Returns YES if the current thread is the actual run loop thread. +{ + return [[NSThread currentThread] isEqual:self.actualRunLoopThread]; +} + +- (NSSet *)actualRunLoopModes +{ + NSSet * result; + + result = self.runLoopModes; + if ( (result == nil) || ([result count] == 0) ) { + result = [NSSet setWithObject:NSDefaultRunLoopMode]; + } + return result; +} + +@synthesize error = _error; + +#pragma mark * Core state transitions + +- (QRunLoopOperationState)state +{ + return self->_state; +} + +- (void)setState:(QRunLoopOperationState)newState + // Change the state of the operation, sending the appropriate KVO notifications. +{ + // any thread + + @synchronized (self) { + QRunLoopOperationState oldState; + + // The following check is really important. The state can only go forward, and there + // should be no redundant changes to the state (that is, newState must never be + // equal to self->_state). + + assert(newState > self->_state); + + // Transitions from executing to finished must be done on the run loop thread. + + assert( (newState != kQRunLoopOperationStateFinished) || self.isActualRunLoopThread ); + + // inited + executing -> isExecuting + // inited + finished -> isFinished + // executing + finished -> isExecuting + isFinished + + oldState = self->_state; + if ( (newState == kQRunLoopOperationStateExecuting) || (oldState == kQRunLoopOperationStateExecuting) ) { + [self willChangeValueForKey:@"isExecuting"]; + } + if (newState == kQRunLoopOperationStateFinished) { + [self willChangeValueForKey:@"isFinished"]; + } + self->_state = newState; + if (newState == kQRunLoopOperationStateFinished) { + [self didChangeValueForKey:@"isFinished"]; + } + if ( (newState == kQRunLoopOperationStateExecuting) || (oldState == kQRunLoopOperationStateExecuting) ) { + [self didChangeValueForKey:@"isExecuting"]; + } + } +} + +- (void)startOnRunLoopThread + // Starts the operation. The actual -start method is very simple, + // deferring all of the work to be done on the run loop thread by this + // method. +{ + assert(self.isActualRunLoopThread); + assert(self.state == kQRunLoopOperationStateExecuting); + + if ([self isCancelled]) { + + // We were cancelled before we even got running. Flip the the finished + // state immediately. + + [self finishWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]]; + } else { + [self operationDidStart]; + } +} + +- (void)cancelOnRunLoopThread + // Cancels the operation. +{ + assert(self.isActualRunLoopThread); + + // We know that a) state was kQRunLoopOperationStateExecuting when we were + // scheduled (that's enforced by -cancel), and b) the state can't go + // backwards (that's enforced by -setState), so we know the state must + // either be kQRunLoopOperationStateExecuting or kQRunLoopOperationStateFinished. + // We also know that the transition from executing to finished always + // happens on the run loop thread. Thus, we don't need to lock here. + // We can look at state and, if we're executing, trigger a cancellation. + + if (self.state == kQRunLoopOperationStateExecuting) { + [self finishWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]]; + } +} + +- (void)finishWithError:(NSError *)error +{ + assert(self.isActualRunLoopThread); + // error may be nil + + if (self.error == nil) { + self.error = error; + } + [self operationWillFinish]; + self.state = kQRunLoopOperationStateFinished; +} + +#pragma mark * Subclass override points + +- (void)operationDidStart +{ + assert(self.isActualRunLoopThread); +} + +- (void)operationWillFinish +{ + assert(self.isActualRunLoopThread); +} + +#pragma mark * Overrides + +- (BOOL)isConcurrent +{ + // any thread + return YES; +} + +- (BOOL)isExecuting +{ + // any thread + return self.state == kQRunLoopOperationStateExecuting; +} + +- (BOOL)isFinished +{ + // any thread + return self.state == kQRunLoopOperationStateFinished; +} + +- (void)start +{ + // any thread + + assert(self.state == kQRunLoopOperationStateInited); + + // We have to change the state here, otherwise isExecuting won't necessarily return + // true by the time we return from -start. Also, we don't test for cancellation + // here because that would a) result in us sending isFinished notifications on a + // thread that isn't our run loop thread, and b) confuse the core cancellation code, + // which expects to run on our run loop thread. Finally, we don't have to worry + // about races with other threads calling -start. Only one thread is allowed to + // start us at a time. + + self.state = kQRunLoopOperationStateExecuting; + [self performSelector:@selector(startOnRunLoopThread) onThread:self.actualRunLoopThread withObject:nil waitUntilDone:NO modes:[self.actualRunLoopModes allObjects]]; +} + +- (void)cancel +{ + BOOL runCancelOnRunLoopThread; + BOOL oldValue; + + // any thread + + // We need to synchronise here to avoid state changes to isCancelled and state + // while we're running. + + @synchronized (self) { + oldValue = [self isCancelled]; + + // Call our super class so that isCancelled starts returning true immediately. + + [super cancel]; + + // If we were the one to set isCancelled (that is, we won the race with regards + // other threads calling -cancel) and we're actually running (that is, we lost + // the race with other threads calling -start and the run loop thread finishing), + // we schedule to run on the run loop thread. + + runCancelOnRunLoopThread = ! oldValue && self.state == kQRunLoopOperationStateExecuting; + } + if (runCancelOnRunLoopThread) { + [self performSelector:@selector(cancelOnRunLoopThread) onThread:self.actualRunLoopThread withObject:nil waitUntilDone:YES modes:[self.actualRunLoopModes allObjects]]; + } +} + +@end diff --git a/UIImage+AFNetworking.h b/UIImage+AFNetworking.h new file mode 100644 index 0000000..994095d --- /dev/null +++ b/UIImage+AFNetworking.h @@ -0,0 +1,28 @@ +// UIImage+AFNetworking.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +@interface UIImage (AFNetworking) + ++ (UIImage *)imageByScalingAndCroppingImage:(UIImage *)image size:(CGSize)size; ++ (UIImage *)imageByRoundingCornersOfImage:(UIImage *)image corners:(UIRectCorner)corners cornerRadii:(CGSize)radii; + +@end diff --git a/UIImage+AFNetworking.m b/UIImage+AFNetworking.m new file mode 100644 index 0000000..377ce7d --- /dev/null +++ b/UIImage+AFNetworking.m @@ -0,0 +1,81 @@ +// UIImage+AFNetworking.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "UIImage+AFNetworking.h" + +@implementation UIImage (AFNetworking) + ++ (UIImage *)imageByScalingAndCroppingImage:(UIImage *)image size:(CGSize)size { + if (image == nil) { + return nil; + } else if (CGSizeEqualToSize(image.size, size) || CGSizeEqualToSize(size, CGSizeZero)) { + return image; + } + + CGSize scaledSize = size; + CGPoint thumbnailPoint = CGPointZero; + + CGFloat widthFactor = size.width / image.size.width; + CGFloat heightFactor = size.height / image.size.height; + CGFloat scaleFactor = (widthFactor > heightFactor) ? widthFactor : heightFactor; + scaledSize.width = image.size.width * scaleFactor; + scaledSize.width = image.size.height * scaleFactor; + if (widthFactor > heightFactor) { + thumbnailPoint.y = (size.height - scaledSize.height) * 0.5; + } else if (widthFactor < heightFactor) { + thumbnailPoint.x = (size.width - scaledSize.width) * 0.5; + } + + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); + [image drawInRect:CGRectMake(thumbnailPoint.x, thumbnailPoint.y, scaledSize.width, scaledSize.height)]; + UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return newImage; +} + ++ (UIImage *)imageByRoundingCornersOfImage:(UIImage *)image corners:(UIRectCorner)corners cornerRadii:(CGSize)radii { + if (image == nil) { + return nil; + } else if(UIGraphicsBeginImageContextWithOptions != NULL) { + UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0); + } else { + UIGraphicsBeginImageContext(image.size); + } + + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextBeginPath(context); + CGContextAddPath(context, [[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0.0f, 0.0f, image.size.width, image.size.height) byRoundingCorners:corners cornerRadii:radii] CGPath]); + CGContextClosePath(context); + CGContextClip(context); + + CGRect rect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height); + [image drawInRect:rect]; + + UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return newImage; + +} + +@end