1. José Manuel Sánchez Peñarroja
  2. BMF

Commits

José Manuel Sánchez Peñarroja  committed 5848a10

Added serial queue to synchronize access to managed objects cache. Added managedcontextoperationstask to save every n operations. Added sender to average time to avoid mangling different requests with the same key. Added sdwebimageloader

  • Participants
  • Parent commits 8283cff
  • Branches master

Comments (0)

Files changed (17)

File BMF.podspec

View file
     sp.ios.source_files = 'bmf/ios/subspecs/m13/**/*.{h,m}'
   end
   
+  s.subspec "SDWebImage" do |sp|
+    sp.ios.dependency 'SDWebImage', '~> 3.6'
+    sp.ios.source_files = 'bmf/ios/subspecs/sdwebimage/**/*.{h,m}'
+  end
+  
 end

File Example/Example.xcworkspace/xcuserdata/josanchez.xcuserdatad/UserInterfaceState.xcuserstate

Binary file modified.

File Example/Example.xcworkspace/xcuserdata/josanchez.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

View file
             shouldBeEnabled = "No"
             ignoreCount = "0"
             continueAfterRunningActions = "No"
-            filePath = "../bmf/ios/view controllers/BMFViewController.m"
-            timestampString = "419869500.021577"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "116"
-            endingLineNumber = "116"
-            landmarkName = "-viewDidLoad"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
             filePath = "../bmf/ios/views/BMFHUDLoaderView.m"
             timestampString = "416247894.071392"
             startingColumnNumber = "9223372036854775807"

File bmf/ios/subspecs/sdwebimage/BMFSDWebImageLoader.h

View file
+//
+//  BMFSDWebImageLoader.h
+//  Pods
+//
+//  Created by Jose Manuel Sánchez Peñarroja on 11/06/14.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+#import "BMFLoaderProtocol.h"
+
+#import <SDWebImage/SDWebImageManager.h>
+
+@interface BMFSDWebImageLoader : NSObject <BMFLoaderProtocol>
+
+@property (nonatomic, readonly) BMFProgress *progress;
+
+@property (nonatomic, strong) NSURL *url;
+
+@property (nonatomic, assign) SDWebImageOptions options;
+
+@end

File bmf/ios/subspecs/sdwebimage/BMFSDWebImageLoader.m

View file
+//
+//  BMFSDWebImageLoader.m
+//  Pods
+//
+//  Created by Jose Manuel Sánchez Peñarroja on 11/06/14.
+//
+//
+
+#import "BMFSDWebImageLoader.h"
+
+#import "BMF.h"
+
+@implementation BMFSDWebImageLoader
+
+@synthesize progress = _progress;
+
+- (id)init
+{
+    self = [super init];
+    if (self) {
+		_progress = [[BMFProgress alloc] init];
+		_options = SDWebImageAllowInvalidSSLCertificates;
+    }
+    return self;
+}
+
+
+- (void) setUrl:(NSURL *)url {
+	_url = url;
+	if (_url) [self.progress setKey:_url.absoluteString];
+}
+
+- (void) cancel {
+	[self.progress stop:nil];
+}
+
+- (void) load:(BMFCompletionBlock) completionBlock {
+	
+	BMFAssertReturn(self.url);
+	BMFAssertReturn(completionBlock);
+
+	[_progress start:self.url.absoluteString];
+	[[SDWebImageManager sharedManager] downloadWithURL:self.url options:self.options progress:^(NSInteger receivedSize, NSInteger expectedSize) {
+		[_progress stop:nil];
+		self.progress.totalUnitCount = expectedSize;
+		self.progress.completedUnitCount = receivedSize;
+	} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
+		[_progress stop:error];
+		completionBlock(image,error);
+	}];
+}
+
+
+@end

File bmf/shared/base/BMF.h

View file
 
 #define BMFLocalized(string,comment) NSLocalizedStringFromTableInBundle(string,@"BMFLocalizable",[BMFBase sharedInstance].bundle,comment)
 
+#pragma mark Utils
 #import "BMFAutolayoutUtils.h"
+#import <BMF/BMFUtils.h>
+#import <BMF/BMFKeyboardManager.h>
+
+#pragma mark Protocols
 #import "BMFParserProtocol.h"
+#import "BMFTaskProtocol.h"
+
+#pragma mark Categories
 #import "NSBundle+BMF.h"
 
-#import <BMF/BMFUtils.h>
-#import <BMF/BMFKeyboardManager.h>

File bmf/shared/data/BMFProgress.m

View file
 		_totalUnitCount = totalUnitCount;
 	});
 
-	if (self.key.length>0) [BMFAverageTime setEffort:totalUnitCount forKey:self.key];
+	if (self.key.length>0) [BMFAverageTime setEffort:totalUnitCount forKey:self.key sender:self];
 }
 
 - (CGFloat) fractionCompleted {
 }*/
 
 - (void) start:(NSString *) key {
+	BMFAssertReturn(key.length>0);
 	BMFAssertReturn(self.children.count==0);
 	
 	self.key = key;
 	
 	[self clear];
 
-	[BMFAverageTime startTime:key effort:self.totalUnitCount];
+	[BMFAverageTime startTime:key effort:self.totalUnitCount sender:self];
 	
 	self.running = YES;
 }
 
 	if (self.key) {
 		if (error) [BMFAverageTime cancelTime:self.key];
-		else [BMFAverageTime stopTime:self.key];
+		else [BMFAverageTime stopTime:self.key sender:self];
 		
 		self.key = nil;
 	}

File bmf/shared/data/loaders/BMFAFURLSessionLoader.m

View file
 		_progress = [[BMFProgress alloc] init];
 		_method = @"GET";
 		
+//		_cache = [[NSCache alloc] init];
+		
 		#if TARGET_OS_IPHONE
 		[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil] subscribeNext:^(id x) {
 			[sessionManager invalidateSessionCancelingTasks:NO];
 	if (_url) [self.progress setKey:_url.absoluteString];
 }
 
+- (BOOL) callBlocksForUrl:(NSURL *)url result:(id)result error:(NSError *)error {
+	if (!self.cache) return NO;
+	
+	NSArray *blocks = [self.cache objectForKey:self.url];
+	if (![blocks isKindOfClass:[NSArray class]]) return NO;
+	
+	for (BMFCompletionBlock block in blocks) {
+		block(result,error);
+	}
+	
+	// Blocks should only be called once
+	[self.cache removeObjectForKey:self.url];
+
+	return YES;
+}
+
 - (void) load:(BMFCompletionBlock) completionBlock {
 	
 	BMFAssertReturn(self.url);
 		
 		if (self.cache) {
 			id responseObject = [self.cache objectForKey:self.url];
-			if (responseObject) {
+			if (!responseObject) {
+				responseObject = [NSMutableArray array];
+			}
+			
+			if ([responseObject isKindOfClass:[NSArray class]]) {
+				[responseObject addObject:completionBlock];
+				[self.cache setObject:responseObject forKey:self.url];
+			}
+			else {
 				DDLogInfo(@"Loader returning cached data");
 				[_progress stop:nil];
 				completionBlock(responseObject,nil);
 			[_progress stop:error];
 			
 			if (error) {
-				completionBlock(nil,error);
+				if (![self callBlocksForUrl:self.url result:nil error:error]) {
+					completionBlock(nil,error);
+				}
 			}
-			else {	
-				if (self.cache) {
-					[self.cache setObject:responseObject forKey:self.url cost:malloc_size((__bridge const void *)(responseObject))];
+			else {
+				if (![self callBlocksForUrl:self.url result:responseObject error:nil]) {
+					completionBlock(responseObject,nil);
 				}
 				
-				completionBlock(responseObject,nil);
+				[self.cache setObject:responseObject forKey:self.url cost:malloc_size((__bridge const void *)(responseObject))];
 			}
 		}];
 		

File bmf/shared/data/operations/BMFAsyncOperation.m

View file
 	BOOL _isFinished;
 }
 
+@synthesize cancelled = _cancelled;
+
 - (instancetype)init
 {
     self = [super init];

File bmf/shared/data/operations/BMFOperationsTask.h

View file
 
 - (void) addOperation:(BMFOperation *) operation;
 
+@property (nonatomic, strong) NSMutableArray *operations;
+
 - (instancetype) initWithOperations:(NSArray *) operations;
 
+- (void) prepareForStart;
+- (void) prepareForFinish;
+
 #pragma mark Actionable
 
 - (void) action:(id)input completion:(BMFCompletionBlock)completion;

File bmf/shared/data/operations/BMFOperationsTask.m

View file
 @interface BMFOperationsTask()
 
 @property (nonatomic, strong) NSOperationQueue *privateQueue;
-@property (nonatomic, strong) NSMutableArray *operations;
 
 @end
 
 	[self.progress addChild:operation.progress];
 }
 
-- (void) updateDependencies {
+- (void) prepareForStart {
 	if (!self.manualDependencies) {
 		[[[self.operations rac_sequence] scanWithStart:nil reduce:^id(id previous, id current) {
-//			DDLogDebug(@"scan previous: %@ current: %@",previous,current);
 			if (previous && current) {
 				[current addDependency:previous];
 			}
 	}
 }
 
+- (void) prepareForFinish {}
+
 - (BOOL) start: (BMFCompletionBlock) completion {
 	
-	[self updateDependencies];
+	[self prepareForStart];
 	
 	if (self.operations.count==0) {
 		DDLogError(@"Tried to run an operations task without operations");
 
 	BMFBlockOperation *blockOp = [[BMFBlockOperation alloc] initWithBlock:^(id sender,BMFCompletionBlock completionBlock){
 		BMFBlockOperation *op = sender;
+		[self prepareForFinish];
+		
 		for (NSOperation *dep in op.dependencies) {
 			if ([dep isKindOfClass:[BMFOperation class]]) {
 				BMFOperation *previous = (BMFOperation *)dep;

File bmf/shared/stats/BMFAverageTime.h

View file
 
 @interface BMFAverageTime : NSObject
 
-+ (void) startTime:(NSString *) key;
++ (void) startTime:(NSString *) key sender:(id) sender;
 
 /// Effort is a measure of how hard it is the task. You can use the number of objects, the number of bytes, the nerwork condition, etc
-+ (void) startTime:(NSString *) key effort:(CGFloat) effort;
-+ (void) setEffort:(CGFloat) effort forKey:(NSString *) key;
++ (void) startTime:(NSString *) key effort:(CGFloat) effort sender:(id) sender;
++ (void) setEffort:(CGFloat) effort forKey:(NSString *) key sender:(id) sender;
 
-+ (void) stopTime:(NSString *) key;
++ (void) stopTime:(NSString *) key sender:(id)sender;
 
 + (double) averageTime:(NSString *) key;
 + (double) averageTime:(NSString *) key effort:(CGFloat) effort;

File bmf/shared/stats/BMFAverageTime.m

View file
 
 #define DEFAULT_PONDERATION 1.2
 
+@interface BMFAverageTimeData : NSObject
+
+@property (nonatomic, copy) NSString *key;
+@property (nonatomic, strong) NSDate *date;
+@property (nonatomic, assign) CGFloat effort;
+
+@end
+
+@implementation BMFAverageTimeData
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        self.effort = 1;
+		self.date = [NSDate date];
+    }
+    return self;
+}
+@end
+
 static BMFAverageTime *averageTime = nil;
 
 @interface BMFAverageTime()
 
 @property (nonatomic, strong) dispatch_queue_t serialQueue;
 @property (nonatomic, strong) NSMutableDictionary *timesDic;
-@property (nonatomic, strong) NSMutableDictionary *effortsDic;
 
 @property (nonatomic, strong) BMFAverageStats *stats;
 @property (nonatomic, strong) BMFAverageStats *effortStats;
 		_serialQueue = dispatch_queue_create("com.bmf.AverageTime", DISPATCH_QUEUE_SERIAL);
 		dispatch_sync(_serialQueue, ^{
 			_timesDic = [NSMutableDictionary dictionary];
-			_effortsDic = [NSMutableDictionary dictionary];
 			NSURL *url = [NSURL fileURLWithPath:[[BMFUtils applicationCacheDirectory] stringByAppendingPathComponent:@"com.bmf.AverageTime"]];
 			_stats = [[BMFAverageStats alloc] initWithFileUrl:url];
 			NSURL *effortUrl = [NSURL fileURLWithPath:[[BMFUtils applicationCacheDirectory] stringByAppendingPathComponent:@"com.bmf.AverageTime.effort"]];
     return self;
 }
 
-+ (void) startTime:(NSString *) key {
++ (NSString *) p_keyWithKey:(NSString *) key sender:(id) sender {
+	return [NSString stringWithFormat:@"%@%d",key,[sender hash]];
+}
+
++ (void) startTime:(NSString *) key sender:(id)sender {
 	BMFAssertReturn(key.length>0);
 	
+	NSString *finalKey = [self p_keyWithKey:key sender:sender];
+	BMFAverageTimeData *data = [BMFAverageTimeData new];
+	data.key = key;
+	
 	dispatch_async(averageTime.serialQueue, ^{
-		averageTime.timesDic[key] = [NSDate date];
+		averageTime.timesDic[finalKey] = data;
 	});
 }
 
-+ (void) startTime:(NSString *) key effort:(CGFloat) effort {
++ (void) startTime:(NSString *) key effort:(CGFloat) effort sender:(id)sender {
 	BMFAssertReturn(key.length>0);
 	BMFAssertReturn(effort>0);
 	
+	NSString *finalKey = [NSString stringWithFormat:@"%@%d",key,[sender hash]];
+	BMFAverageTimeData *data = [BMFAverageTimeData new];
+	data.key = key;
+	data.effort = effort;
+	
 	dispatch_async(averageTime.serialQueue, ^{
-		averageTime.timesDic[key] = [NSDate date];
-		averageTime.effortsDic[key] = @(effort);
+		averageTime.timesDic[finalKey] = data;
 	});
 }
 
-+ (void) setEffort:(CGFloat) effort forKey:(NSString *) key {
++ (void) setEffort:(CGFloat) effort forKey:(NSString *) key sender:(id)sender {
 	BMFAssertReturn(key.length>0);
 	BMFAssertReturn(effort>0);
+
+	NSString *finalKey = [NSString stringWithFormat:@"%@%d",key,[sender hash]];
+
 	dispatch_async(averageTime.serialQueue, ^{
-		averageTime.effortsDic[key] = @(effort);
+		BMFAverageTimeData *data = averageTime.timesDic[finalKey];
+		BMFAssertReturn(data);
+		
+		data.effort = effort;
 	});
 }
 
-+ (void) stopTime:(NSString *) key {
++ (void) stopTime:(NSString *) key sender:(id)sender {
 	BMFAssertReturn(key.length>0);
 
+	NSString *finalKey = [self p_keyWithKey:key sender:sender];
+
 	dispatch_async(averageTime.serialQueue, ^{
-		NSDate *startDate = averageTime.timesDic[key];
-		BMFAssertReturn(startDate);
+		BMFAverageTimeData *data = averageTime.timesDic[finalKey];
+		BMFAssertReturn(data);
 		
-//		DDLogDebug(@"Added stop date for key: %@ %@",key,[NSDate date]);
+		NSDate *startDate = data.date;
+		BMFAssertReturn(startDate);
 		
 		NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:startDate];
 //		DDLogDebug(@"Adding time: %f for key: %@",time,key);
 		
-		[averageTime.timesDic removeObjectForKey:key];
+		[averageTime.timesDic removeObjectForKey:finalKey];
 		
 		[averageTime.stats setPonderation:DEFAULT_PONDERATION forKey:key];
 		
 		[averageTime.stats addValue:time forKey:key];
 		
-		NSNumber *effort = averageTime.effortsDic[key];
-		[averageTime.effortStats addValue:effort.floatValue forKey:key];
+		[averageTime.effortStats addValue:data.effort forKey:key];
 	});
 }
 

File bmf/shared/subspecs/coredata/BMFManagedContextOperationsTask.h

View file
+//
+//  BMFManagedContextOperationsTask.h
+//  Pods
+//
+//  Created by Jose Manuel Sánchez Peñarroja on 13/06/14.
+//
+//
+
+#import "BMFOperationsTask.h"
+
+@interface BMFManagedContextOperationsTask : BMFOperationsTask
+
+@property (nonatomic, strong) NSManagedObjectContext *context;
+
+/// Num of operations to wait to complete before saving the context. If there are less operations than this enqueued the context will be saved when all of them have finished. 10 by default.
+@property (nonatomic, assign) NSUInteger batchCount;
+
+- (instancetype) initWithOperations:(NSArray *) operations inContext:(NSManagedObjectContext *) context;
+
+@end

File bmf/shared/subspecs/coredata/BMFManagedContextOperationsTask.m

View file
+//
+//  BMFManagedContextOperationsTask.m
+//  Pods
+//
+//  Created by Jose Manuel Sánchez Peñarroja on 13/06/14.
+//
+//
+
+#import "BMFManagedContextOperationsTask.h"
+
+#import <ReactiveCocoa/ReactiveCocoa.h>
+#import <ReactiveCocoa/RACEXTScope.h>
+
+@interface BMFManagedContextOperationsTask()
+
+@property (nonatomic, assign) NSUInteger batchIndex;
+
+@end
+
+@implementation BMFManagedContextOperationsTask
+
+- (instancetype) initWithOperations:(NSArray *) operations inContext:(NSManagedObjectContext *) context {
+	self = [super initWithOperations:operations];
+    if (self) {
+		self.context = context;
+		_batchIndex = 0;
+    }
+    return self;
+}
+
+- (instancetype) initWithOperations:(NSArray *) operations {
+	BMFInvalidInit(initWithOperations:inContext:);
+}
+
+- (void) prepareForStart {
+	[super prepareForStart];
+	
+	@weakify(self);
+	[[self.operations rac_sequence] scanWithStart:nil reduce:^id(id previous, NSOperation *current) {
+		@strongify(self);
+		current.completionBlock = ^() {
+			[self operationCompleted];
+		};
+		return current;
+	}];
+}
+
+- (void) prepareForFinish {
+	[super prepareForFinish];
+	
+	self.batchIndex = self.batchCount;
+	
+	[self saveContext];
+}
+
+- (void) operationCompleted {
+	self.batchIndex++;
+	if (self.batchIndex==self.batchCount) {
+		[self saveContext];
+		self.batchIndex = 0;
+	}
+}
+
+- (void) saveContext {
+	[self.context performBlock:^{
+		if (!self.context.hasChanges) return;
+		
+		NSError *error = nil;
+		if (![self.context save:&error]) {
+			DDLogError(@"Error saving BMFManagedContextOperationsTask context: %@",error);
+		}
+	}];
+}
+
+@end

File bmf/shared/subspecs/coredata/BMFManagedObject.h

View file
 @property (nonatomic, strong) NSMutableArray *subscriptions;
 @property (nonatomic, strong) BMFTaskManager *taskManager;
 
++ (id) cachedObjectWithKey:(id) key;
++ (void) setCachedObject: (id) object key:(id)key;
 
 - (void) performInit;
 

File bmf/shared/subspecs/coredata/BMFManagedObject.m

View file
 #import <ReactiveCocoa/ReactiveCocoa.h>
 #import <MagicalRecord/CoreData+MagicalRecord.h>
 
+static NSCache *managedObjectsDataCache = nil;
+static dispatch_queue_t serialQueue = nil;
+
 @implementation BMFManagedObject
 
++ (void) initialize {
+	serialQueue = dispatch_queue_create("Managed object cache queue", DISPATCH_QUEUE_SERIAL);
+
+	dispatch_async(serialQueue, ^{
+		managedObjectsDataCache = [NSCache new];
+	});
+}
+
 @synthesize BMF_isDeleted;
 @synthesize subscriptions;
 @synthesize taskManager;
 	[self.taskManager cancelAll], self.taskManager = nil;
 }
 
++ (id) cachedObjectWithKey:(id) key {
+	if (!key) return nil;
+
+	__block id result = nil;
+	
+	dispatch_async(serialQueue, ^{
+		result = [managedObjectsDataCache objectForKey:key];
+	});
+	
+	return result;
+}
+
++ (void) setCachedObject: (id) object key:(id)key {
+	BMFAssertReturn(key);
+	BMFAssertReturn(object);
+	
+	dispatch_async(serialQueue, ^{
+		[managedObjectsDataCache setObject:object forKey:key];
+	});
+}
+
 - (id<BMFTaskProtocol>) dataLoadTask:(NSString *)urlString {
 	id<BMFNetworkFactoryProtocol> factory = [[BMFBase sharedInstance].factory BMF_castWithProtocol:@protocol(BMFNetworkFactoryProtocol)];
 	return [factory dataLoadTask:urlString parameters:nil sender:self];