From f98b4c0825ff91dcbda7e044dfcdfbacd1169afb Mon Sep 17 00:00:00 2001 From: Hang Chen Date: Thu, 17 Apr 2014 13:01:15 +0800 Subject: [PATCH 1/2] Added CHHTTPConnection to make it support iOS 6 1.Added CHHTTPConnection to make it support iOS 6, actually it even support iOS 5 or below; 2.Added code to check if target is a real device, if so, write operation will becomes synchronized, otherwise memory pressure will cause crash. --- ImageFiller.xcodeproj/project.pbxproj | 10 ++ ImageFiller/CHHTTPConnection.h | 20 ++++ ImageFiller/CHHTTPConnection.m | 79 +++++++++++++ ImageFiller/IFViewController.m | 156 +++++++++++++++++--------- 4 files changed, 211 insertions(+), 54 deletions(-) create mode 100755 ImageFiller/CHHTTPConnection.h create mode 100755 ImageFiller/CHHTTPConnection.m diff --git a/ImageFiller.xcodeproj/project.pbxproj b/ImageFiller.xcodeproj/project.pbxproj index 6c581de..67ea6cf 100644 --- a/ImageFiller.xcodeproj/project.pbxproj +++ b/ImageFiller.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 5014695D18C8D68200785E1B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5014695B18C8D68200785E1B /* InfoPlist.strings */; }; 5014695F18C8D68200785E1B /* ImageFillerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5014695E18C8D68200785E1B /* ImageFillerTests.m */; }; 5014696918C8DCAB00785E1B /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5014696818C8DCAB00785E1B /* AssetsLibrary.framework */; }; + 772F2BE918FF8AFC005258DD /* CHHTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 772F2BE818FF8AFC005258DD /* CHHTTPConnection.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -55,6 +56,8 @@ 5014695C18C8D68200785E1B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 5014695E18C8D68200785E1B /* ImageFillerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ImageFillerTests.m; sourceTree = ""; }; 5014696818C8DCAB00785E1B /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; + 772F2BE718FF8AFC005258DD /* CHHTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHHTTPConnection.h; sourceTree = ""; }; + 772F2BE818FF8AFC005258DD /* CHHTTPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHHTTPConnection.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -116,6 +119,8 @@ 5014693918C8D68200785E1B /* ImageFiller */ = { isa = PBXGroup; children = ( + 772F2BE718FF8AFC005258DD /* CHHTTPConnection.h */, + 772F2BE818FF8AFC005258DD /* CHHTTPConnection.m */, 5014694218C8D68200785E1B /* IFAppDelegate.h */, 5014694318C8D68200785E1B /* IFAppDelegate.m */, 5014694518C8D68200785E1B /* Main.storyboard */, @@ -257,6 +262,7 @@ 5014694018C8D68200785E1B /* main.m in Sources */, 5014694418C8D68200785E1B /* IFAppDelegate.m in Sources */, 5014694A18C8D68200785E1B /* IFViewController.m in Sources */, + 772F2BE918FF8AFC005258DD /* CHHTTPConnection.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -386,6 +392,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ImageFiller/ImageFiller-Prefix.pch"; INFOPLIST_FILE = "ImageFiller/ImageFiller-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -399,6 +406,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ImageFiller/ImageFiller-Prefix.pch"; INFOPLIST_FILE = "ImageFiller/ImageFiller-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -465,6 +473,7 @@ 5014696418C8D68200785E1B /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 5014696518C8D68200785E1B /* Build configuration list for PBXNativeTarget "ImageFillerTests" */ = { isa = XCConfigurationList; @@ -473,6 +482,7 @@ 5014696718C8D68200785E1B /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/ImageFiller/CHHTTPConnection.h b/ImageFiller/CHHTTPConnection.h new file mode 100755 index 0000000..d5dba91 --- /dev/null +++ b/ImageFiller/CHHTTPConnection.h @@ -0,0 +1,20 @@ +// +// CHHTTPConnection.h +// ImageFiller +// +// Created by hangchen on 4/17/14. +// Copyright (c) 2014 Hang Chen (https://github.com/cyndibaby905) + +#import + + +typedef void (^OnComplete) (NSHTTPURLResponse *response, NSData *data, NSError *error); + + +@interface CHHTTPConnection : NSObject + +- (id)initWithRequest:(NSURLRequest *)urlRequest; + +- (BOOL)executeRequestOnComplete:(OnComplete)OnCompleteBlock; + +@end diff --git a/ImageFiller/CHHTTPConnection.m b/ImageFiller/CHHTTPConnection.m new file mode 100755 index 0000000..187ee75 --- /dev/null +++ b/ImageFiller/CHHTTPConnection.m @@ -0,0 +1,79 @@ +// +// CHHTTPConnection.m +// ImageFiller +// +// Created by hangchen on 4/17/14. +// Copyright (c) 2014 Hang Chen (https://github.com/cyndibaby905) + +#import "CHHTTPConnection.h" + + +@interface CHHTTPConnection () + +@property (nonatomic, strong) NSURLRequest *request; +@property (nonatomic, strong) NSHTTPURLResponse *response; +@property (nonatomic, strong) NSMutableData *data; +@property (nonatomic, readonly) NSString *body; + +@property (nonatomic, copy) OnComplete onComplete; + +@end + + +@implementation CHHTTPConnection + +- (id)initWithRequest:(NSURLRequest *)urlRequest +{ + self = [super init]; + if (self) { + self.request = urlRequest; + } + return self; +} + +- (NSString *)body +{ + return [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding]; +} + +- (BOOL)executeRequestOnComplete:(OnComplete)OnCompleteBlock +{ + self.onComplete = OnCompleteBlock; + + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + + NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self]; + return connection != nil; +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)aResponse +{ + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)aResponse; + self.response = httpResponse; + + self.data = [NSMutableData data]; + [self.data setLength:0]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)bytes +{ + [self.data appendData:bytes]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + + if (self.onComplete) + self.onComplete(self.response, self.data, error); +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + + if (self.onComplete) + self.onComplete(self.response, self.data, nil); +} + +@end diff --git a/ImageFiller/IFViewController.m b/ImageFiller/IFViewController.m index 7cd117e..d6acc71 100644 --- a/ImageFiller/IFViewController.m +++ b/ImageFiller/IFViewController.m @@ -10,6 +10,7 @@ #import #import "IFViewController.h" +#import "CHHTTPConnection.h" @interface IFViewController () @@ -17,7 +18,7 @@ @interface IFViewController () @property (assign) NSInteger totalImageDownloadCount; @property (assign) NSInteger totalImageWriteCount; @property (assign) NSInteger totalImagesWrittenCount; -@property (strong) NSMutableArray *images; +@property (strong) NSMutableArray *imagePATHs; @property (strong) ALAssetsLibrary *assetsLibrary; @@ -50,29 +51,45 @@ - (void)addImagesToLibrary self.totalImagesWrittenCount = 0; - dispatch_async(dispatch_get_main_queue(), ^{ - for (NSInteger index = 0; index < self.totalImageWriteCount; index++) { - UIImage *image = self.images[arc4random() % self.images.count]; - - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:-(float)(arc4random() % (5 * 365 * 24 * 3600))]; - - NSDictionary *exifData = @{ (NSString *)kCGImagePropertyExifDateTimeOriginal: [exifDateFormatter stringFromDate:date] }; - - NSDictionary *metadata = @{ (NSString *)kCGImagePropertyExifDictionary: exifData }; - - [self.assetsLibrary writeImageToSavedPhotosAlbum:image.CGImage - metadata:metadata - completionBlock:^(NSURL *assetURL, NSError *error) { - self.totalImagesWrittenCount += 1; - [self progress:self.totalImagesWrittenCount - of:self.totalImageWriteCount]; - - if (self.totalImageWriteCount == self.totalImagesWrittenCount) { - [self log:@"complete!"]; - } - }]; + for (NSInteger index = 0; index < self.totalImageWriteCount; index++) { + UIImage *image = [UIImage imageWithContentsOfFile:self.imagePATHs[arc4random() % self.imagePATHs.count]]; + + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:-(float)(arc4random() % (5 * 365 * 24 * 3600))]; + + NSDictionary *exifData = @{ (NSString *)kCGImagePropertyExifDateTimeOriginal: [exifDateFormatter stringFromDate:date] }; + + NSDictionary *metadata = @{ (NSString *)kCGImagePropertyExifDictionary: exifData }; + + __block BOOL writeFinished = NO; +#if TARGET_IPHONE_SIMULATOR + writeFinished = YES; +#endif + + [self.assetsLibrary writeImageToSavedPhotosAlbum:image.CGImage + metadata:metadata + completionBlock:^(NSURL *assetURL, NSError *error) { + self.totalImagesWrittenCount += 1; + + [self progress:self.totalImagesWrittenCount + of:self.totalImageWriteCount]; + + if (self.totalImageWriteCount == self.totalImagesWrittenCount) { + [self log:@"complete!"]; + } + + writeFinished = YES; + }]; + + //Wati until write operation finished when target is a real device, otherwise memory pressure will cause crash + NSTimeInterval checkEveryInterval = 0.1; + while(!writeFinished) { + @autoreleasepool { + if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:checkEveryInterval]]) + [NSThread sleepForTimeInterval:checkEveryInterval]; + } } - }); + } + } - (void)log:(NSString *)log @@ -92,43 +109,74 @@ - (void)progress:(NSInteger)progress of:(NSInteger)total - (IBAction)loadImages:(id)sender { [self log:@"loading images"]; - - self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; - - self.images = [NSMutableArray array]; + self.imagePATHs = [NSMutableArray array]; NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()]; + if (NSClassFromString(@"NSURLSession")) { + self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + } + + for (NSInteger index = 0; index < self.totalImageDownloadCount; index++) { NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"http://lorempixel.com/400/400/?%i", arc4random()]]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; - NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request - completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { - NSString *filename = [NSString stringWithFormat:@"image%i.jpg", arc4random()]; - NSURL *imageURL = [tempURL URLByAppendingPathComponent:filename]; - - NSError *fileError; - [[NSFileManager defaultManager] moveItemAtURL:location - toURL:imageURL - error:&fileError]; - NSAssert(fileError == nil, @"something happended"); - - UIImage *image = [[UIImage alloc] initWithContentsOfFile:imageURL.path]; - - NSAssert(image.size.width != 0 && image.size.height != 0, @"image is no image"); - - [self.images addObject:image]; - [self log:[NSString stringWithFormat:@"loaded %lu of %li images", (unsigned long)self.images.count, (long)self.totalImageDownloadCount]]; - - [self progress:self.images.count - of:self.totalImageDownloadCount]; - - - if (self.images.count == self.totalImageDownloadCount) { - [self addImagesToLibrary]; - } - }]; - [downloadTask resume]; + if (self.session) { + NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request + completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { + NSString *filename = [NSString stringWithFormat:@"image%i.jpg", arc4random()]; + NSURL *imageURL = [tempURL URLByAppendingPathComponent:filename]; + + NSError *fileError; + [[NSFileManager defaultManager] moveItemAtURL:location + toURL:imageURL + error:&fileError]; + NSAssert(fileError == nil, @"something happended"); + + UIImage *image = [[UIImage alloc] initWithContentsOfFile:imageURL.path]; + + NSAssert(image.size.width != 0 && image.size.height != 0, @"image is no image"); + + [self.imagePATHs addObject:imageURL.path]; + [self log:[NSString stringWithFormat:@"loaded %lu of %li images", (unsigned long)self.imagePATHs.count, (long)self.totalImageDownloadCount]]; + + [self progress:self.imagePATHs.count + of:self.totalImageDownloadCount]; + + + if (self.imagePATHs.count == self.totalImageDownloadCount) { + [self addImagesToLibrary]; + } + }]; + [downloadTask resume]; + } + else { + CHHTTPConnection *connection = [[CHHTTPConnection alloc] initWithRequest:request]; + + [connection executeRequestOnComplete:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { + NSAssert(data != nil, @"Data is nil"); + NSString *filename = [NSString stringWithFormat:@"image%i.jpg", arc4random()]; + NSURL *imageURL = [tempURL URLByAppendingPathComponent:filename]; + + BOOL result = [data writeToURL:imageURL atomically:YES]; + NSAssert(result == YES, @"Write fail"); + + + [self.imagePATHs addObject:imageURL.path]; + + + [self progress:self.imagePATHs.count + of:self.totalImageDownloadCount]; + + + if (self.imagePATHs.count == self.totalImageDownloadCount) { + [self addImagesToLibrary]; + } + + }]; + } } + + } @end From 854ffec62943cc8718492b830b886bc67bf9acf1 Mon Sep 17 00:00:00 2001 From: Hang Chen Date: Thu, 17 Apr 2014 13:39:11 +0800 Subject: [PATCH 2/2] Added max concurrent count support when writing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added max concurrent count support when writing, by default, if target is a simulator, the max concurrent count is NSIntegerMax; if a real device, it’s 100. Tested in iTouch 4, iOS 6, it’s safe. --- ImageFiller/IFViewController.m | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ImageFiller/IFViewController.m b/ImageFiller/IFViewController.m index d6acc71..339b4ae 100644 --- a/ImageFiller/IFViewController.m +++ b/ImageFiller/IFViewController.m @@ -11,6 +11,13 @@ #import "IFViewController.h" #import "CHHTTPConnection.h" +//You can define the max concurrent count when writing to photo library + +#if TARGET_IPHONE_SIMULATOR +#define MAXConcurrentCount NSIntegerMax +#else +#define MAXConcurrentCount 100//Tested in iTouch 4, iOS 6, 100 is safe to use. You can adjust the count if needed. +#endif @interface IFViewController () @@ -50,6 +57,7 @@ - (void)addImagesToLibrary [exifDateFormatter setDateFormat:@"yyyy:MM:dd HH:mm:ss"]; self.totalImagesWrittenCount = 0; + __block NSInteger maxConcurrentCount = MAXConcurrentCount; for (NSInteger index = 0; index < self.totalImageWriteCount; index++) { UIImage *image = [UIImage imageWithContentsOfFile:self.imagePATHs[arc4random() % self.imagePATHs.count]]; @@ -60,11 +68,8 @@ - (void)addImagesToLibrary NSDictionary *metadata = @{ (NSString *)kCGImagePropertyExifDictionary: exifData }; - __block BOOL writeFinished = NO; -#if TARGET_IPHONE_SIMULATOR - writeFinished = YES; -#endif - + + maxConcurrentCount--; [self.assetsLibrary writeImageToSavedPhotosAlbum:image.CGImage metadata:metadata completionBlock:^(NSURL *assetURL, NSError *error) { @@ -77,12 +82,12 @@ - (void)addImagesToLibrary [self log:@"complete!"]; } - writeFinished = YES; + maxConcurrentCount++; }]; //Wati until write operation finished when target is a real device, otherwise memory pressure will cause crash NSTimeInterval checkEveryInterval = 0.1; - while(!writeFinished) { + while(maxConcurrentCount < 0) { @autoreleasepool { if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:checkEveryInterval]]) [NSThread sleepForTimeInterval:checkEveryInterval];