├── .acignore ├── BlocksKit.xcodeproj ├── .acignore └── xcshareddata │ ├── xcdebugger │ └── Breakpoints.xcbkptlist │ └── xcschemes │ ├── iOS Tests.xcscheme │ ├── OS X Tests.xcscheme │ ├── iOS Library.xcscheme │ └── OS X Framework.xcscheme ├── Tests ├── iOS Tests │ ├── Default.png │ ├── Default@2x.png │ ├── Default-568h@2x.png │ ├── PNDAppDelegate.h │ ├── main.m │ └── PNDAppDelegate.m ├── OS X Tests-Prefix.pch ├── iOS Tests-Prefix.pch ├── NSCacheBlocksKitTest.h ├── NSInvocationBlocksKitTest.h ├── NSURLConnectionBlocksKitTest.h ├── UIViewBlocksKitTest.h ├── UIControlBlocksKitTest.h ├── iOS Tests App-Prefix.pch ├── MFMailComposeViewControllerBlocksKitTest.h ├── NSMutableIndexSetBlocksKitTest.h ├── NSMutableSetBlocksKitTest.h ├── NSObjectBlocksKitTest.h ├── NSMutableArrayBlocksKitTest.h ├── UIAlertViewBlocksKitTest.h ├── UIWebViewBlocksKitTest.h ├── MFMessageComposeViewControllerBlocksKitTest.h ├── NSMutableDictionaryBlocksKitTest.h ├── NSMutableOrderedSetBlocksKitTest.h ├── NSObjectAssociatedObjectTest.h ├── NSTimerBlocksKitTest.h ├── NSObjectBlockObservationTest.h ├── UIActionSheetBlocksKitTest.h ├── NSIndexSetBlocksKitTest.h ├── NSDictionaryBlocksKitTest.h ├── NSSetBlocksKitTest.h ├── NSArrayBlocksKitTest.h ├── NSOrderedSetBlocksKitTest.h ├── iOS Tests-Info.plist ├── OS X Tests-Info.plist ├── NSInvocationBlocksKitTest.m ├── BKAsyncTestCase.h ├── NSCacheBlocksKitTest.m ├── A2BlockInvocationTests.h ├── NSURLConnectionBlocksKitTest.m ├── iOS Tests App-Info.plist ├── MFMailComposeViewControllerBlocksKitTest.m ├── UIControlBlocksKitTest.m ├── UIViewBlocksKitTest.m ├── MFMessageComposeViewControllerBlocksKitTest.m ├── NSObjectAssociatedObjectTest.m ├── NSObjectBlocksKitTest.m ├── NSTimerBlocksKitTest.m ├── NSMutableArrayBlocksKitTest.m ├── NSMutableSetBlocksKitTest.m ├── NSMutableIndexSetBlocksKitTest.m ├── NSMutableDictionaryBlocksKitTest.m ├── UIWebViewBlocksKitTest.m ├── UIAlertViewBlocksKitTest.m ├── NSMutableOrderedSetBlocksKitTest.m ├── A2BlockDelegateTests.h ├── UIActionSheetBlocksKitTest.m ├── NSObjectBlockObservationTest.m ├── NSDictionaryBlocksKitTest.m ├── NSIndexSetBlocksKitTest.m └── NSSetBlocksKitTest.m ├── .gitmodules ├── .gitignore ├── BlocksKit ├── NSMutableSet+BlocksKit.m ├── UIKit │ ├── UIPopoverController+BlocksKit.h │ ├── UIWebView+BlocksKit.h │ ├── UIBarButtonItem+BlocksKit.m │ ├── UIControl+BlocksKit.h │ ├── UIPopoverController+BlocksKit.m │ ├── UIBarButtonItem+BlocksKit.h │ ├── UIGestureRecognizer+BlocksKit.m │ ├── UIWebView+BlocksKit.m │ ├── UIControl+BlocksKit.m │ ├── UIView+BlocksKit.h │ ├── UIView+BlocksKit.m │ ├── UIGestureRecognizer+BlocksKit.h │ └── UIActionSheet+BlocksKit.h ├── NSInvocation+BlocksKit.h ├── NSMutableIndexSet+BlocksKit.m ├── BlocksKit-Info.plist ├── NSTimer+BlocksKit.m ├── MessageUI │ ├── MFMailComposeViewController+BlocksKit.h │ ├── MFMessageComposeViewController+BlocksKit.h │ ├── MFMessageComposeViewController+BlocksKit.m │ └── MFMailComposeViewController+BlocksKit.m ├── NSMutableArray+BlocksKit.m ├── NSMutableDictionary+BlocksKit.m ├── NSInvocation+BlocksKit.m ├── NSCache+BlocksKit.m ├── NSMutableIndexSet+BlocksKit.h ├── NSObject+BlocksKit.m ├── NSMutableOrderedSet+BlocksKit.m ├── NSMutableDictionary+BlocksKit.h ├── NSMutableSet+BlocksKit.h ├── NSMutableArray+BlocksKit.h ├── NSMutableOrderedSet+BlocksKit.h ├── NSObject+AssociatedObjects.m ├── NSCache+BlocksKit.h ├── NSTimer+BlocksKit.h ├── BKMacros.h ├── NSIndexSet+BlocksKit.m ├── BKGlobals.h ├── NSObject+A2DynamicDelegate.m ├── NSDictionary+BlocksKit.m ├── BlocksKit.h ├── NSSet+BlocksKit.m ├── NSObject+A2DynamicDelegate.h ├── NSObject+BlocksKit.h ├── NSArray+BlocksKit.m ├── NSOrderedSet+BlocksKit.m ├── NSIndexSet+BlocksKit.h ├── NSObject+AssociatedObjects.h ├── NSDictionary+BlocksKit.h ├── A2BlockDelegate.h └── NSSet+BlocksKit.h ├── LICENSE ├── BlocksKit.podspec └── README.md /.acignore: -------------------------------------------------------------------------------- 1 | .git 2 | .git/* 3 | .gitattributes 4 | .gitignore -------------------------------------------------------------------------------- /BlocksKit.xcodeproj/.acignore: -------------------------------------------------------------------------------- 1 | xcuserdata/* 2 | xcuserdata -------------------------------------------------------------------------------- /Tests/iOS Tests/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trulia/BlocksKit/master/Tests/iOS Tests/Default.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libffi"] 2 | path = libffi 3 | url = https://github.com/pandamonia/libffi-iOS.git 4 | -------------------------------------------------------------------------------- /Tests/iOS Tests/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trulia/BlocksKit/master/Tests/iOS Tests/Default@2x.png -------------------------------------------------------------------------------- /Tests/iOS Tests/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trulia/BlocksKit/master/Tests/iOS Tests/Default-568h@2x.png -------------------------------------------------------------------------------- /Tests/OS X Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'OS X Tests' target in the 'OS X Tests' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Tests/iOS Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'iOS Tests' target in the 'iOS Tests' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/* 3 | *.pbxuser 4 | *.swp 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | .DS_Store 18 | Notes.txt -------------------------------------------------------------------------------- /Tests/NSCacheBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSCacheBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSCacheBlocksKitTest : SenTestCase 10 | 11 | - (void)testDelegate; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Tests/NSInvocationBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSInvocationBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSInvocationBlocksKitTest : SenTestCase 10 | 11 | - (void)testBlockInvocation; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Tests/NSURLConnectionBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLConnectionBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | #import "BKAsyncTestCase.h" 9 | 10 | @interface NSURLConnectionBlocksKitTest : BKAsyncTestCase 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Tests/UIViewBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface UIViewBlocksKitTest : SenTestCase 10 | 11 | - (void)testOnTouchDown; 12 | - (void)testOnTouchMove; 13 | - (void)testOnTouchUp; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Tests/UIControlBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIControlBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface UIControlBlocksKitTest : SenTestCase 10 | 11 | - (void)testHasEventHandler; 12 | - (void)testInvokeEventHandler; 13 | - (void)testRemoveEventHandler; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Tests/iOS Tests/PNDAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // PNDAppDelegate.h 3 | // iOS Tests App 4 | // 5 | // Created by Alexsander Akers on 10/5/12. 6 | // Copyright (c) 2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PNDAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Tests/iOS Tests App-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'iOS Tests App' target in the 'iOS Tests App' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iOS SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /Tests/MFMailComposeViewControllerBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // MFMailComposeViewControllerBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface MFMailComposeViewControllerBlocksKitTest : SenTestCase 10 | 11 | - (void)testCompletionBlock; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Tests/NSMutableIndexSetBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableIndexSetBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSMutableIndexSetBlocksKitTest : SenTestCase 10 | 11 | - (void)testSelect; 12 | - (void)testSelectedNone; 13 | - (void)testReject; 14 | - (void)testRejectedNone; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/NSMutableSetBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableSetBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSMutableSetBlocksKitTest : SenTestCase 10 | 11 | - (void)testSelect; 12 | - (void)testSelectedNone; 13 | - (void)testReject; 14 | - (void)testRejectedAll; 15 | - (void)testMap; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Tests/NSObjectBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObjectBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | #import "BKAsyncTestCase.h" 9 | 10 | @interface NSObjectBlocksKitTest : BKAsyncTestCase 11 | 12 | - (void)testPerformBlockAfterDelay; 13 | - (void)testClassPerformBlockAfterDelay; 14 | - (void)testCancel; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/NSMutableArrayBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableArrayBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSMutableArrayBlocksKitTest : SenTestCase 10 | 11 | - (void)testSelect; 12 | - (void)testSelectedNone; 13 | - (void)testReject; 14 | - (void)testRejectedAll; 15 | - (void)testMap; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Tests/UIAlertViewBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertViewBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface UIAlertViewBlocksKitTest : SenTestCase 10 | 11 | - (void)testInit; 12 | - (void)testAddButtonWithHandler; 13 | - (void)testSetCancelButtonWithHandler; 14 | - (void)testDelegationBlocks; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/UIWebViewBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIWebViewBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface UIWebViewBlocksKitTest : SenTestCase 10 | 11 | - (void)testShouldStartLoad; 12 | - (void)testDidStartLoad; 13 | - (void)testDidFinishLoad; 14 | - (void)testDidFinishWithError; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/MFMessageComposeViewControllerBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // MFMessageComposeViewControllerBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface MFMessageComposeViewControllerBlocksKitTest : SenTestCase 10 | 11 | - (void)testCompletionBlock; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Tests/iOS Tests/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // iOS Tests App 4 | // 5 | // Created by Alexsander Akers on 10/5/12. 6 | // Copyright (c) 2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "PNDAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([PNDAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/NSMutableDictionaryBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableDictionaryBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSMutableDictionaryBlocksKitTest : SenTestCase 10 | 11 | - (void)testSelect; 12 | - (void)testSelectedNone; 13 | - (void)testReject; 14 | - (void)testRejectedAll; 15 | - (void)testMap; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Tests/NSMutableOrderedSetBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableIndexSetBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSMutableOrderedSetBlocksKitTest : SenTestCase 10 | 11 | - (void)testSelect; 12 | - (void)testSelectedNone; 13 | - (void)testReject; 14 | - (void)testRejectedAll; 15 | - (void)testMap; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Tests/NSObjectAssociatedObjectTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObjectAssociatedObjectTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSObjectAssociatedObjectTest : SenTestCase 10 | 11 | - (void)testAssociatedRetainValue; 12 | - (void)testAssociatedCopyValue; 13 | - (void)testAssociatedAssignValue; 14 | - (void)testAssociatedNotFound; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/NSTimerBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSTimerBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | #import "BKAsyncTestCase.h" 9 | 10 | @interface NSTimerBlocksKitTest : BKAsyncTestCase 11 | 12 | - (void)testScheduledTimer; 13 | - (void)testRepeatedlyScheduledTimer; 14 | - (void)testUnscheduledTimer; 15 | - (void)testRepeatableUnscheduledTimer; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /BlocksKit.xcodeproj/xcshareddata/xcdebugger/Breakpoints.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Tests/NSObjectBlockObservationTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObjectBlockObservationTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSObjectBlockObservationTest : SenTestCase 10 | 11 | - (void)testBoolKeyValueObservation; 12 | - (void)testNSNumberKeyValueObservation; 13 | - (void)testNSArrayKeyValueObservation; 14 | - (void)testNSSetKeyValueObservation; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/UIActionSheetBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIActionSheetBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface UIActionSheetBlocksKitTest : SenTestCase 10 | 11 | - (void)testInit; 12 | - (void)testAddButtonWithHandler; 13 | - (void)testSetDestructiveButtonWithHandler; 14 | - (void)testSetCancelButtonWithHandler; 15 | - (void)testDelegationBlocks; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Tests/NSIndexSetBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexSetBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSIndexSetBlocksKitTest : SenTestCase 10 | 11 | - (void)testEach; 12 | - (void)testMatch; 13 | - (void)testNotMatch; 14 | - (void)testSelect; 15 | - (void)testSelectedNone; 16 | - (void)testReject; 17 | - (void)testRejectedNone; 18 | - (void)testAny; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Tests/NSDictionaryBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionaryBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSDictionaryBlocksKitTest : SenTestCase 10 | 11 | - (void)testEach; 12 | - (void)testMatch; 13 | - (void)testSelect; 14 | - (void)testSelectedNone; 15 | - (void)testReject; 16 | - (void)testRejectedAll; 17 | - (void)testMap; 18 | - (void)testAny; 19 | - (void)testAll; 20 | - (void)testNone; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Tests/NSSetBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSSetBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSSetBlocksKitTest : SenTestCase 10 | 11 | - (void)testEach; 12 | - (void)testMatch; 13 | - (void)testNotMatch; 14 | - (void)testSelect; 15 | - (void)testSelectedNone; 16 | - (void)testReject; 17 | - (void)testRejectedAll; 18 | - (void)testMap; 19 | - (void)testReduceWithBlock; 20 | - (void)testAny; 21 | - (void)testAll; 22 | - (void)testNone; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Tests/NSArrayBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArrayBlocksKitTest.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface NSArrayBlocksKitTest : SenTestCase 10 | 11 | - (void)testEach; 12 | - (void)testMatch; 13 | - (void)testNotMatch; 14 | - (void)testSelect; 15 | - (void)testSelectedNone; 16 | - (void)testReject; 17 | - (void)testRejectedAll; 18 | - (void)testMap; 19 | - (void)testReduceWithBlock; 20 | - (void)testAny; 21 | - (void)testAll; 22 | - (void)testNone; 23 | - (void)testCorresponds; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Tests/NSOrderedSetBlocksKitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSOrderedSetBlocksKitTest.h 3 | // BlocksKit 4 | // 5 | // Created by Zachary Waldowski on 10/6/12. 6 | // Copyright (c) 2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface NSOrderedSetBlocksKitTest : SenTestCase 13 | 14 | - (void)testEach; 15 | - (void)testMatch; 16 | - (void)testNotMatch; 17 | - (void)testSelect; 18 | - (void)testSelectedNone; 19 | - (void)testReject; 20 | - (void)testRejectedAll; 21 | - (void)testMap; 22 | - (void)testReduceWithBlock; 23 | - (void)testAny; 24 | - (void)testAll; 25 | - (void)testNone; 26 | - (void)testCorresponds; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Tests/iOS Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | us.pandamonia.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/OS X Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | us.pandamonia.BlocksKit.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/NSInvocationBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSInvocationBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/5/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSInvocationBlocksKitTest.h" 10 | 11 | @implementation NSInvocationBlocksKitTest { 12 | NSInteger _total; 13 | } 14 | 15 | - (void)setUp { 16 | _total = 0; 17 | } 18 | 19 | - (void)action { 20 | _total += 1; 21 | } 22 | 23 | - (void)testBlockInvocation { 24 | BKSenderBlock senderBlock = ^(NSInvocationBlocksKitTest * sender) { 25 | [sender action]; 26 | }; 27 | NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:senderBlock]; 28 | STAssertNotNil(invocation, @"invocation is nil"); 29 | [invocation invoke]; 30 | STAssertEquals(_total, (NSInteger)1, @"total is %d", _total); 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Tests/BKAsyncTestCase.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKAsyncTestCase.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | 8 | typedef NS_ENUM(NSUInteger, SenTestCaseWaitStatus) { 9 | SenTestCaseWaitStatusUnknown = 0, 10 | SenTestCaseWaitStatusSuccess, 11 | SenTestCaseWaitStatusFailure, 12 | SenTestCaseWaitStatusCancelled 13 | }; 14 | 15 | @interface BKAsyncTestCase : SenTestCase 16 | 17 | @property (strong, nonatomic) NSArray *runLoopModes; 18 | 19 | - (void)prepare; 20 | - (void)prepare:(SEL)selector; 21 | - (void)waitForStatus:(NSInteger)status timeout:(NSTimeInterval)timeout; 22 | - (void)waitFor:(NSInteger)status timeout:(NSTimeInterval)timeout; 23 | - (void)waitForTimeout:(NSTimeInterval)timeout; 24 | - (void)notify:(NSInteger)status forSelector:(SEL)selector; 25 | - (void)notify:(NSInteger)status; 26 | - (void)runForInterval:(NSTimeInterval)interval; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /BlocksKit/NSMutableSet+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableSet+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSMutableSet+BlocksKit.h" 7 | 8 | @implementation NSMutableSet (BlocksKit) 9 | 10 | - (void)performSelect:(BKValidationBlock)block { 11 | NSParameterAssert(block != nil); 12 | 13 | NSSet *list = [self objectsPassingTest:^BOOL(id obj, BOOL *stop) { 14 | return block(obj); 15 | }]; 16 | 17 | [self setSet:list]; 18 | } 19 | 20 | - (void)performReject:(BKValidationBlock)block { 21 | [self performSelect:^BOOL(id obj) { 22 | return !block(obj); 23 | }]; 24 | } 25 | 26 | - (void)performMap:(BKTransformBlock)block { 27 | NSParameterAssert(block != nil); 28 | 29 | 30 | NSMutableSet *new = [NSMutableSet setWithCapacity:self.count]; 31 | 32 | [self enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { 33 | [new addObject:block(obj)]; 34 | }]; 35 | 36 | [self setSet:new]; 37 | } 38 | 39 | @end -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIPopoverController+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIPopoverController+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block functionality for UIPopoverController. 9 | 10 | Created by [Alexsander Akers](https://github.com/a2) and contributed to BlocksKit. 11 | 12 | @warning UIPopovercontroller is only available on a platform with UIKit. 13 | */ 14 | @interface UIPopoverController (BlocksKit) 15 | 16 | /** The block to be called when the popover controller will dismiss the popover. Return NO to prevent the dismissal of the view. */ 17 | @property (nonatomic, copy) BOOL (^shouldDismissBlock)(UIPopoverController *); 18 | 19 | /** The block to be called when the user has taken action to dismiss the popover. This is not called when -dismissPopoverAnimated: is called directly. */ 20 | @property (nonatomic, copy) void (^didDismissBlock)(UIPopoverController *); 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Tests/NSCacheBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSCacheBlocksKitTest.m 3 | // BlocksKit 4 | // 5 | // Created by Zachary Waldowski on 10/5/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSCacheBlocksKitTest.h" 10 | 11 | #define OBJECT_COUNT 300 12 | 13 | @implementation NSCacheBlocksKitTest { 14 | NSCache *_subject; 15 | NSInteger _total; 16 | } 17 | 18 | - (void)setUp { 19 | _subject = [NSCache new]; 20 | } 21 | 22 | - (void)tearDown { 23 | _subject = nil; 24 | } 25 | 26 | - (void)cache:(NSCache *)cache willEvictObject:(id)obj { 27 | _total--; 28 | } 29 | 30 | - (void)testDelegate { 31 | _subject.delegate = self; 32 | _total = 2; 33 | __unsafe_unretained NSCacheBlocksKitTest *weakSelf = self; 34 | _subject.willEvictBlock = ^(NSCache *cache, id obj){ 35 | weakSelf->_total--; 36 | }; 37 | [_subject.dynamicDelegate cache:_subject willEvictObject:nil]; 38 | STAssertEquals(_total, (NSInteger)0, @"The delegates should have been called!"); 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Tests/A2BlockInvocationTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // A2BlockInvocationTests.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface A2BlockInvocationTests : SenTestCase 10 | 11 | - (void)testVoidBlockInvocation; 12 | - (void)testReturnObjectBlockInvocation; 13 | - (void)testReturnStructBlockInvocation; 14 | - (void)testPassObjectBlockInvocation; 15 | - (void)testPassCharBlockInvocation; 16 | - (void)testPassUCharBlockInvocation; 17 | - (void)testPassShortBlockInvocation; 18 | - (void)testPassUShortBlockInvocation; 19 | - (void)testPassIntBlockInvocation; 20 | - (void)testPassUIntBlockInvocation; 21 | - (void)testPassLongBlockInvocation; 22 | - (void)testPassULongBlockInvocation; 23 | - (void)testPassLongLongBlockInvocation; 24 | - (void)testPassULongLongBlockInvocation; 25 | - (void)testPassFloatBlockInvocation; 26 | - (void)testPassDoubleBlockInvocation; 27 | - (void)testPassArrayBlockInvocation; 28 | - (void)testPassStructBlockInvocation; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /BlocksKit/NSInvocation+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSInvocation+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** BlocksKit extensions for NSInvocation. */ 9 | @interface NSInvocation (BlocksKit) 10 | 11 | /** Generates an `NSInvocation` instance for a given block. 12 | 13 | NSInvocation *invocation = [NSInvocation invocationWithTarget: target block: ^(id myObject){ 14 | [myObject someMethodWithArg:42.0]; 15 | }]; 16 | 17 | This returns an invocation with the appropriate target, selector, and arguments 18 | without creating the buffers yourself. It is only recommended to call a method 19 | on the argument to the block only once. 20 | 21 | Created by [Jonathan Rentzch](https://github.com/rentzsch) as 22 | `NSInvocation-blocks`. 23 | 24 | @param target The object to "grab" the block invocation from. 25 | @param block A code block. 26 | @return A fully-prepared instance of NSInvocation ready to be invoked. 27 | */ 28 | + (NSInvocation *)invocationWithTarget:(id)target block:(BKSenderBlock)block; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /BlocksKit/NSMutableIndexSet+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableIndexSet+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSMutableIndexSet+BlocksKit.h" 7 | 8 | @implementation NSMutableIndexSet (BlocksKit) 9 | 10 | - (void)performSelect:(BKIndexValidationBlock)block { 11 | NSParameterAssert(block != nil); 12 | 13 | 14 | NSIndexSet *list = [self indexesPassingTest:^BOOL(NSUInteger idx, BOOL *stop) { 15 | return !block(idx); 16 | }]; 17 | 18 | if (!list.count) 19 | return; 20 | 21 | [self removeIndexes:list]; 22 | } 23 | 24 | - (void)performReject:(BKIndexValidationBlock)block { 25 | return [self performSelect:^BOOL(NSUInteger idx) { 26 | return !block(idx); 27 | }]; 28 | } 29 | 30 | - (void)performMap:(BKIndexTransformBlock)block { 31 | NSParameterAssert(block != nil); 32 | 33 | NSMutableIndexSet *new = [self mutableCopy]; 34 | 35 | [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 36 | [new addIndex:block(idx)]; 37 | }]; 38 | 39 | [self removeAllIndexes]; 40 | [self addIndexes: new]; 41 | } 42 | 43 | @end -------------------------------------------------------------------------------- /BlocksKit/BlocksKit-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | us.pandamonia.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2011-2012 Pandamonia LLC. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /BlocksKit/NSTimer+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSTimer+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSTimer+BlocksKit.h" 7 | 8 | @interface NSTimer (BlocksKitPrivate) 9 | + (void)bk_executeBlockFromTimer:(NSTimer *)aTimer; 10 | @end 11 | 12 | @implementation NSTimer (BlocksKit) 13 | 14 | + (id)scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(BKTimerBlock)block repeats:(BOOL)inRepeats { 15 | NSParameterAssert(block); 16 | return [self scheduledTimerWithTimeInterval: inTimeInterval target: self selector: @selector(bk_executeBlockFromTimer:) userInfo: [block copy] repeats: inRepeats]; 17 | } 18 | 19 | + (id)timerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(BKTimerBlock)block repeats:(BOOL)inRepeats { 20 | NSParameterAssert(block); 21 | return [self timerWithTimeInterval: inTimeInterval target: self selector: @selector(bk_executeBlockFromTimer:) userInfo: [block copy] repeats: inRepeats]; 22 | } 23 | 24 | + (void)bk_executeBlockFromTimer:(NSTimer *)aTimer { 25 | NSTimeInterval time = [aTimer timeInterval]; 26 | BKTimerBlock block = [aTimer userInfo]; 27 | if (block) block(time); 28 | } 29 | 30 | @end -------------------------------------------------------------------------------- /BlocksKit/MessageUI/MFMailComposeViewController+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // MFMailComposeViewController+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** MFMailComposeViewController with block callbacks. 9 | 10 | If you provide a completion handler to an instance of 11 | MFMailComposeViewController but do not implement a delegate callback for 12 | mailComposeController:didFinishWithResult:error:, the mail compose view 13 | controller will automatically be dismissed if it was launched modally. 14 | 15 | Created by [Igor Evsukov](https://github.com/evsukov89) and contributed to 16 | BlocksKit. 17 | 18 | @warning UIWebView is only available on a platform with UIKit. 19 | */ 20 | @interface MFMailComposeViewController (BlocksKit) 21 | 22 | /** The block fired on the dismissal of the mail composition interface. 23 | 24 | This block callback is an analog for the 25 | mailComposeController:didFinishWithResult:error: method 26 | of MFMailComposeViewControllerDelegate. 27 | */ 28 | @property (nonatomic, copy) void(^completionBlock)(MFMailComposeViewController *, MFMailComposeResult, NSError *); 29 | 30 | @end -------------------------------------------------------------------------------- /Tests/NSURLConnectionBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLConnectionBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Zachary Waldowski on 12/20/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSURLConnectionBlocksKitTest.h" 10 | 11 | @implementation NSURLConnectionBlocksKitTest 12 | 13 | - (void)testAsyncConnection { 14 | [self prepare]; 15 | NSURL *URL = [NSURL URLWithString:@"http://google.com/"]; 16 | NSURLRequest *request = [NSURLRequest requestWithURL:URL]; 17 | NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest: request]; 18 | conn.successBlock = ^(NSURLConnection *connection, NSURLResponse *response, NSData *data) { 19 | [self notify:data.length ? SenTestCaseWaitStatusSuccess : SenTestCaseWaitStatusFailure forSelector: @selector(testAsyncConnection)]; 20 | }; 21 | conn.failureBlock = ^(NSURLConnection *connection, NSError *err) { 22 | [self notify: SenTestCaseWaitStatusFailure forSelector: @selector(testAsyncConnection)]; 23 | }; 24 | [conn start]; 25 | [self waitForStatus: SenTestCaseWaitStatusSuccess timeout:10.0]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /BlocksKit/NSMutableArray+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableArray+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSMutableArray+BlocksKit.h" 7 | 8 | @implementation NSMutableArray (BlocksKit) 9 | 10 | - (void)performSelect:(BKValidationBlock)block { 11 | NSParameterAssert(block != nil); 12 | 13 | NSIndexSet *list = [self indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { 14 | return !block(obj); 15 | }]; 16 | 17 | if (!list.count) 18 | return; 19 | 20 | [self removeObjectsAtIndexes:list]; 21 | } 22 | 23 | - (void)performReject:(BKValidationBlock)block { 24 | return [self performSelect:^BOOL(id obj) { 25 | return !block(obj); 26 | }]; 27 | } 28 | 29 | - (void)performMap:(BKTransformBlock)block { 30 | NSParameterAssert(block != nil); 31 | 32 | NSMutableArray *new = [self mutableCopy]; 33 | 34 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 35 | id value = block(obj); 36 | 37 | if (!value) 38 | value = [NSNull null]; 39 | 40 | if ([value isEqual:obj]) 41 | return; 42 | 43 | new[idx] = value; 44 | }]; 45 | 46 | [self setArray: new]; 47 | } 48 | 49 | @end -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIWebView+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIWebView+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block callbacks for UIWebView. 9 | 10 | @warning UIWebView is only available on a platform with UIKit. 11 | */ 12 | 13 | @interface UIWebView (BlocksKit) 14 | 15 | /** The block to be decide whether a URL will be loaded. 16 | 17 | @warning If the delegate implements webView:shouldStartLoadWithRequest:navigationType:, 18 | the return values of both the delegate method and the block will be considered. 19 | */ 20 | @property (nonatomic, copy) BOOL(^shouldStartLoadBlock)(UIWebView *, NSURLRequest *, UIWebViewNavigationType); 21 | 22 | /** The block that is fired when the web view starts loading. */ 23 | @property (nonatomic, copy) void(^didStartLoadBlock)(UIWebView *); 24 | 25 | /** The block that is fired when the web view finishes loading. */ 26 | @property (nonatomic, copy) void(^didFinishLoadBlock)(UIWebView *); 27 | 28 | /** The block that is fired when the web view stops loading due to an error. */ 29 | @property (nonatomic, copy) void(^didFinishWithErrorBlock)(UIWebView *, NSError *); 30 | 31 | @end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // 2 | // LICENSE 3 | // BlocksKit 4 | // 5 | 6 | Copyright (c) 2011-2012 Zachary Waldowski, Alexsander Akers, and the BlocksKit Contributors 7 | 8 | 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: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | 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. -------------------------------------------------------------------------------- /BlocksKit/MessageUI/MFMessageComposeViewController+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // MFMessageComposeViewController+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** MFMessageComposeViewController with block callback in addition to delegation. 9 | 10 | If you provide a completion handler to an instance of 11 | MFMessageComposeViewController but do not implement a delegate callback for 12 | messageComposeViewController:didFinishWithResult:error:, the message compose 13 | view controller will automatically be dismissed if it was launched modally. 14 | 15 | Created by [Igor Evsukov](https://github.com/evsukov89) and contributed to 16 | BlocksKit. 17 | 18 | @warning UIWebView is only available on a platform with UIKit. 19 | */ 20 | @interface MFMessageComposeViewController (BlocksKit) 21 | 22 | /** The block fired on the dismissal of the SMS composition interface. 23 | 24 | This block callback is an analog for the 25 | messageComposeViewController:didFinishWithResult: method 26 | of MFMessageComposeViewControllerDelegate. 27 | */ 28 | @property (nonatomic, copy) void(^completionBlock)(MFMessageComposeViewController *, MessageComposeResult); 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /BlocksKit/NSMutableDictionary+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableDictionary+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSMutableDictionary+BlocksKit.h" 7 | 8 | @implementation NSMutableDictionary (BlocksKit) 9 | 10 | - (void)performSelect:(BKKeyValueValidationBlock)block { 11 | NSParameterAssert(block != nil); 12 | 13 | NSArray *keys = [[self keysOfEntriesWithOptions:NSEnumerationConcurrent passingTest:^BOOL(id key, id obj, BOOL *stop) { 14 | return !block(key, obj); 15 | }] allObjects]; 16 | 17 | [self removeObjectsForKeys:keys]; 18 | } 19 | 20 | - (void)performReject:(BKKeyValueValidationBlock)block { 21 | [self performSelect:^BOOL(id key, id obj) { 22 | return !block(key, obj); 23 | }]; 24 | } 25 | 26 | - (void)performMap:(BKKeyValueTransformBlock)block { 27 | NSParameterAssert(block != nil); 28 | 29 | NSMutableDictionary *new = [self mutableCopy]; 30 | 31 | [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 32 | id value = block(key, obj); 33 | 34 | if (!value) 35 | value = [NSNull null]; 36 | 37 | if ([value isEqual:obj]) 38 | return; 39 | 40 | new[key] = value; 41 | }]; 42 | 43 | [self setDictionary: new]; 44 | } 45 | 46 | @end -------------------------------------------------------------------------------- /BlocksKit/NSInvocation+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSInvocation+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSInvocation+BlocksKit.h" 7 | 8 | @interface BKInvocationGrabber : NSProxy 9 | 10 | + (BKInvocationGrabber *)grabberWithTarget:(id)target; 11 | 12 | @property (nonatomic, strong) id target; 13 | @property (nonatomic, strong) NSInvocation *invocation; 14 | 15 | @end 16 | 17 | @implementation BKInvocationGrabber 18 | 19 | + (BKInvocationGrabber *)grabberWithTarget:(id)target { 20 | BKInvocationGrabber *instance = [BKInvocationGrabber alloc]; 21 | instance.target = target; 22 | return instance; 23 | } 24 | 25 | - (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { 26 | return [self.target methodSignatureForSelector: selector]; 27 | } 28 | 29 | - (void)forwardInvocation:(NSInvocation*)invocation { 30 | [invocation setTarget: self.target]; 31 | self.invocation = invocation; 32 | } 33 | 34 | @end 35 | 36 | 37 | @implementation NSInvocation (BlocksKit) 38 | 39 | + (NSInvocation *)invocationWithTarget:(id)target block:(BKSenderBlock)block { 40 | NSParameterAssert(block != nil); 41 | BKInvocationGrabber *grabber = [BKInvocationGrabber grabberWithTarget:target]; 42 | block(grabber); 43 | return grabber.invocation; 44 | } 45 | 46 | @end -------------------------------------------------------------------------------- /BlocksKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'BlocksKit' 3 | s.version = '1.8.1' 4 | s.license = 'MIT' 5 | s.summary = 'The Objective-C block utilities you always wish you had.' 6 | s.homepage = 'https://github.com/pandamonia/BlocksKit' 7 | s.author = { 'Zachary Waldowski' => 'zwaldowski@gmail.com', 8 | 'Alexsander Akers' => 'a2@pandamonia.us' } 9 | s.source = { :git => 'https://github.com/pandamonia/BlocksKit.git', :branch => 'next' } 10 | s.requires_arc = true 11 | s.osx.source_files = 'BlocksKit/*.{h,m}' 12 | s.osx.library = 'ffi' 13 | s.osx.deployment_target = '10.7' 14 | s.ios.dependency 'libffi' 15 | s.ios.frameworks = 'MessageUI' 16 | s.ios.source_files = 'BlocksKit/*.{h,m}', 'BlocksKit/UIKit/*.{h,m}', 'BlocksKit/MessageUI/*.{h,m}' 17 | s.ios.deployment_target = '5.0' 18 | s.documentation = { 19 | :html => 'http://pandamonia.github.com/BlocksKit/Documentation/index.html', 20 | :appledoc => [ 21 | '--project-company', 'Pandamonia LLC', 22 | '--company-id', 'us.pandamonia', 23 | '--no-repeat-first-par', 24 | '--no-warn-invalid-crossref' 25 | ] 26 | } 27 | end 28 | -------------------------------------------------------------------------------- /BlocksKit/NSCache+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSCache+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSCache+BlocksKit.h" 7 | 8 | #pragma mark Custom delegate 9 | 10 | @interface A2DynamicNSCacheDelegate : A2DynamicDelegate 11 | 12 | @end 13 | 14 | @implementation A2DynamicNSCacheDelegate 15 | 16 | - (void)cache:(NSCache *)cache willEvictObject:(id)obj { 17 | id realDelegate = self.realDelegate; 18 | if (realDelegate && [realDelegate respondsToSelector:@selector(cache:willEvictObject:)]) 19 | [realDelegate cache:cache willEvictObject:obj]; 20 | 21 | void (^orig)(NSCache *, id) = [self blockImplementationForMethod:_cmd]; 22 | if (orig) 23 | orig(cache, obj); 24 | } 25 | 26 | @end 27 | 28 | #pragma mark Category 29 | 30 | @implementation NSCache (BlocksKit) 31 | 32 | @dynamic willEvictBlock; 33 | 34 | + (void)load { 35 | @autoreleasepool { 36 | [self registerDynamicDelegate]; 37 | [self linkDelegateMethods: @{ @"willEvictBlock": @"cache:willEvictObject:" }]; 38 | } 39 | } 40 | 41 | #pragma mark Methods 42 | 43 | - (id)objectForKey:(id)key withGetter:(BKReturnBlock)block { 44 | id object = [self objectForKey:key]; 45 | if (object) 46 | return object; 47 | 48 | if (block) { 49 | object = block(); 50 | [self setObject:object forKey:key]; 51 | } 52 | 53 | return object; 54 | } 55 | 56 | @end -------------------------------------------------------------------------------- /Tests/iOS Tests App-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | us.pandamonia.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /BlocksKit/NSMutableIndexSet+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableIndexSet+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block extensions for NSMutableIndexSet. 9 | 10 | These utilities expound upon the BlocksKit additions to the immutable 11 | superclass by allowing certain utilities to work on an instance of the mutable 12 | class, saving memory by not creating an immutable copy of the results. 13 | 14 | @see NSIndexSet(BlocksKit) 15 | */ 16 | @interface NSMutableIndexSet (BlocksKit) 17 | 18 | /** Filters a mutable index set to the indexes matching the block. 19 | 20 | @param block A single-argument, BOOL-returning code block. 21 | @see reject: 22 | */ 23 | - (void)performSelect:(BKIndexValidationBlock)block; 24 | 25 | /** Filters a mutable index set to all indexes but the ones matching the block, 26 | the logical inverse to select:. 27 | 28 | @param block A single-argument, BOOL-returning code block. 29 | @see select: 30 | */ 31 | - (void)performReject:(BKIndexValidationBlock)block; 32 | 33 | /** Transform each index of the index set to a new index, as returned by the 34 | block. 35 | 36 | @param block A block that returns a new index for a index. 37 | @see map: 38 | */ 39 | - (void)performMap:(BKIndexTransformBlock)block; 40 | 41 | 42 | @end -------------------------------------------------------------------------------- /Tests/MFMailComposeViewControllerBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // MFMailComposeViewControllerBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Zachary Waldowski on 12/20/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "MFMailComposeViewControllerBlocksKitTest.h" 10 | 11 | @implementation MFMailComposeViewControllerBlocksKitTest { 12 | MFMailComposeViewController *_subject; 13 | BOOL delegateWorked; 14 | } 15 | 16 | - (void)setUp { 17 | _subject = [MFMailComposeViewController new]; 18 | } 19 | 20 | - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { 21 | delegateWorked = YES; 22 | } 23 | 24 | - (void)testCompletionBlock { 25 | delegateWorked = NO; 26 | __block BOOL blockWorked = NO; 27 | _subject.mailComposeDelegate = self; 28 | _subject.completionBlock = ^(MFMailComposeViewController *controller, MFMailComposeResult result, NSError *err){ 29 | blockWorked = YES; 30 | }; 31 | [[_subject dynamicDelegateForProtocol:@protocol(MFMailComposeViewControllerDelegate)] mailComposeController:_subject didFinishWithResult:MFMailComposeResultSent error:nil]; 32 | STAssertTrue(delegateWorked, @"Delegate method not called."); 33 | STAssertTrue(blockWorked, @"Block handler not called."); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Tests/UIControlBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIControlBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Zachary Waldowski on 12/20/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "UIControlBlocksKitTest.h" 10 | 11 | @implementation UIControlBlocksKitTest { 12 | UIControl *_subject; 13 | NSInteger _total; 14 | } 15 | 16 | - (void)setUp { 17 | _subject = [UIControl new]; 18 | _total = 0; 19 | 20 | __unsafe_unretained UIControlBlocksKitTest *weakSelf = self; 21 | [_subject addEventHandler:^(id sender) { 22 | weakSelf->_total++; 23 | } forControlEvents:UIControlEventTouchUpInside]; 24 | } 25 | 26 | - (void)testHasEventHandler { 27 | BOOL hasHandler = [_subject hasEventHandlersForControlEvents:UIControlEventTouchUpInside]; 28 | STAssertTrue(hasHandler, @"Control doesn't have the handler."); 29 | } 30 | 31 | - (void)testInvokeEventHandler { 32 | [_subject sendActionsForControlEvents:UIControlEventTouchUpInside]; 33 | STAssertEquals(_total, (NSInteger)1, @"Event handler did not get called."); 34 | } 35 | 36 | - (void)testRemoveEventHandler { 37 | [_subject removeEventHandlersForControlEvents:UIControlEventTouchUpInside]; 38 | [_subject sendActionsForControlEvents:UIControlEventTouchUpInside]; 39 | STAssertEquals(_total, (NSInteger)0, @"Event handler still called."); 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Tests/UIViewBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewBlocksKitTest.m 3 | // BlocksKit 4 | // 5 | // Created by Zachary Waldowski on 12/20/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "UIViewBlocksKitTest.h" 10 | 11 | @implementation UIViewBlocksKitTest { 12 | UIView *_subject; 13 | } 14 | 15 | - (void)setUp { 16 | _subject = [UIView new]; 17 | } 18 | 19 | - (void)testOnTouchDown { 20 | __block BOOL onTouchDownBlock = NO; 21 | _subject.onTouchDownBlock = ^(NSSet *touches, UIEvent *event) { 22 | onTouchDownBlock = YES; 23 | }; 24 | 25 | [_subject touchesBegan:[NSSet set] withEvent:nil]; 26 | 27 | STAssertTrue(onTouchDownBlock, @"Block handler was not called"); 28 | } 29 | 30 | - (void)testOnTouchMove { 31 | __block BOOL onTouchMoveBlock = NO; 32 | _subject.onTouchMoveBlock = ^(NSSet *touches, UIEvent *event) { 33 | onTouchMoveBlock = YES; 34 | }; 35 | 36 | [_subject touchesMoved:[NSSet set] withEvent:nil]; 37 | 38 | STAssertTrue(onTouchMoveBlock, @"Block handler was not called"); 39 | } 40 | 41 | - (void)testOnTouchUp { 42 | __block BOOL onTouchUpBlock = NO; 43 | _subject.onTouchUpBlock = ^(NSSet *touches, UIEvent *event) { 44 | onTouchUpBlock = YES; 45 | }; 46 | 47 | [_subject touchesEnded:[NSSet set] withEvent:nil]; 48 | 49 | STAssertTrue(onTouchUpBlock, @"Block handler was not called"); 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /BlocksKit/NSObject+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSObject+BlocksKit.h" 7 | #import 8 | 9 | typedef void(^BKInternalWrappingBlock)(BOOL); 10 | 11 | #define BKTimeDelay(t) dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(NSEC_PER_SEC * t)) 12 | 13 | @implementation NSObject (BlocksKit) 14 | 15 | - (id)performBlock:(BKSenderBlock)block afterDelay:(NSTimeInterval)delay { 16 | NSParameterAssert(block != nil); 17 | 18 | __block BOOL cancelled = NO; 19 | 20 | void(^wrapper)(BOOL) = ^(BOOL cancel) { 21 | if (cancel) { 22 | cancelled = YES; 23 | return; 24 | } 25 | if (!cancelled) block(self); 26 | }; 27 | 28 | dispatch_after(BKTimeDelay(delay), dispatch_get_main_queue(), ^{ 29 | wrapper(NO); 30 | }); 31 | 32 | return [wrapper copy]; 33 | } 34 | 35 | + (id)performBlock:(BKBlock)block afterDelay:(NSTimeInterval)delay { 36 | NSParameterAssert(block != nil); 37 | 38 | __block BOOL cancelled = NO; 39 | 40 | void(^wrapper)(BOOL) = ^(BOOL cancel) { 41 | if (cancel) { 42 | cancelled = YES; 43 | return; 44 | } 45 | if (!cancelled) block(); 46 | }; 47 | 48 | dispatch_after(BKTimeDelay(delay), dispatch_get_main_queue(), ^{ wrapper(NO); }); 49 | 50 | return [wrapper copy]; 51 | } 52 | 53 | + (void)cancelBlock:(id)block { 54 | NSParameterAssert(block != nil); 55 | void(^wrapper)(BOOL) = block; 56 | wrapper(YES); 57 | } 58 | 59 | @end -------------------------------------------------------------------------------- /Tests/MFMessageComposeViewControllerBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // MFMessageComposeViewControllerBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Zachary Waldowski on 12/20/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "MFMessageComposeViewControllerBlocksKitTest.h" 10 | 11 | @implementation MFMessageComposeViewControllerBlocksKitTest { 12 | MFMessageComposeViewController *_subject; 13 | BOOL delegateWorked; 14 | } 15 | 16 | - (void)setUp { 17 | _subject = [MFMessageComposeViewController new]; 18 | } 19 | 20 | - (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result { 21 | delegateWorked = YES; 22 | } 23 | 24 | - (void)testCompletionBlock { 25 | if (![MFMessageComposeViewController canSendText]) 26 | return; 27 | 28 | delegateWorked = NO; 29 | __block BOOL blockWorked = NO; 30 | _subject.messageComposeDelegate = self; 31 | _subject.completionBlock = ^(MFMessageComposeViewController *controller, MessageComposeResult result){ 32 | blockWorked = YES; 33 | }; 34 | [[_subject dynamicDelegateForProtocol:@protocol(MFMessageComposeViewControllerDelegate)] messageComposeViewController:_subject didFinishWithResult:MessageComposeResultSent]; 35 | STAssertTrue(delegateWorked, @"Delegate method not called."); 36 | STAssertTrue(blockWorked, @"Block handler not called."); 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /BlocksKit/NSMutableOrderedSet+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableOrderedSet+BlocksKit.m 3 | // BlocksKit 4 | // 5 | // Created by Zachary Waldowski on 10/6/12. 6 | // Copyright (c) 2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSMutableOrderedSet+BlocksKit.h" 10 | 11 | @implementation NSMutableOrderedSet (BlocksKit) 12 | 13 | - (void)performSelect:(BKValidationBlock)block { 14 | NSParameterAssert(block != nil); 15 | 16 | NSIndexSet *list = [self indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { 17 | return !block(obj); 18 | }]; 19 | 20 | if (!list.count) 21 | return; 22 | 23 | [self removeObjectsAtIndexes:list]; 24 | } 25 | 26 | - (void)performReject:(BKValidationBlock)block { 27 | return [self performSelect:^BOOL(id obj) { 28 | return !block(obj); 29 | }]; 30 | } 31 | 32 | - (void)performMap:(BKTransformBlock)block { 33 | NSParameterAssert(block != nil); 34 | 35 | NSMutableIndexSet *newIndexes = [NSMutableIndexSet indexSet]; 36 | NSMutableArray *newObjects = [NSMutableArray arrayWithCapacity: self.count]; 37 | 38 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 39 | id value = block(obj); 40 | 41 | if (!value) 42 | value = [NSNull null]; 43 | 44 | if ([value isEqual:obj]) 45 | return; 46 | 47 | [newIndexes addIndex: idx]; 48 | [newObjects addObject: obj]; 49 | }]; 50 | 51 | [self replaceObjectsAtIndexes: newIndexes withObjects: newObjects]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIBarButtonItem+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIBarButtonItem+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "UIBarButtonItem+BlocksKit.h" 7 | #import "NSObject+AssociatedObjects.h" 8 | 9 | static char kBarButtonItemBlockKey; 10 | 11 | @interface UIBarButtonItem (BlocksKitPrivate) 12 | - (void)_handleAction:(UIBarButtonItem *)sender; 13 | @end 14 | 15 | @implementation UIBarButtonItem (BlocksKit) 16 | 17 | - (id)initWithBarButtonSystemItem:(UIBarButtonSystemItem)systemItem handler:(BKSenderBlock)action { 18 | self = [self initWithBarButtonSystemItem:systemItem target:self action:@selector(_handleAction:)]; 19 | [self associateCopyOfValue:action withKey:&kBarButtonItemBlockKey]; 20 | return self; 21 | } 22 | 23 | - (id)initWithImage:(UIImage *)image style:(UIBarButtonItemStyle)style handler:(BKSenderBlock)action { 24 | self = [self initWithImage:image style:style target:self action:@selector(_handleAction:)]; 25 | [self associateCopyOfValue:action withKey:&kBarButtonItemBlockKey]; 26 | return self; 27 | } 28 | 29 | - (id)initWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style handler:(BKSenderBlock)action { 30 | self = [self initWithTitle:title style:style target:self action:@selector(_handleAction:)]; 31 | [self associateCopyOfValue:action withKey:&kBarButtonItemBlockKey]; 32 | return self; 33 | } 34 | 35 | - (void)_handleAction:(UIBarButtonItem *)sender { 36 | BKSenderBlock block = [self associatedValueForKey:&kBarButtonItemBlockKey]; 37 | if (block) 38 | block(self); 39 | } 40 | 41 | @end -------------------------------------------------------------------------------- /BlocksKit/NSMutableDictionary+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableDictionary+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block extensions for NSMutableDictionary. 9 | 10 | These utilities expound upon the BlocksKit additions to the immutable 11 | superclass by allowing certain utilities to work on an instance of the mutable 12 | class, saving memory by not creating an immutable copy of the results. 13 | 14 | Includes code by the following: 15 | 16 | - [Martin Schürrer](https://github.com/MSch) 17 | - [Zach Waldowski](https://github.com/zwaldowski) 18 | 19 | @see NSDictionary(BlocksKit) 20 | */ 21 | @interface NSMutableDictionary (BlocksKit) 22 | 23 | /** Filters a mutable dictionary to the key/value pairs matching the block. 24 | 25 | @param block A BOOL-returning code block for a key/value pair. 26 | @see reject: 27 | */ 28 | - (void)performSelect:(BKKeyValueValidationBlock)block; 29 | 30 | /** Filters a mutable dictionary to the key/value pairs not matching the block, 31 | the logical inverse to select:. 32 | 33 | @param block A BOOL-returning code block for a key/value pair. 34 | @see select: 35 | */ 36 | - (void)performReject:(BKKeyValueValidationBlock)block; 37 | 38 | /** Transform each value of the dictionary to a new value, as returned by the 39 | block. 40 | 41 | @param block A block that returns a new value for a given key/value pair. 42 | @see map: 43 | */ 44 | - (void)performMap:(BKKeyValueTransformBlock)block; 45 | 46 | @end -------------------------------------------------------------------------------- /BlocksKit/NSMutableSet+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableSet+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block extensions for NSMutableSet. 9 | 10 | These utilities expound upon the BlocksKit additions to the immutable 11 | superclass by allowing certain utilities to work on an instance of the mutable 12 | class, saving memory by not creating an immutable copy of the results. 13 | 14 | Includes code by the following: 15 | 16 | - [Martin Schürrer](https://github.com/MSch) 17 | - [Zach Waldowski](https://github.com/zwaldowski) 18 | 19 | @see NSSet(BlocksKit) 20 | */ 21 | @interface NSMutableSet (BlocksKit) 22 | 23 | /** Filters a mutable set to the objects matching the block. 24 | 25 | @param block A single-argument, BOOL-returning code block. 26 | @see reject: 27 | */ 28 | - (void)performSelect:(BKValidationBlock)block; 29 | 30 | /** Filters a mutable set to all objects but the ones matching the block, 31 | the logical inverse to select:. 32 | 33 | @param block A single-argument, BOOL-returning code block. 34 | @see select: 35 | */ 36 | - (void)performReject:(BKValidationBlock)block; 37 | 38 | /** Transform the objects in the set to the results of the block. 39 | 40 | This is sometimes referred to as a transform, mutating one of each object: 41 | [controllers map:^id(id obj) { 42 | return [obj view]; 43 | }]; 44 | 45 | @param block A single-argument, object-returning code block. 46 | @see map: 47 | */ 48 | - (void)performMap:(BKTransformBlock)block; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /BlocksKit/NSMutableArray+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableArray+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block extensions for NSMutableArray. 9 | 10 | These utilities expound upon the BlocksKit additions to the immutable 11 | superclass by allowing certain utilities to work on an instance of the mutable 12 | class, saving memory by not creating an immutable copy of the results. 13 | 14 | Includes code by the following: 15 | 16 | - [Martin Schürrer](https://github.com/MSch) 17 | - [Zach Waldowski](https://github.com/zwaldowski) 18 | 19 | @see NSArray(BlocksKit) 20 | */ 21 | @interface NSMutableArray (BlocksKit) 22 | 23 | /** Filters a mutable array to the objects matching the block. 24 | 25 | @param block A single-argument, BOOL-returning code block. 26 | @see reject: 27 | */ 28 | - (void)performSelect:(BKValidationBlock)block; 29 | 30 | /** Filters a mutable array to all objects but the ones matching the block, 31 | the logical inverse to select:. 32 | 33 | @param block A single-argument, BOOL-returning code block. 34 | @see select: 35 | */ 36 | - (void)performReject:(BKValidationBlock)block; 37 | 38 | /** Transform the objects in the array to the results of the block. 39 | 40 | This is sometimes referred to as a transform, mutating one of each object: 41 | [foo performMap:^id(id obj) { 42 | return [dateTransformer dateFromString:obj]; 43 | }]; 44 | 45 | @param block A single-argument, object-returning code block. 46 | @see map: 47 | */ 48 | - (void)performMap:(BKTransformBlock)block; 49 | 50 | @end -------------------------------------------------------------------------------- /BlocksKit/NSMutableOrderedSet+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableOrderedSet+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block extensions for NSMutableOrderedSet. 9 | 10 | These utilities expound upon the BlocksKit additions to the immutable 11 | superclass by allowing certain utilities to work on an instance of the mutable 12 | class, saving memory by not creating an immutable copy of the results. 13 | 14 | Includes code by the following: 15 | 16 | - [Martin Schürrer](https://github.com/MSch) 17 | - [Zach Waldowski](https://github.com/zwaldowski) 18 | 19 | @see NSOrderedSet(BlocksKit) 20 | */ 21 | @interface NSMutableOrderedSet (BlocksKit) 22 | 23 | /** Filters a mutable ordered set to the objects matching the block. 24 | 25 | @param block A single-argument, BOOL-returning code block. 26 | @see reject: 27 | */ 28 | - (void)performSelect:(BKValidationBlock)block; 29 | 30 | /** Filters a mutable ordered set to all objects but the ones matching the 31 | block, the logical inverse to select:. 32 | 33 | @param block A single-argument, BOOL-returning code block. 34 | @see select: 35 | */ 36 | - (void)performReject:(BKValidationBlock)block; 37 | 38 | /** Transform the objects in the ordered set to the results of the block. 39 | 40 | This is sometimes referred to as a transform, mutating one of each object: 41 | [foo performMap:^id(id obj) { 42 | return [dateTransformer dateFromString:obj]; 43 | }]; 44 | 45 | @param block A single-argument, object-returning code block. 46 | @see map: 47 | */ 48 | - (void)performMap:(BKTransformBlock)block; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /BlocksKit/NSObject+AssociatedObjects.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+AssociatedObjects.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSObject+AssociatedObjects.h" 7 | #import 8 | 9 | @implementation NSObject (BKAssociatedObjects) 10 | 11 | #pragma mark - Instance Methods 12 | 13 | - (void)associateValue:(id)value withKey:(const void *)key { 14 | objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 15 | } 16 | 17 | - (void)associateCopyOfValue:(id)value withKey:(const void *)key { 18 | objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_COPY_NONATOMIC); 19 | } 20 | 21 | - (void)weaklyAssociateValue:(id)value withKey:(const void *)key { 22 | objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_ASSIGN); 23 | } 24 | 25 | - (id)associatedValueForKey:(const void *)key { 26 | return objc_getAssociatedObject(self, key); 27 | } 28 | 29 | - (void)removeAllAssociatedObjects { 30 | objc_removeAssociatedObjects(self); 31 | } 32 | 33 | #pragma mark - Class Methods 34 | 35 | + (void)associateValue:(id)value withKey:(const void *)key { 36 | objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 37 | } 38 | 39 | + (void)associateCopyOfValue:(id)value withKey:(const void *)key { 40 | objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_COPY_NONATOMIC); 41 | } 42 | 43 | + (void)weaklyAssociateValue:(id)value withKey:(const void *)key { 44 | objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_ASSIGN); 45 | } 46 | 47 | + (id)associatedValueForKey:(const void *)key { 48 | return objc_getAssociatedObject(self, key); 49 | } 50 | 51 | + (void)removeAllAssociatedObjects { 52 | objc_removeAssociatedObjects(self); 53 | } 54 | 55 | @end -------------------------------------------------------------------------------- /BlocksKit/NSCache+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSCache+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** NSCache with block adding of objects 9 | 10 | This category allows you to conditionally add objects to 11 | an instance of NSCache using blocks. Both the normal 12 | delegation pattern and a block callback for NSCache's one 13 | delegate method are allowed. 14 | 15 | These methods emulate Rails caching behavior. 16 | 17 | Created by [Igor Evsukov](https://github.com/evsukov89) and contributed to BlocksKit. 18 | */ 19 | 20 | @interface NSCache (BlocksKit) 21 | 22 | /** Returns the value associated with a given key. If there is no 23 | object for that key, it uses the result of the block, saves 24 | that to the cache, and returns it. 25 | 26 | This mimics the cache behavior of Ruby on Rails. The following code: 27 | 28 | @products = Rails.cache.fetch('products') do 29 | Product.all 30 | end 31 | 32 | becomes: 33 | 34 | NSMutableArray *products = [cache objectForKey:@"products" withGetter:^id{ 35 | return [Product all]; 36 | }]; 37 | 38 | @return The value associated with *key*, or the object returned 39 | by the block if no value is associated with *key*. 40 | @param key An object identifying the value. 41 | @param getterBlock A block used to get an object if there is no 42 | value in the cache. 43 | */ 44 | - (id)objectForKey:(id)key withGetter:(BKReturnBlock)getterBlock; 45 | 46 | /** Called when an object is about to be evicted from the cache. 47 | 48 | This block callback is an analog for the cache:willEviceObject: 49 | method of NSCacheDelegate. 50 | */ 51 | @property (nonatomic, copy) void(^willEvictBlock)(NSCache *, id); 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIControl+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block control event handling for UIControl. 9 | 10 | Includes code by the following: 11 | 12 | - [Kevin O'Neill](https://github.com/kevinoneill) 13 | - [Zach Waldowski](https://github.com/zwaldowski) 14 | 15 | @warning UIControl is only available on a platform with UIKit. 16 | */ 17 | @interface UIControl (BlocksKit) 18 | 19 | ///----------------------------------- 20 | /// @name Block event handling 21 | ///----------------------------------- 22 | 23 | /** Adds a block for a particular event to an internal dispatch table. 24 | 25 | @param handler A block representing an action message, with an argument for the sender. 26 | @param controlEvents A bitmask specifying the control events for which the action message is sent. 27 | @see removeEventHandlersForControlEvents: 28 | */ 29 | - (void)addEventHandler:(BKSenderBlock)handler forControlEvents:(UIControlEvents)controlEvents; 30 | 31 | /** Removes all blocks for a particular event combination. 32 | @param controlEvents A bitmask specifying the control events for which the block will be removed. 33 | @see addEventHandler:forControlEvents: 34 | */ 35 | - (void)removeEventHandlersForControlEvents:(UIControlEvents)controlEvents; 36 | 37 | /** Checks to see if the control has any blocks for a particular event combination. 38 | @param controlEvents A bitmask specifying the control events for which to check for blocks. 39 | @see addEventHandler:forControlEvents: 40 | @return Returns YES if there are blocks for these control events, NO otherwise. 41 | */ 42 | - (BOOL)hasEventHandlersForControlEvents:(UIControlEvents)controlEvents; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /BlocksKit/NSTimer+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSTimer+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Simple category on NSTimer to give it blocks capability. 9 | 10 | Created by [Jiva DeVoe](https://github.com/jivadevoe) as `NSTimer-Blocks`. 11 | */ 12 | @interface NSTimer (BlocksKit) 13 | 14 | /** Creates and returns a block-based NSTimer object and schedules it on the current run loop. 15 | 16 | @param inTimeInterval The number of seconds between firings of the timer. 17 | @param inBlock The block that the NSTimer fires. 18 | @param inRepeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires. 19 | @return A new NSTimer object, configured according to the specified parameters. 20 | */ 21 | + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(BKTimerBlock)inBlock repeats:(BOOL)inRepeats; 22 | 23 | /** Creates and returns a block-based NSTimer initialized with the specified block. 24 | 25 | You must add the new timer to a run loop, using `-addTimer:forMode:`. Then, 26 | after seconds seconds have elapsed, the timer fires the block. If the timer 27 | is configured to repeat, there is no need to subsequently re-add the timer. 28 | 29 | @param inTimeInterval The number of seconds between firings of the timer. 30 | @param inBlock The block that the NSTimer fires. 31 | @param inRepeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires. 32 | @return A new NSTimer object, configured according to the specified parameters. 33 | */ 34 | + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(BKTimerBlock)inBlock repeats:(BOOL)inRepeats; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /BlocksKit/MessageUI/MFMessageComposeViewController+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // MFMessageComposeViewController+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "MFMessageComposeViewController+BlocksKit.h" 7 | 8 | #pragma mark Custom delegate 9 | 10 | @interface A2DynamicMFMessageComposeViewControllerDelegate : A2DynamicDelegate 11 | @end 12 | 13 | @implementation A2DynamicMFMessageComposeViewControllerDelegate 14 | 15 | - (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result { 16 | id realDelegate = self.realDelegate; 17 | BOOL shouldDismiss = (realDelegate && [realDelegate respondsToSelector:@selector(messageComposeViewController:didFinishWithResult:)]); 18 | if (shouldDismiss) 19 | [realDelegate messageComposeViewController:controller didFinishWithResult:result]; 20 | 21 | void(^block)(MFMessageComposeViewController *, MessageComposeResult) = [self blockImplementationForMethod:_cmd]; 22 | if (block) 23 | block(controller, result); 24 | 25 | if (!shouldDismiss) { 26 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000 27 | [controller dismissModalViewControllerAnimated:YES]; 28 | #else 29 | [controller dismissViewControllerAnimated:YES completion:nil]; 30 | #endif 31 | } 32 | } 33 | 34 | @end 35 | 36 | #pragma mark - Category 37 | 38 | @implementation MFMessageComposeViewController (BlocksKit) 39 | 40 | @dynamic completionBlock; 41 | 42 | + (void)load { 43 | @autoreleasepool { 44 | [self registerDynamicDelegateNamed:@"messageComposeDelegate" forProtocol:@protocol(MFMessageComposeViewControllerDelegate)]; 45 | [self linkDelegateMethods: @{ @"completionBlock": @"messageComposeViewController:didFinishWithResult:" }]; 46 | } 47 | } 48 | 49 | @end -------------------------------------------------------------------------------- /BlocksKit/MessageUI/MFMailComposeViewController+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // MFMailComposeViewController+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "MFMailComposeViewController+BlocksKit.h" 7 | 8 | #pragma mark Custom delegate 9 | 10 | @interface A2DynamicMFMailComposeViewControllerDelegate : A2DynamicDelegate 11 | @end 12 | 13 | @implementation A2DynamicMFMailComposeViewControllerDelegate 14 | 15 | - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { 16 | id realDelegate = self.realDelegate; 17 | BOOL shouldDismiss = (realDelegate && [realDelegate respondsToSelector:@selector(mailComposeController:didFinishWithResult:error:)]); 18 | 19 | if (shouldDismiss) 20 | [realDelegate mailComposeController:controller didFinishWithResult:result error:error]; 21 | 22 | void(^block)(MFMailComposeViewController *, MFMailComposeResult, NSError *) = [self blockImplementationForMethod:_cmd]; 23 | if (block) 24 | block(controller, result, error); 25 | 26 | if (!shouldDismiss) { 27 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000 28 | [controller dismissModalViewControllerAnimated:YES]; 29 | #else 30 | [controller dismissViewControllerAnimated:YES completion:nil]; 31 | #endif 32 | 33 | } 34 | } 35 | 36 | @end 37 | 38 | #pragma mark Category 39 | 40 | @implementation MFMailComposeViewController (BlocksKit) 41 | 42 | @dynamic completionBlock; 43 | 44 | + (void)load { 45 | @autoreleasepool { 46 | [self registerDynamicDelegateNamed:@"mailComposeDelegate" forProtocol:@protocol(MFMailComposeViewControllerDelegate)]; 47 | [self linkDelegateMethods: @{ @"completionBlock": @"mailComposeController:didFinishWithResult:error:" }]; 48 | } 49 | } 50 | 51 | @end -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIPopoverController+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIPopoverController+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "UIPopoverController+BlocksKit.h" 7 | 8 | #pragma mark - Delegate 9 | 10 | @interface A2DynamicUIPopoverControllerDelegate : A2DynamicDelegate 11 | 12 | @end 13 | 14 | @implementation A2DynamicUIPopoverControllerDelegate 15 | 16 | - (BOOL) popoverControllerShouldDismissPopover: (UIPopoverController *) popoverController 17 | { 18 | BOOL should = YES; 19 | 20 | id realDelegate = self.realDelegate; 21 | if (realDelegate && [realDelegate respondsToSelector: @selector(popoverControllerShouldDismissPopover:)]) 22 | should &= [realDelegate popoverControllerShouldDismissPopover: popoverController]; 23 | 24 | BOOL (^block)(UIPopoverController *) = [self blockImplementationForMethod: _cmd]; 25 | if (block) should &= block(popoverController); 26 | 27 | return should; 28 | } 29 | 30 | - (void) popoverControllerDidDismissPopover: (UIPopoverController *) popoverController 31 | { 32 | id realDelegate = self.realDelegate; 33 | if (realDelegate && [realDelegate respondsToSelector: @selector(popoverControllerDidDismissPopover:)]) 34 | [realDelegate popoverControllerDidDismissPopover: popoverController]; 35 | 36 | void (^block)(UIPopoverController *) = [self blockImplementationForMethod: _cmd]; 37 | if (block) block(popoverController); 38 | } 39 | 40 | @end 41 | 42 | #pragma mark - Category 43 | 44 | @implementation UIPopoverController (BlocksKit) 45 | 46 | @dynamic didDismissBlock, shouldDismissBlock; 47 | 48 | + (void) load 49 | { 50 | @autoreleasepool 51 | { 52 | [self registerDynamicDelegate]; 53 | [self linkDelegateMethods: @{ @"didDismissBlock": @"popoverControllerDidDismissPopover:", @"shouldDismissBlock": @"popoverControllerShouldDismissPopover:" }]; 54 | } 55 | } 56 | 57 | @end -------------------------------------------------------------------------------- /BlocksKit/BKMacros.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKMacros.h 3 | // BlocksKit 4 | // 5 | // Includes code by Michael Ash. . 6 | // 7 | 8 | #import "NSArray+BlocksKit.h" 9 | #import "NSSet+BlocksKit.h" 10 | #import "NSDictionary+BlocksKit.h" 11 | #import "NSIndexSet+BlocksKit.h" 12 | 13 | #ifndef __BLOCKSKIT_MACROS__ 14 | #define __BLOCKSKIT_MACROS__ 15 | 16 | #define __BK_EACH_WRAPPER(...) (^{ __block CFMutableDictionaryRef MA_eachTable = nil; \ 17 | (void)MA_eachTable; \ 18 | __typeof__(__VA_ARGS__) MA_retval = __VA_ARGS__; \ 19 | if(MA_eachTable) \ 20 | CFRelease(MA_eachTable); \ 21 | return MA_retval; \ 22 | }()) 23 | 24 | #define BK_EACH(collection, ...) __BK_EACH_WRAPPER([collection each:^(id obj) { __VA_ARGS__ }]; 25 | #define BK_APPLY(collection, ...) __BK_EACH_WRAPPER([collection apply:^(id obj) { __VA_ARGS__ }]; 26 | #define BK_MAP(collection, ...) __BK_EACH_WRAPPER([collection map: ^id (id obj) { return (__VA_ARGS__); }]) 27 | #define BK_SELECT(collection, ...) __BK_EACH_WRAPPER([collection select: ^BOOL (id obj) { return (__VA_ARGS__) != 0; }]) 28 | #define BK_REJECT(collection, ...) __BK_EACH_WRAPPER([collection select: ^BOOL (id obj) { return (__VA_ARGS__) == 0; }]) 29 | #define BK_MATCH(collection, ...) __BK_EACH_WRAPPER([collection match: ^BOOL (id obj) { return (__VA_ARGS__) != 0; }]) 30 | #define BK_REDUCE(collection, initial, ...) __BK_EACH_WRAPPER([collection reduce: (initial) block: ^id (id a, id b) { return (__VA_ARGS__); }]) 31 | 32 | #ifndef EACH 33 | #define EACH BK_EACH 34 | #endif 35 | 36 | #ifndef APPLY 37 | #define APPLY BK_APPLY 38 | #endif 39 | 40 | #ifndef MAP 41 | #define MAP BK_MAP 42 | #endif 43 | 44 | #ifndef SELECT 45 | #define SELECT BK_SELECT 46 | #endif 47 | 48 | #ifndef REJECT 49 | #define REJECT BK_REJECT 50 | #endif 51 | 52 | #ifndef MATCH 53 | #define MATCH BK_MATCH 54 | #endif 55 | 56 | #ifndef REDUCE 57 | #define REDUCE BK_REDUCE 58 | #endif 59 | 60 | #endif -------------------------------------------------------------------------------- /Tests/NSObjectAssociatedObjectTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObjectAssociatedObjectTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/6/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSObjectAssociatedObjectTest.h" 10 | 11 | static char kAssociationKey; 12 | static char kNotFoundKey; 13 | 14 | @implementation NSObjectAssociatedObjectTest 15 | 16 | - (void)tearDown { 17 | [self removeAllAssociatedObjects]; 18 | } 19 | 20 | - (void)testAssociatedRetainValue { 21 | NSMutableString *subject = [NSMutableString stringWithString: @"Hello"]; 22 | [self associateValue:subject withKey: &kAssociationKey]; 23 | [subject appendString: @" BlocksKit"]; 24 | 25 | // Value is retained 26 | NSString *associated = [self associatedValueForKey: &kAssociationKey]; 27 | STAssertTrue([associated isEqualToString: @"Hello BlocksKit"], @"associated value is %@", associated); 28 | } 29 | 30 | - (void)testAssociatedCopyValue { 31 | NSMutableString *subject = [NSMutableString stringWithString: @"Hello"]; 32 | [self associateCopyOfValue: subject withKey: &kAssociationKey]; 33 | [subject appendString: @" BlocksKit"]; 34 | 35 | // Value is copied 36 | NSString *associated = [self associatedValueForKey: &kAssociationKey]; 37 | STAssertTrue([associated isEqualToString: @"Hello"], @"associated value is %@", associated); 38 | } 39 | 40 | - (void)testAssociatedAssignValue { 41 | NSString *subject = @"Hello BlocksKit"; 42 | [self weaklyAssociateValue:subject withKey:&kAssociationKey]; 43 | void *brokenPtr = (__bridge void *)subject; 44 | subject = nil; 45 | void *associated = (__bridge void *)[self associatedValueForKey:&kAssociationKey]; 46 | STAssertEquals(brokenPtr, associated, @"assign associated values equal"); 47 | } 48 | 49 | - (void)testAssociatedNotFound { 50 | NSString *associated = [self associatedValueForKey:&kNotFoundKey]; 51 | STAssertNil(associated,@"associated value is not found for kNotFoundKey"); 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /BlocksKit.xcodeproj/xcshareddata/xcschemes/iOS Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /BlocksKit.xcodeproj/xcshareddata/xcschemes/OS X Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Tests/NSObjectBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObjectBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/4/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSObjectBlocksKitTest.h" 10 | 11 | @implementation NSObjectBlocksKitTest { 12 | NSMutableString *_subject; 13 | } 14 | 15 | - (void)setUp { 16 | _subject = [@"Hello " mutableCopy]; 17 | } 18 | 19 | - (void)tearDown { 20 | _subject = nil; 21 | } 22 | 23 | - (void)testPerformBlockAfterDelay { 24 | BKSenderBlock senderBlock = ^(NSObjectBlocksKitTest *sender) { 25 | [_subject appendString:@"BlocksKit"]; 26 | [sender notify: SenTestCaseWaitStatusSuccess forSelector: @selector(testPerformBlockAfterDelay)]; 27 | }; 28 | [self prepare]; 29 | id block = [self performBlock:senderBlock afterDelay:0.5]; 30 | STAssertNotNil(block,@"block is nil"); 31 | [self waitForStatus: SenTestCaseWaitStatusSuccess timeout:1.0]; 32 | STAssertEqualObjects(_subject,@"Hello BlocksKit",@"subject string is %@",_subject); 33 | } 34 | 35 | - (void)testClassPerformBlockAfterDelay { 36 | NSObjectBlocksKitTest *test = self; 37 | NSMutableString *subject = [NSMutableString stringWithString:@"Hello "]; 38 | [self prepare]; 39 | id blk = [NSObject performBlock:^{ 40 | [subject appendString:@"BlocksKit"]; 41 | [test notify: SenTestCaseWaitStatusSuccess forSelector: @selector(testClassPerformBlockAfterDelay)]; 42 | } afterDelay:0.5]; 43 | STAssertNotNil(blk,@"block is nil"); 44 | [self waitForStatus: SenTestCaseWaitStatusSuccess timeout:1.0]; 45 | STAssertEqualObjects(subject,@"Hello BlocksKit",@"subject string is %@",subject); 46 | } 47 | 48 | - (void)testCancel { 49 | [self prepare]; 50 | id block = [self performBlock:^(NSObjectBlocksKitTest * sender) { 51 | [_subject appendString:@"BlocksKit"]; 52 | [sender notify: SenTestCaseWaitStatusSuccess]; 53 | } afterDelay:0.1]; 54 | STAssertNotNil(block,@"block is nil"); 55 | [NSObject cancelBlock:block]; 56 | [self waitForTimeout:0.5]; 57 | STAssertEqualObjects(_subject,@"Hello ",@"subject string is %@",_subject); 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /BlocksKit/NSIndexSet+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexSet+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSIndexSet+BlocksKit.h" 7 | 8 | @implementation NSIndexSet (BlocksKit) 9 | 10 | - (void)each:(BKIndexBlock)block { 11 | NSParameterAssert(block != nil); 12 | 13 | [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 14 | block(idx); 15 | }]; 16 | } 17 | 18 | - (void)apply:(BKIndexBlock)block { 19 | NSParameterAssert(block != nil); 20 | 21 | [self enumerateIndexesWithOptions:NSEnumerationConcurrent usingBlock:^(NSUInteger idx, BOOL *stop) { 22 | block(idx); 23 | }]; 24 | } 25 | 26 | - (NSUInteger)match:(BKIndexValidationBlock)block { 27 | NSParameterAssert(block != nil); 28 | 29 | return [self indexPassingTest:^BOOL(NSUInteger idx, BOOL *stop) { 30 | return block(idx); 31 | }]; 32 | } 33 | 34 | - (NSIndexSet *)select:(BKIndexValidationBlock)block { 35 | NSParameterAssert(block != nil); 36 | 37 | NSIndexSet *list = [self indexesPassingTest:^BOOL(NSUInteger idx, BOOL *stop) { 38 | return block(idx); 39 | }]; 40 | 41 | if (!list.count) 42 | return nil; 43 | 44 | return list; 45 | } 46 | 47 | - (NSIndexSet *)reject:(BKIndexValidationBlock)block { 48 | return [self select:^BOOL(NSUInteger idx) { 49 | return !block(idx); 50 | }]; 51 | } 52 | 53 | - (NSIndexSet *)map:(BKIndexTransformBlock)block { 54 | NSParameterAssert(block != nil); 55 | 56 | NSMutableIndexSet *list = [NSMutableIndexSet indexSet]; 57 | 58 | [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 59 | [list addIndex:block(idx)]; 60 | }]; 61 | 62 | if (!list.count) 63 | return nil; 64 | 65 | return list; 66 | } 67 | 68 | - (BOOL)any:(BKIndexValidationBlock)block { 69 | return [self match: block] != NSNotFound; 70 | } 71 | 72 | - (BOOL)none:(BKIndexValidationBlock)block { 73 | return [self match: block] == NSNotFound; 74 | } 75 | 76 | - (BOOL)all:(BKIndexValidationBlock)block { 77 | NSParameterAssert(block != nil); 78 | 79 | __block BOOL result = YES; 80 | 81 | [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 82 | if (!block(idx)) { 83 | result = NO; 84 | *stop = YES; 85 | } 86 | }]; 87 | 88 | return result; 89 | } 90 | 91 | @end -------------------------------------------------------------------------------- /BlocksKit/BKGlobals.h: -------------------------------------------------------------------------------- 1 | // 2 | // BKGlobals.h 3 | // BlocksKit 4 | // 5 | 6 | #import 7 | #import 8 | 9 | #import "A2BlockDelegate.h" 10 | #import "NSObject+A2DynamicDelegate.h" 11 | 12 | #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) 13 | #define BK_HAS_UIKIT 0 14 | #define BK_HAS_APPKIT 1 15 | #elif (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) 16 | #define BK_HAS_UIKIT 1 17 | #define BK_HAS_APPKIT 0 18 | #else 19 | #define BK_HAS_UIKIT 0 20 | #define BK_HAS_APPKIT 0 21 | #endif 22 | 23 | #ifndef DEPRECATED_ATTRIBUTE_M 24 | #if __has_attribute(deprecated) 25 | #define DEPRECATED_ATTRIBUTE_M(...) __attribute__((deprecated(__VA_ARGS__))) 26 | #else 27 | #define DEPRECATED_ATTRIBUTE_M(...) DEPRECATED_ATTRIBUTE 28 | #endif 29 | #endif 30 | 31 | #import 32 | 33 | #if BK_HAS_APPKIT 34 | #import 35 | #endif 36 | 37 | #if BK_HAS_UIKIT 38 | #import 39 | #import 40 | 41 | typedef void (^BKGestureRecognizerBlock)(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location); 42 | typedef void (^BKTouchBlock)(NSSet* set, UIEvent* event); 43 | #endif 44 | 45 | typedef void (^BKBlock)(void); // compatible with dispatch_block_t 46 | typedef void (^BKSenderBlock)(id sender); 47 | typedef void (^BKSenderKeyPathBlock)(id obj, NSString *keyPath); 48 | typedef void (^BKKeyValueBlock)(id key, id obj); 49 | typedef void (^BKIndexBlock)(NSUInteger index); 50 | typedef void (^BKTimerBlock)(NSTimeInterval time); 51 | typedef void (^BKResponseBlock)(NSURLResponse *response); 52 | 53 | typedef void (^BKObservationBlock)(id obj, NSDictionary *change); 54 | typedef void (^BKMultipleObservationBlock)(id obj, NSString *keyPath, NSDictionary *change); 55 | 56 | typedef BOOL (^BKValidationBlock)(id obj); 57 | typedef BOOL (^BKKeyValueValidationBlock)(id key, id obj); 58 | typedef BOOL (^BKIndexValidationBlock)(NSUInteger index); 59 | 60 | typedef id (^BKReturnBlock)(void); 61 | typedef id (^BKTransformBlock)(id obj); 62 | typedef id (^BKKeyValueTransformBlock)(id key, id obj); 63 | typedef id (^BKAccumulationBlock)(id sum, id obj); 64 | 65 | typedef NSUInteger (^BKIndexTransformBlock)(NSUInteger index); -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIBarButtonItem+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIBarButtonItem+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block event initialization for UIBarButtonItem. 9 | 10 | This set of extensions has near-drop-in replacements 11 | for the standard set of UIBarButton item initializations, 12 | using a block handler instead of a target/selector. 13 | 14 | Includes code by the following: 15 | 16 | - [Kevin O'Neill](https://github.com/kevinoneill) 17 | - [Zach Waldowski](https://github.com/zwaldowski) 18 | 19 | @warning UIBarButtonItem is only available on a platform with UIKit. 20 | */ 21 | @interface UIBarButtonItem (BlocksKit) 22 | 23 | /** Creates and returns a configured item containing the specified system item. 24 | 25 | @return Newly initialized item with the specified properties. 26 | @param systemItem The system item to use as the item representation. One of the constants defined in UIBarButtonSystemItem. 27 | @param action The block that gets fired on the button press. 28 | */ 29 | - (id)initWithBarButtonSystemItem:(UIBarButtonSystemItem)systemItem handler:(BKSenderBlock)action; 30 | 31 | /** Creates and returns a configured item using the specified image and style. 32 | 33 | @return Newly initialized item with the specified properties. 34 | @param image The item’s image. If nil an image is not displayed. 35 | If this image is too large to fit on the bar, it is scaled to fit 36 | The size of a toolbar and navigation bar image is 20 x 20 points. 37 | @param style The style of the item. One of the constants defined in UIBarButtonItemStyle. 38 | @param action The block that gets fired on the button press. 39 | */ 40 | - (id)initWithImage:(UIImage *)image style:(UIBarButtonItemStyle)style handler:(BKSenderBlock)action; 41 | 42 | /** Creates and returns a configured item using the specified text and style. 43 | 44 | @return Newly initialized item with the specified properties. 45 | @param title The text displayed on the button item. 46 | @param style The style of the item. One of the constants defined in UIBarButtonItemStyle. 47 | @param action The block that gets fired on the button press. 48 | */ 49 | - (id)initWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style handler:(BKSenderBlock)action; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [BlocksKit](http://pandamonia.github.com/BlocksKit) 2 | =================================================== 3 | 4 | Blocks in C and Objective-C are downright magical. They make coding easier and potentially quicker, not to mention faster on the front end with multithreading and Grand Central Dispatch. BlocksKit hopes to facilitate this kind of programming by removing some of the annoying - and, in some cases, impeding - limits on coding with blocks. 5 | 6 | BlocksKit is a framework for OS X Lion and newer and a static library for iOS 5 and newer. 7 | 8 | Installation 9 | ============ 10 | 11 | BlocksKit can be added to a project using [CocoaPods](https://github.com/cocoapods/cocoapods). We also distribute a static library build. 12 | 13 | ### Library 14 | 15 | * Download a release of BlocksKit. 16 | * Move libBlocksKit.a and Headers to your project's folder, preferably a subfolder like "Vendor". 17 | * In "Build Phases", Drag libBlocksKit.a into your target's "Link Binary With Libraries" build phase. 18 | * In the build settings of your target or project, change "Other Linker Flags" to `-ObjC -all_load`. Make sure your app is linked with CoreGraphics, Foundation, MessageUI, and UIKit. 19 | * Change (or add) to "Header Search Paths" the relative path to BlocksKit's headers, like `$(SRCROOT)/Vendor/Headers`. 20 | * Insert `#import `` in your project's prefix header. 21 | 22 | Documentation 23 | ============= 24 | 25 | An Xcode 4 compatible documentation set is available [using this Atom link](http://pandamonia.github.com/BlocksKit/us.pandamonia.BlocksKit.atom). You may also view the documentation [online](http://pandamonia.github.com/BlocksKit/Documentation). 26 | 27 | License 28 | ======= 29 | 30 | BlocksKit is created and maintained by [Pandamonia LLC](https://github.com/pandamonia) under the MIT license. **The project itself is free for use in any and all projects.** You can use BlocksKit in any project, public or private, with or without attribution - though we prefer attribution! It helps us. 31 | 32 | Unsure about your rights? [Read more.](http://pandamonia.github.com/BlocksKit/index.html#license) 33 | 34 | Individual credits for included code exist in the header files and documentation. We thank them for their contributions to the open source community. -------------------------------------------------------------------------------- /BlocksKit.xcodeproj/xcshareddata/xcschemes/iOS Library.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 44 | 45 | 51 | 52 | 54 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /BlocksKit.xcodeproj/xcshareddata/xcschemes/OS X Framework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 44 | 45 | 51 | 52 | 54 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Tests/NSTimerBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSTimerBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/5/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSTimerBlocksKitTest.h" 10 | 11 | @implementation NSTimerBlocksKitTest { 12 | NSInteger _total; 13 | } 14 | 15 | - (void)setUp { 16 | _total = 0; 17 | } 18 | 19 | - (void)testScheduledTimer { 20 | BKTimerBlock timerBlock = ^(NSTimeInterval time) { 21 | _total++; 22 | NSLog(@"total is %lu", (unsigned long)_total); 23 | }; 24 | [self prepare]; 25 | NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 block:timerBlock repeats:NO]; 26 | STAssertNotNil(timer,@"timer is nil"); 27 | [self waitForTimeout:0.5]; 28 | STAssertEquals(_total, (NSInteger)1, @"total is %d", _total); 29 | } 30 | 31 | - (void)testRepeatedlyScheduledTimer { 32 | BKTimerBlock timerBlock = ^(NSTimeInterval time) { 33 | _total++; 34 | NSLog(@"total is %lu", (unsigned long)_total); 35 | }; 36 | [self prepare]; 37 | NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 block:timerBlock repeats:YES]; 38 | STAssertNotNil(timer,@"timer is nil"); 39 | [self waitForTimeout:0.5]; 40 | [timer invalidate]; 41 | STAssertTrue(_total > 3, @"total is %d", _total); 42 | } 43 | 44 | - (void)testUnscheduledTimer { 45 | BKTimerBlock timerBlock = ^(NSTimeInterval time) { 46 | _total++; 47 | NSLog(@"total is %lu", (unsigned long)_total); 48 | }; 49 | [self prepare]; 50 | NSTimer *timer = [NSTimer timerWithTimeInterval:0.1 block:timerBlock repeats:NO]; 51 | STAssertNotNil(timer,@"timer is nil"); 52 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; 53 | [self waitForTimeout:0.5]; 54 | STAssertEquals(_total, (NSInteger)1, @"total is %d", _total); 55 | } 56 | 57 | - (void)testRepeatableUnscheduledTimer { 58 | BKTimerBlock timerBlock = ^(NSTimeInterval time) { 59 | _total += 1; 60 | NSLog(@"total is %lu", (unsigned long)_total); 61 | }; 62 | [self prepare]; 63 | NSTimer *timer = [NSTimer timerWithTimeInterval:0.1 block:timerBlock repeats:YES]; 64 | STAssertNotNil(timer,@"timer is nil"); 65 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; 66 | [self waitForTimeout:0.5]; 67 | [timer invalidate]; 68 | STAssertTrue(_total > 3, @"total is %d", _total); 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /BlocksKit/NSObject+A2DynamicDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+A2DynamicDelegate.m 3 | // BlocksKit 4 | // 5 | // Created by Zachary Waldowski on 10/24/12. 6 | // Copyright (c) 2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSObject+A2DynamicDelegate.h" 10 | #import "NSObject+AssociatedObjects.h" 11 | 12 | extern Protocol *a2_dataSourceProtocol(Class cls); 13 | extern Protocol *a2_delegateProtocol(Class cls); 14 | 15 | static dispatch_queue_t a2_backgroundQueue(void) 16 | { 17 | static dispatch_once_t onceToken; 18 | static dispatch_queue_t backgroundQueue = nil; 19 | dispatch_once(&onceToken, ^{ 20 | backgroundQueue = dispatch_queue_create("us.pandamonia.A2DynamicDelegate.backgroundQueue", DISPATCH_QUEUE_SERIAL); 21 | }); 22 | return backgroundQueue; 23 | } 24 | 25 | @implementation NSObject (A2DynamicDelegate) 26 | 27 | - (id) dynamicDataSource 28 | { 29 | Protocol *protocol = a2_dataSourceProtocol([self class]); 30 | return [self dynamicDelegateForProtocol: protocol]; 31 | } 32 | - (id) dynamicDelegate 33 | { 34 | Protocol *protocol = a2_delegateProtocol([self class]); 35 | return [self dynamicDelegateForProtocol: protocol]; 36 | } 37 | - (id) dynamicDelegateForProtocol: (Protocol *) protocol 38 | { 39 | /** 40 | * Storing the dynamic delegate as an associated object of the delegating 41 | * object not only allows us to later retrieve the delegate, but it also 42 | * creates a strong relationship to the delegate. Since delegates are weak 43 | * references on the part of the delegating object, a dynamic delegate 44 | * would be deallocated immediately after its declaring scope ends. 45 | * Therefore, this strong relationship is required to ensure that the 46 | * delegate's lifetime is at least as long as that of the delegating object. 47 | **/ 48 | 49 | __block A2DynamicDelegate *dynamicDelegate; 50 | 51 | dispatch_sync(a2_backgroundQueue(), ^{ 52 | dynamicDelegate = [self associatedValueForKey: (__bridge const void *)protocol]; 53 | 54 | if (!dynamicDelegate) 55 | { 56 | Class cls = NSClassFromString([@"A2Dynamic" stringByAppendingString: NSStringFromProtocol(protocol)]) ?: [A2DynamicDelegate class]; 57 | dynamicDelegate = [[cls alloc] initWithProtocol: protocol]; 58 | [self associateValue: dynamicDelegate withKey: (__bridge const void *)protocol]; 59 | } 60 | }); 61 | 62 | return dynamicDelegate; 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /BlocksKit/NSDictionary+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSDictionary+BlocksKit.h" 7 | 8 | @implementation NSDictionary (BlocksKit) 9 | 10 | - (void)each:(BKKeyValueBlock)block { 11 | NSParameterAssert(block != nil); 12 | 13 | [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 14 | block(key, obj); 15 | }]; 16 | } 17 | 18 | - (void)apply:(BKKeyValueBlock)block { 19 | NSParameterAssert(block != nil); 20 | 21 | [self enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id key, id obj, BOOL *stop) { 22 | block(key, obj); 23 | }]; 24 | } 25 | 26 | - (id)match:(BKKeyValueValidationBlock)block { 27 | NSParameterAssert(block != nil); 28 | 29 | return self[[[self keysOfEntriesPassingTest:^(id key, id obj, BOOL *stop) { 30 | if (block(key, obj)) { 31 | *stop = YES; 32 | return YES; 33 | } 34 | return NO; 35 | }] anyObject]]; 36 | } 37 | 38 | - (NSDictionary *)select:(BKKeyValueValidationBlock)block { 39 | NSParameterAssert(block != nil); 40 | 41 | NSArray *keys = [[self keysOfEntriesPassingTest:^(id key, id obj, BOOL *stop) { 42 | return block(key, obj); 43 | }] allObjects]; 44 | 45 | NSArray *objects = [self objectsForKeys:keys notFoundMarker:[NSNull null]]; 46 | 47 | return [NSDictionary dictionaryWithObjects:objects forKeys:keys]; 48 | } 49 | 50 | - (NSDictionary *)reject:(BKKeyValueValidationBlock)block { 51 | return [self select:^BOOL(id key, id obj) { 52 | return !block(key, obj); 53 | }]; 54 | } 55 | 56 | - (NSDictionary *)map:(BKKeyValueTransformBlock)block { 57 | NSParameterAssert(block != nil); 58 | 59 | NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:self.count]; 60 | 61 | [self each:^(id key, id obj) { 62 | id value = block(key, obj); 63 | if (!value) 64 | value = [NSNull null]; 65 | 66 | result[key] = value; 67 | }]; 68 | 69 | return result; 70 | } 71 | 72 | - (BOOL)any:(BKKeyValueValidationBlock)block { 73 | return [self match: block] != nil; 74 | } 75 | 76 | - (BOOL)none:(BKKeyValueValidationBlock)block { 77 | return [self match: block] == nil; 78 | } 79 | 80 | - (BOOL)all:(BKKeyValueValidationBlock)block { 81 | NSParameterAssert(block != nil); 82 | 83 | __block BOOL result = YES; 84 | 85 | [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 86 | if (!block(key, obj)) { 87 | result = NO; 88 | *stop = YES; 89 | } 90 | }]; 91 | 92 | return result; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /BlocksKit/BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // BlocksKit 3 | // 4 | // The Objective-C block utilities you always wish you had. 5 | // 6 | // Copyright (c) 2011-2012 Pandamonia LLC. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "BKGlobals.h" 28 | 29 | #import "BKMacros.h" 30 | 31 | #import "NSObject+BlocksKit.h" 32 | #import "NSObject+AssociatedObjects.h" 33 | #import "NSObject+BlockObservation.h" 34 | 35 | #import "NSArray+BlocksKit.h" 36 | #import "NSMutableArray+BlocksKit.h" 37 | #import "NSSet+BlocksKit.h" 38 | #import "NSMutableSet+BlocksKit.h" 39 | #import "NSDictionary+BlocksKit.h" 40 | #import "NSMutableDictionary+BlocksKit.h" 41 | #import "NSIndexSet+BlocksKit.h" 42 | #import "NSMutableIndexSet+BlocksKit.h" 43 | #import "NSOrderedSet+BlocksKit.h" 44 | #import "NSMutableOrderedSet+BlocksKit.h" 45 | 46 | #import "NSInvocation+BlocksKit.h" 47 | #import "NSTimer+BlocksKit.h" 48 | 49 | #import "NSURLConnection+BlocksKit.h" 50 | #import "NSCache+BlocksKit.h" 51 | 52 | #if BK_HAS_UIKIT 53 | #import "UIAlertView+BlocksKit.h" 54 | #import "UIActionSheet+BlocksKit.h" 55 | #import "UIControl+BlocksKit.h" 56 | #import "UIBarButtonItem+BlocksKit.h" 57 | #import "UIGestureRecognizer+BlocksKit.h" 58 | #import "UIView+BlocksKit.h" 59 | #import "UIWebView+BlocksKit.h" 60 | #import "MFMailComposeViewController+BlocksKit.h" 61 | #import "MFMessageComposeViewController+BlocksKit.h" 62 | #else 63 | // AppKit extensions 64 | #endif 65 | -------------------------------------------------------------------------------- /BlocksKit/NSSet+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSSet+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSSet+BlocksKit.h" 7 | 8 | @implementation NSSet (BlocksKit) 9 | 10 | - (void)each:(BKSenderBlock)block { 11 | NSParameterAssert(block != nil); 12 | 13 | [self enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { 14 | block(obj); 15 | }]; 16 | } 17 | 18 | - (void)apply:(BKSenderBlock)block { 19 | NSParameterAssert(block != nil); 20 | 21 | [self enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, BOOL *stop) { 22 | block(obj); 23 | }]; 24 | } 25 | 26 | - (id)match:(BKValidationBlock)block { 27 | NSParameterAssert(block != nil); 28 | 29 | return [[self objectsPassingTest:^BOOL(id obj, BOOL *stop) { 30 | if (block(obj)) { 31 | *stop = YES; 32 | return YES; 33 | } 34 | return NO; 35 | }] anyObject]; 36 | } 37 | 38 | - (NSSet *)select:(BKValidationBlock)block { 39 | NSParameterAssert(block != nil); 40 | 41 | return [self objectsPassingTest:^BOOL(id obj, BOOL *stop) { 42 | return (block(obj)); 43 | }]; 44 | } 45 | 46 | - (NSSet *)reject:(BKValidationBlock)block { 47 | NSParameterAssert(block != nil); 48 | 49 | return [self objectsPassingTest:^BOOL(id obj, BOOL *stop) { 50 | return (!block(obj)); 51 | }]; 52 | } 53 | 54 | - (NSSet *)map:(BKTransformBlock)block { 55 | NSParameterAssert(block != nil); 56 | 57 | NSMutableSet *result = [NSMutableSet setWithCapacity: self.count]; 58 | 59 | [self enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { 60 | id value = block(obj); 61 | if (!value) 62 | value = [NSNull null]; 63 | 64 | [result addObject:value]; 65 | }]; 66 | 67 | return result; 68 | } 69 | 70 | - (id)reduce:(id)initial withBlock:(BKAccumulationBlock)block { 71 | NSParameterAssert(block != nil); 72 | 73 | __block id result = initial; 74 | 75 | [self enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { 76 | result = block(result, obj); 77 | }]; 78 | 79 | return result; 80 | } 81 | 82 | - (BOOL)any:(BKValidationBlock)block { 83 | return [self match: block] != nil; 84 | } 85 | 86 | - (BOOL)none:(BKValidationBlock)block { 87 | return [self match: block] == nil; 88 | } 89 | 90 | - (BOOL)all:(BKValidationBlock)block { 91 | NSParameterAssert(block != nil); 92 | 93 | __block BOOL result = YES; 94 | 95 | [self enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { 96 | if (!block(obj)) { 97 | result = NO; 98 | *stop = YES; 99 | } 100 | }]; 101 | 102 | return result; 103 | } 104 | 105 | @end -------------------------------------------------------------------------------- /BlocksKit/NSObject+A2DynamicDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+A2DynamicDelegate.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | #import "A2DynamicDelegate.h" 8 | 9 | /** The A2DynamicDelegate category to NSObject provides the primary interface 10 | by which dynamic delegates are generated for a given object. */ 11 | @interface NSObject (A2DynamicDelegate) 12 | 13 | /** Creates or gets a dynamic data source for the reciever. 14 | 15 | A2DynamicDelegate assumes a protocol name `FooBarDataSource` 16 | for instances of class `FooBar`. The object is given a strong 17 | attachment to the reciever, and is automatically deallocated 18 | when the reciever is released. 19 | 20 | If the user implements a `A2DynamicFooBarDataSource` subclass 21 | of A2DynamicDelegate, its implementation of any method 22 | will be used over the block. If the block needs to be used, 23 | it can be called from within the custom 24 | implementation using blockImplementationForMethod:. 25 | 26 | @see blockImplementationForMethod: 27 | @return A dynamic data source. 28 | */ 29 | - (id) dynamicDataSource; 30 | 31 | /** Creates or gets a dynamic delegate for the reciever. 32 | 33 | A2DynamicDelegate assumes a protocol name `FooBarDelegate` 34 | for instances of class `FooBar`. The object is given a strong 35 | attachment to the reciever, and is automatically deallocated 36 | when the reciever is released. 37 | 38 | If the user implements a `A2DynamicFooBarDelegate` subclass 39 | of A2DynamicDelegate, its implementation of any method 40 | will be used over the block. If the block needs to be used, 41 | it can be called from within the custom 42 | implementation using blockImplementationForMethod:. 43 | 44 | @see blockImplementationForMethod: 45 | @return A dynamic delegate. 46 | */ 47 | - (id) dynamicDelegate; 48 | 49 | /** Creates or gets a dynamic protocol implementation for 50 | the reciever. The designated initializer. 51 | 52 | The object is given a strong attachment to the reciever, 53 | and is automatically deallocated when the reciever is released. 54 | 55 | If the user implements a subclass of A2DynamicDelegate prepended 56 | with `A2Dynamic`, such as `A2DynamicFooProvider`, its 57 | implementation of any method will be used over the block. 58 | If the block needs to be used, it can be called from within the 59 | custom implementation using blockImplementationForMethod:. 60 | 61 | @param protocol A custom protocol. 62 | @return A dynamic protocol implementation. 63 | @see blockImplementationForMethod: 64 | */ 65 | - (id) dynamicDelegateForProtocol: (Protocol *) protocol; 66 | 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /BlocksKit/NSObject+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | #import "NSObject+AssociatedObjects.h" 8 | 9 | /** Block execution on *any* object. 10 | 11 | This category overhauls the `performSelector:` utilities on 12 | NSObject to instead use blocks. Not only are the blocks performed 13 | extremely speedily, thread-safely, and asynchronously using 14 | Grand Central Dispatch, but each convenience method also returns 15 | a pointer that can be used to cancel the execution before it happens! 16 | 17 | Includes code by the following: 18 | 19 | - [Peter Steinberger](https://github.com/steipete) 20 | - [Zach Waldowski](https://github.com/zwaldowski) 21 | 22 | */ 23 | @interface NSObject (BlocksKit) 24 | 25 | /** Executes a block after a given delay on the reciever. 26 | 27 | [array performBlock:^(id obj){ 28 | [obj addObject:self]; 29 | [self release]; 30 | } afterDelay:0.5f]; 31 | 32 | @warning *Important:* Use of the **self** reference in a block will 33 | reference the current implementation context. The block argument, 34 | `obj`, should be used instead. 35 | 36 | @param block A single-argument code block, where `obj` is the reciever. 37 | @param delay A measure in seconds. 38 | @return Returns a pointer to the block that may or may not execute the given block. 39 | */ 40 | - (id)performBlock:(BKSenderBlock)block afterDelay:(NSTimeInterval)delay; 41 | 42 | /** Executes a block after a given delay. 43 | 44 | This class method is functionally identical to its instance method version. It still executes 45 | asynchronously via GCD. However, the current context is not passed so that the block is performed 46 | in a general context. 47 | 48 | Block execution is very useful, particularly for small events that you would like delayed. 49 | 50 | [object performBlock:^(){ 51 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 52 | } afterDelay:0.5f]; 53 | 54 | @see performBlock:afterDelay: 55 | @param block A code block. 56 | @param delay A measure in seconds. 57 | @return Returns a pointer to the block that may or may not execute the given block. 58 | */ 59 | + (id)performBlock:(BKBlock)block afterDelay:(NSTimeInterval)delay; 60 | 61 | /** Cancels the potential execution of a block. 62 | 63 | @warning *Important:* It is not recommended to cancel a block executed 64 | with no delay (a delay of 0.0). While it it still possible to catch the block 65 | before GCD has executed it, it has likely already been executed and disposed of. 66 | 67 | @param block A pointer to a containing block, as returned from one of the 68 | `performBlock` selectors. 69 | */ 70 | + (void)cancelBlock:(id)block; 71 | 72 | @end -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIGestureRecognizer+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIGestureRecognizer+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "UIGestureRecognizer+BlocksKit.h" 7 | #import "NSObject+AssociatedObjects.h" 8 | #import "NSObject+BlocksKit.h" 9 | 10 | static char kGestureRecognizerBlockKey; 11 | static char kGestureRecognizerDelayKey; 12 | static char kGestureRecognizerCancelKey; 13 | 14 | @interface UIGestureRecognizer (BlocksKitInternal) 15 | - (void)_handleAction:(UIGestureRecognizer *)recognizer; 16 | @end 17 | 18 | @implementation UIGestureRecognizer (BlocksKit) 19 | 20 | + (id)recognizerWithHandler:(BKGestureRecognizerBlock)block delay:(NSTimeInterval)delay { 21 | return [[[self class] alloc] initWithHandler:block delay:delay]; 22 | } 23 | 24 | - (id)initWithHandler:(BKGestureRecognizerBlock)block delay:(NSTimeInterval)delay { 25 | if ((self = [self initWithTarget:self action:@selector(_handleAction:)])) { 26 | self.handler = block; 27 | self.handlerDelay = delay; 28 | } 29 | return self; 30 | } 31 | 32 | + (id)recognizerWithHandler:(BKGestureRecognizerBlock)block { 33 | return [self recognizerWithHandler:block delay:0.0]; 34 | } 35 | 36 | - (id)initWithHandler:(BKGestureRecognizerBlock)block { 37 | return [self initWithHandler:block delay:0.0]; 38 | } 39 | 40 | - (void)_handleAction:(UIGestureRecognizer *)recognizer { 41 | BKGestureRecognizerBlock handler = recognizer.handler; 42 | if (!handler) 43 | return; 44 | 45 | NSTimeInterval delay = self.handlerDelay; 46 | CGPoint location = [self locationInView:self.view]; 47 | BKBlock block = ^{ 48 | handler(self, self.state, location); 49 | }; 50 | 51 | if (!delay) { 52 | block(); 53 | return; 54 | } 55 | 56 | id cancel = [NSObject performBlock:block afterDelay:delay]; 57 | [self associateCopyOfValue:cancel withKey:&kGestureRecognizerCancelKey]; 58 | } 59 | 60 | - (void)setHandler:(BKGestureRecognizerBlock)handler { 61 | [self associateCopyOfValue:handler withKey:&kGestureRecognizerBlockKey]; 62 | } 63 | 64 | - (BKGestureRecognizerBlock)handler { 65 | return [self associatedValueForKey:&kGestureRecognizerBlockKey]; 66 | } 67 | 68 | - (void)setHandlerDelay:(NSTimeInterval)delay { 69 | NSNumber *delayValue = delay ? @(delay) : nil; 70 | [self associateValue:delayValue withKey:&kGestureRecognizerDelayKey]; 71 | } 72 | 73 | - (NSTimeInterval)handlerDelay { 74 | NSNumber *delay = [self associatedValueForKey:&kGestureRecognizerDelayKey]; 75 | return delay ? [delay doubleValue] : 0.0; 76 | } 77 | 78 | - (void)setDelay:(NSTimeInterval)delay { 79 | [self setHandlerDelay:delay]; 80 | } 81 | 82 | - (NSTimeInterval)delay { 83 | return [self handlerDelay]; 84 | } 85 | 86 | - (void)cancel { 87 | [NSObject cancelBlock:[self associatedValueForKey:&kGestureRecognizerCancelKey]]; 88 | } 89 | 90 | @end -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIWebView+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIWebView+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "UIWebView+BlocksKit.h" 7 | 8 | #pragma mark Custom delegate 9 | 10 | @interface A2DynamicUIWebViewDelegate : A2DynamicDelegate 11 | @end 12 | 13 | @implementation A2DynamicUIWebViewDelegate 14 | 15 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { 16 | BOOL ret = YES; 17 | 18 | id realDelegate = self.realDelegate; 19 | if (realDelegate && [realDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) 20 | ret = [realDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; 21 | 22 | BOOL (^block)(UIWebView *, NSURLRequest *, UIWebViewNavigationType) = [self blockImplementationForMethod:_cmd]; 23 | if (block) 24 | ret &= block(webView, request, navigationType); 25 | 26 | return ret; 27 | } 28 | 29 | - (void)webViewDidStartLoad:(UIWebView *)webView { 30 | id realDelegate = self.realDelegate; 31 | if (realDelegate && [realDelegate respondsToSelector:@selector(webViewDidStartLoad:)]) 32 | [realDelegate webViewDidStartLoad:webView]; 33 | 34 | void(^block)(UIWebView *) = [self blockImplementationForMethod:_cmd]; 35 | if (block) 36 | block(webView); 37 | } 38 | 39 | - (void)webViewDidFinishLoad:(UIWebView *)webView { 40 | id realDelegate = self.realDelegate; 41 | if (realDelegate && [realDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) 42 | [realDelegate webViewDidFinishLoad:webView]; 43 | 44 | void(^block)(UIWebView *) = [self blockImplementationForMethod:_cmd]; 45 | if (block) 46 | block(webView); 47 | } 48 | 49 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { 50 | id realDelegate = self.realDelegate; 51 | if (realDelegate && [realDelegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) 52 | [realDelegate webView:webView didFailLoadWithError:error]; 53 | 54 | void(^block)(UIWebView *, NSError *) = [self blockImplementationForMethod:_cmd]; 55 | if (block) 56 | block(webView, error); 57 | } 58 | 59 | @end 60 | 61 | #pragma mark Category 62 | 63 | @implementation UIWebView (BlocksKit) 64 | 65 | @dynamic shouldStartLoadBlock, didStartLoadBlock, didFinishLoadBlock, didFinishWithErrorBlock; 66 | 67 | + (void)load { 68 | @autoreleasepool { 69 | [self registerDynamicDelegate]; 70 | [self linkDelegateMethods: @{ 71 | @"shouldStartLoadBlock": @"webView:shouldStartLoadWithRequest:navigationType:", 72 | @"didStartLoadBlock": @"webViewDidStartLoad:", 73 | @"didFinishLoadBlock": @"webViewDidFinishLoad:", 74 | @"didFinishWithErrorBlock": @"webView:didFailLoadWithError:" 75 | }]; 76 | } 77 | } 78 | 79 | @end -------------------------------------------------------------------------------- /Tests/NSMutableArrayBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableArrayBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/7/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSMutableArrayBlocksKitTest.h" 10 | 11 | @implementation NSMutableArrayBlocksKitTest { 12 | NSMutableArray *_subject; 13 | NSInteger _total; 14 | } 15 | 16 | - (void)setUp { 17 | _subject = [@[ @"1", @"22", @"333" ] mutableCopy]; 18 | _total = 0; 19 | } 20 | 21 | - (void)testSelect { 22 | BKValidationBlock validationBlock = ^(NSString *obj) { 23 | _total += [obj length]; 24 | BOOL match = ([obj intValue] < 300) ? YES : NO; 25 | return match; 26 | }; 27 | [_subject performSelect:validationBlock]; 28 | 29 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 30 | NSMutableArray *target = [@[ @"1", @"22" ] mutableCopy]; 31 | STAssertEqualObjects(_subject,target,@"selected items are %@",_subject); 32 | } 33 | 34 | - (void)testSelectedNone { 35 | BKValidationBlock validationBlock = ^(NSString *obj) { 36 | _total += [obj length]; 37 | BOOL match = ([obj intValue] > 400) ? YES : NO; 38 | return match; 39 | }; 40 | [_subject performSelect:validationBlock]; 41 | 42 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 43 | STAssertEquals(_subject.count,(NSUInteger)0,@"no item is selected"); 44 | } 45 | 46 | - (void)testReject { 47 | BKValidationBlock validationBlock = ^(NSString *obj) { 48 | _total += [obj length]; 49 | BOOL match = ([obj intValue] > 300) ? YES : NO; 50 | return match; 51 | }; 52 | [_subject performReject:validationBlock]; 53 | 54 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 55 | NSMutableArray *target = [@[ @"1", @"22" ] mutableCopy]; 56 | STAssertEqualObjects(_subject,target,@"not rejected items are %@",_subject); 57 | } 58 | 59 | - (void)testRejectedAll { 60 | BKValidationBlock validationBlock = ^(NSString *obj) { 61 | _total += [obj length]; 62 | BOOL match = ([obj intValue] < 400) ? YES : NO; 63 | return match; 64 | }; 65 | [_subject performReject:validationBlock]; 66 | 67 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 68 | STAssertEquals(_subject.count,(NSUInteger)0,@"all items are rejected"); 69 | } 70 | 71 | - (void)testMap { 72 | BKTransformBlock transformBlock = ^(NSString *obj) { 73 | _total += [obj length]; 74 | return [obj substringToIndex:1]; 75 | }; 76 | [_subject performMap:transformBlock]; 77 | 78 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 79 | NSMutableArray *target = [@[ @"1", @"2", @"3" ] mutableCopy]; 80 | STAssertEqualObjects(_subject,target,@"transformed items are %@",_subject); 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /Tests/NSMutableSetBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableSetBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/7/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSMutableSetBlocksKitTest.h" 10 | 11 | 12 | @implementation NSMutableSetBlocksKitTest { 13 | NSMutableSet *_subject; 14 | NSInteger _total; 15 | } 16 | 17 | - (void)setUp { 18 | _subject = [NSMutableSet setWithArray: @[ @"1", @"22", @"333"]];; 19 | _total = 0; 20 | } 21 | 22 | - (void)testSelect { 23 | BKValidationBlock validationBlock = ^(NSString *obj) { 24 | _total += [obj length]; 25 | BOOL match = ([obj intValue] < 300) ? YES : NO; 26 | return match; 27 | }; 28 | [_subject performSelect:validationBlock]; 29 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 30 | NSMutableSet *target = [NSMutableSet setWithArray: @[ @"1", @"22" ]]; 31 | STAssertEqualObjects(_subject,target,@"selected items are %@",_subject); 32 | } 33 | 34 | - (void)testSelectedNone { 35 | BKValidationBlock validationBlock = ^(NSString *obj) { 36 | _total += [obj length]; 37 | BOOL match = ([obj intValue] > 400) ? YES : NO; 38 | return match; 39 | }; 40 | [_subject performSelect:validationBlock]; 41 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 42 | STAssertEquals(_subject.count,(NSUInteger)0,@"no item is selected"); 43 | } 44 | 45 | - (void)testReject { 46 | BKValidationBlock validationBlock = ^(NSString *obj) { 47 | _total += [obj length]; 48 | BOOL match = ([obj intValue] > 300) ? YES : NO; 49 | return match; 50 | }; 51 | [_subject performReject:validationBlock]; 52 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 53 | NSMutableSet *target = [NSMutableSet setWithArray: @[ @"1", @"22" ]]; 54 | STAssertEqualObjects(_subject,target,@"not rejected items are %@",_subject); 55 | } 56 | 57 | - (void)testRejectedAll { 58 | BKValidationBlock validationBlock = ^(NSString *obj) { 59 | _total += [obj length]; 60 | BOOL match = ([obj intValue] < 400) ? YES : NO; 61 | return match; 62 | }; 63 | [_subject performReject:validationBlock]; 64 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 65 | STAssertEquals(_subject.count,(NSUInteger)0,@"all items are rejected"); 66 | } 67 | 68 | - (void)testMap { 69 | BKTransformBlock transformBlock = ^(NSString *obj) { 70 | _total += [obj length]; 71 | return [obj substringToIndex:1]; 72 | }; 73 | [_subject performMap:transformBlock]; 74 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 75 | NSMutableSet *target = [NSMutableSet setWithArray: @[ @"1", @"2", @"3" ]]; 76 | STAssertEqualObjects(_subject,target,@"transformed items are %@",_subject); 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /Tests/NSMutableIndexSetBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableIndexSetBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/7/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSMutableIndexSetBlocksKitTest.h" 10 | 11 | 12 | @implementation NSMutableIndexSetBlocksKitTest { 13 | NSMutableIndexSet *_subject; 14 | NSMutableArray *_target; 15 | } 16 | 17 | - (void)setUp { 18 | _target = [@[@"0", @"0", @"0", @"0"] mutableCopy]; 19 | _subject = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(1, 3)]; 20 | } 21 | 22 | - (void)testSelect { 23 | NSMutableString *order = [NSMutableString string]; 24 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 25 | [order appendFormat:@"%lu", (unsigned long)index]; 26 | BOOL match = index < 3 ? YES : NO; //1,2 27 | return match; 28 | }; 29 | [_subject performSelect:indexValidationBlock]; 30 | STAssertEqualObjects(order,@"123",@"the index loop order is %@",order); 31 | NSMutableIndexSet *target = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(1,2)]; 32 | STAssertEqualObjects(_subject,target,@"the selected index set is %@",_subject); 33 | } 34 | 35 | - (void)testSelectedNone { 36 | NSMutableString *order = [NSMutableString string]; 37 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 38 | [order appendFormat:@"%lu", (unsigned long)index]; 39 | BOOL match = index == 0 ? YES : NO; 40 | return match; 41 | }; 42 | [_subject performSelect:indexValidationBlock]; 43 | STAssertEqualObjects(order,@"123",@"the index loop order is %@",order); 44 | STAssertEquals(_subject.count,(NSUInteger)0,@"no index found"); 45 | } 46 | 47 | - (void)testReject { 48 | NSMutableString *order = [NSMutableString string]; 49 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 50 | [order appendFormat:@"%lu", (unsigned long)index]; 51 | BOOL match = [_target[index] isEqual: @"0"] ? YES : NO; 52 | return match; 53 | }; 54 | [_subject performReject:indexValidationBlock]; 55 | STAssertEqualObjects(order,@"123",@"the index loop order is %@",order); 56 | STAssertEquals(_subject.count,(NSUInteger)0,@"all indexes are rejected"); 57 | } 58 | 59 | - (void)testRejectedNone { 60 | NSMutableString *order = [NSMutableString string]; 61 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 62 | [order appendFormat:@"%lu", (unsigned long)index]; 63 | BOOL match = [_target[index] isEqual: @"0"] ? NO : YES; 64 | return match; 65 | }; 66 | [_subject performReject:indexValidationBlock]; 67 | STAssertEqualObjects(order,@"123",@"the index loop order is %@",order); 68 | NSMutableIndexSet *target = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(1,3)]; 69 | STAssertEqualObjects(_subject,target,@"the rejected index set is %@",_subject); 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /Tests/NSMutableDictionaryBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableDictionaryBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/7/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSMutableDictionaryBlocksKitTest.h" 10 | 11 | @implementation NSMutableDictionaryBlocksKitTest { 12 | NSMutableDictionary *_subject; 13 | NSInteger _total; 14 | } 15 | 16 | - (void)setUp { 17 | _subject = [@{ 18 | @"1" : @(1), 19 | @"2" : @(2), 20 | @"3" : @(3) 21 | } mutableCopy]; 22 | _total = 0; 23 | } 24 | 25 | - (void)testSelect { 26 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 27 | _total += [value intValue] + [key intValue]; 28 | BOOL select = [value intValue] < 3 ? YES : NO; 29 | return select; 30 | }; 31 | [_subject performSelect:validationBlock]; 32 | STAssertEquals(_total,(NSInteger)12,@"2*(1+2+3) = %d",_total); 33 | NSDictionary *target = @{ @"1" : @(1), @"2" : @(2) }; 34 | STAssertEqualObjects(_subject,target,@"selected dictionary is %@",_subject); 35 | } 36 | 37 | - (void)testSelectedNone { 38 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 39 | _total += [value intValue] + [key intValue]; 40 | BOOL select = [value intValue] > 4 ? YES : NO; 41 | return select; 42 | }; 43 | [_subject performSelect:validationBlock]; 44 | STAssertEquals(_total,(NSInteger)12,@"2*(1+2+3) = %d",_total); 45 | STAssertEquals(_subject.count,(NSUInteger)0,@"no item is selected"); 46 | } 47 | 48 | - (void)testReject { 49 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 50 | _total += [value intValue] + [key intValue]; 51 | BOOL reject = [value intValue] > 2 ? YES : NO; 52 | return reject; 53 | }; 54 | [_subject performReject:validationBlock]; 55 | STAssertEquals(_total,(NSInteger)12,@"2*(1+2+3) = %d",_total); 56 | NSDictionary *target = @{ @"1" : @(1), @"2" : @(2) }; 57 | STAssertEqualObjects(_subject,target,@"dictionary after reject is %@",_subject); 58 | } 59 | 60 | - (void)testRejectedAll { 61 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 62 | _total += [value intValue] + [key intValue]; 63 | BOOL reject = [value intValue] < 4 ? YES : NO; 64 | return reject; 65 | }; 66 | [_subject performReject:validationBlock]; 67 | STAssertEquals(_total,(NSInteger)12,@"2*(1+2+3) = %d",_total); 68 | STAssertEquals(_subject.count,(NSUInteger)0,@"all items are rejected"); 69 | } 70 | 71 | - (void)testMap { 72 | BKKeyValueTransformBlock transformBlock = ^id(id key,id value) { 73 | _total += [value intValue] + [key intValue]; 74 | return @(_total); 75 | }; 76 | [_subject performMap:transformBlock]; 77 | STAssertEquals(_total,(NSInteger)12,@"2*(1+2+3) = %d",_total); 78 | NSDictionary *target = @{ 79 | @"1" : @(2), 80 | @"2" : @(6), 81 | @"3" : @(12) 82 | }; 83 | STAssertEqualObjects(_subject,target,@"transformed dictionary is %@",_subject); 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /Tests/UIWebViewBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIWebViewBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Zachary Waldowski on 12/20/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "UIWebViewBlocksKitTest.h" 10 | 11 | @implementation UIWebViewBlocksKitTest { 12 | UIWebView *_subject; 13 | BOOL shouldStartLoadDelegate, didStartLoadDelegate, didFinishLoadDelegate, didFinishWithErrorDelegate; 14 | } 15 | 16 | - (void)setUp { 17 | _subject = [[UIWebView alloc] initWithFrame: (CGRect){0, 0, 0, 0}]; 18 | } 19 | 20 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { 21 | shouldStartLoadDelegate = YES; 22 | return YES; 23 | } 24 | 25 | - (void)webViewDidStartLoad:(UIWebView *)webView { 26 | didStartLoadDelegate = YES; 27 | } 28 | 29 | - (void)webViewDidFinishLoad:(UIWebView *)webView { 30 | didFinishLoadDelegate = YES; 31 | } 32 | 33 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { 34 | didFinishWithErrorDelegate = YES; 35 | } 36 | 37 | - (void)testShouldStartLoad { 38 | _subject.delegate = self; 39 | 40 | __block BOOL shouldStartLoadBlock = NO; 41 | _subject.shouldStartLoadBlock = ^BOOL(UIWebView *view, NSURLRequest *req, UIWebViewNavigationType type){ 42 | shouldStartLoadBlock = YES; 43 | return YES; 44 | }; 45 | 46 | BOOL shouldStartLoad = [_subject.dynamicDelegate webView:_subject shouldStartLoadWithRequest:nil navigationType:UIWebViewNavigationTypeLinkClicked]; 47 | 48 | STAssertTrue(shouldStartLoad, @"Web view is allowed to load"); 49 | STAssertTrue(shouldStartLoadBlock, @"Block handler was called"); 50 | STAssertTrue(shouldStartLoadDelegate, @"Delegate was called"); 51 | } 52 | 53 | - (void)testDidStartLoad { 54 | _subject.delegate = self; 55 | 56 | __block BOOL didStartLoadBlock = NO; 57 | _subject.didStartLoadBlock = ^(UIWebView *view){ 58 | didStartLoadBlock = YES; 59 | }; 60 | 61 | [_subject.dynamicDelegate webViewDidStartLoad:_subject]; 62 | 63 | STAssertTrue(didStartLoadBlock, @"Block handler was called"); 64 | STAssertTrue(didStartLoadDelegate, @"Delegate was called"); 65 | } 66 | 67 | - (void)testDidFinishLoad { 68 | _subject.delegate = self; 69 | 70 | __block BOOL didFinishLoadBlock = NO; 71 | _subject.didFinishLoadBlock = ^(UIWebView *view){ 72 | didFinishLoadBlock = YES; 73 | }; 74 | 75 | [_subject.dynamicDelegate webViewDidFinishLoad:_subject]; 76 | 77 | STAssertTrue(didFinishLoadBlock, @"Block handler was called"); 78 | STAssertTrue(didFinishLoadDelegate, @"Delegate was called"); 79 | } 80 | 81 | - (void)testDidFinishWithError { 82 | _subject.delegate = self; 83 | 84 | __block BOOL didFinishWithErrorBlock = NO; 85 | _subject.didFinishWithErrorBlock = ^(UIWebView *view, NSError *err){ 86 | didFinishWithErrorBlock = YES; 87 | }; 88 | 89 | [_subject.dynamicDelegate webView:_subject didFailLoadWithError:nil]; 90 | 91 | STAssertTrue(didFinishWithErrorBlock, @"Block handler was called"); 92 | STAssertTrue(didFinishWithErrorDelegate, @"Delegate was called"); 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /BlocksKit/NSArray+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "NSArray+BlocksKit.h" 7 | 8 | @implementation NSArray (BlocksKit) 9 | 10 | - (void)each:(BKSenderBlock)block { 11 | NSParameterAssert(block != nil); 12 | 13 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 14 | block(obj); 15 | }]; 16 | } 17 | 18 | - (void)apply:(BKSenderBlock)block { 19 | NSParameterAssert(block != nil); 20 | 21 | [self enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 22 | block(obj); 23 | }]; 24 | } 25 | 26 | - (id)match:(BKValidationBlock)block { 27 | NSParameterAssert(block != nil); 28 | 29 | NSUInteger index = [self indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { 30 | return block(obj); 31 | }]; 32 | 33 | if (index == NSNotFound) 34 | return nil; 35 | 36 | return self[index]; 37 | } 38 | 39 | - (NSArray *)select:(BKValidationBlock)block { 40 | NSParameterAssert(block != nil); 41 | 42 | return [self objectsAtIndexes:[self indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { 43 | return block(obj); 44 | }]]; 45 | } 46 | 47 | - (NSArray *)reject:(BKValidationBlock)block { 48 | return [self select:^BOOL(id obj) { 49 | return !block(obj); 50 | }]; 51 | } 52 | 53 | - (NSArray *)map:(BKTransformBlock)block { 54 | NSParameterAssert(block != nil); 55 | 56 | NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.count]; 57 | 58 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 59 | id value = block(obj); 60 | if (!value) 61 | value = [NSNull null]; 62 | 63 | [result addObject:value]; 64 | }]; 65 | 66 | return result; 67 | } 68 | 69 | - (id)reduce:(id)initial withBlock:(BKAccumulationBlock)block { 70 | NSParameterAssert(block != nil); 71 | 72 | __block id result = initial; 73 | 74 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 75 | result = block(result, obj); 76 | }]; 77 | 78 | return result; 79 | } 80 | 81 | - (BOOL)any:(BKValidationBlock)block { 82 | return [self match: block] != nil; 83 | } 84 | 85 | - (BOOL)none:(BKValidationBlock)block { 86 | return [self match: block] == nil; 87 | } 88 | 89 | - (BOOL)all:(BKValidationBlock)block { 90 | NSParameterAssert(block != nil); 91 | 92 | __block BOOL result = YES; 93 | 94 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 95 | if (!block(obj)) { 96 | result = NO; 97 | *stop = YES; 98 | } 99 | }]; 100 | 101 | return result; 102 | } 103 | 104 | - (BOOL) corresponds: (NSArray *) list withBlock: (BKKeyValueValidationBlock) block { 105 | NSParameterAssert(block != nil); 106 | 107 | __block BOOL result = NO; 108 | 109 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 110 | if (idx < list.count) { 111 | id obj2 = list[idx]; 112 | result = block(obj, obj2); 113 | } else { 114 | result = NO; 115 | } 116 | *stop = !result; 117 | }]; 118 | 119 | return result; 120 | } 121 | 122 | @end -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIControl+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "UIControl+BlocksKit.h" 7 | #import "NSObject+AssociatedObjects.h" 8 | #import "NSSet+BlocksKit.h" 9 | 10 | static char kControlHandlersKey; 11 | 12 | #pragma mark Private 13 | 14 | @interface BKControlWrapper : NSObject 15 | 16 | - (id)initWithHandler:(BKSenderBlock)handler forControlEvents:(UIControlEvents)controlEvents; 17 | @property (nonatomic, copy) BKSenderBlock handler; 18 | @property (nonatomic) UIControlEvents controlEvents; 19 | 20 | @end 21 | 22 | @implementation BKControlWrapper 23 | 24 | - (id)initWithHandler:(BKSenderBlock)handler forControlEvents:(UIControlEvents)controlEvents { 25 | if ((self = [super init])) { 26 | self.handler = handler; 27 | self.controlEvents = controlEvents; 28 | } 29 | return self; 30 | } 31 | 32 | - (id)copyWithZone:(NSZone *)zone { 33 | return [[BKControlWrapper alloc] initWithHandler:self.handler forControlEvents:self.controlEvents]; 34 | } 35 | 36 | - (void)invoke:(id)sender { 37 | self.handler(sender); 38 | } 39 | 40 | @end 41 | 42 | #pragma mark Category 43 | 44 | @implementation UIControl (BlocksKit) 45 | 46 | - (void)addEventHandler:(BKSenderBlock)handler forControlEvents:(UIControlEvents)controlEvents { 47 | NSParameterAssert(handler); 48 | 49 | NSMutableDictionary *events = [self associatedValueForKey:&kControlHandlersKey]; 50 | if (!events) { 51 | events = [NSMutableDictionary dictionary]; 52 | [self associateValue:events withKey:&kControlHandlersKey]; 53 | } 54 | 55 | NSNumber *key = @(controlEvents); 56 | NSMutableSet *handlers = events[key]; 57 | if (!handlers) { 58 | handlers = [NSMutableSet set]; 59 | events[key] = handlers; 60 | } 61 | 62 | BKControlWrapper *target = [[BKControlWrapper alloc] initWithHandler:handler forControlEvents:controlEvents]; 63 | [handlers addObject:target]; 64 | [self addTarget:target action:@selector(invoke:) forControlEvents:controlEvents]; 65 | } 66 | 67 | - (void)removeEventHandlersForControlEvents:(UIControlEvents)controlEvents { 68 | NSMutableDictionary *events = [self associatedValueForKey:&kControlHandlersKey]; 69 | if (!events) { 70 | events = [NSMutableDictionary dictionary]; 71 | [self associateValue:events withKey:&kControlHandlersKey]; 72 | } 73 | 74 | NSNumber *key = @(controlEvents); 75 | NSSet *handlers = events[key]; 76 | 77 | if (!handlers) 78 | return; 79 | 80 | [handlers each:^(id sender) { 81 | [self removeTarget:sender action:NULL forControlEvents:controlEvents]; 82 | }]; 83 | 84 | [events removeObjectForKey:key]; 85 | } 86 | 87 | - (BOOL)hasEventHandlersForControlEvents:(UIControlEvents)controlEvents { 88 | NSMutableDictionary *events = [self associatedValueForKey:&kControlHandlersKey]; 89 | if (!events) { 90 | events = [NSMutableDictionary dictionary]; 91 | [self associateValue:events withKey:&kControlHandlersKey]; 92 | } 93 | 94 | NSNumber *key = @(controlEvents); 95 | NSSet *handlers = events[key]; 96 | 97 | if (!handlers) 98 | return NO; 99 | 100 | return !!handlers.count; 101 | } 102 | 103 | @end -------------------------------------------------------------------------------- /Tests/UIAlertViewBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertViewBlocksKitTest.m 3 | // BlocksKit 4 | // 5 | // Created by Zachary Waldowski on 12/20/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "UIAlertViewBlocksKitTest.h" 10 | 11 | @implementation UIAlertViewBlocksKitTest { 12 | UIAlertView *_subject; 13 | } 14 | 15 | - (void)setUp { 16 | _subject = [[UIAlertView alloc] initWithTitle:@"Hello BlocksKit" message:@"This is a message."]; 17 | } 18 | 19 | - (void)testInit { 20 | STAssertTrue([_subject isKindOfClass:[UIAlertView class]],@"subject is UIAlertView"); 21 | STAssertFalse([_subject.delegate isEqual: _subject.dynamicDelegate], @"the delegate is not dynamic"); 22 | STAssertEqualObjects(_subject.title,@"Hello BlocksKit",@"the UIAlertView title is %@",_subject.title); 23 | STAssertEquals(_subject.numberOfButtons, (NSInteger)0,@"the action sheet has %d buttons",_subject.numberOfButtons); 24 | STAssertFalse(_subject.isVisible,@"the action sheet is not visible"); 25 | } 26 | 27 | - (void)testAddButtonWithHandler { 28 | __block NSInteger total = 0; 29 | 30 | NSInteger index1 = [_subject addButtonWithTitle:@"Button 1" handler:^{ total++; }]; 31 | NSInteger index2 = [_subject addButtonWithTitle:@"Button 2" handler:^{ total += 2; }]; 32 | 33 | STAssertEquals(_subject.numberOfButtons,(NSInteger)2,@"the alert view has %d buttons",_subject.numberOfButtons); 34 | 35 | NSString *title = @"Button"; 36 | title = [_subject buttonTitleAtIndex:index1]; 37 | STAssertEqualObjects(title,@"Button 1",@"the UIActionSheet adds a button with title %@",title); 38 | 39 | title = [_subject buttonTitleAtIndex:index2]; 40 | STAssertEqualObjects(title,@"Button 2",@"the UIActionSheet adds a button with title %@",title); 41 | 42 | [_subject.dynamicDelegate alertView:_subject clickedButtonAtIndex:index1]; 43 | [_subject.dynamicDelegate alertView:_subject clickedButtonAtIndex:index2]; 44 | 45 | STAssertEquals(total, 3, @"Not all block handlers were called."); 46 | } 47 | 48 | - (void)testSetCancelButtonWithHandler { 49 | __block BOOL blockCalled = NO; 50 | 51 | NSInteger index = [_subject setCancelButtonWithTitle:@"Cancel" handler:^{ blockCalled = YES; }]; 52 | STAssertEquals(_subject.numberOfButtons,1,@"the alert view has %d buttons",_subject.numberOfButtons); 53 | STAssertEquals(_subject.cancelButtonIndex,index,@"the alert view cancel button index is %d",_subject.cancelButtonIndex); 54 | 55 | NSString *title = [_subject buttonTitleAtIndex:index]; 56 | STAssertEqualObjects(title,@"Cancel",@"the UIActionSheet adds a button with title %@",title); 57 | 58 | [_subject.dynamicDelegate alertViewCancel:_subject]; 59 | 60 | STAssertTrue(blockCalled, @"Block handler was not called."); 61 | } 62 | 63 | - (void)testDelegationBlocks { 64 | __block BOOL willShow = NO; 65 | __block BOOL didShow = NO; 66 | 67 | _subject.willShowBlock = ^(UIAlertView *view) { willShow = YES; }; 68 | _subject.didShowBlock = ^(UIAlertView *view) { didShow = YES; }; 69 | 70 | [_subject.dynamicDelegate willPresentAlertView:_subject]; 71 | [_subject.dynamicDelegate didPresentAlertView:_subject]; 72 | 73 | STAssertTrue(willShow, @"willShowBlock not fired."); 74 | STAssertTrue(didShow, @"didShowblock not fired."); 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIView+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Convenience on-touch methods for UIView. 9 | 10 | Includes code by the following: 11 | 12 | - Kevin O'Neill. . 2011. BSD. 13 | - Jake Marsh. . 2011. 14 | - Zach Waldowski. . 2011. 15 | 16 | @warning UIView is only available on a platform with UIKit. 17 | */ 18 | @interface UIView (BlocksKit) 19 | 20 | /** Abstract creation of a block-backed UITapGestureRecognizer. 21 | 22 | This method allows for the recognition of any arbitrary number 23 | of fingers tapping any number of times on a view. An instance 24 | of UITapGesture recognizer is allocated for the block and added 25 | to the recieving view. 26 | 27 | @warning This method has an _additive_ effect. Do not call it multiple 28 | times to set-up or tear-down. The view will discard the gesture recognizer 29 | on release. 30 | 31 | @param numberOfTouches The number of fingers tapping that will trigger the block. 32 | @param numberOfTaps The number of taps required to trigger the block. 33 | @param block The handler for the UITapGestureRecognizer 34 | @see whenTapped: 35 | @see whenDoubleTapped: 36 | */ 37 | - (void)whenTouches:(NSUInteger)numberOfTouches tapped:(NSUInteger)numberOfTaps handler:(BKBlock)block; 38 | 39 | /** Adds a recognizer for one finger tapping once. 40 | 41 | @warning This method has an _additive_ effect. Do not call it multiple 42 | times to set-up or tear-down. The view will discard the gesture recognizer 43 | on release. 44 | 45 | @param block The handler for the tap recognizer 46 | @see whenDoubleTapped: 47 | @see whenTouches:tapped:handler: 48 | */ 49 | - (void)whenTapped:(BKBlock)block; 50 | 51 | /** Adds a recognizer for one finger tapping twice. 52 | 53 | @warning This method has an _additive_ effect. Do not call it multiple 54 | times to set-up or tear-down. The view will discard the gesture recognizer 55 | on release. 56 | 57 | @param block The handler for the tap recognizer 58 | @see whenTapped: 59 | @see whenTouches:tapped:handler: 60 | */ 61 | - (void)whenDoubleTapped:(BKBlock)block; 62 | 63 | /** A convenience wrapper that non-recursively loops through the subviews of a view. 64 | 65 | @param block A code block that interacts with a UIView sender. 66 | */ 67 | - (void)eachSubview:(void(^)(UIView *))block; 68 | 69 | /** The block that gets called on a finger down. 70 | 71 | Internally, this method overrides the touchesBegan:withEvent: 72 | selector of UIView and is mechanically similar to 73 | UIControlEventTouchDown. 74 | */ 75 | @property (nonatomic, copy) BKTouchBlock onTouchDownBlock; 76 | 77 | /** The block that gets called on a finger drag. 78 | 79 | Internally, this method overrides the touchesMoved:withEvent: 80 | selector of UIView. 81 | */ 82 | @property (nonatomic, copy) BKTouchBlock onTouchMoveBlock; 83 | 84 | /** The block that gets called on a finger up. 85 | 86 | Internally, this method overrides the touchesBegan:withEvent: 87 | selector of UIView and is mechanically similar to 88 | UIControlEventTouchCancel. 89 | */ 90 | @property (nonatomic, copy) BKTouchBlock onTouchUpBlock; 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /Tests/NSMutableOrderedSetBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableOrderedSetBlocksKitTest.m 3 | // BlocksKit 4 | // 5 | // Created by Zachary Waldowski on 10/6/12. 6 | // Copyright (c) 2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSMutableOrderedSetBlocksKitTest.h" 10 | 11 | @implementation NSMutableOrderedSetBlocksKitTest { 12 | id _subject; 13 | NSInteger _total; 14 | BOOL _hasClassAvailable; 15 | } 16 | 17 | - (void)setUp { 18 | Class BKOrderedSet = NSClassFromString(@"NSMutableOrderedSet"); 19 | if (BKOrderedSet) { 20 | _hasClassAvailable = YES; 21 | _subject = [NSMutableOrderedSet orderedSetWithArray: @[ @"1", @"22", @"333" ]]; 22 | } else { 23 | _hasClassAvailable = NO; 24 | } 25 | _total = 0; 26 | } 27 | 28 | - (void)testSelect { 29 | BKValidationBlock validationBlock = ^(NSString *obj) { 30 | _total += [obj length]; 31 | BOOL match = ([obj intValue] < 300) ? YES : NO; 32 | return match; 33 | }; 34 | NSMutableOrderedSet *subject = _subject; 35 | [subject performSelect:validationBlock]; 36 | 37 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 38 | NSMutableOrderedSet *target = [NSMutableOrderedSet orderedSetWithArray: @[ @"1", @"22" ]]; 39 | STAssertEqualObjects(subject,target,@"selected items are %@",_subject); 40 | } 41 | 42 | - (void)testSelectedNone { 43 | BKValidationBlock validationBlock = ^(NSString *obj) { 44 | _total += [obj length]; 45 | BOOL match = ([obj intValue] > 400) ? YES : NO; 46 | return match; 47 | }; 48 | NSMutableOrderedSet *subject = _subject; 49 | [subject performSelect:validationBlock]; 50 | 51 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 52 | STAssertEquals(subject.count,(NSUInteger)0,@"no item is selected"); 53 | } 54 | 55 | - (void)testReject { 56 | BKValidationBlock validationBlock = ^(NSString *obj) { 57 | _total += [obj length]; 58 | BOOL match = ([obj intValue] > 300) ? YES : NO; 59 | return match; 60 | }; 61 | NSMutableOrderedSet *subject = _subject; 62 | [subject performReject:validationBlock]; 63 | 64 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 65 | NSMutableOrderedSet *target = [NSMutableOrderedSet orderedSetWithArray: @[ @"1", @"22" ]]; 66 | STAssertEqualObjects(subject,target,@"not rejected items are %@",_subject); 67 | } 68 | 69 | - (void)testRejectedAll { 70 | BKValidationBlock validationBlock = ^(NSString *obj) { 71 | _total += [obj length]; 72 | BOOL match = ([obj intValue] < 400) ? YES : NO; 73 | return match; 74 | }; 75 | NSMutableOrderedSet *subject = _subject; 76 | [subject performReject:validationBlock]; 77 | 78 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 79 | STAssertEquals(subject.count,(NSUInteger)0,@"all items are rejected"); 80 | } 81 | 82 | - (void)testMap { 83 | BKTransformBlock transformBlock = ^(NSString *obj) { 84 | _total += [obj length]; 85 | return [obj substringToIndex:1]; 86 | }; 87 | NSMutableOrderedSet *subject = _subject; 88 | [subject performMap:transformBlock]; 89 | 90 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 91 | NSMutableOrderedSet *target = [NSMutableOrderedSet orderedSetWithArray: @[ @"1", @"22", @"333" ]]; 92 | STAssertEqualObjects(subject,target,@"transformed items are %@",_subject); 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /BlocksKit/NSOrderedSet+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSOrderedSet+BlocksKit.m 3 | // BlocksKit 4 | // 5 | // Created by Zachary Waldowski on 10/5/12. 6 | // Copyright (c) 2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSOrderedSet+BlocksKit.h" 10 | 11 | @implementation NSOrderedSet (BlocksKit) 12 | 13 | - (void)each:(BKSenderBlock)block { 14 | NSParameterAssert(block != nil); 15 | 16 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 17 | block(obj); 18 | }]; 19 | } 20 | 21 | - (void)apply:(BKSenderBlock)block { 22 | NSParameterAssert(block != nil); 23 | 24 | [self enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 25 | block(obj); 26 | }]; 27 | } 28 | 29 | - (id)match:(BKValidationBlock)block { 30 | NSParameterAssert(block != nil); 31 | 32 | NSUInteger index = [self indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { 33 | return block(obj); 34 | }]; 35 | 36 | if (index == NSNotFound) 37 | return nil; 38 | 39 | return self[index]; 40 | } 41 | 42 | - (NSOrderedSet *)select:(BKValidationBlock)block { 43 | NSParameterAssert(block != nil); 44 | 45 | NSArray *objects = [self objectsAtIndexes:[self indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { 46 | return block(obj); 47 | }]]; 48 | 49 | if (!objects.count) 50 | return [[self class] orderedSet]; 51 | 52 | return [[self class] orderedSetWithArray: objects]; 53 | } 54 | 55 | - (NSOrderedSet *)reject:(BKValidationBlock)block { 56 | return [self select:^BOOL(id obj) { 57 | return !block(obj); 58 | }]; 59 | } 60 | 61 | - (NSOrderedSet *)map:(BKTransformBlock)block { 62 | NSParameterAssert(block != nil); 63 | 64 | NSMutableOrderedSet *result = [NSMutableOrderedSet orderedSetWithCapacity: self.count]; 65 | 66 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 67 | id value = block(obj); 68 | if (!value) 69 | value = [NSNull null]; 70 | 71 | [result addObject:value]; 72 | }]; 73 | 74 | return result; 75 | } 76 | 77 | - (id)reduce:(id)initial withBlock:(BKAccumulationBlock)block { 78 | NSParameterAssert(block != nil); 79 | 80 | __block id result = initial; 81 | 82 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 83 | result = block(result, obj); 84 | }]; 85 | 86 | return result; 87 | } 88 | 89 | - (BOOL)any:(BKValidationBlock)block { 90 | return [self match: block] != nil; 91 | } 92 | 93 | - (BOOL)none:(BKValidationBlock)block { 94 | return [self match: block] == nil; 95 | } 96 | 97 | - (BOOL) all: (BKValidationBlock)block { 98 | NSParameterAssert(block != nil); 99 | 100 | __block BOOL result = YES; 101 | 102 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 103 | if (!block(obj)) { 104 | result = NO; 105 | *stop = YES; 106 | } 107 | }]; 108 | 109 | return result; 110 | } 111 | 112 | - (BOOL) corresponds: (NSOrderedSet *) list withBlock: (BKKeyValueValidationBlock) block { 113 | NSParameterAssert(block != nil); 114 | 115 | __block BOOL result = NO; 116 | 117 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 118 | if (idx < list.count) { 119 | id obj2 = list[idx]; 120 | result = block(obj, obj2); 121 | } else { 122 | result = NO; 123 | } 124 | *stop = !result; 125 | }]; 126 | 127 | return result; 128 | } 129 | 130 | @end -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIView+BlocksKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+BlocksKit.m 3 | // BlocksKit 4 | // 5 | 6 | #import "UIView+BlocksKit.h" 7 | #import "NSObject+AssociatedObjects.h" 8 | #import "UIGestureRecognizer+BlocksKit.h" 9 | #import "NSArray+BlocksKit.h" 10 | 11 | static char kViewTouchDownBlockKey; 12 | static char kViewTouchMoveBlockKey; 13 | static char kViewTouchUpBlockKey; 14 | 15 | @implementation UIView (BlocksKit) 16 | 17 | - (void)whenTouches:(NSUInteger)numberOfTouches tapped:(NSUInteger)numberOfTaps handler:(BKBlock)block { 18 | if (!block) 19 | return; 20 | 21 | UITapGestureRecognizer *gesture = [UITapGestureRecognizer recognizerWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) { 22 | if (state == UIGestureRecognizerStateRecognized) block(); 23 | }]; 24 | 25 | [gesture setNumberOfTouchesRequired:numberOfTouches]; 26 | [gesture setNumberOfTapsRequired:numberOfTaps]; 27 | 28 | [[self.gestureRecognizers select:^BOOL(id obj) { 29 | if ([obj isKindOfClass:[UITapGestureRecognizer class]]) { 30 | BOOL rightTouches = ([(UITapGestureRecognizer *)obj numberOfTouchesRequired] == numberOfTouches); 31 | BOOL rightTaps = ([(UITapGestureRecognizer *)obj numberOfTapsRequired] == numberOfTaps); 32 | return (rightTouches && rightTaps); 33 | } 34 | return NO; 35 | }] each:^(id obj) { 36 | [gesture requireGestureRecognizerToFail:(UITapGestureRecognizer *)obj]; 37 | }]; 38 | 39 | [self addGestureRecognizer:gesture]; 40 | } 41 | 42 | - (void)whenTapped:(BKBlock)block { 43 | [self whenTouches:1 tapped:1 handler:block]; 44 | } 45 | 46 | - (void)whenDoubleTapped:(BKBlock)block { 47 | [self whenTouches:2 tapped:1 handler:block]; 48 | } 49 | 50 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 51 | [super touchesBegan:touches withEvent:event]; 52 | BKTouchBlock block = [self associatedValueForKey:&kViewTouchDownBlockKey]; 53 | if (block) 54 | block(touches, event); 55 | } 56 | 57 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 58 | [super touchesMoved:touches withEvent:event]; 59 | BKTouchBlock block = [self associatedValueForKey:&kViewTouchMoveBlockKey]; 60 | if (block) 61 | block(touches, event); 62 | } 63 | 64 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 65 | [super touchesEnded:touches withEvent:event]; 66 | BKTouchBlock block = [self associatedValueForKey:&kViewTouchUpBlockKey]; 67 | if (block) 68 | block(touches, event); 69 | } 70 | 71 | - (void)eachSubview:(void(^)(UIView *))block { 72 | [self.subviews each:(BKSenderBlock)block]; 73 | } 74 | 75 | #pragma mark Properties 76 | 77 | - (void)setOnTouchDownBlock:(BKTouchBlock)block { 78 | [self associateCopyOfValue:block withKey:&kViewTouchDownBlockKey]; 79 | } 80 | 81 | - (BKTouchBlock)onTouchDownBlock { 82 | return [self associatedValueForKey:&kViewTouchDownBlockKey]; 83 | } 84 | 85 | - (void)setOnTouchMoveBlock:(BKTouchBlock)block { 86 | [self associateCopyOfValue:block withKey:&kViewTouchMoveBlockKey]; 87 | } 88 | 89 | - (BKTouchBlock)onTouchMoveBlock { 90 | return [self associatedValueForKey:&kViewTouchMoveBlockKey]; 91 | } 92 | 93 | - (void)setOnTouchUpBlock:(BKTouchBlock)block { 94 | [self associateCopyOfValue:block withKey:&kViewTouchUpBlockKey]; 95 | } 96 | 97 | - (BKTouchBlock)onTouchUpBlock { 98 | return [self associatedValueForKey:&kViewTouchUpBlockKey]; 99 | } 100 | 101 | @end -------------------------------------------------------------------------------- /Tests/iOS Tests/PNDAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // PNDAppDelegate.m 3 | // iOS Tests App 4 | // 5 | // Created by Alexsander Akers on 10/5/12. 6 | // Copyright (c) 2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "PNDAppDelegate.h" 10 | 11 | @implementation PNDAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 16 | self.window.backgroundColor = [UIColor whiteColor]; 17 | 18 | UIViewController *rootViewController = [[UIViewController alloc] init]; 19 | UILabel *label = [[UILabel alloc] init]; 20 | label.font = [UIFont systemFontOfSize: [UIFont labelFontSize]]; 21 | label.text = @"Testing…"; 22 | [label sizeToFit]; 23 | 24 | label.frame = CGRectMake((rootViewController.view.bounds.size.width - label.frame.size.width) / 2.0, (rootViewController.view.bounds.size.height - label.frame.size.height) / 2.0, label.frame.size.width, label.frame.size.height); 25 | [rootViewController.view addSubview: label]; 26 | 27 | UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleGray]; 28 | activityIndicator.frame = CGRectMake((rootViewController.view.bounds.size.width - activityIndicator.frame.size.width) / 2.0, (rootViewController.view.bounds.size.height - label.frame.size.height) / 2.0 - activityIndicator.frame.size.height - 8, activityIndicator.frame.size.width, activityIndicator.frame.size.height); 29 | [activityIndicator startAnimating]; 30 | [rootViewController.view addSubview: activityIndicator]; 31 | 32 | self.window.rootViewController = rootViewController; 33 | [self.window makeKeyAndVisible]; 34 | 35 | return YES; 36 | } 37 | 38 | - (void)applicationWillResignActive:(UIApplication *)application 39 | { 40 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 41 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 42 | } 43 | 44 | - (void)applicationDidEnterBackground:(UIApplication *)application 45 | { 46 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 47 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 48 | } 49 | 50 | - (void)applicationWillEnterForeground:(UIApplication *)application 51 | { 52 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 53 | } 54 | 55 | - (void)applicationDidBecomeActive:(UIApplication *)application 56 | { 57 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 58 | } 59 | 60 | - (void)applicationWillTerminate:(UIApplication *)application 61 | { 62 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /Tests/A2BlockDelegateTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // A2BlockDelegateTests.h 3 | // BlocksKit Unit Tests 4 | // 5 | 6 | #import 7 | #import 8 | #import 9 | #import "A2DynamicDelegateTests.h" 10 | 11 | #pragma mark - 12 | 13 | @interface TestReturnObject (A2BlockDelegate) 14 | 15 | @property (nonatomic, copy) NSString *(^testReturnObjectBlock)(void); 16 | 17 | @end 18 | 19 | #pragma mark - 20 | 21 | @interface TestReturnStruct (A2BlockDelegate) 22 | 23 | @property (nonatomic, copy) MyStruct(^testReturnStructBlock)(void); 24 | 25 | @end 26 | 27 | #pragma mark - 28 | 29 | @interface TestPassObject (A2BlockDelegate) 30 | 31 | @property (nonatomic, copy) BOOL(^testWithObjectBlock)(NSString *); 32 | 33 | @end 34 | 35 | #pragma mark - 36 | 37 | @interface TestPassChar (A2BlockDelegate) 38 | 39 | @property (nonatomic, copy) BOOL(^testWithCharBlock)(char); 40 | 41 | @end 42 | 43 | #pragma mark - 44 | 45 | @interface TestPassUChar (A2BlockDelegate) 46 | 47 | @property (nonatomic, copy) BOOL(^testWithUCharBlock)(unsigned char); 48 | 49 | @end 50 | 51 | #pragma mark - 52 | 53 | @interface TestPassShort (A2BlockDelegate) 54 | 55 | @property (nonatomic, copy) BOOL(^testWithShortBlock)(short); 56 | 57 | @end 58 | 59 | #pragma mark - 60 | 61 | @interface TestPassUShort (A2BlockDelegate) 62 | 63 | @property (nonatomic, copy) BOOL(^testWithUShortBlock)(unsigned short); 64 | 65 | @end 66 | 67 | #pragma mark - 68 | 69 | @interface TestPassInt (A2BlockDelegate) 70 | 71 | @property (nonatomic, copy) BOOL(^testWithIntBlock)(int); 72 | 73 | @end 74 | 75 | #pragma mark - 76 | 77 | @interface TestPassUInt (A2BlockDelegate) 78 | 79 | @property (nonatomic, copy) BOOL(^testWithUIntBlock)(unsigned int); 80 | 81 | @end 82 | 83 | #pragma mark - 84 | 85 | @interface TestPassLong (A2BlockDelegate) 86 | 87 | @property (nonatomic, copy) BOOL(^testWithLongBlock)(long); 88 | 89 | @end 90 | 91 | #pragma mark - 92 | 93 | @interface TestPassULong (A2BlockDelegate) 94 | 95 | @property (nonatomic, copy) BOOL(^testWithULongBlock)(unsigned long); 96 | 97 | @end 98 | 99 | #pragma mark - 100 | 101 | @interface TestPassLongLong (A2BlockDelegate) 102 | 103 | @property (nonatomic, copy) BOOL(^testWithLongLongBlock)(long long); 104 | 105 | @end 106 | 107 | #pragma mark - 108 | 109 | @interface TestPassULongLong (A2BlockDelegate) 110 | 111 | @property (nonatomic, copy) BOOL(^testWithULongLongBlock)(unsigned long long); 112 | 113 | @end 114 | 115 | #pragma mark - 116 | 117 | @interface TestPassFloat (A2BlockDelegate) 118 | 119 | @property (nonatomic, copy) BOOL(^testWithFloatBlock)(float); 120 | 121 | @end 122 | 123 | #pragma mark -= 124 | 125 | @interface TestPassDouble (A2BlockDelegate) 126 | 127 | @property (nonatomic, copy) BOOL(^testWithDoubleBlock)(double); 128 | 129 | @end 130 | 131 | #pragma mark - 132 | 133 | @interface TestPassArray (A2BlockDelegate) 134 | 135 | @property (nonatomic, copy) BOOL(^testWithArrayBlock)(int []); 136 | 137 | @end 138 | 139 | #pragma mark - 140 | 141 | @interface TestPassStruct (A2BlockDelegate) 142 | 143 | @property (nonatomic, copy) BOOL(^testWithStructBlock)(MyStruct); 144 | 145 | @end 146 | 147 | #pragma mark - 148 | 149 | @interface A2BlockDelegateTests : SenTestCase 150 | 151 | - (void)testReturnObject; 152 | - (void)testReturnStruct; 153 | - (void)testPassObject; 154 | - (void)testPassChar; 155 | - (void)testPassUChar; 156 | - (void)testPassShort; 157 | - (void)testPassUShort; 158 | - (void)testPassInt; 159 | - (void)testPassUInt; 160 | - (void)testPassLong; 161 | - (void)testPassULong; 162 | - (void)testPassLongLong; 163 | - (void)testPassULongLong; 164 | - (void)testPassFloat; 165 | - (void)testPassDouble; 166 | - (void)testPassArray; 167 | - (void)testPassStruct; 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /BlocksKit/NSIndexSet+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexSet+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block extensions for NSIndexSet. 9 | 10 | Both inspired by and resembling Smalltalk syntax, these utilities 11 | allows for iteration of an array in a concise way that 12 | saves quite a bit of boilerplate code for filtering or finding 13 | objects or an object. 14 | 15 | Includes code by the following: 16 | 17 | - [Robin Lu](https://github.com/robin) 18 | - [Michael Ash](https://github.com/mikeash) 19 | - [Zach Waldowski](https://github.com/zwaldowski) 20 | 21 | @see NSArray(BlocksKit) 22 | @see NSDictionary(BlocksKit) 23 | @see NSSet(BlocksKit) 24 | */ 25 | @interface NSIndexSet (BlocksKit) 26 | 27 | /** Loops through an index set and executes the given block at each index. 28 | 29 | @param block A single-argument, void-returning code block. 30 | */ 31 | - (void)each:(BKIndexBlock)block; 32 | 33 | /** Enumerates each index in an index set concurrently and executes the 34 | given block once per index. 35 | 36 | Enumeration will occur on appropriate background queues. 37 | Be aware that the block will not necessarily be executed 38 | in order for each index. 39 | 40 | @param block A single-argument, void-returning code block. 41 | */ 42 | - (void)apply:(BKIndexBlock)block; 43 | 44 | /** Loops through an array and returns the index matching the block. 45 | 46 | @param block A single-argument, `BOOL`-returning code block. 47 | @return Returns the index if found, `NSNotFound` otherwise. 48 | @see select: 49 | */ 50 | - (NSUInteger)match:(BKIndexValidationBlock)block; 51 | 52 | /** Loops through an index set and returns an all indexes matching the block. 53 | 54 | @param block A single-argument, BOOL-returning code block. 55 | @return Returns an index set of matching indexes found. 56 | @see match: 57 | */ 58 | - (NSIndexSet *)select:(BKIndexValidationBlock)block; 59 | 60 | /** Loops through an index set and returns an all indexes but the ones matching the block. 61 | 62 | This selector performs *literally* the exact same function as select: but in reverse. 63 | 64 | @param block A single-argument, BOOL-returning code block. 65 | @return Returns an index set of all indexes but those matched. 66 | */ 67 | - (NSIndexSet *)reject:(BKIndexValidationBlock)block; 68 | 69 | /** Call the block once for each index and create an index set with the new values. 70 | 71 | @param block A block that returns a new index for an index. 72 | @return An index set of the indexes returned by the block. 73 | */ 74 | - (NSIndexSet *)map:(BKIndexTransformBlock)block; 75 | 76 | /** Loops through an index set to find whether any of the indexes matche the block. 77 | 78 | This method is similar to the Scala list `exists`. It is functionally 79 | identical to match: but returns a `BOOL` instead. It is not recommended 80 | to use any: as a check condition before executing match:, since it would 81 | require two loops through the index set. 82 | 83 | @param block A single-argument, BOOL-returning code block. 84 | @return YES for the first time the block returns YES for an index, NO otherwise. 85 | */ 86 | - (BOOL)any:(BKIndexValidationBlock)block; 87 | 88 | /** Loops through an index set to find whether all objects match the block. 89 | 90 | @param block A single-argument, BOOL-returning code block. 91 | @return YES if the block returns YES for all indexes in the array, NO otherwise. 92 | */ 93 | - (BOOL)all:(BKIndexValidationBlock)block; 94 | 95 | /** Loops through an index set to find whether no objects match the block. 96 | 97 | This selector performs *literally* the exact same function as all: but in reverse. 98 | 99 | @param block A single-argument, BOOL-returning code block. 100 | @return YES if the block returns NO for all indexes in the array, NO otherwise. 101 | */ 102 | - (BOOL)none:(BKIndexValidationBlock)block; 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Tests/UIActionSheetBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIActionSheetBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/7/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "UIActionSheetBlocksKitTest.h" 10 | 11 | @implementation UIActionSheetBlocksKitTest { 12 | UIActionSheet *_subject; 13 | } 14 | 15 | - (void)setUp { 16 | _subject = [[UIActionSheet alloc] initWithTitle:@"Hello BlocksKit"]; 17 | } 18 | 19 | - (void)testInit { 20 | STAssertTrue([_subject isKindOfClass:[UIActionSheet class]],@"subject is UIActionSheet"); 21 | STAssertFalse([_subject.delegate isEqual: _subject.dynamicDelegate], @"the delegate is not the dynamic delegate"); 22 | STAssertEqualObjects(_subject.title,@"Hello BlocksKit",@"the UIActionSheet title is %@",_subject.title); 23 | STAssertEquals(_subject.numberOfButtons,0,@"the action sheet has %d buttons",_subject.numberOfButtons); 24 | STAssertFalse(_subject.isVisible,@"the action sheet is not visible"); 25 | } 26 | 27 | - (void)testAddButtonWithHandler { 28 | __block NSInteger total = 0; 29 | 30 | NSInteger index1 = [_subject addButtonWithTitle:@"Button 1" handler:^{ total++; }]; 31 | NSInteger index2 = [_subject addButtonWithTitle:@"Button 2" handler:^{ total += 2; }]; 32 | 33 | STAssertEquals(_subject.numberOfButtons,2,@"the action sheet has %d buttons",_subject.numberOfButtons); 34 | 35 | NSString *title = @"Button"; 36 | title = [_subject buttonTitleAtIndex:index1]; 37 | STAssertEqualObjects(title,@"Button 1",@"the UIActionSheet adds a button with title %@",title); 38 | 39 | title = [_subject buttonTitleAtIndex:index2]; 40 | STAssertEqualObjects(title,@"Button 2",@"the UIActionSheet adds a button with title %@",title); 41 | 42 | [_subject.dynamicDelegate actionSheet:_subject clickedButtonAtIndex:index1]; 43 | [_subject.dynamicDelegate actionSheet:_subject clickedButtonAtIndex:index2]; 44 | 45 | STAssertEquals(total, 3, @"Not all block handlers were called."); 46 | } 47 | 48 | - (void)testSetDestructiveButtonWithHandler { 49 | __block BOOL blockCalled = NO; 50 | 51 | NSInteger index = [_subject setDestructiveButtonWithTitle:@"Delete" handler:^{ blockCalled = YES; }]; 52 | STAssertEquals(_subject.numberOfButtons,1,@"the action sheet has %d buttons",_subject.numberOfButtons); 53 | STAssertEquals(_subject.destructiveButtonIndex,index,@"the action sheet destructive button index is %d",_subject.destructiveButtonIndex); 54 | 55 | NSString *title = [_subject buttonTitleAtIndex:index]; 56 | STAssertEqualObjects(title,@"Delete",@"the UIActionSheet adds a button with title %@",title); 57 | 58 | [_subject.dynamicDelegate actionSheet:_subject clickedButtonAtIndex:index]; 59 | 60 | STAssertTrue(blockCalled, @"Block handler was not called."); 61 | } 62 | 63 | - (void)testSetCancelButtonWithHandler { 64 | __block BOOL blockCalled = NO; 65 | 66 | NSInteger index = [_subject setCancelButtonWithTitle:@"Cancel" handler:^{ blockCalled = YES; }]; 67 | STAssertEquals(_subject.numberOfButtons,1,@"the action sheet has %d buttons",_subject.numberOfButtons); 68 | STAssertEquals(_subject.cancelButtonIndex,index,@"the action sheet cancel button index is %d",_subject.cancelButtonIndex); 69 | 70 | NSString *title = [_subject buttonTitleAtIndex:index]; 71 | STAssertEqualObjects(title,@"Cancel",@"the UIActionSheet adds a button with title %@",title); 72 | 73 | [_subject.dynamicDelegate actionSheetCancel:_subject]; 74 | 75 | STAssertTrue(blockCalled, @"Block handler was not called."); 76 | } 77 | 78 | - (void)testDelegationBlocks { 79 | __block BOOL willShow = NO; 80 | __block BOOL didShow = NO; 81 | 82 | _subject.willShowBlock = ^(UIActionSheet *sheet) { willShow = YES; }; 83 | _subject.didShowBlock = ^(UIActionSheet *sheet) { didShow = YES; }; 84 | 85 | [_subject.dynamicDelegate willPresentActionSheet:_subject]; 86 | [_subject.dynamicDelegate didPresentActionSheet:_subject]; 87 | 88 | STAssertTrue(willShow, @"willShowBlock not fired."); 89 | STAssertTrue(didShow, @"didShowblock not fired."); 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /BlocksKit/NSObject+AssociatedObjects.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+AssociatedObjects.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Objective-C wrapper for 10.6+ associated object API. 9 | 10 | In Mac OS X Snow Leopard and iOS 3.0, Apple introduced an addition to the 11 | Objective-C Runtime called associated objects. Associated objects allow for the 12 | pairing of a random key and object pair to be saved on an instance. 13 | 14 | In BlocksKit, associated objects allow us to emulate instance variables in the 15 | ategories we use. 16 | 17 | Class methods also exist for each association. These associations are unique to 18 | each class, and exist for the lifetime of the application unless set to `nil`. 19 | Each class is a unique meta-object; the ultimate singleton. 20 | 21 | Created by [Andy Matuschak](https://github.com/andymatuschak) as 22 | `AMAssociatedObjects`. 23 | */ 24 | @interface NSObject (BKAssociatedObjects) 25 | 26 | /** Strongly associates an object with the reciever. 27 | 28 | The associated value is retained as if it were a property 29 | synthesized with `nonatomic` and `retain`. 30 | 31 | Using retained association is strongly recommended for most 32 | Objective-C object derivative of NSObject, particularly 33 | when it is subject to being externally released or is in an 34 | `NSAutoreleasePool`. 35 | 36 | @param value Any object. 37 | @param key A unique key pointer. 38 | */ 39 | - (void)associateValue:(id)value withKey:(const void *)key; 40 | 41 | /** Strongly associates an object with the receiving class. 42 | 43 | @see associateValue:withKey: 44 | @param value Any object. 45 | @param key A unique key pointer. 46 | */ 47 | + (void)associateValue:(id)value withKey:(const void *)key; 48 | 49 | /** Associates a copy of an object with the reciever. 50 | 51 | The associated value is copied as if it were a property 52 | synthesized with `nonatomic` and `copy`. 53 | 54 | Using copied association is recommended for a block or 55 | otherwise `NSCopying`-compliant instances like NSString. 56 | 57 | @param value Any object, pointer, or value. 58 | @param key A unique key pointer. 59 | */ 60 | - (void)associateCopyOfValue:(id)value withKey:(const void *)key; 61 | 62 | /** Associates a copy of an object with the receiving class. 63 | 64 | @see associateCopyOfValue:withKey: 65 | @param value Any object, pointer, or value. 66 | @param key A unique key pointer. 67 | */ 68 | + (void)associateCopyOfValue:(id)value withKey:(const void *)key; 69 | 70 | /** Weakly associates an object with the reciever. 71 | 72 | A weak association will cause the pointer to be set to zero 73 | or nil upon the disappearance of what it references; 74 | in other words, the associated object is not kept alive. 75 | 76 | @param value Any object. 77 | @param key A unique key pointer. 78 | */ 79 | - (void)weaklyAssociateValue:(id)value withKey:(const void *)key; 80 | 81 | /** Weakly associates an object with the receiving class. 82 | 83 | @see weaklyAssociateValue:withKey: 84 | @param value Any object. 85 | @param key A unique key pointer. 86 | */ 87 | + (void)weaklyAssociateValue:(id)value withKey:(const void *)key; 88 | 89 | /** Returns the associated value for a key on the reciever. 90 | 91 | @param key A unique key pointer. 92 | @return The object associated with the key, or `nil` if not found. 93 | */ 94 | - (id)associatedValueForKey:(const void *)key; 95 | 96 | /** Returns the associated value for a key on the receiving class. 97 | 98 | @see associatedValueForKey: 99 | @param key A unique key pointer. 100 | @return The object associated with the key, or `nil` if not found. 101 | */ 102 | + (id)associatedValueForKey:(const void *)key; 103 | 104 | /** Returns the reciever to a clean state by removing all 105 | associated objects, releasing them if necessary. */ 106 | - (void)removeAllAssociatedObjects; 107 | 108 | /** Returns the recieving class to a clean state by removing 109 | all associated objects, releasing them if necessary. */ 110 | + (void)removeAllAssociatedObjects; 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /BlocksKit/NSDictionary+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block extension for NSDictionary. 9 | 10 | Both inspired by and resembling Smalltalk syntax, this utility 11 | allows iteration of a dictionary in a concise way that 12 | saves quite a bit of boilerplate code. 13 | 14 | Includes code by the following: 15 | 16 | - [Mirko Kiefer](https://github.com/mirkok) 17 | - [Zach Waldowski](https://github.com/zwaldowski) 18 | 19 | @see NSArray(BlocksKit) 20 | @see NSSet(BlocksKit) 21 | */ 22 | @interface NSDictionary (BlocksKit) 23 | 24 | /** Loops through the dictionary and executes the given block using each item. 25 | 26 | @param block A block that performs an action using a key/value pair. 27 | */ 28 | - (void)each:(BKKeyValueBlock)block; 29 | 30 | /** Enumerates through the dictionary concurrently and executes 31 | the given block once for each pair. 32 | 33 | Enumeration will occur on appropriate background queues; 34 | the system will spawn threads as need for execution. This 35 | will have a noticeable speed increase, especially on dual-core 36 | devices, but you *must* be aware of the thread safety of the 37 | objects you message from within the block. 38 | 39 | @param block A block that performs an action using a key/value pair. 40 | */ 41 | - (void)apply:(BKKeyValueBlock)block; 42 | 43 | /** Loops through a dictionary to find the first key/value pair matching the block. 44 | 45 | match: is functionally identical to select:, but will stop and return 46 | the value on the first match. 47 | 48 | @param block A BOOL-returning code block for a key/value pair. 49 | @return The value of the first pair found; 50 | */ 51 | - (id)match:(BKKeyValueValidationBlock)block; 52 | 53 | /** Loops through a dictionary to find the key/value pairs matching the block. 54 | 55 | @param block A BOOL-returning code block for a key/value pair. 56 | @return Returns a dictionary of the objects found. 57 | */ 58 | - (NSDictionary *)select:(BKKeyValueValidationBlock)block; 59 | 60 | /** Loops through a dictionary to find the key/value pairs not matching the block. 61 | 62 | This selector performs *literally* the exact same function as select: but in reverse. 63 | 64 | This is useful, as one may expect, for filtering objects. 65 | NSDictionary *strings = [userData reject:^BOOL(id key, id value) { 66 | return ([obj isKindOfClass:[NSString class]]); 67 | }]; 68 | 69 | @param block A BOOL-returning code block for a key/value pair. 70 | @return Returns a dictionary of all objects not found. 71 | */ 72 | - (NSDictionary *)reject:(BKKeyValueValidationBlock)block; 73 | 74 | /** Call the block once for each object and create a dictionary with the same keys 75 | and a new set of values. 76 | 77 | @param block A block that returns a new value for a key/value pair. 78 | @return Returns a dictionary of the objects returned by the block. 79 | */ 80 | - (NSDictionary *)map:(BKKeyValueTransformBlock)block; 81 | 82 | /** Loops through a dictionary to find whether any key/value pair matches the block. 83 | 84 | This method is similar to the Scala list `exists`. It is functionally 85 | identical to match: but returns a `BOOL` instead. It is not recommended 86 | to use any: as a check condition before executing match:, since it would 87 | require two loops through the dictionary. 88 | 89 | @param block A two-argument, BOOL-returning code block. 90 | @return YES for the first time the block returns YES for a key/value pair, NO otherwise. 91 | */ 92 | - (BOOL)any:(BKKeyValueValidationBlock)block; 93 | 94 | /** Loops through a dictionary to find whether no key/value pairs match the block. 95 | 96 | This selector performs *literally* the exact same function as all: but in reverse. 97 | 98 | @param block A two-argument, BOOL-returning code block. 99 | @return YES if the block returns NO for all key/value pairs in the dictionary, NO otherwise. 100 | */ 101 | - (BOOL)none:(BKKeyValueValidationBlock)block; 102 | 103 | /** Loops through a dictionary to find whether all key/value pairs match the block. 104 | 105 | @param block A two-argument, BOOL-returning code block. 106 | @return YES if the block returns YES for all key/value pairs in the dictionary, NO otherwise. 107 | */ 108 | - (BOOL)all:(BKKeyValueValidationBlock)block; 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /Tests/NSObjectBlockObservationTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObjectBlockObservationTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/5/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSObjectBlockObservationTest.h" 10 | 11 | @interface SubjectKVCAndKVO : NSObject 12 | 13 | @property (nonatomic, getter=isKVC) BOOL kvc; //scalar 14 | @property (nonatomic, strong) NSNumber *number; //scalar 15 | @property (nonatomic, strong) NSObjectBlockObservationTest *test; //to-one 16 | @property (nonatomic, strong) NSMutableArray *names; //ordered to-many 17 | @property (nonatomic, strong) NSMutableSet *members; //unordered to-many 18 | 19 | - (void)insertObject:(NSString *)name inNamesAtIndex:(NSUInteger)index; 20 | - (void)removeObjectFromNamesAtIndex:(NSUInteger)index; 21 | - (void)addMembersObject:(NSString *)member; 22 | - (void)removeMembersObject:(NSString *)member; 23 | 24 | @end 25 | 26 | @implementation SubjectKVCAndKVO 27 | 28 | - (id)initSubjectWithTest:(NSObjectBlockObservationTest *)test { 29 | if ( (self = [super init]) ) { 30 | self.kvc = YES; 31 | self.test = test; 32 | self.number = @(0); 33 | self.names = [@[ @"one", @"two" ] mutableCopy]; 34 | self.members = [NSMutableSet setWithArray: @[ @"foo", @"bar" ]]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)insertObject:(NSString *)name inNamesAtIndex:(NSUInteger)index { 40 | [_names insertObject:name atIndex:index]; 41 | } 42 | 43 | - (void)removeObjectFromNamesAtIndex:(NSUInteger)index { 44 | [_names removeObjectAtIndex:index]; 45 | } 46 | 47 | - (void)addMembersObject:(NSString *)member { 48 | [_members addObject:member]; 49 | } 50 | 51 | - (void)removeMembersObject:(NSString *)member { 52 | [_members removeObject:member]; 53 | } 54 | 55 | @end 56 | 57 | @implementation NSObjectBlockObservationTest { 58 | SubjectKVCAndKVO *_subject; 59 | NSInteger _total; 60 | } 61 | 62 | - (void)setUp { 63 | _subject = [[SubjectKVCAndKVO alloc] initSubjectWithTest:self]; 64 | _total = 0; 65 | } 66 | 67 | - (void)action { 68 | _total += 1; 69 | } 70 | 71 | - (void)testBoolKeyValueObservation { 72 | BKSenderBlock observeBlock = ^(id obj) { 73 | [(NSObjectBlockObservationTest *)obj action]; 74 | }; 75 | NSString *token = [self addObserverForKeyPath:@"subject.kvc" task:observeBlock]; 76 | 77 | [self setValue:@NO forKeyPath:@"subject.kvc"]; 78 | STAssertFalse(_subject.kvc, @"kvc is NO"); 79 | STAssertEquals(_total, (NSInteger)1, @"total is %d", _total); 80 | [self removeObserverForKeyPath:@"subject.kvc" identifier:token]; 81 | } 82 | 83 | - (void)testNSNumberKeyValueObservation { 84 | BKSenderBlock observeBlock = ^(id obj) { 85 | [(NSObjectBlockObservationTest *)obj action]; 86 | }; 87 | NSString *token = [self addObserverForKeyPath:@"subject.number" task:observeBlock]; 88 | 89 | NSNumber *number = @1; 90 | [self setValue:number forKeyPath:@"subject.number"]; 91 | STAssertEquals(_subject.number,number,@"number is %@",_subject.number); 92 | STAssertEquals(_total, (NSInteger)1, @"total is %d", _total); 93 | 94 | [self removeObserverForKeyPath:@"subject.number" identifier:token]; 95 | } 96 | 97 | - (void)testNSArrayKeyValueObservation { 98 | BKSenderBlock observeBlock = ^(id obj) { 99 | [(NSObjectBlockObservationTest *)obj action]; 100 | }; 101 | NSString *token = [self addObserverForKeyPath:@"subject.names" task:observeBlock]; 102 | 103 | NSMutableArray *names = [self mutableArrayValueForKeyPath:@"subject.names"]; 104 | names[0] = @"1"; 105 | names[1] = @"2"; 106 | NSArray *target = @[ @"1", @"2" ]; 107 | STAssertEqualObjects(_subject.names,target,@"names are %@",_subject.names); 108 | STAssertEquals(_total, (NSInteger)2, @"total is %d", _total); 109 | [self removeObserverForKeyPath:@"subject.names" identifier:token]; 110 | } 111 | 112 | - (void)testNSSetKeyValueObservation { 113 | BKSenderBlock observeBlock = ^(id obj) { 114 | [(NSObjectBlockObservationTest *)obj action]; 115 | }; 116 | NSString *token = [self addObserverForKeyPath:@"subject.members" task:observeBlock]; 117 | 118 | NSMutableSet *members = [self mutableSetValueForKeyPath:@"subject.members"]; 119 | [members removeObject:@"bar"]; 120 | [members addObject:@"one"]; 121 | NSSet *target = [NSSet setWithArray: @[ @"foo", @"one" ]]; 122 | STAssertEqualObjects(_subject.members,target,@"members are %@",_subject.members); 123 | STAssertEquals(_total, (NSInteger)2, @"total is %d", _total); 124 | [self removeObserverForKeyPath:@"subject.members" identifier:token]; 125 | } 126 | @end 127 | -------------------------------------------------------------------------------- /BlocksKit/A2BlockDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // A2BlockDelegate.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** The A2BlockDelegate category extends features provided by A2DynamicDelegate 9 | to create custom block properties in a category on a delegating object and 10 | dynamically map them to delegate (`UIAlertViewDelegate`), data source 11 | (`UITableViewDataSource`), or other delegated protocol 12 | (`NSErrorRecoveryAttempting`) methods. 13 | 14 | A2BlockDelegate also supports replacing the delegate of a given class with an 15 | automatic - though optional - version of these block-based properties, while 16 | still allowing for traditional method-based delegate implementations. 17 | 18 | Simply call one of the methods in the category on a class to add a block 19 | property to that class, preferably during a `+load` method in a category. 20 | 21 | To interplay between classes that support delegation but are extended to have 22 | block properties through delegate replacement extended to have block 23 | properties, one should implement an `A2Dynamic` subclass of 24 | `A2DynamicDelegate`. This behavior is documented in detail in the class 25 | documentation for A2DynamicDelegate, and examples exist in BlocksKit. 26 | */ 27 | @interface NSObject (A2BlockDelegate) 28 | 29 | /** @name Linking Block Properties */ 30 | 31 | /** Synthesizes multiple properties and links them to the appropriate selector 32 | in the data source protocol. 33 | 34 | A2DynamicDelegate assumes a protocol name `FooBarDataSource` for instances of 35 | class `FooBar`. The method will generate appropriate `setHandler:` and 36 | `handler` implementations for each property name and selector name string pair. 37 | 38 | @param selectorsForPropertyNames A dictionary with property names as keys and 39 | selector strings as objects. 40 | */ 41 | + (void) linkDataSourceMethods: (NSDictionary *) selectorsForPropertyNames; 42 | 43 | /** Synthesizes multiple properties and links them to the appropriate selector 44 | in the delegate protocol. 45 | 46 | A2DynamicDelegate assumes a protocol name `FooBarDelegate` for instances of 47 | class `FooBar`. The method will generate appropriate `setHandler:` and 48 | `handler` implementations for each property name and selector name string pair. 49 | 50 | @param selectorsForPropertyNames A dictionary with property names as keys and 51 | selectors strings as objects. 52 | */ 53 | + (void) linkDelegateMethods: (NSDictionary *) selectorsForPropertyNames; 54 | 55 | /** Synthesizes multiple properties and links them to the appropriate selector 56 | in the given protocol. 57 | 58 | The method will generate appropriate `setHandler:` and `handler` 59 | implementations for each property name and selector name string pair. 60 | 61 | @param protocol A protocol that declares all of the given selectors. Must not 62 | be NULL. 63 | @param selectorsForPropertyNames A dictionary with property names as keys and 64 | selector strings as objects. 65 | */ 66 | + (void) linkProtocol: (Protocol *) protocol methods: (NSDictionary *) selectorsForPropertyNames; 67 | 68 | /** @name Delegate replacement properties */ 69 | 70 | /** Registers a dynamic data source replacement using the property name 71 | `dataSource` and the protocol name `FooBarDataSource` for an instance of 72 | `FooBar`. */ 73 | + (void) registerDynamicDataSource; 74 | 75 | /** Registers a dynamic delegate replacement using the property name `delegate` 76 | and the protocol name `FooBarDelegate` for an instance of `FooBar`. */ 77 | + (void) registerDynamicDelegate; 78 | 79 | /** Registers a dynamic data source replacement using the given property name 80 | and the protocol name `FooBarDataSource` for an instance of `FooBar`. 81 | 82 | @param dataSourceName The name of the class' data source property. Must not be 83 | nil. 84 | */ 85 | + (void) registerDynamicDataSourceNamed: (NSString *) dataSourceName; 86 | 87 | /** Registers a dynamic delegate replacement using the given property name and 88 | the protocol name `FooBarDelegate` for an instance of `FooBar`. 89 | 90 | @param delegateName The name of the class' delegate property. Must not be nil. 91 | */ 92 | + (void) registerDynamicDelegateNamed: (NSString *) delegateName; 93 | 94 | /** Registers a dynamic protocol implementation replacement 95 | using the given property name and the given protocol. 96 | 97 | @param delegateName The name of the class' delegation protocol property, such 98 | as `safeDelegate`. Must not be nil. 99 | @param protocol A properly encoded protocol. Must not be NULL. 100 | */ 101 | + (void) registerDynamicDelegateNamed: (NSString *) delegateName forProtocol: (Protocol *) protocol; 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIGestureRecognizer+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIGestureRecognizer+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block functionality for UIGestureRecognizer. 9 | 10 | Use of the delay property is pretty straightforward, although 11 | cancellation might be a little harder to swallow. An example 12 | follows: 13 | UITapGestureRecognizer *singleTap = [UITapGestureRecognizer recognizerWithHandler:^(id sender) { 14 | NSLog(@"Single tap."); 15 | } delay:0.18]; 16 | [self addGestureRecognizer:singleTap]; 17 | 18 | UITapGestureRecognizer *doubleTap = [UITapGestureRecognizer recognizerWithHandler:^(id sender) { 19 | [singleTap cancel]; 20 | NSLog(@"Double tap."); 21 | }]; 22 | doubleTap.numberOfTapsRequired = 2; 23 | [self addGestureRecognizer:doubleTap]; 24 | 25 | Believe it or not, the above code is fully memory-safe and efficient. Eagle-eyed coders 26 | will notice that this setup emulates UIGestureRecognizer's requireGestureRecognizerToFail:, 27 | and, yes, it totally apes it. Not only is this setup much faster on the user's end of 28 | things, it is more flexible and allows for much more complicated setups. 29 | 30 | Includes code by the following: 31 | 32 | - [Kevin O'Neill](https://github.com/kevinoneill) 33 | - [Zach Waldowski](https://github.com/zwaldowski) 34 | 35 | @warning UIGestureRecognizer is only available on a platform with UIKit. 36 | 37 | @warning It is not recommended to use the Apple-supplied locationInView and state 38 | methods on a *delayed* block-backed gesture recognizer, as these properties are 39 | likely to have been cleared by the time by the block fires. It is instead recommended 40 | to use the arguments provided to the block. 41 | */ 42 | 43 | @interface UIGestureRecognizer (BlocksKit) 44 | 45 | /** An autoreleased gesture recognizer that will, on firing, call 46 | the given block asynchronously after a number of seconds. 47 | 48 | @return An autoreleased instance of a concrete UIGestureRecognizer subclass, or `nil`. 49 | @param block The block which handles an executed gesture. 50 | @param delay A number of seconds after which the block will fire. 51 | */ 52 | + (id)recognizerWithHandler:(BKGestureRecognizerBlock)block delay:(NSTimeInterval)delay; 53 | 54 | /** Initializes an allocated gesture recognizer that will call the given block 55 | after a given delay. 56 | 57 | An alternative to the designated initializer. 58 | 59 | @return An initialized instance of a concrete UIGestureRecognizer subclass or `nil`. 60 | @param block The block which handles an executed gesture. 61 | @param delay A number of seconds after which the block will fire. 62 | */ 63 | - (id)initWithHandler:(BKGestureRecognizerBlock)block delay:(NSTimeInterval)delay; 64 | 65 | /** An autoreleased gesture recognizer that will call the given block. 66 | 67 | For convenience and compatibility reasons, this method is indentical 68 | to using recognizerWithHandler:delay: with a delay of 0.0. 69 | 70 | @return An initialized and autoreleased instance of a concrete UIGestureRecognizer 71 | subclass, or `nil`. 72 | @param block The block which handles an executed gesture. 73 | */ 74 | + (id)recognizerWithHandler:(BKGestureRecognizerBlock)block; 75 | 76 | /** Initializes an allocated gesture recognizer that will call the given block. 77 | 78 | This method is indentical to calling initWithHandler:delay: with a delay of 0.0. 79 | 80 | @return An initialized instance of a concrete UIGestureRecognizer subclass or `nil`. 81 | @param block The block which handles an executed gesture. 82 | */ 83 | - (id)initWithHandler:(BKGestureRecognizerBlock)block; 84 | 85 | /** Allows the block that will be fired by the gesture recognizer 86 | to be modified after the fact. 87 | */ 88 | @property (nonatomic, copy) BKGestureRecognizerBlock handler; 89 | 90 | /** Allows the length of the delay after which the gesture 91 | recognizer will be fired to modify. */ 92 | @property (nonatomic) NSTimeInterval handlerDelay; 93 | 94 | /** Allows the length of the delay after which the gesture 95 | recognizer will be fired to modify. 96 | 97 | @warning Due to a collision with an internal method in 98 | UILongPressGestureRecognizer, this method was replaced 99 | with handlerDelay starting in BlocksKit 1.0.5. 100 | 101 | */ 102 | @property (nonatomic) NSTimeInterval delay DEPRECATED_ATTRIBUTE_M("Use handlerDelay"); 103 | 104 | /** If the recognizer happens to be fired, calling this method 105 | will stop it from firing, but only if a delay is set. 106 | 107 | @warning This method is not for arbitrarily canceling the 108 | firing of a recognizer, but will only function for a block 109 | handler *after the recognizer has already been fired*. Be 110 | sure to make your delay times accomodate this likelihood. 111 | */ 112 | - (void)cancel; 113 | 114 | @end -------------------------------------------------------------------------------- /Tests/NSDictionaryBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionaryBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/3/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSDictionaryBlocksKitTest.h" 10 | 11 | @implementation NSDictionaryBlocksKitTest { 12 | NSDictionary *_subject; 13 | NSInteger _total; 14 | } 15 | 16 | - (void)setUp { 17 | _subject = @{ 18 | @"1" : @(1), 19 | @"2" : @(2), 20 | @"3" : @(3), 21 | }; 22 | _total = 0; 23 | } 24 | 25 | - (void)tearDown { 26 | _subject = nil; 27 | } 28 | 29 | - (void)testEach { 30 | BKKeyValueBlock keyValueBlock = ^(id key,id value) { 31 | _total += [value intValue] + [key intValue]; 32 | }; 33 | 34 | [_subject each:keyValueBlock]; 35 | STAssertEquals(_total, (NSInteger)12, @"2*(1+2+3) = %d", _total); 36 | } 37 | 38 | - (void)testMatch { 39 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 40 | _total += [value intValue] + [key intValue]; 41 | BOOL select = [value intValue] < 3 ? YES : NO; 42 | return select; 43 | }; 44 | NSDictionary *selected = [_subject match:validationBlock]; 45 | STAssertEquals(_total, (NSInteger)2, @"2*1 = %d", _total); 46 | STAssertEqualObjects(selected, @(1), @"selected value is %@", selected); 47 | } 48 | 49 | - (void)testSelect { 50 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 51 | _total += [value intValue] + [key intValue]; 52 | BOOL select = [value intValue] < 3 ? YES : NO; 53 | return select; 54 | }; 55 | NSDictionary *selected = [_subject select: validationBlock]; 56 | STAssertEquals(_total, (NSInteger)12, @"2*(1+2+3) = %d", _total); 57 | NSDictionary *target = @{ @"1" : @(1), @"2" : @(2) }; 58 | STAssertEqualObjects(selected, target, @"selected dictionary is %@", selected); 59 | } 60 | 61 | - (void)testSelectedNone { 62 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 63 | _total += [value intValue] + [key intValue]; 64 | BOOL select = [value intValue] > 4 ? YES : NO; 65 | return select; 66 | }; 67 | NSDictionary *selected = [_subject select: validationBlock]; 68 | STAssertEquals(_total, (NSInteger)12, @"2*(1+2+3) = %d", _total); 69 | STAssertTrue(selected.count == 0, @"none item is selected"); 70 | } 71 | 72 | - (void)testReject { 73 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 74 | _total += [value intValue] + [key intValue]; 75 | BOOL reject = [value intValue] < 3 ? YES : NO; 76 | return reject; 77 | }; 78 | NSDictionary *rejected = [_subject reject: validationBlock]; 79 | STAssertEquals(_total, (NSInteger)12, @"2*(1+2+3) = %d", _total); 80 | NSDictionary *target = @{ @"3" : @(3) }; 81 | STAssertEqualObjects(rejected, target, @"dictionary after rejection is %@", rejected); 82 | } 83 | 84 | - (void)testRejectedAll { 85 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 86 | _total += [value intValue] + [key intValue]; 87 | BOOL reject = [value intValue] < 4 ? YES : NO; 88 | return reject; 89 | }; 90 | NSDictionary *rejected = [_subject reject: validationBlock]; 91 | STAssertEquals(_total, (NSInteger)12, @"2*(1+2+3) = %d", _total); 92 | STAssertTrue(rejected.count == 0, @"all items are selected"); 93 | } 94 | 95 | - (void)testMap { 96 | BKKeyValueTransformBlock transformBlock = ^id(id key,id value) { 97 | _total += [value intValue] + [key intValue]; 98 | return @(_total); 99 | }; 100 | NSDictionary *transformed = [_subject map: transformBlock]; 101 | STAssertEquals(_total, (NSInteger)12, @"2*(1+2+3) = %d", _total); 102 | NSDictionary *target = @{ @"1" : @(2), @"2" : @(6), @"3" : @(12) }; 103 | STAssertEqualObjects(transformed,target,@"transformed dictionary is %@",transformed); 104 | } 105 | 106 | - (void)testAny { 107 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 108 | _total += [value intValue] + [key intValue]; 109 | BOOL select = [value intValue] < 3 ? YES : NO; 110 | return select; 111 | }; 112 | BOOL isSelected = [_subject any: validationBlock]; 113 | STAssertEquals(_total, (NSInteger)2, @"2*1 = %d", _total); 114 | STAssertEquals(isSelected, YES, @"found selected value is %i", isSelected); 115 | } 116 | 117 | - (void)testAll { 118 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 119 | _total += [value intValue] + [key intValue]; 120 | BOOL select = [value intValue] < 4 ? YES : NO; 121 | return select; 122 | }; 123 | BOOL allSelected = [_subject all: validationBlock]; 124 | STAssertEquals(_total, (NSInteger)12, @"2*(1+2+3) = %d", _total); 125 | STAssertTrue(allSelected, @"all values matched test"); 126 | } 127 | 128 | - (void)testNone { 129 | BKKeyValueValidationBlock validationBlock = ^(id key,id value) { 130 | _total += [value intValue] + [key intValue]; 131 | BOOL select = [value intValue] < 2 ? YES : NO; 132 | return select; 133 | }; 134 | BOOL noneSelected = [_subject all: validationBlock]; 135 | STAssertEquals(_total, (NSInteger)6, @"2*(1+2) = %d", _total); 136 | STAssertFalse(noneSelected, @"not all values matched test"); 137 | } 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /BlocksKit/NSSet+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSSet+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** Block extensions for NSSet. 9 | 10 | Both inspired by and resembling Smalltalk syntax, these utilities allows for 11 | iteration of a set in a logical way that saves quite a bit of boilerplate code 12 | for filtering or finding objects or an object. 13 | 14 | Includes code by the following: 15 | 16 | - [Michael Ash](https://github.com/mikeash) 17 | - [Corey Floyd](https://github.com/coreyfloyd) 18 | - [Aleks Nesterow](https://github.com/nesterow) 19 | - [Zach Waldowski](https://github.com/zwaldowski) 20 | 21 | @see NSArray(BlocksKit) 22 | @see NSDictionary(BlocksKit) 23 | */ 24 | @interface NSSet (BlocksKit) 25 | 26 | /** Loops through a set and executes the given block with each object. 27 | 28 | @param block A single-argument, void-returning code block. 29 | */ 30 | - (void)each:(BKSenderBlock)block; 31 | 32 | /** Enumerates through a set concurrently and executes 33 | the given block once for each object. 34 | 35 | Enumeration will occur on appropriate background queues. This 36 | will have a noticeable speed increase, especially on dual-core 37 | devices, but you *must* be aware of the thread safety of the 38 | objects you message from within the block. 39 | 40 | @param block A single-argument, void-returning code block. 41 | */ 42 | - (void)apply:(BKSenderBlock)block; 43 | 44 | /** Loops through a set to find the object matching the block. 45 | 46 | match: is functionally identical to select:, but will stop and return 47 | on the first match. 48 | 49 | @param block A single-argument, BOOL-returning code block. 50 | @return Returns the object if found, `nil` otherwise. 51 | @see select: 52 | */ 53 | - (id)match:(BKValidationBlock)block; 54 | 55 | /** Loops through a set to find the objects matching the block. 56 | 57 | @param block A single-argument, BOOL-returning code block. 58 | @return Returns a set of the objects found. 59 | @see match: 60 | */ 61 | - (NSSet *)select:(BKValidationBlock)block; 62 | 63 | /** Loops through a set to find the objects not matching the block. 64 | 65 | This selector performs *literally* the exact same function as select, but in reverse. 66 | 67 | This is useful, as one may expect, for removing objects from a set: 68 | NSSet *new = [reusableWebViews reject:^BOOL(id obj) { 69 | return ([obj isLoading]); 70 | }]; 71 | 72 | @param block A single-argument, BOOL-returning code block. 73 | @return Returns an array of all objects not found. 74 | */ 75 | - (NSSet *)reject:(BKValidationBlock)block; 76 | 77 | /** Call the block once for each object and create a set of the return values. 78 | 79 | This is sometimes referred to as a transform, mutating one of each object: 80 | NSSet *new = [mimeTypes map:^id(id obj) { 81 | return [@"x-company-" stringByAppendingString:obj]); 82 | }]; 83 | 84 | @param block A single-argument, object-returning code block. 85 | @return Returns a set of the objects returned by the block. 86 | */ 87 | - (NSSet *)map:(BKTransformBlock)block; 88 | 89 | /** Arbitrarily accumulate objects using a block. 90 | 91 | The concept of this selector is difficult to illustrate in words. The sum can 92 | be any NSObject, including (but not limited to) a string, number, or value. 93 | 94 | You can also do something like summing the count of an item: 95 | NSUInteger numberOfBodyParts = [[bodyList reduce:nil withBlock:^id(id sum, id obj) { 96 | return @([sum integerValue] + obj.numberOfAppendages); 97 | }] unsignedIntegerValue]; 98 | 99 | @param initial The value of the reduction at its start. 100 | @param block A block that takes the current sum and the next object to return the new sum. 101 | @return An accumulated value. 102 | */ 103 | - (id)reduce:(id)initial withBlock:(BKAccumulationBlock)block; 104 | 105 | /** Loops through a set to find whether any object matches the block. 106 | 107 | This method is similar to the Scala list `exists`. It is functionally 108 | identical to match: but returns a `BOOL` instead. It is not recommended 109 | to use any: as a check condition before executing match:, since it would 110 | require two loops through the array. 111 | 112 | @param block A single-argument, BOOL-returning code block. 113 | @return YES for the first time the block returns YES for an object, NO otherwise. 114 | */ 115 | - (BOOL)any:(BKValidationBlock)block; 116 | 117 | /** Loops through a set to find whether no objects match the block. 118 | 119 | This selector performs *literally* the exact same function as all: but in reverse. 120 | 121 | @param block A single-argument, BOOL-returning code block. 122 | @return YES if the block returns NO for all objects in the set, NO otherwise. 123 | */ 124 | - (BOOL)none:(BKValidationBlock)block; 125 | 126 | /** Loops through a set to find whether all objects match the block. 127 | 128 | @param block A single-argument, BOOL-returning code block. 129 | @return YES if the block returns YES for all objects in the set, NO otherwise. 130 | */ 131 | - (BOOL)all:(BKValidationBlock)block; 132 | 133 | @end -------------------------------------------------------------------------------- /Tests/NSIndexSetBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexSetBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/3/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSIndexSetBlocksKitTest.h" 10 | 11 | @implementation NSIndexSetBlocksKitTest { 12 | NSIndexSet *_subject; 13 | NSMutableArray *_target; 14 | } 15 | 16 | - (void)setUp { 17 | _target = [@[@"0", @"0", @"0", @"0"] mutableCopy]; 18 | _subject = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(1, 3)]; 19 | } 20 | 21 | - (void)testEach { 22 | NSMutableString *order = [NSMutableString string]; 23 | BKIndexBlock indexBlock = ^(NSUInteger index) { 24 | [order appendFormat:@"%lu", (unsigned long)index]; 25 | _target[index] = [NSString stringWithFormat:@"%lu", (unsigned long)index]; 26 | }; 27 | [_subject each: indexBlock]; 28 | STAssertTrue([order isEqualToString: @"123"], @"the index loop order is %@", order); 29 | NSArray *target = @[ @"0", @"1", @"2", @"3" ]; 30 | STAssertEqualObjects(_target, target, @"the target array becomes %@", _target); 31 | } 32 | 33 | - (void)testMatch { 34 | NSMutableString *order = [NSMutableString string]; 35 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 36 | [order appendFormat:@"%lu", (unsigned long)index]; 37 | BOOL match = NO; 38 | if (index%2 == 0 ) { 39 | _target[index] = [NSString stringWithFormat:@"%lu", (unsigned long)index]; 40 | match = YES; 41 | } 42 | return match; 43 | }; 44 | NSUInteger found = [_subject match:indexValidationBlock]; 45 | STAssertTrue([order isEqualToString: @"12"], @"the index loop order is %@", order); 46 | STAssertEqualObjects(_target[found], @"2", @"the target array becomes %@", _target); 47 | } 48 | 49 | - (void)testNotMatch { 50 | NSMutableString *order = [NSMutableString string]; 51 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 52 | [order appendFormat: @"%lu", (unsigned long)index]; 53 | BOOL match = index > 4 ? YES : NO; 54 | return match; 55 | }; 56 | NSUInteger found = [_subject match: indexValidationBlock]; 57 | STAssertTrue([order isEqualToString: @"123"], @"the index loop order is %@", order); 58 | STAssertEquals((NSUInteger)found, (NSUInteger)NSNotFound, @"no items are found"); 59 | } 60 | 61 | - (void)testSelect { 62 | NSMutableString *order = [NSMutableString string]; 63 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 64 | [order appendFormat:@"%lu", (unsigned long)index]; 65 | BOOL match = index < 3 ? YES : NO; 66 | return match; 67 | }; 68 | NSIndexSet *found = [_subject select: indexValidationBlock]; 69 | STAssertTrue([order isEqualToString: @"123"], @"the index loop order is %@", order); 70 | NSIndexSet *target = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(1,2)]; 71 | STAssertEqualObjects(found, target, @"the selected index set is %@", found); 72 | } 73 | 74 | - (void)testSelectedNone { 75 | NSMutableString *order = [NSMutableString string]; 76 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 77 | [order appendFormat:@"%lu", (unsigned long)index]; 78 | BOOL match = index == 0 ? YES : NO; 79 | return match; 80 | }; 81 | NSIndexSet *found = [_subject select: indexValidationBlock]; 82 | STAssertTrue([order isEqualToString: @"123"], @"the index loop order is %@", order); 83 | STAssertNil(found,@"no index found"); 84 | } 85 | 86 | - (void)testReject { 87 | NSMutableString *order = [NSMutableString string]; 88 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 89 | [order appendFormat:@"%lu", (unsigned long)index]; 90 | BOOL match = [_target[index] isEqual: @"0"] ? YES : NO; 91 | return match; 92 | }; 93 | NSIndexSet *found = [_subject reject:indexValidationBlock]; 94 | STAssertTrue([order isEqualToString: @"123"], @"the index loop order is %@", order); 95 | STAssertNil(found,@"all indexes are rejected"); 96 | } 97 | 98 | - (void)testRejectedNone { 99 | NSMutableString *order = [NSMutableString string]; 100 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 101 | [order appendFormat:@"%lu", (unsigned long)index]; 102 | BOOL match = [_target[index] isEqual: @"0"] ? NO : YES; 103 | return match; 104 | }; 105 | NSIndexSet *found = [_subject reject:indexValidationBlock]; 106 | STAssertTrue([order isEqualToString: @"123"], @"the index loop order is %@", order); 107 | STAssertEqualObjects(found, _subject, @"all indexes that are not rejected %@", found); 108 | } 109 | 110 | - (void)testAny { 111 | __block NSMutableString *order = [NSMutableString string]; 112 | BKIndexValidationBlock indexValidationBlock = ^(NSUInteger index) { 113 | [order appendFormat:@"%lu", (unsigned long)index]; 114 | BOOL match = NO; 115 | if (index%2 == 0 ) { 116 | _target[index] = [NSString stringWithFormat:@"%lu", (unsigned long)index]; 117 | match = YES; 118 | } 119 | return match; 120 | }; 121 | BOOL didFind = [_subject any: indexValidationBlock]; 122 | STAssertTrue([order isEqualToString: @"12"], @"the index loop order is %@", order); 123 | STAssertTrue(didFind, @"result found in target array"); 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /BlocksKit/UIKit/UIActionSheet+BlocksKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIActionSheet+BlocksKit.h 3 | // BlocksKit 4 | // 5 | 6 | #import "BKGlobals.h" 7 | 8 | /** UIActionSheet without delegates! 9 | 10 | This set of extensions and convenience classes allows 11 | for an instance of UIAlertView without the implementation 12 | of a delegate. Any time you instantiate a UIAlertView 13 | using the methods here, you must add buttons using 14 | addButtonWithTitle:handler: to make sure nothing breaks. 15 | 16 | A typical invocation might go like this: 17 | UIActionSheet *testSheet = [UIActionSheet actionSheetWithTitle:@"Please select one."]; 18 | [testSheet addButtonWithTitle:@"Zip" handler:^{ NSLog(@"Zip!"); }]; 19 | [testSheet addButtonWithTitle:@"Zap" handler:^{ NSLog(@"Zap!"); }]; 20 | [testSheet addButtonWithTitle:@"Zop" handler:^{ NSLog(@"Zop!"); }]; 21 | [testSheet setDestructiveButtonWithTitle:@"No!" handler:^{ NSLog(@"Fine!"); }]; 22 | [testSheet setCancelButtonWithTitle:nil handler:^{ NSLog(@"Never mind, then!"); }]; 23 | [testSheet showInView:self.view]; 24 | 25 | Includes code by the following: 26 | 27 | - [Landon Fuller](http://landonf.bikemonkey.org), "Using Blocks". 28 | - [Peter Steinberger](https://github.com/steipete) 29 | - [Zach Waldowski](https://github.com/zwaldowski) 30 | 31 | @warning UIActionSheet is only available on a platform with UIKit. 32 | */ 33 | @interface UIActionSheet (BlocksKit) 34 | 35 | ///----------------------------------- 36 | /// @name Creating action sheets 37 | ///----------------------------------- 38 | 39 | /** Creates and returns a new action sheet with only a title and cancel button. 40 | 41 | @param title The header of the action sheet. 42 | @return A newly created action sheet. 43 | */ 44 | + (id)actionSheetWithTitle:(NSString *)title; 45 | 46 | /** Returns a configured action sheet with only a title and cancel button. 47 | 48 | @param title The header of the action sheet. 49 | @return An instantiated actionSheet. 50 | */ 51 | - (id)initWithTitle:(NSString *)title; 52 | 53 | ///----------------------------------- 54 | /// @name Adding buttons 55 | ///----------------------------------- 56 | 57 | /** Add a new button with an associated code block. 58 | 59 | @param title The text of the button. 60 | @param block A block of code. 61 | */ 62 | - (NSInteger)addButtonWithTitle:(NSString *)title handler:(BKBlock)block; 63 | 64 | /** Set the destructive (red) button with an associated code block. 65 | 66 | @warning Because buttons cannot be removed from an action sheet, 67 | be aware that the effects of calling this method are cumulative. 68 | Previously added destructive buttons will become normal buttons. 69 | 70 | @param title The text of the button. 71 | @param block A block of code. 72 | */ 73 | - (NSInteger)setDestructiveButtonWithTitle:(NSString *)title handler:(BKBlock)block; 74 | 75 | /** Set the title and trigger of the cancel button. 76 | 77 | `block` can be set to `nil`, but this is generally useless as 78 | the cancel button is configured already to do nothing. 79 | 80 | iPhone users will have the button shown regardless; if the title is 81 | set to `nil`, it will automatically be localized. 82 | 83 | @param title The text of the button. 84 | @param block A block of code. 85 | */ 86 | - (NSInteger)setCancelButtonWithTitle:(NSString *)title handler:(BKBlock)block; 87 | 88 | ///----------------------------------- 89 | /// @name Altering actions 90 | ///----------------------------------- 91 | 92 | /** Sets the block that is to be fired when a button is pressed. 93 | 94 | @param block A code block, or nil to set no response. 95 | @param index The index of a button already added to the action sheet. 96 | */ 97 | - (void)setHandler:(BKBlock)block forButtonAtIndex:(NSInteger)index; 98 | 99 | /** The block that is to be fired when a button is pressed. 100 | 101 | @param index The index of a button already added to the action sheet. 102 | @return A code block, or nil if no block is assigned. 103 | */ 104 | - (BKBlock)handlerForButtonAtIndex:(NSInteger)index; 105 | 106 | /** The block to be fired when the action sheet is dismissed with the cancel 107 | button and/or action. 108 | 109 | This property performs the same action as setCancelButtonWithTitle:handler: 110 | but with `title` set to nil. Contrary to setCancelButtonWithTitle:handler:, 111 | you can set this property multiple times and multiple cancel buttons will 112 | not be generated. 113 | */ 114 | @property (nonatomic, copy) BKBlock cancelBlock; 115 | 116 | /** The block to be fired before the action sheet will show. */ 117 | @property (nonatomic, copy) void (^willShowBlock)(UIActionSheet *); 118 | 119 | /** The block to be fired when the action sheet shows. */ 120 | @property (nonatomic, copy) void (^didShowBlock)(UIActionSheet *); 121 | 122 | /** The block to be fired before the action sheet will dismiss. */ 123 | @property (nonatomic, copy) void (^willDismissBlock)(UIActionSheet *, NSInteger); 124 | 125 | /** The block to be fired after the action sheet dismisses. */ 126 | @property (nonatomic, copy) void (^didDismissBlock)(UIActionSheet *, NSInteger); 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /Tests/NSSetBlocksKitTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSSetBlocksKitTest.m 3 | // BlocksKit Unit Tests 4 | // 5 | // Created by Kai Wu on 7/4/11. 6 | // Copyright (c) 2011-2012 Pandamonia LLC. All rights reserved. 7 | // 8 | 9 | #import "NSSetBlocksKitTest.h" 10 | 11 | @implementation NSSetBlocksKitTest { 12 | NSSet *_subject; 13 | NSInteger _total; 14 | } 15 | 16 | - (void)setUp { 17 | _subject = [NSSet setWithArray: @[ @"1", @"22", @"333" ]]; 18 | _total = 0; 19 | } 20 | 21 | - (void)testEach { 22 | BKSenderBlock senderBlock = ^(NSString *sender) { 23 | _total += [sender length]; 24 | }; 25 | [_subject each:senderBlock]; 26 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 27 | } 28 | 29 | - (void)testMatch { 30 | BKValidationBlock validationBlock = ^(NSString *obj) { 31 | _total += [obj length]; 32 | BOOL match = ([obj intValue] == 22) ? YES : NO; 33 | return match; 34 | }; 35 | id found = [_subject match:validationBlock]; 36 | STAssertEquals(_total,(NSInteger)3,@"total length of \"122\" is %d",_total); 37 | STAssertEquals(found,@"22",@"matched object is %@",found); 38 | } 39 | 40 | - (void)testNotMatch { 41 | BKValidationBlock validationBlock = ^(NSString *obj) { 42 | _total += [obj length]; 43 | BOOL match = ([obj intValue] == 4444) ? YES : NO; 44 | return match; 45 | }; 46 | id found = [_subject match:validationBlock]; 47 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 48 | STAssertNil(found,@"no matched object"); 49 | } 50 | 51 | - (void)testSelect { 52 | BKValidationBlock validationBlock = ^(NSString *obj) { 53 | _total += [obj length]; 54 | BOOL match = ([obj intValue] < 300) ? YES : NO; 55 | return match; 56 | }; 57 | NSSet *found = [_subject select:validationBlock]; 58 | 59 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 60 | NSSet *target = [NSSet setWithArray: @[ @"1", @"22" ]]; 61 | STAssertEqualObjects(found,target,@"selected items are %@",found); 62 | } 63 | 64 | - (void)testSelectedNone { 65 | BKValidationBlock validationBlock = ^(NSString *obj) { 66 | _total += [obj length]; 67 | BOOL match = ([obj intValue] > 400) ? YES : NO; 68 | return match; 69 | }; 70 | NSSet *found = [_subject select:validationBlock]; 71 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 72 | STAssertTrue(found.count == 0,@"no item is selected"); 73 | } 74 | 75 | - (void)testReject { 76 | BKValidationBlock validationBlock = ^(NSString *obj) { 77 | _total += [obj length]; 78 | BOOL match = ([obj intValue] > 300) ? YES : NO; 79 | return match; 80 | }; 81 | NSSet *left = [_subject reject:validationBlock]; 82 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 83 | NSSet *target = [NSSet setWithArray: @[ @"1", @"22" ]]; 84 | STAssertEqualObjects(left,target,@"not rejected items are %@",left); 85 | } 86 | 87 | - (void)testRejectedAll { 88 | BKValidationBlock validationBlock = ^(NSString *obj) { 89 | _total += [obj length]; 90 | BOOL match = ([obj intValue] < 400) ? YES : NO; 91 | return match; 92 | }; 93 | NSSet *left = [_subject reject:validationBlock]; 94 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 95 | STAssertTrue(left.count == 0,@"all items are rejected"); 96 | } 97 | 98 | - (void)testMap { 99 | BKTransformBlock transformBlock = ^(NSString *obj) { 100 | _total += [obj length]; 101 | return [obj substringToIndex:1]; 102 | }; 103 | NSSet *transformed = [_subject map:transformBlock]; 104 | 105 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 106 | NSSet *target = [NSSet setWithArray: @[ @"1", @"2", @"3" ]]; 107 | STAssertEqualObjects(transformed,target,@"transformed items are %@",transformed); 108 | } 109 | 110 | - (void)testReduceWithBlock { 111 | BKAccumulationBlock accumlationBlock = ^(NSString *sum, NSString *obj) { 112 | return [sum stringByAppendingString:obj]; 113 | }; 114 | NSString *concatenated = [_subject reduce:@"" withBlock:accumlationBlock]; 115 | STAssertTrue([concatenated isEqualToString: @"122333"], @"concatenated string is %@", concatenated); 116 | } 117 | 118 | - (void)testAny { 119 | BKValidationBlock validationBlock = ^(NSString *obj) { 120 | _total += [obj length]; 121 | BOOL match = ([obj intValue] == 22) ? YES : NO; 122 | return match; 123 | }; 124 | BOOL wasFound = [_subject any:validationBlock]; 125 | STAssertEquals(_total,(NSInteger)3,@"total length of \"122\" is %d",_total); 126 | STAssertTrue(wasFound,@"matched object was found"); 127 | } 128 | 129 | - (void)testAll { 130 | BKValidationBlock validationBlock = ^(NSString *obj) { 131 | _total += [obj length]; 132 | BOOL match = ([obj intValue] < 444) ? YES : NO; 133 | return match; 134 | }; 135 | 136 | BOOL allMatched = [_subject all: validationBlock]; 137 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 138 | STAssertTrue(allMatched, @"Not all values matched"); 139 | } 140 | 141 | - (void)testNone { 142 | BKValidationBlock validationBlock = ^(NSString *obj) { 143 | _total += [obj length]; 144 | BOOL match = ([obj intValue] < 1) ? YES : NO; 145 | return match; 146 | }; 147 | 148 | BOOL noneMatched = [_subject none: validationBlock]; 149 | STAssertEquals(_total,(NSInteger)6,@"total length of \"122333\" is %d",_total); 150 | STAssertTrue(noneMatched, @"Some values matched"); 151 | } 152 | 153 | @end 154 | --------------------------------------------------------------------------------