Commits

José Manuel Sánchez Peñarroja  committed 760d280

Now the imagepickeractivity adds itself as an aspect so it’s not deallocated before completion, and then removes itself

  • Participants
  • Parent commits 3d20b55

Comments (0)

Files changed (15)

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

Binary file modified.

File Example/Example/Example.xcodeproj/project.pbxproj

 		AC82DEDD1869873E00CB335E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AC82DEDB1869873E00CB335E /* InfoPlist.strings */; };
 		AC82DEDF1869873E00CB335E /* ExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC82DEDE1869873E00CB335E /* ExampleTests.m */; };
 		AC85DA0718FD3DFD00F60102 /* bmfValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC85DA0618FD3DFD00F60102 /* bmfValueTests.m */; };
+		AC949FC81965696000EEBC21 /* bmfThrottleAspectTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AC949FC71965696000EEBC21 /* bmfThrottleAspectTest.m */; };
 		ACA63AD41961874900247D56 /* bmfEnumerateDataSourceAspectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ACA63AD31961874900247D56 /* bmfEnumerateDataSourceAspectTests.m */; };
 		ACAEE81E193747760020C42B /* bmfStatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ACAEE81D193747760020C42B /* bmfStatTests.m */; };
 		ACCFF1491910E65B002BC1FD /* bmfUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ACCFF1481910E65B002BC1FD /* bmfUtilsTests.m */; };
 		AC82DEDC1869873E00CB335E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		AC82DEDE1869873E00CB335E /* ExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleTests.m; sourceTree = "<group>"; };
 		AC85DA0618FD3DFD00F60102 /* bmfValueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = bmfValueTests.m; sourceTree = "<group>"; };
+		AC949FC71965696000EEBC21 /* bmfThrottleAspectTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = bmfThrottleAspectTest.m; sourceTree = "<group>"; };
 		AC99835E18A4DAEF004AB11B /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
 		ACA63AD31961874900247D56 /* bmfEnumerateDataSourceAspectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = bmfEnumerateDataSourceAspectTests.m; sourceTree = "<group>"; };
 		ACAEE81D193747760020C42B /* bmfStatTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = bmfStatTests.m; sourceTree = "<group>"; };
 				ACCFF1481910E65B002BC1FD /* bmfUtilsTests.m */,
 				ACAEE81D193747760020C42B /* bmfStatTests.m */,
 				AC4B04F71962F2DF00D27486 /* BMFDataStoreSelectionTests.m */,
+				AC949FC71965696000EEBC21 /* bmfThrottleAspectTest.m */,
 				AC1D14C5187D700E008598EB /* Supporting Files */,
 			);
 			path = bmfTests;
 				AC52FBF218F43993007FA26E /* BMFDataStoresModel.xcdatamodeld in Sources */,
 				AC472F5F1929E968004FCBEC /* bmfObjectDataStoreTests.m in Sources */,
 				AC4B04F81962F2DF00D27486 /* BMFDataStoreSelectionTests.m in Sources */,
+				AC949FC81965696000EEBC21 /* bmfThrottleAspectTest.m in Sources */,
 				AC4FE88518F8298B00D8E845 /* bmfLoaderTests.m in Sources */,
 				ACA63AD41961874900247D56 /* bmfEnumerateDataSourceAspectTests.m in Sources */,
 			);

File Example/Example/bmfTests/bmfThrottleAspectTest.m

 
 #import <XCTest/XCTest.h>
 
-@interface bmfThrottleAspectTest : XCTestCase
+#import <Specta/Specta.h>
+#define EXP_SHORTHAND
+#import <Expecta/Expecta.h>
+#import <OCMock/OCMock.h>
 
-@end
+#import <BMF/BMFThrottleAspect.h>
 
-@implementation bmfThrottleAspectTest
+SpecBegin(ThrottleAspect)
 
-- (void)setUp
-{
-    [super setUp];
-    // Put setup code here. This method is called before the invocation of each test method in the class.
-}
+describe(@"BMFThrottleAspect", ^AsyncBlock {
+	
+	it(@"check initialization parameters", ^{
+		__block BMFThrottleAspect *aspect = nil;
+		
+		expect(^{ aspect = [[BMFThrottleAspect alloc] init]; }).to.raiseAny();
+		expect(aspect).to.beNil();
+		expect(^{ aspect = [[BMFThrottleAspect alloc] initWithInterval:-1 actionBlock:^(id sender) {} identifier:@"blah"]; }).to.raiseAny();
+		expect(aspect).to.beNil();
+		expect(^{ aspect = [[BMFThrottleAspect alloc] initWithInterval:0 actionBlock:^(id sender) {} identifier:@"blah"]; }).to.raiseAny();
+		expect(aspect).to.beNil();
+		expect(^{ aspect = [[BMFThrottleAspect alloc] initWithInterval:10 actionBlock:nil identifier:@"blah"]; }).to.raiseAny();
+		expect(aspect).to.beNil();
+		expect(^{ aspect = [[BMFThrottleAspect alloc] initWithInterval:10 actionBlock:^(id sender) {} identifier:nil]; }).to.raiseAny();
+		expect(aspect).to.beNil();
+		expect(^{ aspect = [[BMFThrottleAspect alloc] initWithInterval:10 actionBlock:^(id sender) {} identifier:@""]; }).to.raiseAny();
+		expect(aspect).to.beNil();
+		expect(^{ aspect = [[BMFThrottleAspect alloc] initWithInterval:10 actionBlock:^(id sender) {} identifier:@"blah"]; }).toNot.raiseAny();
+		
+		expect(aspect).notTo.beNil();
+		expect(^{ aspect.minimumTimeInterval = 0; }).to.raiseAny();
+		expect(^{ aspect.actionBlock = nil; }).to.raiseAny();
+		expect(^{ aspect.identifier = nil; }).to.raiseAny();
+		expect(^{ aspect.identifier = @""; }).to.raiseAny();
+	});
+	
+	it(@"shouldn't call before the minimum time interval", ^{
+		__block NSDate *date = nil;
+		BMFThrottleAspect *aspect = [[BMFThrottleAspect alloc] initWithInterval:1 actionBlock:^(id sender) {
+			NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];
+			expect(interval).to.beGreaterThanOrEqualTo(1);
+			
+			[aspect stop];
+			done();
+		} identifier:@"tests"];
+		
+		date = [NSDate date];
+		[aspect start];
+	});
+	
+	it(@"shouldn't call before the minimum time interval if we set a tolerance", ^AsyncBlock{
+		__block NSDate *date = nil;
+		BMFThrottleAspect *aspect = [[BMFThrottleAspect alloc] initWithInterval:1 actionBlock:^(id sender) {
+			NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];
+			expect(interval).to.beGreaterThanOrEqualTo(1);
+			
+			[aspect stop];
+			done();
+		} identifier:@"tests"];
+		
+		aspect.tolerance = 1;
+		
+		date = [NSDate date];
+		[aspect start];
 
-- (void)tearDown
-{
-    // Put teardown code here. This method is called after the invocation of each test method in the class.
-    [super tearDown];
-}
+	});
+	
+	it(@"should allow changing the minimum time interval", ^AsyncBlock{
+		__block NSDate *date = nil;
+		BMFThrottleAspect *aspect = [[BMFThrottleAspect alloc] initWithInterval:1 actionBlock:^(id sender) {
+			NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];
+			expect(interval).to.beGreaterThanOrEqualTo(2);
+			
+			[aspect stop];
+			done();
+		} identifier:@"tests"];
+		
+		aspect.minimumTimeInterval = 2;
+		
+		date = [NSDate date];
+		[aspect start];
+	});
+});
 
-- (void)testExample
-{
-    XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__);
-}
 
-@end
+SpecEnd

File bmf/ios/activities/BMFImagePickerActivity.m

 #pragma mark UIImagePickerControllerDelegate
 
 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
-
+	
+	DDLogInfo(@"did finish");
 	id result = nil;
 	
 	result = info[UIImagePickerControllerEditedImage];
 	self.activityCompletionBlock(result,nil);
 	
 	[self.viewController dismissViewControllerAnimated:YES completion:nil];
+	
+	[self completed];
 }
 
 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
+	DDLogInfo(@"did cancel");
 	self.activityCompletionBlock(nil,[NSError errorWithDomain:@"Activity" code:BMFErrorCancelled userInfo:nil]);
 	[self.viewController dismissViewControllerAnimated:YES completion:nil];
+	
+	[self completed];
+}
+
+- (void) dealloc {
+	DDLogInfo(@"dealloc image picker activity");
 }
 
 @end

File bmf/ios/aspects/BMFViewBorderAspect.h

 
 #import "BMFAspect.h"
 
-@interface BMFButtonBorderAspect : BMFAspect
+@interface BMFViewBorderAspect : BMFAspect
+
+/// 1 by default
+@property (nonatomic, assign) CGFloat borderWidth;
+
+/// nil by default. If it's nil the button tintColor will be used
+@property (nonatomic, strong) UIColor *borderColor;
 
 @end

File bmf/ios/aspects/BMFViewBorderAspect.m

 //
 //
 
-#import "BMFButtonBorderAspect.h"
+#import "BMFViewBorderAspect.h"
 
-@implementation BMFButtonBorderAspect
+#import "BMF.h"
+#import <ReactiveCocoa/RACEXTScope.h>
+#import <ReactiveCocoa/ReactiveCocoa.h>
+
+@interface BMFViewBorderAspect()
+
+@property (nonatomic, weak) UIView *view;
+
+@end
+
+@implementation BMFViewBorderAspect
+
+- (void) setView:(UIView *)view {
+	self.object = view;
+}
+
+- (UIView *) view {
+	return self.object;
+}
+
+- (instancetype) init {
+	self = [super init];
+	if (self) {
+		_borderWidth = 1;
+
+		@weakify(self);
+		[RACObserve(self, view.tintColor) subscribeNext:^(id x) {
+			@strongify(self);
+			[self p_updateView];
+		}];
+	}
+	return self;
+}
+
+- (void) setObject:(id)object {
+	
+	BMFAssertReturn([object isKindOfClass:[UIView class]]);
+	
+	[super setObject:object];
+	
+	[self p_updateView];
+}
+
+- (void) setBorderWidth:(CGFloat)borderWidth {
+	_borderWidth = borderWidth;
+
+	[self p_updateView];
+}
+
+- (void) setBorderColor:(UIColor *)borderColor {
+	_borderColor = borderColor;
+
+	[self p_updateView];
+}
+
+- (void) p_updateView {
+	UIView *view = self.object;
+	view.layer.borderWidth = self.borderWidth;
+	
+	if (self.borderColor) view.layer.borderColor = self.borderColor.CGColor;
+	else view.layer.borderColor = view.tintColor.CGColor;
+}
 
 @end

File bmf/ios/subspecs/coredata/BMFFRCDataStore.m

 /*
 - (BOOL) performProcess:(id)input completion:(BMFNodeProcessCompletionBlock)completionBlock {
 	return [self loadData:completionBlock];
-}
+}*/
 
 - (BOOL) loadData:(BMFCompletionBlock) completionBlock {
 	NSError *error = nil;
 	if (completionBlock) completionBlock(self.fr.fetchedObjects,error);
 	
 	return result;
-}*/
+}
 
 @end

File bmf/shared/activities/BMFActivity.h

 #import "BMFActivityProtocol.h"
 #import "BMFGroup.h"
 
-@interface BMFActivity : BMFGroup <BMFActivityProtocol,BMFActionableProtocol>
+@interface BMFActivity : BMFGroup <BMFActivityProtocol,BMFActionableProtocol, BMFAspectProtocol>
 
 /// Optional, but some activities might require a view controller to work
 #if TARGET_OS_IPHONE
 
 - (void) run: (BMFCompletionBlock) completionBlock;
 
+/// Call this from any subclass for cleaning up
+- (void) completed;
+
 #pragma mark Actionable
 
 - (void) action:(id)input completion:(BMFCompletionBlock)completion;

File bmf/shared/activities/BMFActivity.m

 	self.localizedValueChanged = YES;
 }
 
+- (void) setViewController:(UIViewController *)viewController {
+	_viewController = viewController;
+	[_viewController BMF_addAspect:self];
+}
+
 - (NSString *) localizedValue {
 	if (self.localizedValueChanged) return _localizedValue;
 	return self.value;
 	[self run:completion];
 }
 
+- (void) completed {
+	[self.viewController BMF_removeAspect:self];
+}
+
 - (void) run: (BMFCompletionBlock) completionBlock {
 	[NSException raise:@"Activity subclass didn't implement run: selector" format:@"%@",self];
 }

File bmf/shared/aspects/BMFThrottleAspect.h

 
 #import "BMFAspect.h"
 
+#import "BMFTypes.h"
+
 @interface BMFThrottleAspect : BMFAspect
 
+@property (nonatomic, assign) NSTimeInterval minimumTimeInterval;
+@property (nonatomic, copy) BMFActionBlock actionBlock;
+@property (nonatomic, copy) NSString *identifier;
+
+/// 0 if not specified
+@property (nonatomic, assign) NSTimeInterval tolerance;
+
+- (instancetype) initWithInterval:(NSTimeInterval)interval actionBlock:(BMFActionBlock) actionBlock identifier:(NSString *) identifier;
+
+- (void) start;
+- (void) stop;
+
 @end

File bmf/shared/aspects/BMFThrottleAspect.m

 
 #import "BMFThrottleAspect.h"
 
+@interface BMFThrottleAspect()
+
+@property (nonatomic, strong) NSDate *lastRunDate;
+@property (nonatomic, strong) NSTimer *timer;
+
+@end
+
 @implementation BMFThrottleAspect
 
+- (instancetype) initWithInterval:(NSTimeInterval)interval actionBlock:(BMFActionBlock) actionBlock identifier:(NSString *) identifier {
+	BMFAssertReturnNil(interval>0);
+	BMFAssertReturnNil(actionBlock);
+	BMFAssertReturnNil(identifier.length>0);
+	
+	self = [super init];
+    if (self) {
+		_minimumTimeInterval = interval;
+        _actionBlock = actionBlock;
+		_identifier = [identifier copy];
+		[self start];
+    }
+    return self;
+}
+
+- (instancetype)init {
+	BMFInvalidInit(initWithActionBlock:identifier:);
+}
+
+- (void) dealloc {
+	[self stop];
+}
+
+#pragma mark Accessors
+
+- (void) setMinimumTimeInterval:(NSTimeInterval)minimumTimeInterval {
+	if (minimumTimeInterval==_minimumTimeInterval) return;
+	
+	_minimumTimeInterval = minimumTimeInterval;
+	[self start];
+}
+
+- (void) setTolerance:(NSTimeInterval)tolerance {
+	if (tolerance==_tolerance) return;
+	
+	_tolerance = tolerance;
+	[self start];
+}
+
+- (void) setLastRunDate:(NSDate *)lastRunDate {
+	[[NSUserDefaults standardUserDefaults] setObject:lastRunDate forKey:self.identifier];
+	[[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (NSDate *) lastRunDate {
+	return [[NSUserDefaults standardUserDefaults] objectForKey:self.identifier];
+}
+
+- (void) start {
+	[self stop];
+	
+	NSDate *nextFireDate = [NSDate date];
+	
+	NSDate *date = self.lastRunDate;
+	if (date) {
+		nextFireDate = [date dateByAddingTimeInterval:self.minimumTimeInterval];
+	}
+	
+	self.timer = [NSTimer timerWithTimeInterval:self.minimumTimeInterval+self.tolerance target:self selector:@selector(p_run:) userInfo:nil repeats:YES];
+	self.timer.fireDate = nextFireDate;
+	self.timer.tolerance = self.tolerance;
+	[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
+}
+
+- (void) stop {
+	[self.timer invalidate];
+	self.timer = nil;
+}
+				  
+- (void) p_run:(NSTimer *) timer {
+	self.actionBlock(self);
+	self.lastRunDate = [NSDate date];
+}
+
 @end

File bmf/shared/base/BMFArrayProxy.h

 
 @class RACSignal;
 
-@interface BMFArrayProxy : NSProxy
+@interface BMFArrayProxy : NSProxy <BMFAspectProtocol
 #if TARGET_OS_IPHONE
- <BMFViewControllerBehaviorProtocol>
+,BMFViewControllerBehaviorProtocol>
+#else
+>
 #endif
 
-#if TARGET_OS_IPHONE
-@property (nonatomic, weak) UIViewController *viewController;
-#endif
+@property (nonatomic, weak) id object;
 
 @property (nonatomic, readonly) NSMutableSet *destinationObjects;
 

File bmf/shared/base/BMFArrayProxy.m

 	[(RACSubject *)self.destinationsSignal sendCompleted];
 }
 
-#if TARGET_OS_IPHONE
-- (void) setViewController:(UIViewController *)viewController {
+- (void) setObject:(id)object {
 	dispatch_async(self.serialQueue, ^{
 		for (id obj in self.destinationObjects) {
-			if ([obj respondsToSelector:@selector(setViewController:)]) {
+			if ([obj respondsToSelector:@selector(setObject:)]) {
 				dispatch_async(dispatch_get_main_queue(), ^{
-					[obj setViewController:viewController];
+					[obj setObject:object];
 				});
 			}
 		}
 	});
 	
-	_viewController = viewController;
+	_object = object;
 }
-#endif
 
 - (void) addDestinationObject:(id) object {
 	
 	BMFAssertReturn(object);
 		
-	
-	#if TARGET_OS_IPHONE
-
-	if ([object respondsToSelector:@selector(setViewController:)]) {
-		[object setViewController:self.viewController];
+	if (self.object && [object respondsToSelector:@selector(setObject:)]) {
+		[object setObject:self.object];
 	}
-	
-	#endif
 
 	__block NSSet *destinationsCopy = nil;
 	dispatch_sync(self.serialQueue, ^{

File bmf/shared/themes/BMFTheme.m

 	}
 }
 
-+ (NSString *) name {
++ (UIColor *) tintColor {
 	BMFAbstractMethod();
 }
 
++ (NSString *) name {
+	return @"default";
+}
+
 + (void) setupInitialAppearance {
-	BMFAbstractMethod();
+	[[UIApplication sharedApplication] keyWindow].tintColor = self.tintColor;
 }
 
 + (void) setupView:(id) view {

File bmf/shared/themes/BMFThemeProtocol.h

 
 @protocol BMFThemeProtocol <BMFRegistrableProtocol>
 
++ (UIColor *) tintColor;
+
 + (id<BMFThemeProtocol>) currentTheme;
 + (void) setCurrentTheme:(Class<BMFThemeProtocol>) theme;