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

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
  • Branches master

Comments (0)

Files changed (15)

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

Binary file modified.

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

View file
 		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

View file
 
 #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

View file
 #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

View file
 
 #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

View file
 //
 //
 
-#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

View file
 /*
 - (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

View file
 #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

View file
 	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

View file
 
 #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

View file
 
 #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

View file
 
 @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

View file
 	[(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

View file
 	}
 }
 
-+ (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

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