| | |
| | | 2D47AB2520E21186008BB5C9 /* ShareSDK.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 2D47AB0F20E21186008BB5C9 /* ShareSDK.bundle */; }; |
| | | 2D47AB2620E21186008BB5C9 /* ShareSDKConnector.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D47AB1020E21186008BB5C9 /* ShareSDKConnector.framework */; }; |
| | | 2D47AB2B20E235EC008BB5C9 /* FileSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D47AB2A20E235EC008BB5C9 /* FileSignature.m */; }; |
| | | 2D4A107620E49B3600AB72DE /* WXUserModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D4A107520E49B3600AB72DE /* WXUserModule.m */; }; |
| | | 2D4A107920E4B47E00AB72DE /* WXUtilModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D4A107820E4B47E00AB72DE /* WXUtilModule.m */; }; |
| | | 2D4A107C20E4C5C000AB72DE /* WeexNativeController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D4A107B20E4C5C000AB72DE /* WeexNativeController.m */; }; |
| | | 2D4D84CC20E61A750012FDB6 /* WXCutomNewStrategyComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D4D84CB20E61A750012FDB6 /* WXCutomNewStrategyComponent.m */; }; |
| | | 2D73332320C90D6800336CC8 /* WebP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D73332220C90D6700336CC8 /* WebP.framework */; }; |
| | | AD52310E1F1B6A24007FCFA1 /* OrderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AD52310D1F1B6A24007FCFA1 /* OrderViewController.m */; }; |
| | | /* End PBXBuildFile section */ |
| | |
| | | 2D47AB1020E21186008BB5C9 /* ShareSDKConnector.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ShareSDKConnector.framework; sourceTree = "<group>"; }; |
| | | 2D47AB2920E235EC008BB5C9 /* FileSignature.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileSignature.h; sourceTree = "<group>"; }; |
| | | 2D47AB2A20E235EC008BB5C9 /* FileSignature.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileSignature.m; sourceTree = "<group>"; }; |
| | | 2D4A107420E49B3600AB72DE /* WXUserModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WXUserModule.h; sourceTree = "<group>"; }; |
| | | 2D4A107520E49B3600AB72DE /* WXUserModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WXUserModule.m; sourceTree = "<group>"; }; |
| | | 2D4A107720E4B47D00AB72DE /* WXUtilModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WXUtilModule.h; sourceTree = "<group>"; }; |
| | | 2D4A107820E4B47E00AB72DE /* WXUtilModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WXUtilModule.m; sourceTree = "<group>"; }; |
| | | 2D4A107A20E4C5C000AB72DE /* WeexNativeController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WeexNativeController.h; sourceTree = "<group>"; }; |
| | | 2D4A107B20E4C5C000AB72DE /* WeexNativeController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WeexNativeController.m; sourceTree = "<group>"; }; |
| | | 2D4D84CA20E61A750012FDB6 /* WXCutomNewStrategyComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WXCutomNewStrategyComponent.h; sourceTree = "<group>"; }; |
| | | 2D4D84CB20E61A750012FDB6 /* WXCutomNewStrategyComponent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WXCutomNewStrategyComponent.m; sourceTree = "<group>"; }; |
| | | 2D73332220C90D6700336CC8 /* WebP.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebP.framework; path = Pods/YYImage/Vendor/WebP.framework; sourceTree = "<group>"; }; |
| | | 2D9767D720C7D7FE006EE24F /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; |
| | | A10E40577F379E83D85926C6 /* Pods_MIduo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MIduo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; |
| | |
| | | 1875228D1E654D6900B6AE15 /* ç½é¡µè§å¾ */ = { |
| | | isa = PBXGroup; |
| | | children = ( |
| | | 2D4A107A20E4C5C000AB72DE /* WeexNativeController.h */, |
| | | 2D4A107B20E4C5C000AB72DE /* WeexNativeController.m */, |
| | | 180DCD951EA85874008EC06B /* XYRWebViewController.h */, |
| | | 180DCD961EA85874008EC06B /* XYRWebViewController.m */, |
| | | 1875228E1E654D6900B6AE15 /* SureWebViewController.h */, |
| | |
| | | 18AC53451E5A917C006D1FDF /* æ°æ® */ = { |
| | | isa = PBXGroup; |
| | | children = ( |
| | | 2D4A107320E49A8C00AB72DE /* WeexDomain */, |
| | | 2D47AB2920E235EC008BB5C9 /* FileSignature.h */, |
| | | 2D47AB2A20E235EC008BB5C9 /* FileSignature.m */, |
| | | 18AC53831E5A9840006D1FDF /* YTHsharedManger.h */, |
| | |
| | | path = Required; |
| | | sourceTree = "<group>"; |
| | | }; |
| | | 2D4A107320E49A8C00AB72DE /* WeexDomain */ = { |
| | | isa = PBXGroup; |
| | | children = ( |
| | | 2D4D84C820E619320012FDB6 /* Module */, |
| | | 2D4D84C920E619920012FDB6 /* Component */, |
| | | ); |
| | | path = WeexDomain; |
| | | sourceTree = "<group>"; |
| | | }; |
| | | 2D4D84C820E619320012FDB6 /* Module */ = { |
| | | isa = PBXGroup; |
| | | children = ( |
| | | 2D4A107420E49B3600AB72DE /* WXUserModule.h */, |
| | | 2D4A107520E49B3600AB72DE /* WXUserModule.m */, |
| | | 2D4A107720E4B47D00AB72DE /* WXUtilModule.h */, |
| | | 2D4A107820E4B47E00AB72DE /* WXUtilModule.m */, |
| | | ); |
| | | path = Module; |
| | | sourceTree = "<group>"; |
| | | }; |
| | | 2D4D84C920E619920012FDB6 /* Component */ = { |
| | | isa = PBXGroup; |
| | | children = ( |
| | | 2D4D84CA20E61A750012FDB6 /* WXCutomNewStrategyComponent.h */, |
| | | 2D4D84CB20E61A750012FDB6 /* WXCutomNewStrategyComponent.m */, |
| | | ); |
| | | path = Component; |
| | | sourceTree = "<group>"; |
| | | }; |
| | | 3EB2FCBC06FCFF59022C5ECD /* Frameworks */ = { |
| | | isa = PBXGroup; |
| | | children = ( |
| | |
| | | "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", |
| | | "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework", |
| | | "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework", |
| | | "${BUILT_PRODUCTS_DIR}/ReactiveCocoa/ReactiveCocoa.framework", |
| | | "${BUILT_PRODUCTS_DIR}/SDAutoLayout/SDAutoLayout.framework", |
| | | "${BUILT_PRODUCTS_DIR}/SDCycleScrollView/SDCycleScrollView.framework", |
| | | "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", |
| | | "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", |
| | |
| | | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", |
| | | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework", |
| | | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework", |
| | | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactiveCocoa.framework", |
| | | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDAutoLayout.framework", |
| | | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDCycleScrollView.framework", |
| | | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", |
| | | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", |
| | |
| | | 1810FBFC1E7FBDBB005B42B3 /* SideSlipBaseTableViewCell.m in Sources */, |
| | | 1897D9BA1E5FDB70003DF6FE /* searchTableViewCell.m in Sources */, |
| | | 1896F25D1FD0E652009D6EEF /* classificationViewController.m in Sources */, |
| | | 2D4A107620E49B3600AB72DE /* WXUserModule.m in Sources */, |
| | | 1845C510209C562E009C639B /* ZXQRCodeFinderPatternFinder.m in Sources */, |
| | | 1845C4BD209C562E009C639B /* ZXRSSExpandedBlockParsedResult.m in Sources */, |
| | | 187522291E6526AE00B6AE15 /* CLLockVC.m in Sources */, |
| | |
| | | 187522901E654D6900B6AE15 /* SureWebViewController.m in Sources */, |
| | | 18D399AD1EA765AD00A01CD3 /* thirdClassificationViewController.m in Sources */, |
| | | 1897D99C1E5FCFAD003DF6FE /* RedPacketDetailTableViewCell.m in Sources */, |
| | | 2D4A107C20E4C5C000AB72DE /* WeexNativeController.m in Sources */, |
| | | 1845C514209C562E009C639B /* ZXQRCodeEncoder.m in Sources */, |
| | | 1845C507209C562E009C639B /* ZXQRCodeDecoderMetaData.m in Sources */, |
| | | 18826A991F3C4C9C00A8E7B5 /* XWFilterAnimator+XWMod.m in Sources */, |
| | |
| | | 18E5AE041E5EC9DB009F85E0 /* SettingTypeTwoTableViewCell.m in Sources */, |
| | | 18D3E3001FEB6C32001F29F5 /* userInfoTableViewCellS1.m in Sources */, |
| | | 18D4305A1E7A30770007CCB5 /* FilterBaseCollectionViewCell.m in Sources */, |
| | | 2D4A107920E4B47E00AB72DE /* WXUtilModule.m in Sources */, |
| | | 1845C497209C562D009C639B /* ZXRGBLuminanceSource.m in Sources */, |
| | | 18D430631E7A3AD90007CCB5 /* SideSlipPriceTableViewCell.m in Sources */, |
| | | 1845C486209C562D009C639B /* ZXGridSampler.m in Sources */, |
| | |
| | | 18826A8D1F3C4C9C00A8E7B5 /* XWCoolAnimator+XWLines.m in Sources */, |
| | | 1845C452209C562D009C639B /* ZXBizcardResultParser.m in Sources */, |
| | | 1845C476209C562D009C639B /* ZXMonochromeRectangleDetector.m in Sources */, |
| | | 2D4D84CC20E61A750012FDB6 /* WXCutomNewStrategyComponent.m in Sources */, |
| | | 1845C4D4209C562E009C639B /* ZXCode39Writer.m in Sources */, |
| | | 1892034020BE51C6008C1045 /* LBXScanNetAnimation.m in Sources */, |
| | | 1845C4B3209C562E009C639B /* ZXAI013103decoder.m in Sources */, |
| | |
| | | buildSettings = { |
| | | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
| | | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; |
| | | CLANG_ENABLE_OBJC_WEAK = YES; |
| | | CODE_SIGN_ENTITLEMENTS = MIduo/MIduo.entitlements; |
| | | DEVELOPMENT_TEAM = 98HSDT7AP4; |
| | | ENABLE_BITCODE = NO; |
| | |
| | | buildSettings = { |
| | | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
| | | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; |
| | | CLANG_ENABLE_OBJC_WEAK = YES; |
| | | CODE_SIGN_ENTITLEMENTS = MIduo/MIduo.entitlements; |
| | | DEVELOPMENT_TEAM = 98HSDT7AP4; |
| | | ENABLE_BITCODE = NO; |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <Bucket |
| | | type = "0" |
| | | version = "2.0"> |
| | | <Breakpoints> |
| | | <BreakpointProxy |
| | | BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> |
| | | <BreakpointContent |
| | | shouldBeEnabled = "Yes" |
| | | ignoreCount = "0" |
| | | continueAfterRunningActions = "No" |
| | | filePath = "MIduo/数据/WeexDomain/WXUtilModule.m" |
| | | timestampString = "551950582.96917" |
| | | startingColumnNumber = "9223372036854775807" |
| | | endingColumnNumber = "9223372036854775807" |
| | | startingLineNumber = "135" |
| | | endingLineNumber = "135" |
| | | landmarkName = "-share:" |
| | | landmarkType = "7"> |
| | | </BreakpointContent> |
| | | </BreakpointProxy> |
| | | <BreakpointProxy |
| | | BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> |
| | | <BreakpointContent |
| | | shouldBeEnabled = "Yes" |
| | | ignoreCount = "0" |
| | | continueAfterRunningActions = "No" |
| | | filePath = "MIduo/界面/首页/recommendViewController.m" |
| | | timestampString = "551873944.957873" |
| | | startingColumnNumber = "9223372036854775807" |
| | | endingColumnNumber = "9223372036854775807" |
| | | startingLineNumber = "516" |
| | | endingLineNumber = "516" |
| | | landmarkName = "-collectionView:didSelectItemAtIndexPath:" |
| | | landmarkType = "7"> |
| | | </BreakpointContent> |
| | | </BreakpointProxy> |
| | | <BreakpointProxy |
| | | BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> |
| | | <BreakpointContent |
| | | shouldBeEnabled = "Yes" |
| | | ignoreCount = "0" |
| | | continueAfterRunningActions = "No" |
| | | filePath = "MIduo/界面/网页视图/商品详情/GoodDeTrViewController.m" |
| | | timestampString = "551873944.958277" |
| | | startingColumnNumber = "9223372036854775807" |
| | | endingColumnNumber = "9223372036854775807" |
| | | startingLineNumber = "290" |
| | | endingLineNumber = "290" |
| | | landmarkName = "-shareClicked:" |
| | | landmarkType = "7"> |
| | | </BreakpointContent> |
| | | </BreakpointProxy> |
| | | <BreakpointProxy |
| | | BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> |
| | | <BreakpointContent |
| | | shouldBeEnabled = "Yes" |
| | | ignoreCount = "0" |
| | | continueAfterRunningActions = "No" |
| | | filePath = "MIduo/界面/购物车/shoppingCartViewController.m" |
| | | timestampString = "551873944.958539" |
| | | startingColumnNumber = "9223372036854775807" |
| | | endingColumnNumber = "9223372036854775807" |
| | | startingLineNumber = "399" |
| | | endingLineNumber = "399" |
| | | landmarkName = "-webView:shouldStartLoadWithRequest:navigationType:" |
| | | landmarkType = "7"> |
| | | </BreakpointContent> |
| | | </BreakpointProxy> |
| | | <BreakpointProxy |
| | | BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> |
| | | <BreakpointContent |
| | | shouldBeEnabled = "Yes" |
| | | ignoreCount = "0" |
| | | continueAfterRunningActions = "No" |
| | | filePath = "MIduo/数据/WeexDomain/Component/WXCutomNewStrategyComponent.m" |
| | | timestampString = "551956825.528976" |
| | | startingColumnNumber = "9223372036854775807" |
| | | endingColumnNumber = "9223372036854775807" |
| | | startingLineNumber = "174" |
| | | endingLineNumber = "174" |
| | | landmarkName = "-buttonBack" |
| | | landmarkType = "7"> |
| | | </BreakpointContent> |
| | | </BreakpointProxy> |
| | | </Breakpoints> |
| | | </Bucket> |
| | |
| | | #import "SJNComponet.h" |
| | | #import "WXImgLoaderDefaultImpl.h" |
| | | //#import "WXNavigationDefaultImpl.h" |
| | | |
| | | /****** Weex ******/ |
| | | // SDK |
| | | #import <WeexSDK/WeexSDK.h> |
| | | // Module |
| | | #import "WXUserModule.h" |
| | | #import "WXUtilModule.h" |
| | | // Component |
| | | #import "WXCutomNewStrategyComponent.h" |
| | | |
| | | @interface AppDelegate ()<UNUserNotificationCenterDelegate,UITabBarControllerDelegate,WXApiDelegate> |
| | | |
| | | @end |
| | |
| | | |
| | | return YES; |
| | | } |
| | | |
| | | #pragma mark --- Weexåå§åé
ç½® --- |
| | | - (void)WeexInit{ |
| | | |
| | | //business configuration |
| | | NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; |
| | | // CFShow(infoDictionary); |
| | |
| | | |
| | | //register custom module and componentï¼optional |
| | | [WXSDKEngine registerComponent:@"navgation" withClass:[SJNComponet class]]; |
| | | [WXSDKEngine registerComponent:@"topMenu" withClass:[WXCutomNewStrategyComponent class]]; |
| | | |
| | | [WXSDKEngine registerModule:@"event" withClass:[SJModule class]]; |
| | | [WXSDKEngine registerModule:@"userModule" withClass:[WXUserModule class]]; |
| | | [WXSDKEngine registerModule:@"utilModule" withClass:[WXUtilModule class]]; |
| | | |
| | | //register the implementation of protocol, optional |
| | | [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)]; |
| | | |
| | | //set the log level |
| | | [WXLog setLogLevel: WXLogLevelAll]; |
| | | // 设置æ¥å¿çº§å« |
| | | [WXLog setLogLevel:WXLogLevelError]; |
| | | } |
| | | |
| | | - (void)loginDeviceToken{ |
| | | |
| | | NSMutableDictionary *dic = [[NSMutableDictionary alloc] init]; |
| | |
| | | [dic setObject:@"1" forKey:@"page"]; |
| | | [dic setObject:[[NSUserDefaults standardUserDefaults] objectForKey:@"userId"] forKey:@"uid"]; |
| | | |
| | | [JYNetWorking Post:[NSString stringWithFormat:@"%@/%@",domainHTTP,@"customer/findAccountMessageList"] param:dic success:^(NSDictionary *object) { |
| | | NSLog(@"%@",object); |
| | | [JYNetWorking Post:[NSString stringWithFormat:@"%@/%@",domainHTTP,@"customer/findAccountMessageList"] |
| | | param:dic |
| | | success:^(NSDictionary *object) { |
| | | |
| | | ALLog(@"%@",object); |
| | | // NSInteger code = [object[@"code"]integerValue]; |
| | | NSDictionary *dic = object[@"data"]; |
| | | NSArray *array = dic[@"list"]; |
| | |
| | | #import "Common.h" |
| | | //#import <Foundation/Foundation.h> |
| | | //#import <UIKit/UIKit.h> |
| | | #import "FileSignature.h" |
| | | |
| | | #endif /* PrefixHeader_pch */ |
| | |
| | | #import "NSString+YTH.h" |
| | | #import "UIImageView+YTH.h" |
| | | |
| | | // ç¾å |
| | | #import "FileSignature.h" |
| | | // æ§ä»¶èªéåº |
| | | #import "SDAutoLayout.h" |
| | | // RAC |
| | | #import <ReactiveCocoa/ReactiveCocoa.h> |
| | | |
| | | #import <AlibabaAuthSDK/ALBBSDK.h> //æ·å®ç»å½ |
| | | #import <WXApi.h> //微信ç»å½ |
| | | |
| | |
| | | // 2.ç¨ä»£ç å½¢å¼ä»£ç |
| | | #define UIColorFromRGBValue(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] |
| | | |
| | | #define UICOLOR_FROM_RGB(rgbValue,trans) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:trans] |
| | | |
| | | // 3.å±å¹å®½é« |
| | | #define KScreenHp [[UIScreen mainScreen] bounds].size.height*1.0 // å±å¹é«åº¦ |
| | | #define KScreenWp [[UIScreen mainScreen] bounds].size.width*1.0 // å±å¹å®½åº¦ |
| | |
| | | // 6.æ¯å¼ å¾çåé¢çåç¼ |
| | | #define ImageUrl @"http://sovideo.cn:8080/BuWan" |
| | | |
| | | // Logè¾åºæ ¼å¼ |
| | | // 7.Logè¾åºæ ¼å¼ |
| | | #ifdef DEBUG |
| | | #define ALLog(...) NSLog(@"%s 第%dè¡ \n %@\n\n",__func__,__LINE__,[NSString stringWithFormat:__VA_ARGS__]) |
| | | #else |
| | | #define ALLog(...) |
| | | #endif |
| | | |
| | | // --------------- åç§å®½é« ------------- |
| | | // å±å¹å®½é« |
| | | #define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width |
| | | #define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height |
| | | // å·¥å
·æ é«åº¦ |
| | | #define TAB_BAR_HEIGHT self.tabBarController.tabBar.frame.size.height |
| | | // å¯¼èªæ é«åº¦+ç¶ææ é«åº¦ |
| | | #define NAV_STATUS_BAR_HEIGHT ([[UIApplication sharedApplication] statusBarFrame].size.height + self.navigationController.navigationBar.frame.size.height) |
| | | // ç¶ææ é«åº¦ |
| | | #define STATUS_BAR_HEIGHT [[UIApplication sharedApplication] statusBarFrame].size.height |
| | | // å¯¼èªæ é«åº¦ |
| | | #define NAV_BAR_HEIGHT 44 |
| | | // å è½½æ¬å°å¾ç |
| | | #define ALIMAGE_NAMED(name)[UIImage imageNamed:name] |
| | | |
New file |
| | |
| | | // |
| | | // WXCutomNewStrategyComponent.h |
| | | // MIduo |
| | | // |
| | | // Created by éåºè¿å°ç§ææéå
¬å¸ on 2018/6/29. |
| | | // Copyright © 2018年 yeshi. All rights reserved. |
| | | // |
| | | |
| | | #import <WeexSDK/WeexSDK.h> |
| | | |
| | | @interface WXCutomNewStrategyComponent : WXComponent |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // WXCutomNewStrategyComponent.m |
| | | // MIduo |
| | | // |
| | | // Created by éåºè¿å°ç§ææéå
¬å¸ on 2018/6/29. |
| | | // Copyright © 2018年 yeshi. All rights reserved. |
| | | // |
| | | |
| | | #import "WXCutomNewStrategyComponent.h" |
| | | |
| | | @interface WXCutomNewStrategyComponent () |
| | | |
| | | /// èæ¯ |
| | | @property (nonatomic, strong, nullable) UIView *navView; |
| | | /// å·¦æé® |
| | | @property (nonatomic, strong, nullable) UIButton *buttonBack; |
| | | /// æ é¢ |
| | | @property (nonatomic, strong, nullable) UILabel *labelTitle; |
| | | /// ç°çº¿ |
| | | @property (nonatomic, strong, nullable) UIView *line; |
| | | /// title |
| | | @property (nonatomic, copy, nullable) NSString *title; |
| | | |
| | | @end |
| | | |
| | | @implementation WXCutomNewStrategyComponent |
| | | |
| | | @synthesize weexInstance; |
| | | |
| | | - (instancetype)initWithRef:(NSString *)ref |
| | | type:(NSString *)type |
| | | styles:(NSDictionary *)styles |
| | | attributes:(NSDictionary *)attributes |
| | | events:(NSArray *)events |
| | | weexInstance:(WXSDKInstance *)weexInstance { |
| | | |
| | | self = [super initWithRef:ref |
| | | type:type |
| | | styles:styles |
| | | attributes:attributes |
| | | events:events |
| | | weexInstance:weexInstance]; |
| | | |
| | | if (self) { |
| | | |
| | | // åå°ä¸»çº¿ç¨è°ç¨æ¹æ³ |
| | | dispatch_async(dispatch_get_main_queue(), ^{ |
| | | |
| | | [self viewConfig]; |
| | | }); |
| | | |
| | | if (attributes[@"title"]) { |
| | | |
| | | self.title = attributes[@"title"]; |
| | | } |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark --- é
ç½®è§å¾ --- |
| | | - (void)viewConfig { |
| | | |
| | | // if (!([self.weexInstance.viewController isKindOfClass:[MeController class]])) { |
| | | |
| | | [self.navView addSubview:self.buttonBack]; |
| | | self.buttonBack.sd_layout |
| | | .leftSpaceToView(self.navView, 0) |
| | | .bottomSpaceToView(self.navView, 0) |
| | | .heightIs(44) |
| | | .widthIs(60); |
| | | // } |
| | | |
| | | [self.navView addSubview:self.labelTitle]; |
| | | self.labelTitle.sd_layout |
| | | .leftSpaceToView(self.navView, 80) |
| | | .rightSpaceToView(self.navView, 80) |
| | | .heightIs(44) |
| | | .bottomSpaceToView(self.navView, 0); |
| | | |
| | | // [self.navView addSubview:self.line]; |
| | | // self.line.sd_layout |
| | | // .leftSpaceToView(self.navView, 0) |
| | | // .rightSpaceToView(self.navView, 0) |
| | | // .bottomSpaceToView(self.navView, 0) |
| | | // .heightIs(0.5); |
| | | } |
| | | |
| | | - (UIView *)loadView { |
| | | |
| | | [super loadView]; |
| | | |
| | | UIView *views = [[UIView alloc] init]; |
| | | |
| | | CGRect rect; |
| | | |
| | | if (IsiPhoneX) { |
| | | |
| | | rect = CGRectMake(0, 0, SCREEN_WIDTH, 88); |
| | | |
| | | } else { |
| | | |
| | | rect = CGRectMake(0, 0, SCREEN_WIDTH, 64); |
| | | } |
| | | |
| | | views.frame = rect; |
| | | |
| | | [views addSubview:self.navView]; |
| | | |
| | | return views; |
| | | } |
| | | |
| | | - (void)viewDidLoad { |
| | | [super viewDidLoad]; |
| | | |
| | | self.labelTitle.text = self.title; |
| | | } |
| | | |
| | | - (void)updateAttributes:(NSDictionary *)attributes { |
| | | |
| | | [super updateAttributes:attributes]; |
| | | |
| | | if (attributes[@"title"]) { |
| | | |
| | | self.title = attributes[@"title"]; |
| | | |
| | | self.labelTitle.text = self.title; |
| | | } |
| | | } |
| | | |
| | | #pragma mark --- æå è½½ --- |
| | | - (UIView *)navView { |
| | | |
| | | if (!_navView) { |
| | | _navView = [[UIView alloc] init]; |
| | | _navView.backgroundColor = YTHColor(229, 0, 92); |
| | | |
| | | CGRect rect; |
| | | |
| | | if (IsiPhoneX) { |
| | | |
| | | rect = CGRectMake(0, 0, SCREEN_WIDTH, 88); |
| | | |
| | | } else { |
| | | |
| | | rect = CGRectMake(0, 0, SCREEN_WIDTH, 64); |
| | | } |
| | | |
| | | _navView.frame = rect; |
| | | |
| | | } |
| | | return _navView; |
| | | } |
| | | |
| | | - (UIButton *)buttonBack { |
| | | |
| | | if (!_buttonBack) { |
| | | _buttonBack = [UIButton buttonWithType:UIButtonTypeCustom]; |
| | | |
| | | UIImageView *imgViewBack = [[UIImageView alloc] init]; |
| | | imgViewBack.image = ALIMAGE_NAMED(@"第äºçè¿åæé®"); |
| | | |
| | | [_buttonBack addSubview:imgViewBack]; |
| | | |
| | | CGFloat imaWidth = imgViewBack.image.size.width; |
| | | CGFloat imaHeight = imgViewBack.image.size.height; |
| | | |
| | | imgViewBack.frame = CGRectMake(8, (44 - imaHeight) / 2, imaWidth, imaHeight); |
| | | |
| | | @weakify(self) |
| | | [[_buttonBack rac_signalForControlEvents:UIControlEventTouchUpInside] |
| | | subscribeNext:^(UIButton *button) { |
| | | @strongify(self) |
| | | |
| | | [self.weexInstance.viewController.navigationController popViewControllerAnimated:YES]; |
| | | }]; |
| | | } |
| | | return _buttonBack; |
| | | } |
| | | |
| | | - (UILabel *)labelTitle { |
| | | |
| | | if (!_labelTitle) { |
| | | _labelTitle = [[UILabel alloc] init]; |
| | | _labelTitle.textColor = [UIColor whiteColor]; |
| | | _labelTitle.font = [UIFont boldSystemFontOfSize:17.0]; |
| | | _labelTitle.textAlignment = NSTextAlignmentCenter; |
| | | } |
| | | return _labelTitle; |
| | | } |
| | | |
| | | - (UIView *)line { |
| | | |
| | | if (!_line) { |
| | | _line = [[UIView alloc] init]; |
| | | _line.backgroundColor = UICOLOR_FROM_RGB(0x000000, 0.3); |
| | | } |
| | | return _line; |
| | | } |
| | | |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // WXUserModule.h |
| | | // MIduo |
| | | // |
| | | // Created by éåºè¿å°ç§ææéå
¬å¸ on 2018/6/28. |
| | | // Copyright © 2018年 yeshi. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import <WeexSDK/WeexSDK.h> |
| | | |
| | | @interface WXUserModule : NSObject <WXModuleProtocol> |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // WXUserModule.m |
| | | // MIduo |
| | | // |
| | | // Created by éåºè¿å°ç§ææéå
¬å¸ on 2018/6/28. |
| | | // Copyright © 2018年 yeshi. All rights reserved. |
| | | // |
| | | |
| | | #import "WXUserModule.h" |
| | | |
| | | @implementation WXUserModule |
| | | |
| | | @synthesize weexInstance; |
| | | |
| | | WX_EXPORT_METHOD_SYNC(@selector(getUserInfo)) |
| | | WX_EXPORT_METHOD(@selector(login)) |
| | | |
| | | #pragma mark --- è·åç¨æ·ä¿¡æ¯ --- |
| | | - (NSString *)getUserInfo { |
| | | |
| | | NSString *str = @"æµè¯ä¿¡æ¯"; |
| | | |
| | | return str; |
| | | } |
| | | |
| | | #pragma mark --- 跳转å°ç»å½çé¢ --- |
| | | - (void)login { |
| | | |
| | | LoginViewController *LoginVC = [[LoginViewController alloc] init]; |
| | | LoginVC.hidesBottomBarWhenPushed = YES; |
| | | [weexInstance.viewController.navigationController pushViewController:LoginVC animated:YES]; |
| | | } |
| | | |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // WXUtilModule.h |
| | | // MIduo |
| | | // |
| | | // Created by éåºè¿å°ç§ææéå
¬å¸ on 2018/6/28. |
| | | // Copyright © 2018年 yeshi. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import <WeexSDK/WeexSDK.h> |
| | | |
| | | @interface WXUtilModule : NSObject <WXModuleProtocol> |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // WXUtilModule.m |
| | | // MIduo |
| | | // |
| | | // Created by éåºè¿å°ç§ææéå
¬å¸ on 2018/6/28. |
| | | // Copyright © 2018年 yeshi. All rights reserved. |
| | | // |
| | | |
| | | #import "WXUtilModule.h" |
| | | #import "ShonpingShareViewController.h" |
| | | #import "SureWebViewController.h" |
| | | #import "GoodDeTrViewController.h" |
| | | |
| | | #define Version [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] //buildå· |
| | | #define Package @"com.yeshi.ec.RebateTicket" //ç¸å½äºå
å |
| | | |
| | | @implementation WXUtilModule |
| | | |
| | | @synthesize weexInstance; |
| | | |
| | | /// çæç¾å |
| | | WX_EXPORT_METHOD_SYNC(@selector(getSign:)) |
| | | /// 跳转å°ååè¿åº¦é¡µ |
| | | WX_EXPORT_METHOD(@selector(jumpGoodsSplash:)) |
| | | /// 跳转åæçåå详æ
|
| | | WX_EXPORT_METHOD(@selector(jumpGoodsDetail:)) |
| | | /// 跳转ç½é¡µ |
| | | WX_EXPORT_METHOD(@selector(jumpWeb:)) |
| | | /// å享 |
| | | WX_EXPORT_METHOD(@selector(share:)) |
| | | /// ç»æå½åé¡µé¢ |
| | | WX_EXPORT_METHOD(@selector(finishPage)) |
| | | |
| | | #pragma mark --- çæç¾å --- |
| | | - (NSString *)getSign:(NSString *)result { |
| | | |
| | | // å°JSONå符串转å为NSDataç±»å |
| | | NSData *data = [result dataUsingEncoding:NSUTF8StringEncoding]; |
| | | NSDictionary *tempParms = [NSJSONSerialization JSONObjectWithData:data |
| | | options:0 |
| | | error:nil]; |
| | | |
| | | NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithDictionary:tempParms]; |
| | | |
| | | |
| | | NSString *sign = [self sortingDictionaryWithdic:dic]; |
| | | |
| | | return sign; |
| | | } |
| | | |
| | | - (NSString *)sortingDictionaryWithdic:(NSDictionary *)dic { |
| | | |
| | | NSMutableArray *array = @[].mutableCopy; |
| | | |
| | | for (NSInteger index = 0; index < dic.allKeys.count; index ++) { |
| | | |
| | | [array addObject:[NSString stringWithFormat:@"%@=%@",dic.allKeys[index],dic.allValues[index]]]; |
| | | } |
| | | |
| | | NSArray *resultArray = [array sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { |
| | | /* |
| | | æåºç»æ |
| | | NSComparisonResult resuest = [obj1 compare:obj2];为ä»å°å°å¤§,å³ååº; |
| | | NSComparisonResult resuest = [obj2 compare:obj1];为ä»å¤§å°å°,å³éåº; |
| | | 注æ:compareæ¹æ³æ¯åºå大å°åç,峿ç
§ASCIIæåº |
| | | */ |
| | | //æåºæä½ |
| | | NSComparisonResult resuest = [obj1 compare:obj2]; |
| | | |
| | | return resuest; |
| | | }]; |
| | | |
| | | ALLog(@"resultArray:%@",resultArray); |
| | | |
| | | NSString *resultString = @""; |
| | | |
| | | for (NSInteger index = 0; index < resultArray.count; index ++) { |
| | | |
| | | if (index == 0) { |
| | | |
| | | resultString = [NSString stringWithFormat:@"%@",resultArray[index]]; |
| | | |
| | | continue; |
| | | } |
| | | |
| | | resultString = [NSString stringWithFormat:@"%@&%@",resultString,resultArray[index]]; |
| | | } |
| | | |
| | | resultString = [resultString stringByAppendingString:@"&buXiNjie2017!"]; |
| | | resultString = [NSString md5:resultString]; |
| | | |
| | | return resultString; |
| | | } |
| | | |
| | | #pragma mark --- 跳转å°ååè¿åº¦é¡µ --- |
| | | - (void)jumpGoodsSplash:(NSString *)taobaoGoodsID { |
| | | |
| | | GoodDeTrViewController *goodsDetailVC = [[GoodDeTrViewController alloc]init]; |
| | | |
| | | goodsDetailVC.hidesBottomBarWhenPushed = YES; |
| | | goodsDetailVC.goodsID = taobaoGoodsID; |
| | | |
| | | [weexInstance.viewController.navigationController pushViewController:goodsDetailVC animated:YES]; |
| | | } |
| | | |
| | | #pragma mark --- 跳转åæåå详æ
--- |
| | | - (void)jumpGoodsDetail:(NSString *)goodsID { |
| | | |
| | | SureWebViewController *webView=[[SureWebViewController alloc] init]; |
| | | |
| | | [webView backClicked:^(NSString *string) { |
| | | |
| | | }]; |
| | | |
| | | webView.goodsId = goodsID; |
| | | webView.canDownRefresh = YES; |
| | | webView.isGoodsDetail = YES; |
| | | webView.hidesBottomBarWhenPushed = YES; |
| | | |
| | | [weexInstance.viewController.navigationController pushViewController:webView animated:YES]; |
| | | } |
| | | |
| | | #pragma mark --- 跳跳转ç½é¡µ --- |
| | | - (void)jumpWeb:(NSString *)url { |
| | | |
| | | ShonpingShareViewController *shopVC = [[ShonpingShareViewController alloc]init]; |
| | | shopVC.urlString = url; |
| | | shopVC.hidesBottomBarWhenPushed=YES; |
| | | [weexInstance.viewController.navigationController pushViewController:shopVC animated:YES]; |
| | | |
| | | } |
| | | |
| | | #pragma mark --- å享 --- |
| | | - (void)share:(NSString *)type { |
| | | |
| | | } |
| | | |
| | | #pragma mark --- åç»æå½åé¡µé¢ --- |
| | | - (void)finishPage { |
| | | |
| | | [weexInstance.viewController.navigationController popViewControllerAnimated:YES]; |
| | | } |
| | | |
| | | @end |
| | |
| | | #import "MineThirdTableViewCell.h" |
| | | #import "shoppingCartViewController.h" |
| | | #import "SJFirstLessonsViewController.h" |
| | | |
| | | #import "WeexNativeController.h" |
| | | |
| | | #define Version [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] //buildå· |
| | | #define Package @"com.yeshi.ec.RebateTicket" //ç¸å½äºå
å |
| | | @interface MineViewController ()<UITableViewDelegate,UITableViewDataSource,LXAlertViewDelegate>{ |
| | |
| | | } |
| | | - (void)viewDidLoad { |
| | | [super viewDidLoad]; |
| | | |
| | | self.view.backgroundColor=XYRBackgroundColor; |
| | | |
| | | //åå§åæ°æ® |
| | |
| | | [dic setObject:[[NSUserDefaults standardUserDefaults] objectForKey:@"userId"] forKey:@"uid"]; |
| | | |
| | | [JYNetWorking Post:[NSString stringWithFormat:@"%@/%@",domainHTTP,@"customer/findAccountMessageList"] param:dic success:^(NSDictionary *object) { |
| | | NSLog(@"%@",object); |
| | | |
| | | ALLog(@"object:%@",object); |
| | | // NSInteger code = [object[@"code"]integerValue]; |
| | | NSDictionary *dic = object[@"data"]; |
| | | NSArray *array = dic[@"list"]; |
| | |
| | | }]; |
| | | |
| | | } |
| | | /** |
| | | å®å¶å¯¼èªæ |
| | | */ |
| | | |
| | | #pragma mark --- å®å¶å¯¼èªæ --- |
| | | -(void)CreatNavigationBar{ |
| | | //设置æé® |
| | | UIView *messageView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 30, 30)]; |
| | |
| | | self.navigationController.navigationBar.titleTextAttributes=@{NSForegroundColorAttributeName:[UIColor whiteColor],NSFontAttributeName:[UIFont boldSystemFontOfSize:15]}; |
| | | } |
| | | |
| | | /** |
| | | å建TableView |
| | | */ |
| | | #pragma mark --- å建TableView --- |
| | | -(void)CreatTableView{ |
| | | |
| | | if (!_MineTableView) { |
| | | _MineTableView=[[UITableView alloc] initWithFrame:CGRectMake(0, -10, KScreenW, KScreenH-kToolBarH-kStatusBarH) style:UITableViewStylePlain]; |
| | | _MineTableView.delegate=self; |
| | | _MineTableView.dataSource=self; |
| | | _MineTableView.estimatedRowHeight = 0; |
| | | _MineTableView.estimatedSectionFooterHeight = 0; |
| | | _MineTableView.estimatedSectionHeaderHeight = 0; |
| | | _MineTableView.tableFooterView=[UIView new]; |
| | | _MineTableView.backgroundColor=[UIColor clearColor]; |
| | | _MineTableView.separatorStyle = UITableViewCellSelectionStyleNone; |
| | | _MineTableView.showsVerticalScrollIndicator=NO; |
| | | // _MineTableView.showsHorizontalScrollIndicator=NO; |
| | | // _MineTableView.bounces = NO; |
| | | |
| | | //注åcell |
| | | [_MineTableView registerNib:[UINib nibWithNibName:@"MineRedEnvelopesTableViewCell" bundle:nil] forCellReuseIdentifier:@"MineRedEnvelopesTableViewCell"]; |
| | |
| | | [dic setValue:[[NSUserDefaults standardUserDefaults] objectForKey:@"userId"] forKey:@"id"]; |
| | | |
| | | [JYNetWorking Post:[NSString stringWithFormat:@"%@/%@",domainHTTP,@"customer/getuserinfo"] param:dic success:^(NSDictionary *object) { |
| | | _userInfo = object[@"data"][@"user"];//ä¿åå½åç¨æ·ä¿¡æ¯ |
| | | self.userInfo = object[@"data"][@"user"];//ä¿åå½åç¨æ·ä¿¡æ¯ |
| | | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(LogOutForTaoBao) name:@"MineViewController" object:nil];//çå¬éåºç»å½çéç¥ |
| | | [self loginDeviceToken]; |
| | | //åå¨è·åå°çæ°æ® |
| | | NSData *data=[NSKeyedArchiver archivedDataWithRootObject:_userInfo]; |
| | | NSData *data=[NSKeyedArchiver archivedDataWithRootObject:self.userInfo]; |
| | | [data writeToFile:USERINFO atomically:YES]; |
| | | [_MineTableView reloadData];//å·æ° |
| | | [self->_MineTableView reloadData];//å·æ° |
| | | |
| | | } fail:^(id object) { |
| | | |
| | |
| | | MineThirdTableViewCell ç¹å»äºä»¶ |
| | | */ |
| | | |
| | | #pragma mark --- æ°ææ»ç¥ --- |
| | | - (void)noviceTaped:(UIButton *)sender{ |
| | | XYRWebViewController *specialJumpVC=[[XYRWebViewController alloc] init]; |
| | | specialJumpVC.url=[YTHsharedManger startManger].weexUrlString; |
| | | specialJumpVC.canDownRefresh=YES; |
| | | specialJumpVC.backToRedBag=^(){ |
| | | |
| | | }; |
| | | specialJumpVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:specialJumpVC animated:YES]; |
| | | // SJFirstLessonsViewController * firstVC = [[SJFirstLessonsViewController alloc]init]; |
| | | // firstVC .hidesBottomBarWhenPushed = YES; |
| | | // [self.navigationController pushViewController:firstVC animated:YES]; |
| | | // XYRWebViewController *specialJumpVC=[[XYRWebViewController alloc] init]; |
| | | // |
| | | // specialJumpVC.url=[YTHsharedManger startManger].weexUrlString; |
| | | // specialJumpVC.canDownRefresh=YES; |
| | | // specialJumpVC.backToRedBag=^(){ |
| | | // |
| | | // }; |
| | | // specialJumpVC.hidesBottomBarWhenPushed = YES; |
| | | // |
| | | // [self.navigationController pushViewController:specialJumpVC animated:YES]; |
| | | |
| | | WeexNativeController *weexNativeVc = [[WeexNativeController alloc] init]; |
| | | |
| | | weexNativeVc.urlString = @"http://192.168.1.122:9000/weex/gonglue.js"; |
| | | weexNativeVc.hidesBottomBarWhenPushed = YES; |
| | | |
| | | [self.navigationController pushViewController:weexNativeVc animated:YES]; |
| | | } |
| | | |
| | | #pragma mark --- 帮å©ä¸å¿ --- |
| | | - (void)helpTaped:(UIButton *)sender{ |
| | | |
| | | XYRWebViewController *SureWebVC=[[XYRWebViewController alloc] init]; |
| | | SureWebVC.url=[[NSUserDefaults standardUserDefaults] objectForKey:@"help"]; |
| | | SureWebVC.backToRedBag=^(){ |
| | |
| | | [self.navigationController pushViewController:SureWebVC animated:YES]; |
| | | } |
| | | |
| | | #pragma mark --- è系客æ --- |
| | | - (void)serviceTaped:(UIButton *)sender{ |
| | | |
| | | ServiceViewController *serviceVC=[[ServiceViewController alloc] init]; |
| | |
| | | |
| | | } |
| | | |
| | | #pragma mark --- è´ç©è½¦ --- |
| | | - (void)buyCarTaped:(UIButton *)sender{ |
| | | |
| | | shoppingCartViewController * shopCarVC = [[shoppingCartViewController alloc]init]; |
| | | shopCarVC.hidesBottomBarWhenPushed = YES; |
| | | [self.navigationController pushViewController:shopCarVC animated:YES]; |
| | | } |
| | | |
| | | #pragma mark -LXAlertViewDelegate |
| | | - (void)lxAlertView:(LXAlertView *)alertView withButtonIndex:(NSInteger)buttonIndex{ |
| | | if (buttonIndex==780) {//ä¸ç»å½ |
| | |
| | | |
| | | #pragma mark -UITableViewDelegate |
| | | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ |
| | | |
| | | if (indexPath.section==0) { |
| | | |
| | | if(indexPath.row==0){ |
| | | |
| | | return 120; |
| | | |
| | | }else if(indexPath.row==1){ |
| | | |
| | | return 68; |
| | | } |
| | | |
| | | }else if(indexPath.section==1){ |
| | | |
| | | return 100; |
| | | } |
| | | |
| | | if (indexPath.section == 2) { |
| | | |
| | | if (indexPath.row == 0) { |
| | | |
| | | return 100; |
| | | |
| | | }else{ |
| | | |
| | | return 50; |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ |
| | | |
| | | if (section>1) { |
| | | |
| | | return 8; |
| | | |
| | | }else{ |
| | | |
| | | return CGFLOAT_MIN; |
| | | } |
| | | } |
| | | |
| | | - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{ |
| | | |
| | | return CGFLOAT_MIN; |
| | | } |
| | | |
| | | - (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ |
| | | |
| | | UIView *headerView=[[UIView alloc] initWithFrame:CGRectMake(0, 0, KScreenW, 8)]; |
| | | headerView.backgroundColor=[UIColor clearColor]; |
| | | |
| | | return headerView; |
| | | } |
| | | |
| | | - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath{ |
| | | |
| | | if(indexPath.section==0){ |
| | | |
| | | if(indexPath.row==0){ |
| | | |
| | | return NO; |
| | | } |
| | | |
| | | }else if(indexPath.section==1){ |
| | | |
| | | return NO; |
| | | } |
| | | return YES; |
| | | } |
| | | |
| | | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ |
| | | |
| | | [tableView deselectRowAtIndexPath:indexPath animated:YES]; |
| | | |
| | | if (indexPath.section==0) { |
| | | |
| | | switch (indexPath.row) { |
| | | case 1:{//æç红å
|
| | | |
| | | case 1: { |
| | | // æç红å
|
| | | // RedEnvelopesViewController *RedEnvelopesVC=[[RedEnvelopesViewController alloc] init]; |
| | | // RedEnvelopesVC.hidesBottomBarWhenPushed=YES; |
| | | // [self.navigationController pushViewController:RedEnvelopesVC animated:YES]; |
| | | |
| | | if ([[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]!=nil) { |
| | | |
| | | BalanceViewController *balanceVC=[[BalanceViewController alloc] init]; |
| | | |
| | | balanceVC.hidesBottomBarWhenPushed=YES; |
| | | balanceVC.datasource = _userInfo; |
| | | [self.navigationController pushViewController:balanceVC animated:YES]; |
| | | }else{ |
| | | //ç»å½çé¢ |
| | | LoginViewController *LoginVC=[[LoginViewController alloc] init]; |
| | | LoginVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:LoginVC animated:YES]; |
| | | } |
| | | |
| | | [self.navigationController pushViewController:balanceVC animated:YES]; |
| | | |
| | | } else { |
| | | |
| | | [self onLogin]; |
| | | } |
| | | } |
| | | break; |
| | | default: |
| | |
| | | } |
| | | |
| | | }else if (indexPath.section==2){ |
| | | |
| | | switch (indexPath.row) { |
| | | |
| | | case 1:{ |
| | | |
| | | if ([[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]!=nil) { |
| | | |
| | | ComplainViewController *complainVC=[[ComplainViewController alloc] init]; |
| | | complainVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:complainVC animated:YES]; |
| | | |
| | | }else{ |
| | | //ç»å½çé¢ |
| | | LoginViewController *LoginVC=[[LoginViewController alloc] init]; |
| | | LoginVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:LoginVC animated:YES]; |
| | | |
| | | [self onLogin]; |
| | | } |
| | | |
| | | }break; |
| | |
| | | case 2:{ |
| | | //å享æå¥½ç¤¼ |
| | | if([[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]!=nil){ |
| | | |
| | | MainInviteViewController *mianVC = [[MainInviteViewController alloc]init]; |
| | | |
| | | // XYRWebViewController *webView=[[XYRWebViewController alloc] init]; |
| | | // webView.url = [NSString stringWithFormat:@"%@?id=%@",[[NSUserDefaults standardUserDefaults] objectForKey:@"haoli"],[[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]];; |
| | | // webView.canDownRefresh = YES; |
| | | // webView.canshare = YES; |
| | | // ebView.canshare = YES; |
| | | // webView.backToRedBag = ^{ |
| | | // |
| | | // }; |
| | |
| | | mianVC.hidesBottomBarWhenPushed=YES; |
| | | |
| | | [self.navigationController pushViewController:mianVC animated:YES]; |
| | | |
| | | }else{ |
| | | //ç»å½çé¢ |
| | | LoginViewController *LoginVC=[[LoginViewController alloc] init]; |
| | | LoginVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:LoginVC animated:YES]; |
| | | |
| | | [self onLogin]; |
| | | } |
| | | } |
| | | break; |
| | | case 5:{//å¸®å© |
| | | |
| | | case 5: { |
| | | // å¸®å© |
| | | XYRWebViewController *SureWebVC=[[XYRWebViewController alloc] init]; |
| | | SureWebVC.url=[[NSUserDefaults standardUserDefaults] objectForKey:@"help"]; |
| | | SureWebVC.backToRedBag=^(){ |
| | | |
| | | }; |
| | | |
| | | SureWebVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:SureWebVC animated:YES]; |
| | | |
| | | } |
| | | break; |
| | | case 4:{ |
| | | |
| | | ServiceViewController *serviceVC=[[ServiceViewController alloc] init]; |
| | | serviceVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:serviceVC animated:YES]; |
| | | |
| | | }break; |
| | | |
| | | case 3:{//å
³äºæä»¬ |
| | | XYRWebViewController *aboutWebVC=[[XYRWebViewController alloc] init]; |
| | | aboutWebVC.url=[[NSUserDefaults standardUserDefaults] objectForKey:@"about"]; |
| | | aboutWebVC.backToRedBag=^(){ |
| | | // XYRWebViewController *aboutWebVC=[[XYRWebViewController alloc] init]; |
| | | // aboutWebVC.url=[[NSUserDefaults standardUserDefaults] objectForKey:@"about"]; |
| | | // aboutWebVC.backToRedBag=^(){ |
| | | // |
| | | // }; |
| | | // aboutWebVC.hidesBottomBarWhenPushed=YES; |
| | | // [self.navigationController pushViewController:aboutWebVC animated:YES]; |
| | | |
| | | }; |
| | | aboutWebVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:aboutWebVC animated:YES]; |
| | | WeexNativeController *weexNativeVc = [[WeexNativeController alloc] init]; |
| | | |
| | | weexNativeVc.urlString = @"http://192.168.1.122:9000/weex/weex_test.js"; |
| | | weexNativeVc.hidesBottomBarWhenPushed = YES; |
| | | |
| | | [self.navigationController pushViewController:weexNativeVc animated:YES]; |
| | | } |
| | | break; |
| | | |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | #pragma mark -UITableViewDataSource |
| | | #pragma mark --- UITableViewDataSource --- |
| | | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ |
| | | |
| | | return 3; |
| | | } |
| | | |
| | | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ |
| | | |
| | | if(section==0){ |
| | | |
| | | return 2; |
| | | |
| | | }else if(section==1){ |
| | | |
| | | return 1; |
| | | |
| | | }else{ |
| | | |
| | | return 4; |
| | | } |
| | | } |
| | | |
| | | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ |
| | | |
| | | if (indexPath.section==0) { |
| | | |
| | | if (indexPath.row==0) { |
| | | |
| | | MineHeaderTableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@"MineHeaderTableViewCell"]; |
| | | NSMutableAttributedString *mineRedBagMessage=[NSMutableAttributedString new]; |
| | | cell.backgroundColor=YTHColor(229, 0, 92); |
| | | |
| | | //æç红å
|
| | | { |
| | | NSMutableAttributedString *TipMessage=[[NSMutableAttributedString alloc] initWithString:@"ä½é¢ "]; |
| | |
| | | //红å
éé¢ |
| | | { |
| | | NSString *myHongBao=@"0.00"; |
| | | |
| | | if ([[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]!=nil) { |
| | | |
| | | if ([[_userInfo allKeys] containsObject:@"myHongBao"]){ |
| | | |
| | | int YY=[[_userInfo objectForKey:@"myHongBao"] floatValue]*100; |
| | | |
| | | if (YY/10000>0) { |
| | | |
| | | cell.redBagMessageW.constant=165.0f; |
| | | |
| | | }else if (YY/1000>0){ |
| | | |
| | | cell.redBagMessageW.constant=145.0f; |
| | | |
| | | }else{ |
| | | |
| | | cell.redBagMessageW.constant=130.0f; |
| | | } |
| | | |
| | |
| | | yuanMessage.yy_color=[UIColor whiteColor]; |
| | | [mineRedBagMessage appendAttributedString:yuanMessage]; |
| | | } |
| | | |
| | | cell.redBagMessage.numberOfLines = 1; |
| | | cell.redBagMessage.textVerticalAlignment = YYTextVerticalAlignmentTop; |
| | | cell.redBagMessage.attributedText = mineRedBagMessage; |
| | | cell.IDLabel.text = [NSString stringWithFormat:@"ID:%@",[[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]]; |
| | | |
| | | if ([[NSUserDefaults standardUserDefaults] objectForKey:@"userId"] ==nil) { |
| | | |
| | | cell.IDLabel.hidden = YES; |
| | | cell.gradeButton.hidden = YES; |
| | | |
| | | }else{ |
| | | |
| | | cell.IDLabel.hidden = NO; |
| | | cell.gradeButton.hidden = NO; |
| | | } |
| | | // NSInteger rank = [_userInfo[@"rank"]integerValue]; |
| | | // NSString *gradeString ; |
| | | |
| | | cell.gradeImage.hidden = NO; |
| | | |
| | | // if (rank == 0) { |
| | | // gradeString = @"æ®é"; |
| | | // cell.gradeImage.hidden = YES; |
| | | // }else if (rank == 1) { |
| | | // gradeString = @"éå "; |
| | | // }else if(rank == 2){ |
| | | // gradeString = @"é¶å "; |
| | | // }else { |
| | | // gradeString = @"éå "; |
| | | // } |
| | | // [cell.gradeButton setTitle:[NSString stringWithFormat:@"ç级:%@ä¼å>",gradeString] forState:UIControlStateNormal]; |
| | | // if (rank == 0) { |
| | | // [cell.gradeButton setTitle:[NSString stringWithFormat:@"ç级:%@>",gradeString] forState:UIControlStateNormal]; |
| | | // |
| | | // } |
| | | [cell.gradeLabelImage setYthImageWithURL:[_userInfo objectForKey:@"rankNamePicture"] placeholderImage:[UIImage imageNamed:@""]]; |
| | | // [cell.gradeButton addTarget:self action:@selector(gradeTaped:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.gradeLabelImage setYthImageWithURL:[_userInfo objectForKey:@"rankNamePicture"] |
| | | placeholderImage:[UIImage imageNamed:@""]]; |
| | | |
| | | NSString * rankIconString = [NSString stringWithFormat:@"%@",[_userInfo objectForKey:@"rankIcon"]]; |
| | | |
| | | [cell.gradeImage setYthImageWithURL:rankIconString placeholderImage:[UIImage imageNamed:@""]]; |
| | | |
| | | if (rankIconString.length == 0) { |
| | | |
| | | cell.gradeImage.hidden = YES; |
| | | } |
| | | |
| | | cell.gradeImage.contentMode = UIViewContentModeScaleAspectFit; |
| | | |
| | | //ç«å³ç»å½æé® |
| | | { |
| | | [cell.login addTarget:self action:@selector(LoginWithTaoBao:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.imageLogin addTarget:self action:@selector(LoginWithTaoBao:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.login addTarget:self |
| | | action:@selector(LoginWithTaoBao:) |
| | | forControlEvents:UIControlEventTouchUpInside]; |
| | | |
| | | [cell.imageLogin addTarget:self |
| | | action:@selector(LoginWithTaoBao:) |
| | | forControlEvents:UIControlEventTouchUpInside]; |
| | | } |
| | | |
| | | //æç红å
æé® |
| | | { |
| | | [cell.MIneRedPacketBtn addTarget:self action:@selector(mineRedPacketHasClicked:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.MIneRedPacketBtn addTarget:self |
| | | action:@selector(mineRedPacketHasClicked:) |
| | | forControlEvents:UIControlEventTouchUpInside]; |
| | | } |
| | | |
| | | //æç红å
å°å¸®å© |
| | | { |
| | | [cell.helper addTarget:self action:@selector(RedPacketHelperClicked:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.helper addTarget:self |
| | | action:@selector(RedPacketHelperClicked:) |
| | | forControlEvents:UIControlEventTouchUpInside]; |
| | | } |
| | | |
| | | //廿ç°çç¹å»äºä»¶ |
| | | [cell.withdraw addTarget:self action:@selector(gotoWithDraw:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.withdraw addTarget:self |
| | | action:@selector(gotoWithDraw:) |
| | | forControlEvents:UIControlEventTouchUpInside]; |
| | | |
| | | if([[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]!=nil){ |
| | | |
| | | //设置头å |
| | | [cell.TaoBaoIcon setYthImageWithURL:[_userInfo objectForKey:@"portrait"] placeholderImage:[UIImage imageNamed:@"é»è®¤å¤´å1"]]; |
| | | [cell.TaoBaoIcon setYthImageWithURL:[_userInfo objectForKey:@"portrait"] |
| | | placeholderImage:[UIImage imageNamed:@"é»è®¤å¤´å1"]]; |
| | | |
| | | //设置æµç§° |
| | | [cell.login setTitle:[_userInfo objectForKey:@"nickName"] forState:UIControlStateSelected]; |
| | | [cell.login setTitleColor:[UIColor whiteColor] forState:UIControlStateSelected]; |
| | | [cell.login setTitle:[_userInfo objectForKey:@"nickName"] |
| | | forState:UIControlStateSelected]; |
| | | [cell.login setTitleColor:[UIColor whiteColor] |
| | | forState:UIControlStateSelected]; |
| | | [cell.login setTintColor:[UIColor clearColor]]; |
| | | [cell.login setSelected:YES]; |
| | | [cell.imageLogin setSelected:YES]; |
| | | |
| | | cell.gradeLabelImage.hidden = NO; |
| | | cell.gradeButton.hidden = NO; |
| | | cell.loginLayout.constant = cell.login.titleLabel.text.length * 16 + 10; |
| | | |
| | | }else{ |
| | | |
| | | [cell.TaoBaoIcon setImage:[UIImage imageNamed:@"é»è®¤å¤´å1"]]; |
| | | [cell.login setSelected:NO]; |
| | | cell.loginLayout.constant = cell.login.titleLabel.text.length * 16 + 10; |
| | | [cell.imageLogin setSelected:NO]; |
| | | |
| | | cell.loginLayout.constant = cell.login.titleLabel.text.length * 16 + 10; |
| | | cell.gradeLabelImage.hidden = YES; |
| | | cell.gradeButton.hidden = YES; |
| | | cell.gradeImage.hidden = YES; |
| | | } |
| | | |
| | | return cell; |
| | | |
| | | }else if(indexPath.row==1){ |
| | | MineRedEnvelopesTableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@"MineRedEnvelopesTableViewCell"]; |
| | | |
| | | MineRedEnvelopesTableViewCell *cell = |
| | | [tableView dequeueReusableCellWithIdentifier:@"MineRedEnvelopesTableViewCell"]; |
| | | |
| | | cell.selectionStyle=UITableViewCellSelectionStyleNone; |
| | | |
| | | NSString *noOpenHongBao=@"0.00"; |
| | | |
| | | if ([[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]!=nil) { |
| | | |
| | | if ([[_userInfo allKeys] containsObject:@"myHongBao"]){ |
| | | noOpenHongBao=[NSString stringWithFormat:@"%.2f",[[_userInfo objectForKey:@"myHongBao"]doubleValue]]; |
| | | |
| | | noOpenHongBao=[NSString stringWithFormat:@"%.2f", |
| | | [[_userInfo objectForKey:@"myHongBao"]doubleValue]]; |
| | | } |
| | | } |
| | | |
| | | cell.notOpenRMB.text=noOpenHongBao; |
| | | // if([[_userInfo objectForKey:@"canOpenHongBao"] intValue]>0){ |
| | | // [cell.redPoint setHidden:NO]; |
| | | // }else{ |
| | | // [cell.redPoint setHidden:YES]; |
| | | // } |
| | | |
| | | return cell; |
| | | } |
| | | |
| | | }else if (indexPath.section==1){ |
| | | |
| | | if(indexPath.row==0){ |
| | | |
| | | MineToolBarTableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@"MineToolBarTableViewCell"]; |
| | | cell.accessoryType=UITableViewCellAccessoryNone; |
| | | |
| | | [cell.orderBtn addTarget:self action:@selector(GoToOrder:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.collectBtn addTarget:self action:@selector(GoTocollect:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.FootprintBtn addTarget:self action:@selector(GoToFootprint:) forControlEvents:UIControlEventTouchUpInside]; |
| | | |
| | | return cell; |
| | | } |
| | | |
| | | }else if (indexPath.section==2){ |
| | | |
| | | if (indexPath.row == 0) { |
| | | |
| | | MineThirdTableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@"MineThirdTableViewCell"]; |
| | | // if (!cell) { |
| | | // cell = [[NSBundle mainBundle] loadNibNamed:@"MineThirdTableViewCell" owner:self options:nil].firstObject; |
| | | // } |
| | | |
| | | [cell.noviceButton addTarget:self action:@selector(noviceTaped:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.helpButton addTarget:self action:@selector(helpTaped:) forControlEvents:UIControlEventTouchUpInside]; |
| | | [cell.servieveButton addTarget:self action:@selector(serviceTaped:) forControlEvents:UIControlEventTouchUpInside]; |
| | |
| | | cell.selectionStyle = UITableViewCellSelectionStyleNone; |
| | | return cell; |
| | | } |
| | | |
| | | MineHelperTableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@"MineHelperTableViewCell"]; |
| | | cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; |
| | | [cell.cellImage setImage:[UIImage imageNamed:grounpData2[indexPath.row - 1]]]; |
| | | [cell.cellTitle setText:grounpData2[indexPath.row - 1]]; |
| | | |
| | | if (indexPath.row == 2) { |
| | | [cell.cellTitle setText:@"é请æå¥"]; |
| | | } |
| | |
| | | return nil; |
| | | } |
| | | |
| | | /** |
| | | 订å |
| | | */ |
| | | #pragma mark --- 订å --- |
| | | -(void)GoToOrder:(UIButton *)sender{ |
| | | |
| | | NSString * openIDString = [NSString stringWithFormat:@"%@",_userInfo[@"openid"]]; |
| | | |
| | | if (openIDString.length != 0 && [[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]!=nil) { |
| | |
| | | OrderVC.type=@"订å"; |
| | | OrderVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:OrderVC animated:YES]; |
| | | |
| | | }else{ |
| | | |
| | | if ([[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]==nil) {//ç¨æ·æç»å½ |
| | | //ç»å½çé¢ |
| | | LoginViewController *LoginVC=[[LoginViewController alloc] init]; |
| | | LoginVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:LoginVC animated:YES]; |
| | | }else{//ç¨æ·ç»å½äºï¼ä½æ¯å¹¶æªç»å®æ·å® |
| | | |
| | | } else { |
| | | //ç¨æ·ç»å½äºï¼ä½æ¯å¹¶æªç»å®æ·å® |
| | | Alert=[[LXAlertView alloc] init]; |
| | | Alert.delegate=self; |
| | | Alert.isDriving=YES; |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | è¿å
¥æ¶èçé¢ |
| | | */ |
| | | #pragma mark --- è¿å
¥æ¶èçé¢ --- |
| | | -(void)GoTocollect:(UIButton *)sender{ |
| | | |
| | | if ([[NSUserDefaults standardUserDefaults] objectForKey:@"userId"]==nil) {//ç¨æ·æç»å½ |
| | | //ç»å½çé¢ |
| | | LoginViewController *LoginVC=[[LoginViewController alloc] init]; |
| | | LoginVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:LoginVC animated:YES]; |
| | | |
| | | }else{ |
| | | |
| | | MinCollectViewController *collectVC = [[MinCollectViewController alloc]init]; |
| | | collectVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:collectVC animated:YES]; |
| | | // Alert=[[LXAlertView alloc] init]; |
| | | // Alert.delegate=self; |
| | | // Alert.isDriving=YES; |
| | | // Alert.fatherViewController=self; |
| | | // [Alert show]; |
| | | |
| | | } |
| | | } |
| | | |
| | | } |
| | | -(NSMutableDictionary *)CommonDictionary{ |
| | | NSString *device=[NSString DeviceIdentifier]; |
| | | NSString *AppSecre=@"23649898"; |
| | | NSString *apiversion=@"1"; |
| | | NSString *platform=@"ios"; |
| | | NSString *appid = @"24567001"; |
| | | NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithCapacity:0]; |
| | | |
| | | [dic setObject:AppSecre forKey:@"appkey"]; |
| | | [dic setObject:device forKey:@"device"]; |
| | | [dic setObject:Package forKey:@"packages"]; |
| | | [dic setObject:Version forKey:@"version"]; |
| | | [dic setObject:apiversion forKey:@"apiversion"]; |
| | | [dic setObject:platform forKey:@"platform"]; |
| | | [dic setObject:appid forKey:@"appid"]; |
| | | //ç°å¨çæ¶é´ |
| | | NSTimeInterval timeNow = [[NSDate date] timeIntervalSince1970]; |
| | | long long int date = (long long int)timeNow*1000; |
| | | NSString *timeStr=[NSString stringWithFormat:@"%lld",date]; |
| | | [dic setObject:timeStr forKey:@"time"]; |
| | | |
| | | //å 坿¹æ³ |
| | | NSArray * array = [[NSArray alloc]initWithObjects:AppSecre,device,Package,Version,apiversion,platform,timeStr,nil]; |
| | | //æåº |
| | | NSArray *resultArrayrr = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { |
| | | NSComparisonResult result = [obj1 compare:obj2]; |
| | | return result == NSOrderedDescending; // ååº |
| | | }]; |
| | | |
| | | NSString *sign =[[NSString alloc] init]; |
| | | for (int i=0; i<resultArrayrr.count; i++) { |
| | | sign=[sign stringByAppendingString:resultArrayrr[i]]; |
| | | } |
| | | sign=[sign stringByAppendingString:@"buXiNjie2017!"]; |
| | | [dic setObject:[NSString md5:sign] forKey:@"sign"]; |
| | | return dic; |
| | | } |
| | | |
| | | /** |
| | | 足迹 |
| | | */ |
| | | #pragma mark --- 足迹 --- |
| | | -(void)GoToFootprint:(UIButton *)sender{ |
| | | |
| | | FootprintsViewController *FootprintsVC=[[FootprintsViewController alloc] init]; |
| | | FootprintsVC.hidesBottomBarWhenPushed=YES; |
| | | [self.navigationController pushViewController:FootprintsVC animated:YES]; |
| | | } |
| | | |
| | | #pragma mark --- ç»å½ --- |
| | | - (void)onLogin { |
| | | // ç»å½çé¢ |
| | | LoginViewController *LoginVC=[[LoginViewController alloc] init]; |
| | | LoginVC.hidesBottomBarWhenPushed = YES; |
| | | [self.navigationController pushViewController:LoginVC animated:YES]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // WeexNativeController.h |
| | | // MIduo |
| | | // |
| | | // Created by éåºè¿å°ç§ææéå
¬å¸ on 2018/6/28. |
| | | // Copyright © 2018年 yeshi. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @interface WeexNativeController : UIViewController |
| | | |
| | | /// 渲æå°å |
| | | @property (nonatomic, copy) NSString *urlString; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // WeexNativeController.m |
| | | // MIduo |
| | | // |
| | | // Created by éåºè¿å°ç§ææéå
¬å¸ on 2018/6/28. |
| | | // Copyright © 2018年 yeshi. All rights reserved. |
| | | // |
| | | |
| | | #import "WeexNativeController.h" |
| | | #import <WeexSDK/WXSDKInstance.h> |
| | | |
| | | @interface WeexNativeController () |
| | | |
| | | @property (nonatomic, strong) WXSDKInstance *instance; |
| | | |
| | | @end |
| | | |
| | | @implementation WeexNativeController |
| | | |
| | | - (void)viewWillAppear:(BOOL)animated { |
| | | [super viewWillAppear:animated]; |
| | | |
| | | [self.navigationController setNavigationBarHidden:YES animated:animated]; |
| | | } |
| | | |
| | | - (void)viewWillDisappear:(BOOL)animated { |
| | | |
| | | [super viewWillDisappear:animated]; |
| | | |
| | | [self.navigationController setNavigationBarHidden:NO animated:animated]; |
| | | } |
| | | |
| | | - (void)viewDidLoad { |
| | | [super viewDidLoad]; |
| | | |
| | | [self viewConfig]; |
| | | } |
| | | |
| | | - (void)viewConfig { |
| | | |
| | | // èæ¯ |
| | | self.view.backgroundColor = [UIColor whiteColor]; |
| | | // åå§å |
| | | self.instance = [[WXSDKInstance alloc] init]; |
| | | self.instance.viewController = self; |
| | | |
| | | // CGRect rect = CGRectMake(0, NAV_STATUS_BAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - NAV_STATUS_BAR_HEIGHT - TAB_BAR_HEIGHT); |
| | | |
| | | CGRect rect = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); |
| | | |
| | | self.instance.frame = rect; |
| | | |
| | | __weak typeof(self) weakSelf = self; |
| | | |
| | | _instance.onCreate = ^(UIView *view) { |
| | | |
| | | [weakSelf.view addSubview:view]; |
| | | }; |
| | | // 渲æé误 |
| | | _instance.onFailed = ^(NSError *error) { |
| | | ALLog(@"error:%@",error); |
| | | //process failure |
| | | }; |
| | | // 渲æå®æ |
| | | _instance.renderFinish = ^ (UIView *view) { |
| | | ALLog(@"success"); |
| | | //process renderFinish |
| | | }; |
| | | // @"http://192.168.1.122:9000/weex/gonglue.js" |
| | | // @"http://192.168.1.122:9000/weex/weex_test.js" |
| | | // @"http://192.168.1.122:9000/weex/refresh.js" |
| | | |
| | | if (self.urlString) { |
| | | |
| | | NSURL *url = [NSURL URLWithString:self.urlString]; |
| | | // å¼å§æ¸²æ |
| | | [self.instance renderWithURL:url]; |
| | | } |
| | | } |
| | | |
| | | #pragma mark --- 鿝 --- |
| | | - (void)dealloc { |
| | | |
| | | [self.instance destroyInstance]; |
| | | |
| | | [self.instance forceGarbageCollection]; |
| | | } |
| | | |
| | | - (void)didReceiveMemoryWarning { |
| | | [super didReceiveMemoryWarning]; |
| | | // Dispose of any resources that can be recreated. |
| | | } |
| | | |
| | | @end |
| | |
| | | [dic setObject:_goodsID forKey:@"id"]; |
| | | |
| | | [JYNetWorking Post:[NSString stringWithFormat:@"%@/%@",domainHTTP,@"share/getGoodsShareUrl"] param:dic success:^(NSDictionary *object) { |
| | | NSLog(@"%@",object); |
| | | |
| | | ShonpingShareViewController *shopVC = [[ShonpingShareViewController alloc]init]; |
| | | shopVC.urlString = object[@"data"]; |
| | | shopVC.hidesBottomBarWhenPushed=YES; |
| | |
| | | pod 'IQKeyboardManager' |
| | | pod 'WechatOpenSDK' |
| | | pod 'WeexSDK', '0.17.0' |
| | | pod 'SDAutoLayout' |
| | | pod 'ReactiveCocoa', :git => 'https://github.com/zhao0/ReactiveCocoa.git', :tag => '2.5.2' |
| | | |
| | | pod âUMengUShare/UIâ |
| | | # éæå¾®ä¿¡(ç²¾ç®ç0.2M) |
| | |
| | | - SecurityGuardSDK |
| | | - SGMain |
| | | - SGSecurityBody |
| | | - ReactiveCocoa (2.5.2): |
| | | - ReactiveCocoa/UI (= 2.5.2) |
| | | - ReactiveCocoa/Core (2.5.2): |
| | | - ReactiveCocoa/no-arc |
| | | - ReactiveCocoa/no-arc (2.5.2) |
| | | - ReactiveCocoa/UI (2.5.2): |
| | | - ReactiveCocoa/Core |
| | | - SDAutoLayout (2.2.0) |
| | | - SDCycleScrollView (1.75): |
| | | - SDWebImage (>= 4.0.0) |
| | | - SDWebImage (4.2.2): |
| | |
| | | - Masonry |
| | | - MBProgressHUD |
| | | - MJRefresh |
| | | - ReactiveCocoa (from `https://github.com/zhao0/ReactiveCocoa.git`, tag `2.5.2`) |
| | | - SDAutoLayout |
| | | - SDCycleScrollView (~> 1.75) |
| | | - SDWebImage |
| | | - SVProgressHUD |
| | |
| | | - Masonry |
| | | - MBProgressHUD |
| | | - MJRefresh |
| | | - SDAutoLayout |
| | | - SDCycleScrollView |
| | | - SDWebImage |
| | | - SVProgressHUD |
| | |
| | | - YYText |
| | | - YYWebImage |
| | | |
| | | EXTERNAL SOURCES: |
| | | ReactiveCocoa: |
| | | :git: https://github.com/zhao0/ReactiveCocoa.git |
| | | :tag: 2.5.2 |
| | | |
| | | CHECKOUT OPTIONS: |
| | | ReactiveCocoa: |
| | | :git: https://github.com/zhao0/ReactiveCocoa.git |
| | | :tag: 2.5.2 |
| | | |
| | | SPEC CHECKSUMS: |
| | | AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67 |
| | | AlibabaAuthSDK: b5d7c1964f195c1182d9410e3158c469d25876fa |
| | |
| | | MBProgressHUD: e7baa36a220447d8aeb12769bf0585582f3866d9 |
| | | MJRefresh: 5f8552bc25ca8751c010f621c1098dbdaacbccd6 |
| | | mtop: 1513ec32f69174bd7c95a07d393f26ef1cad3af7 |
| | | ReactiveCocoa: 3ff25b1bd992ac79c5c79b26b6c0c1713b715bb2 |
| | | SDAutoLayout: c5509e77ae75735d9a39f7bc83dba6d3f75d49d8 |
| | | SDCycleScrollView: 884b88f0266dd4708a0e1934975c69cb971707b1 |
| | | SDWebImage: 89a9d32cd520bbb46eb14e541d5109b3564af198 |
| | | SecurityGuardSDK: 05f6d782189160f2b23b731eed5cb314ce584855 |
| | |
| | | YYText: 5c461d709e24d55a182d1441c41dc639a18a4849 |
| | | YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928 |
| | | |
| | | PODFILE CHECKSUM: cdfe344091c61fb3d985762320ca1222bb9dbe95 |
| | | PODFILE CHECKSUM: 350af71aa54c8676cfffcde27ef14709c686e016 |
| | | |
| | | COCOAPODS: 1.5.3 |
New file |
| | |
| | | { |
| | | "name": "ReactiveCocoa", |
| | | "version": "2.5.2", |
| | | "summary": "A framework for composing and transforming streams of values.", |
| | | "homepage": "https://github.com/blog/1107-reactivecocoa-is-now-open-source", |
| | | "authors": { |
| | | "Josh Abernathy": "josh@github.com" |
| | | }, |
| | | "source": { |
| | | "git": "https://github.com/ReactiveCocoa/ReactiveCocoa.git", |
| | | "tag": "2.5.2" |
| | | }, |
| | | "license": "MIT", |
| | | "description": "ReactiveCocoa (RAC) is an Objective-C framework for Functional Reactive Programming. It provides APIs for composing and transforming streams of values.", |
| | | "requires_arc": true, |
| | | "platforms": { |
| | | "ios": "6.0", |
| | | "osx": "10.8" |
| | | }, |
| | | "default_subspecs": "UI", |
| | | "prepare_command": "find . \\( -regex '.*EXT.*\\.[mh]$' -o -regex '.*metamacros\\.[mh]$' \\) -execdir mv {} RAC{} \\;\nfind . -regex '.*\\.[hm]' -exec sed -i '' -E 's@\"(EXT.*|metamacros)\\.h\"@\"RAC\\1.h\"@' {} \\;\nfind . -regex '.*\\.[hm]' -exec sed -i '' -E 's@<ReactiveCocoa/(EXT.*)\\.h>@<ReactiveCocoa/RAC\\1.h>@' {} \\;", |
| | | "subspecs": [ |
| | | { |
| | | "name": "no-arc", |
| | | "source_files": "ReactiveCocoa/RACObjCRuntime.{h,m}", |
| | | "requires_arc": false |
| | | }, |
| | | { |
| | | "name": "UI", |
| | | "dependencies": { |
| | | "ReactiveCocoa/Core": [ |
| | | |
| | | ] |
| | | }, |
| | | "source_files": "ReactiveCocoa/*{AppKit,NSControl,NSText,UI,MK}*", |
| | | "ios": { |
| | | "exclude_files": "ReactiveCocoa/*{AppKit,NSControl,NSText}*" |
| | | }, |
| | | "osx": { |
| | | "exclude_files": "ReactiveCocoa/*{UI,MK}*" |
| | | } |
| | | }, |
| | | { |
| | | "name": "Core", |
| | | "dependencies": { |
| | | "ReactiveCocoa/no-arc": [ |
| | | |
| | | ] |
| | | }, |
| | | "source_files": [ |
| | | "ReactiveCocoa/*.{d,h,m}", |
| | | "ReactiveCocoa/extobjc/*.{h,m}" |
| | | ], |
| | | "private_header_files": "ReactiveCocoa/*Private.h", |
| | | "exclude_files": "ReactiveCocoa/*{RACObjCRuntime,AppKit,NSControl,NSText,UIActionSheet,UI,MK}*" |
| | | } |
| | | ] |
| | | } |
| | |
| | | - SecurityGuardSDK |
| | | - SGMain |
| | | - SGSecurityBody |
| | | - ReactiveCocoa (2.5.2): |
| | | - ReactiveCocoa/UI (= 2.5.2) |
| | | - ReactiveCocoa/Core (2.5.2): |
| | | - ReactiveCocoa/no-arc |
| | | - ReactiveCocoa/no-arc (2.5.2) |
| | | - ReactiveCocoa/UI (2.5.2): |
| | | - ReactiveCocoa/Core |
| | | - SDAutoLayout (2.2.0) |
| | | - SDCycleScrollView (1.75): |
| | | - SDWebImage (>= 4.0.0) |
| | | - SDWebImage (4.2.2): |
| | |
| | | - Masonry |
| | | - MBProgressHUD |
| | | - MJRefresh |
| | | - ReactiveCocoa (from `https://github.com/zhao0/ReactiveCocoa.git`, tag `2.5.2`) |
| | | - SDAutoLayout |
| | | - SDCycleScrollView (~> 1.75) |
| | | - SDWebImage |
| | | - SVProgressHUD |
| | |
| | | - Masonry |
| | | - MBProgressHUD |
| | | - MJRefresh |
| | | - SDAutoLayout |
| | | - SDCycleScrollView |
| | | - SDWebImage |
| | | - SVProgressHUD |
| | |
| | | - YYText |
| | | - YYWebImage |
| | | |
| | | EXTERNAL SOURCES: |
| | | ReactiveCocoa: |
| | | :git: https://github.com/zhao0/ReactiveCocoa.git |
| | | :tag: 2.5.2 |
| | | |
| | | CHECKOUT OPTIONS: |
| | | ReactiveCocoa: |
| | | :git: https://github.com/zhao0/ReactiveCocoa.git |
| | | :tag: 2.5.2 |
| | | |
| | | SPEC CHECKSUMS: |
| | | AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67 |
| | | AlibabaAuthSDK: b5d7c1964f195c1182d9410e3158c469d25876fa |
| | |
| | | MBProgressHUD: e7baa36a220447d8aeb12769bf0585582f3866d9 |
| | | MJRefresh: 5f8552bc25ca8751c010f621c1098dbdaacbccd6 |
| | | mtop: 1513ec32f69174bd7c95a07d393f26ef1cad3af7 |
| | | ReactiveCocoa: 3ff25b1bd992ac79c5c79b26b6c0c1713b715bb2 |
| | | SDAutoLayout: c5509e77ae75735d9a39f7bc83dba6d3f75d49d8 |
| | | SDCycleScrollView: 884b88f0266dd4708a0e1934975c69cb971707b1 |
| | | SDWebImage: 89a9d32cd520bbb46eb14e541d5109b3564af198 |
| | | SecurityGuardSDK: 05f6d782189160f2b23b731eed5cb314ce584855 |
| | |
| | | YYText: 5c461d709e24d55a182d1441c41dc639a18a4849 |
| | | YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928 |
| | | |
| | | PODFILE CHECKSUM: cdfe344091c61fb3d985762320ca1222bb9dbe95 |
| | | PODFILE CHECKSUM: 350af71aa54c8676cfffcde27ef14709c686e016 |
| | | |
| | | COCOAPODS: 1.5.3 |
| | |
| | | buildForAnalyzing = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "3A22172D434A84E2BED002011ABFFA64" |
| | | BlueprintIdentifier = "48016486296AF72C325ED70A8AB16F71" |
| | | BuildableName = "Pods_MIduo.framework" |
| | | BlueprintName = "Pods-MIduo" |
| | | ReferencedContainer = "container:Pods.xcodeproj"> |
| | |
| | | <MacroExpansion> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "3A22172D434A84E2BED002011ABFFA64" |
| | | BlueprintIdentifier = "48016486296AF72C325ED70A8AB16F71" |
| | | BuildableName = "Pods_MIduo.framework" |
| | | BlueprintName = "Pods-MIduo" |
| | | ReferencedContainer = "container:Pods.xcodeproj"> |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <Scheme |
| | | LastUpgradeVersion = "0930" |
| | | version = "1.3"> |
| | | <BuildAction |
| | | parallelizeBuildables = "YES" |
| | | buildImplicitDependencies = "YES"> |
| | | <BuildActionEntries> |
| | | <BuildActionEntry |
| | | buildForAnalyzing = "YES" |
| | | buildForTesting = "YES" |
| | | buildForRunning = "YES" |
| | | buildForProfiling = "YES" |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "22747522C67490AB7C743C0EDC3DD15C" |
| | | BlueprintName = "ReactiveCocoa" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "ReactiveCocoa.framework"> |
| | | </BuildableReference> |
| | | </BuildActionEntry> |
| | | </BuildActionEntries> |
| | | </BuildAction> |
| | | <TestAction |
| | | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
| | | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
| | | shouldUseLaunchSchemeArgsEnv = "YES" |
| | | buildConfiguration = "Debug"> |
| | | <AdditionalOptions> |
| | | </AdditionalOptions> |
| | | </TestAction> |
| | | <LaunchAction |
| | | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
| | | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
| | | launchStyle = "0" |
| | | useCustomWorkingDirectory = "NO" |
| | | ignoresPersistentStateOnLaunch = "NO" |
| | | debugDocumentVersioning = "YES" |
| | | debugServiceExtension = "internal" |
| | | buildConfiguration = "Debug" |
| | | allowLocationSimulation = "YES"> |
| | | <AdditionalOptions> |
| | | </AdditionalOptions> |
| | | </LaunchAction> |
| | | <ProfileAction |
| | | savedToolIdentifier = "" |
| | | useCustomWorkingDirectory = "NO" |
| | | debugDocumentVersioning = "YES" |
| | | buildConfiguration = "Release" |
| | | shouldUseLaunchSchemeArgsEnv = "YES"> |
| | | </ProfileAction> |
| | | <AnalyzeAction |
| | | buildConfiguration = "Debug"> |
| | | </AnalyzeAction> |
| | | <ArchiveAction |
| | | buildConfiguration = "Release" |
| | | revealArchiveInOrganizer = "YES"> |
| | | </ArchiveAction> |
| | | </Scheme> |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <Scheme |
| | | LastUpgradeVersion = "0930" |
| | | version = "1.3"> |
| | | <BuildAction |
| | | parallelizeBuildables = "YES" |
| | | buildImplicitDependencies = "YES"> |
| | | <BuildActionEntries> |
| | | <BuildActionEntry |
| | | buildForAnalyzing = "YES" |
| | | buildForTesting = "YES" |
| | | buildForRunning = "YES" |
| | | buildForProfiling = "YES" |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "C220E04FFDB20994ECFAF4229DC6519C" |
| | | BlueprintName = "SDAutoLayout" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "SDAutoLayout.framework"> |
| | | </BuildableReference> |
| | | </BuildActionEntry> |
| | | </BuildActionEntries> |
| | | </BuildAction> |
| | | <TestAction |
| | | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
| | | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
| | | shouldUseLaunchSchemeArgsEnv = "YES" |
| | | buildConfiguration = "Debug"> |
| | | <AdditionalOptions> |
| | | </AdditionalOptions> |
| | | </TestAction> |
| | | <LaunchAction |
| | | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
| | | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
| | | launchStyle = "0" |
| | | useCustomWorkingDirectory = "NO" |
| | | ignoresPersistentStateOnLaunch = "NO" |
| | | debugDocumentVersioning = "YES" |
| | | debugServiceExtension = "internal" |
| | | buildConfiguration = "Debug" |
| | | allowLocationSimulation = "YES"> |
| | | <AdditionalOptions> |
| | | </AdditionalOptions> |
| | | </LaunchAction> |
| | | <ProfileAction |
| | | savedToolIdentifier = "" |
| | | useCustomWorkingDirectory = "NO" |
| | | debugDocumentVersioning = "YES" |
| | | buildConfiguration = "Release" |
| | | shouldUseLaunchSchemeArgsEnv = "YES"> |
| | | </ProfileAction> |
| | | <AnalyzeAction |
| | | buildConfiguration = "Debug"> |
| | | </AnalyzeAction> |
| | | <ArchiveAction |
| | | buildConfiguration = "Release" |
| | | revealArchiveInOrganizer = "YES"> |
| | | </ArchiveAction> |
| | | </Scheme> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "E0F5B143DCFD49C7743D124A754004DB" |
| | | BlueprintIdentifier = "710215391D75D349326B1ED2B527B6F8" |
| | | BlueprintName = "SDCycleScrollView" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "SDCycleScrollView.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "82A0FD10ACF7A1B27DFD9D000077ED25" |
| | | BlueprintIdentifier = "C2AF5635AA2D5DE6AF427D00DDF08B8B" |
| | | BlueprintName = "SDWebImage" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "SDWebImage.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "AC07BE35ABEE396697331DAF1F1C2194" |
| | | BlueprintIdentifier = "BE245B5C9297BF1C6CEBC216DCF583C9" |
| | | BlueprintName = "SVProgressHUD" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "SVProgressHUD.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "6CE053D037D9F8F34C38CE2542E758AE" |
| | | BlueprintIdentifier = "7BBD6DAC561AC67DE8B0778A50DE76FA" |
| | | BlueprintName = "UICollectionViewLeftAlignedLayout" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "UICollectionViewLeftAlignedLayout.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "936EAFEECAFF4B0C24365F6FA0F579F8" |
| | | BlueprintIdentifier = "F5CC79CABF3EB68155BBAC44E6BEB02F" |
| | | BlueprintName = "WeexSDK" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "WeexSDK.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "F2E3E3E84749C68EADE5D091728D6003" |
| | | BlueprintIdentifier = "B7AC216168E7D33E12FE87F8033E8188" |
| | | BlueprintName = "YYCache" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "YYCache.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "2F43E20F0C6BC7A4D165C6ED6020EC01" |
| | | BlueprintIdentifier = "D7F52115619489CC5BF8E43AF326506F" |
| | | BlueprintName = "YYImage" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "YYImage.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "30110AFEC19F9992820EC7BFF62F8F71" |
| | | BlueprintIdentifier = "CCCA6A4BD5213B6BAB5028A6C0AA8E88" |
| | | BlueprintName = "YYKeyboardManager" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "YYKeyboardManager.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "2A875314E3E852FC303A3A10B689EA2F" |
| | | BlueprintIdentifier = "958B834E1966066B2D163978D140ED27" |
| | | BlueprintName = "YYModel" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "YYModel.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "7B6602B179E085507A7FA0DAF65A5C88" |
| | | BlueprintIdentifier = "AD2B403DBB958A70AC5AA6C751B1C28F" |
| | | BlueprintName = "YYText" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "YYText.framework"> |
| | |
| | | buildForArchiving = "YES"> |
| | | <BuildableReference |
| | | BuildableIdentifier = "primary" |
| | | BlueprintIdentifier = "35B77BA8685871584392661E95FA51D0" |
| | | BlueprintIdentifier = "5B4D3D6FFB2EA70AF6FECCA3860395DD" |
| | | BlueprintName = "YYWebImage" |
| | | ReferencedContainer = "container:Pods.xcodeproj" |
| | | BuildableName = "YYWebImage.framework"> |
New file |
| | |
| | | **Copyright (c) 2012 - 2015, GitHub, Inc.** |
| | | **All rights reserved.** |
| | | |
| | | 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: |
| | | |
| | | The above copyright notice and this permission notice shall be included in all |
| | | copies or substantial portions of the Software. |
| | | |
| | | 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. |
New file |
| | |
| | | # ReactiveCocoa [](https://github.com/Carthage/Carthage) |
| | | |
| | | ReactiveCocoa (RAC) is an Objective-C framework inspired by [Functional Reactive |
| | | Programming][]. It provides APIs for **composing and transforming streams of |
| | | values**. |
| | | |
| | | If you're already familiar with functional reactive programming or know the basic |
| | | premise of ReactiveCocoa, check out the [Documentation][] folder for a framework |
| | | overview and more in-depth information about how it all works in practice. |
| | | |
| | | ## New to ReactiveCocoa? |
| | | |
| | | ReactiveCocoa is documented like crazy, and there's a wealth of introductory |
| | | material available to explain what RAC is and how you can use it. |
| | | |
| | | If you want to learn more, we recommend these resources, roughly in order: |
| | | |
| | | 1. [Introduction](#introduction) |
| | | 1. [When to use ReactiveCocoa](#when-to-use-reactivecocoa) |
| | | 1. [Framework Overview][] |
| | | 1. [Basic Operators][] |
| | | 1. [Header documentation](ReactiveCocoa) |
| | | 1. Previously answered [Stack Overflow](https://github.com/ReactiveCocoa/ReactiveCocoa/wiki) |
| | | questions and [GitHub issues](https://github.com/ReactiveCocoa/ReactiveCocoa/issues?labels=question&state=closed) |
| | | 1. The rest of the [Documentation][] folder |
| | | 1. [Functional Reactive Programming on iOS](https://leanpub.com/iosfrp/) |
| | | (eBook) |
| | | |
| | | If you have any further questions, please feel free to [file an issue](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/new). |
| | | |
| | | ## Introduction |
| | | |
| | | ReactiveCocoa is inspired by [functional reactive |
| | | programming](http://blog.maybeapps.com/post/42894317939/input-and-output). |
| | | Rather than using mutable variables which are replaced and modified in-place, |
| | | RAC provides signals (represented by `RACSignal`) that capture present and |
| | | future values. |
| | | |
| | | By chaining, combining, and reacting to signals, software can be written |
| | | declaratively, without the need for code that continually observes and updates |
| | | values. |
| | | |
| | | For example, a text field can be bound to the latest time, even as it changes, |
| | | instead of using additional code that watches the clock and updates the |
| | | text field every second. It works much like KVO, but with blocks instead of |
| | | overriding `-observeValueForKeyPath:ofObject:change:context:`. |
| | | |
| | | Signals can also represent asynchronous operations, much like [futures and |
| | | promises][]. This greatly simplifies asynchronous software, including networking |
| | | code. |
| | | |
| | | One of the major advantages of RAC is that it provides a single, unified |
| | | approach to dealing with asynchronous behaviors, including delegate methods, |
| | | callback blocks, target-action mechanisms, notifications, and KVO. |
| | | |
| | | Here's a simple example: |
| | | |
| | | ```objc |
| | | // When self.username changes, logs the new name to the console. |
| | | // |
| | | // RACObserve(self, username) creates a new RACSignal that sends the current |
| | | // value of self.username, then the new value whenever it changes. |
| | | // -subscribeNext: will execute the block whenever the signal sends a value. |
| | | [RACObserve(self, username) subscribeNext:^(NSString *newName) { |
| | | NSLog(@"%@", newName); |
| | | }]; |
| | | ``` |
| | | |
| | | But unlike KVO notifications, signals can be chained together and operated on: |
| | | |
| | | ```objc |
| | | // Only logs names that starts with "j". |
| | | // |
| | | // -filter returns a new RACSignal that only sends a new value when its block |
| | | // returns YES. |
| | | [[RACObserve(self, username) |
| | | filter:^(NSString *newName) { |
| | | return [newName hasPrefix:@"j"]; |
| | | }] |
| | | subscribeNext:^(NSString *newName) { |
| | | NSLog(@"%@", newName); |
| | | }]; |
| | | ``` |
| | | |
| | | Signals can also be used to derive state. Instead of observing properties and |
| | | setting other properties in response to the new values, RAC makes it possible to |
| | | express properties in terms of signals and operations: |
| | | |
| | | ```objc |
| | | // Creates a one-way binding so that self.createEnabled will be |
| | | // true whenever self.password and self.passwordConfirmation |
| | | // are equal. |
| | | // |
| | | // RAC() is a macro that makes the binding look nicer. |
| | | // |
| | | // +combineLatest:reduce: takes an array of signals, executes the block with the |
| | | // latest value from each signal whenever any of them changes, and returns a new |
| | | // RACSignal that sends the return value of that block as values. |
| | | RAC(self, createEnabled) = [RACSignal |
| | | combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ] |
| | | reduce:^(NSString *password, NSString *passwordConfirm) { |
| | | return @([passwordConfirm isEqualToString:password]); |
| | | }]; |
| | | ``` |
| | | |
| | | Signals can be built on any stream of values over time, not just KVO. For |
| | | example, they can also represent button presses: |
| | | |
| | | ```objc |
| | | // Logs a message whenever the button is pressed. |
| | | // |
| | | // RACCommand creates signals to represent UI actions. Each signal can |
| | | // represent a button press, for example, and have additional work associated |
| | | // with it. |
| | | // |
| | | // -rac_command is an addition to NSButton. The button will send itself on that |
| | | // command whenever it's pressed. |
| | | self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) { |
| | | NSLog(@"button was pressed!"); |
| | | return [RACSignal empty]; |
| | | }]; |
| | | ``` |
| | | |
| | | Or asynchronous network operations: |
| | | |
| | | ```objc |
| | | // Hooks up a "Log in" button to log in over the network. |
| | | // |
| | | // This block will be run whenever the login command is executed, starting |
| | | // the login process. |
| | | self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) { |
| | | // The hypothetical -logIn method returns a signal that sends a value when |
| | | // the network request finishes. |
| | | return [client logIn]; |
| | | }]; |
| | | |
| | | // -executionSignals returns a signal that includes the signals returned from |
| | | // the above block, one for each time the command is executed. |
| | | [self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) { |
| | | // Log a message whenever we log in successfully. |
| | | [loginSignal subscribeCompleted:^{ |
| | | NSLog(@"Logged in successfully!"); |
| | | }]; |
| | | }]; |
| | | |
| | | // Executes the login command when the button is pressed. |
| | | self.loginButton.rac_command = self.loginCommand; |
| | | ``` |
| | | |
| | | Signals can also represent timers, other UI events, or anything else that |
| | | changes over time. |
| | | |
| | | Using signals for asynchronous operations makes it possible to build up more |
| | | complex behavior by chaining and transforming those signals. Work can easily be |
| | | triggered after a group of operations completes: |
| | | |
| | | ```objc |
| | | // Performs 2 network operations and logs a message to the console when they are |
| | | // both completed. |
| | | // |
| | | // +merge: takes an array of signals and returns a new RACSignal that passes |
| | | // through the values of all of the signals and completes when all of the |
| | | // signals complete. |
| | | // |
| | | // -subscribeCompleted: will execute the block when the signal completes. |
| | | [[RACSignal |
| | | merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]] |
| | | subscribeCompleted:^{ |
| | | NSLog(@"They're both done!"); |
| | | }]; |
| | | ``` |
| | | |
| | | Signals can be chained to sequentially execute asynchronous operations, instead |
| | | of nesting callbacks with blocks. This is similar to how [futures and promises][] |
| | | are usually used: |
| | | |
| | | ```objc |
| | | // Logs in the user, then loads any cached messages, then fetches the remaining |
| | | // messages from the server. After that's all done, logs a message to the |
| | | // console. |
| | | // |
| | | // The hypothetical -logInUser methods returns a signal that completes after |
| | | // logging in. |
| | | // |
| | | // -flattenMap: will execute its block whenever the signal sends a value, and |
| | | // returns a new RACSignal that merges all of the signals returned from the block |
| | | // into a single signal. |
| | | [[[[client |
| | | logInUser] |
| | | flattenMap:^(User *user) { |
| | | // Return a signal that loads cached messages for the user. |
| | | return [client loadCachedMessagesForUser:user]; |
| | | }] |
| | | flattenMap:^(NSArray *messages) { |
| | | // Return a signal that fetches any remaining messages. |
| | | return [client fetchMessagesAfterMessage:messages.lastObject]; |
| | | }] |
| | | subscribeNext:^(NSArray *newMessages) { |
| | | NSLog(@"New messages: %@", newMessages); |
| | | } completed:^{ |
| | | NSLog(@"Fetched all messages."); |
| | | }]; |
| | | ``` |
| | | |
| | | RAC even makes it easy to bind to the result of an asynchronous operation: |
| | | |
| | | ```objc |
| | | // Creates a one-way binding so that self.imageView.image will be set as the user's |
| | | // avatar as soon as it's downloaded. |
| | | // |
| | | // The hypothetical -fetchUserWithUsername: method returns a signal which sends |
| | | // the user. |
| | | // |
| | | // -deliverOn: creates new signals that will do their work on other queues. In |
| | | // this example, it's used to move work to a background queue and then back to the main thread. |
| | | // |
| | | // -map: calls its block with each user that's fetched and returns a new |
| | | // RACSignal that sends values returned from the block. |
| | | RAC(self.imageView, image) = [[[[client |
| | | fetchUserWithUsername:@"joshaber"] |
| | | deliverOn:[RACScheduler scheduler]] |
| | | map:^(User *user) { |
| | | // Download the avatar (this is done on a background queue). |
| | | return [[NSImage alloc] initWithContentsOfURL:user.avatarURL]; |
| | | }] |
| | | // Now the assignment will be done on the main thread. |
| | | deliverOn:RACScheduler.mainThreadScheduler]; |
| | | ``` |
| | | |
| | | That demonstrates some of what RAC can do, but it doesn't demonstrate why RAC is |
| | | so powerful. It's hard to appreciate RAC from README-sized examples, but it |
| | | makes it possible to write code with less state, less boilerplate, better code |
| | | locality, and better expression of intent. |
| | | |
| | | For more sample code, check out [C-41][] or [GroceryList][], which are real iOS |
| | | apps written using ReactiveCocoa. Additional information about RAC can be found |
| | | in the [Documentation][] folder. |
| | | |
| | | ## When to use ReactiveCocoa |
| | | |
| | | Upon first glance, ReactiveCocoa is very abstract, and it can be difficult to |
| | | understand how to apply it to concrete problems. |
| | | |
| | | Here are some of the use cases that RAC excels at. |
| | | |
| | | ### Handling asynchronous or event-driven data sources |
| | | |
| | | Much of Cocoa programming is focused on reacting to user events or changes in |
| | | application state. Code that deals with such events can quickly become very |
| | | complex and spaghetti-like, with lots of callbacks and state variables to handle |
| | | ordering issues. |
| | | |
| | | Patterns that seem superficially different, like UI callbacks, network |
| | | responses, and KVO notifications, actually have a lot in common. [RACSignal][] |
| | | unifies all these different APIs so that they can be composed together and |
| | | manipulated in the same way. |
| | | |
| | | For example, the following code: |
| | | |
| | | ```objc |
| | | |
| | | static void *ObservationContext = &ObservationContext; |
| | | |
| | | - (void)viewDidLoad { |
| | | [super viewDidLoad]; |
| | | |
| | | [LoginManager.sharedManager addObserver:self forKeyPath:@"loggingIn" options:NSKeyValueObservingOptionInitial context:&ObservationContext]; |
| | | [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(loggedOut:) name:UserDidLogOutNotification object:LoginManager.sharedManager]; |
| | | |
| | | [self.usernameTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged]; |
| | | [self.passwordTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged]; |
| | | [self.logInButton addTarget:self action:@selector(logInPressed:) forControlEvents:UIControlEventTouchUpInside]; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | [LoginManager.sharedManager removeObserver:self forKeyPath:@"loggingIn" context:ObservationContext]; |
| | | [NSNotificationCenter.defaultCenter removeObserver:self]; |
| | | } |
| | | |
| | | - (void)updateLogInButton { |
| | | BOOL textFieldsNonEmpty = self.usernameTextField.text.length > 0 && self.passwordTextField.text.length > 0; |
| | | BOOL readyToLogIn = !LoginManager.sharedManager.isLoggingIn && !self.loggedIn; |
| | | self.logInButton.enabled = textFieldsNonEmpty && readyToLogIn; |
| | | } |
| | | |
| | | - (IBAction)logInPressed:(UIButton *)sender { |
| | | [[LoginManager sharedManager] |
| | | logInWithUsername:self.usernameTextField.text |
| | | password:self.passwordTextField.text |
| | | success:^{ |
| | | self.loggedIn = YES; |
| | | } failure:^(NSError *error) { |
| | | [self presentError:error]; |
| | | }]; |
| | | } |
| | | |
| | | - (void)loggedOut:(NSNotification *)notification { |
| | | self.loggedIn = NO; |
| | | } |
| | | |
| | | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { |
| | | if (context == ObservationContext) { |
| | | [self updateLogInButton]; |
| | | } else { |
| | | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | â¦Â could be expressed in RAC like so: |
| | | |
| | | ```objc |
| | | - (void)viewDidLoad { |
| | | [super viewDidLoad]; |
| | | |
| | | @weakify(self); |
| | | |
| | | RAC(self.logInButton, enabled) = [RACSignal |
| | | combineLatest:@[ |
| | | self.usernameTextField.rac_textSignal, |
| | | self.passwordTextField.rac_textSignal, |
| | | RACObserve(LoginManager.sharedManager, loggingIn), |
| | | RACObserve(self, loggedIn) |
| | | ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) { |
| | | return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue); |
| | | }]; |
| | | |
| | | [[self.logInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *sender) { |
| | | @strongify(self); |
| | | |
| | | RACSignal *loginSignal = [LoginManager.sharedManager |
| | | logInWithUsername:self.usernameTextField.text |
| | | password:self.passwordTextField.text]; |
| | | |
| | | [loginSignal subscribeError:^(NSError *error) { |
| | | @strongify(self); |
| | | [self presentError:error]; |
| | | } completed:^{ |
| | | @strongify(self); |
| | | self.loggedIn = YES; |
| | | }]; |
| | | }]; |
| | | |
| | | RAC(self, loggedIn) = [[NSNotificationCenter.defaultCenter |
| | | rac_addObserverForName:UserDidLogOutNotification object:nil] |
| | | mapReplace:@NO]; |
| | | } |
| | | ``` |
| | | |
| | | ### Chaining dependent operations |
| | | |
| | | Dependencies are most often found in network requests, where a previous request |
| | | to the server needs to complete before the next one can be constructed, and so |
| | | on: |
| | | |
| | | ```objc |
| | | [client logInWithSuccess:^{ |
| | | [client loadCachedMessagesWithSuccess:^(NSArray *messages) { |
| | | [client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) { |
| | | NSLog(@"Fetched all messages."); |
| | | } failure:^(NSError *error) { |
| | | [self presentError:error]; |
| | | }]; |
| | | } failure:^(NSError *error) { |
| | | [self presentError:error]; |
| | | }]; |
| | | } failure:^(NSError *error) { |
| | | [self presentError:error]; |
| | | }]; |
| | | ``` |
| | | |
| | | ReactiveCocoa makes this pattern particularly easy: |
| | | |
| | | ```objc |
| | | [[[[client logIn] |
| | | then:^{ |
| | | return [client loadCachedMessages]; |
| | | }] |
| | | flattenMap:^(NSArray *messages) { |
| | | return [client fetchMessagesAfterMessage:messages.lastObject]; |
| | | }] |
| | | subscribeError:^(NSError *error) { |
| | | [self presentError:error]; |
| | | } completed:^{ |
| | | NSLog(@"Fetched all messages."); |
| | | }]; |
| | | ``` |
| | | |
| | | ### Parallelizing independent work |
| | | |
| | | Working with independent data sets in parallel and then combining them into |
| | | a final result is non-trivial in Cocoa, and often involves a lot of |
| | | synchronization: |
| | | |
| | | ```objc |
| | | __block NSArray *databaseObjects; |
| | | __block NSArray *fileContents; |
| | | |
| | | NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init]; |
| | | NSBlockOperation *databaseOperation = [NSBlockOperation blockOperationWithBlock:^{ |
| | | databaseObjects = [databaseClient fetchObjectsMatchingPredicate:predicate]; |
| | | }]; |
| | | |
| | | NSBlockOperation *filesOperation = [NSBlockOperation blockOperationWithBlock:^{ |
| | | NSMutableArray *filesInProgress = [NSMutableArray array]; |
| | | for (NSString *path in files) { |
| | | [filesInProgress addObject:[NSData dataWithContentsOfFile:path]]; |
| | | } |
| | | |
| | | fileContents = [filesInProgress copy]; |
| | | }]; |
| | | |
| | | NSBlockOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{ |
| | | [self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents]; |
| | | NSLog(@"Done processing"); |
| | | }]; |
| | | |
| | | [finishOperation addDependency:databaseOperation]; |
| | | [finishOperation addDependency:filesOperation]; |
| | | [backgroundQueue addOperation:databaseOperation]; |
| | | [backgroundQueue addOperation:filesOperation]; |
| | | [backgroundQueue addOperation:finishOperation]; |
| | | ``` |
| | | |
| | | The above code can be cleaned up and optimized by simply composing signals: |
| | | |
| | | ```objc |
| | | RACSignal *databaseSignal = [[databaseClient |
| | | fetchObjectsMatchingPredicate:predicate] |
| | | subscribeOn:[RACScheduler scheduler]]; |
| | | |
| | | RACSignal *fileSignal = [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) { |
| | | NSMutableArray *filesInProgress = [NSMutableArray array]; |
| | | for (NSString *path in files) { |
| | | [filesInProgress addObject:[NSData dataWithContentsOfFile:path]]; |
| | | } |
| | | |
| | | [subscriber sendNext:[filesInProgress copy]]; |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | [[RACSignal |
| | | combineLatest:@[ databaseSignal, fileSignal ] |
| | | reduce:^ id (NSArray *databaseObjects, NSArray *fileContents) { |
| | | [self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents]; |
| | | return nil; |
| | | }] |
| | | subscribeCompleted:^{ |
| | | NSLog(@"Done processing"); |
| | | }]; |
| | | ``` |
| | | |
| | | ### Simplifying collection transformations |
| | | |
| | | Higher-order functions like `map`, `filter`, `fold`/`reduce` are sorely missing |
| | | from Foundation, leading to loop-focused code like this: |
| | | |
| | | ```objc |
| | | NSMutableArray *results = [NSMutableArray array]; |
| | | for (NSString *str in strings) { |
| | | if (str.length < 2) { |
| | | continue; |
| | | } |
| | | |
| | | NSString *newString = [str stringByAppendingString:@"foobar"]; |
| | | [results addObject:newString]; |
| | | } |
| | | ``` |
| | | |
| | | [RACSequence][] allows any Cocoa collection to be manipulated in a uniform and |
| | | declarative way: |
| | | |
| | | ```objc |
| | | RACSequence *results = [[strings.rac_sequence |
| | | filter:^ BOOL (NSString *str) { |
| | | return str.length >= 2; |
| | | }] |
| | | map:^(NSString *str) { |
| | | return [str stringByAppendingString:@"foobar"]; |
| | | }]; |
| | | ``` |
| | | |
| | | ## System Requirements |
| | | |
| | | ReactiveCocoa supports OS X 10.8+ and iOS 8.0+. |
| | | |
| | | ## Importing ReactiveCocoa |
| | | |
| | | To add RAC to your application: |
| | | |
| | | 1. Add the ReactiveCocoa repository as a submodule of your application's |
| | | repository. |
| | | 1. Run `script/bootstrap` from within the ReactiveCocoa folder. |
| | | 1. Drag and drop `ReactiveCocoa.xcodeproj` into your |
| | | application's Xcode project or workspace. |
| | | 1. On the "Build Phases" tab of your application target, add RAC to the "Link |
| | | Binary With Libraries" phase. |
| | | * **On iOS**, add `libReactiveCocoa-iOS.a`. |
| | | * **On OS X**, add `ReactiveCocoa.framework`. RAC must also be added to any |
| | | "Copy Frameworks" build phase. If you don't already have one, simply add |
| | | a "Copy Files" build phase and target the "Frameworks" destination. |
| | | 1. Add `"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/include" |
| | | $(inherited)` to the "Header Search Paths" build setting (this is only |
| | | necessary for archive builds, but it has no negative effect otherwise). |
| | | 1. **For iOS targets**, add `-ObjC` to the "Other Linker Flags" build setting. |
| | | 1. **If you added RAC to a project (not a workspace)**, you will also need to |
| | | add the appropriate RAC target to the "Target Dependencies" of your |
| | | application. |
| | | |
| | | If you would prefer to use [CocoaPods](http://cocoapods.org), there are some |
| | | [ReactiveCocoa |
| | | podspecs](https://github.com/CocoaPods/Specs/tree/master/Specs/ReactiveCocoa) that |
| | | have been generously contributed by third parties. |
| | | |
| | | To see a project already set up with RAC, check out [C-41][] or [GroceryList][], |
| | | which are real iOS apps written using ReactiveCocoa. |
| | | |
| | | ## Standalone Development |
| | | |
| | | If youâre working on RAC in isolation instead of integrating it into another project, youâll want to open `ReactiveCocoa.xcworkspace` and not the `.xcodeproj`. |
| | | |
| | | ## More Info |
| | | |
| | | ReactiveCocoa is based on .NET's [Reactive |
| | | Extensions](http://msdn.microsoft.com/en-us/data/gg577609) (Rx). Most of the |
| | | principles of Rx apply to RAC as well. There are some really good Rx resources |
| | | out there: |
| | | |
| | | * [Reactive Extensions MSDN entry](http://msdn.microsoft.com/en-us/library/hh242985.aspx) |
| | | * [Reactive Extensions for .NET Introduction](http://leecampbell.blogspot.com/2010/08/reactive-extensions-for-net.html) |
| | | * [Rx - Channel 9 videos](http://channel9.msdn.com/tags/Rx/) |
| | | * [Reactive Extensions wiki](http://rxwiki.wikidot.com/) |
| | | * [101 Rx Samples](http://rxwiki.wikidot.com/101samples) |
| | | * [Programming Reactive Extensions and LINQ](http://www.amazon.com/Programming-Reactive-Extensions-Jesse-Liberty/dp/1430237473) |
| | | |
| | | RAC and Rx are both frameworks inspired by functional reactive programming. Here |
| | | are some resources related to FRP: |
| | | |
| | | * [What is FRP? - Elm Language](http://elm-lang.org/learn/What-is-FRP.elm) |
| | | * [What is Functional Reactive Programming - Stack Overflow](http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming/1030631#1030631) |
| | | * [Specification for a Functional Reactive Language - Stack Overflow](http://stackoverflow.com/questions/5875929/specification-for-a-functional-reactive-programming-language#5878525) |
| | | * [Escape from Callback Hell](http://elm-lang.org/learn/Escape-from-Callback-Hell.elm) |
| | | * [Principles of Reactive Programming on Coursera](https://www.coursera.org/course/reactive) |
| | | |
| | | [Basic Operators]: Documentation/BasicOperators.md |
| | | [Documentation]: Documentation |
| | | [Framework Overview]: Documentation/FrameworkOverview.md |
| | | [Functional Reactive Programming]: http://en.wikipedia.org/wiki/Functional_reactive_programming |
| | | [GroceryList]: https://github.com/jspahrsummers/GroceryList |
| | | [RACSequence]: ReactiveCocoa/RACSequence.h |
| | | [RACSignal]: ReactiveCocoa/RACSignal.h |
| | | [futures and promises]: http://en.wikipedia.org/wiki/Futures_and_promises |
| | | [C-41]: https://github.com/AshFurrow/C-41 |
New file |
| | |
| | | // |
| | | // MKAnnotationView+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Zak Remer on 3/31/15. |
| | | // Copyright (c) 2015 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | #import <MapKit/MapKit.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | @interface MKAnnotationView (RACSignalSupport) |
| | | |
| | | /// A signal which will send a RACUnit whenever -prepareForReuse is invoked upon |
| | | /// the receiver. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// [[[self.cancelButton |
| | | /// rac_signalForControlEvents:UIControlEventTouchUpInside] |
| | | /// takeUntil:self.rac_prepareForReuseSignal] |
| | | /// subscribeNext:^(UIButton *x) { |
| | | /// // do other things |
| | | /// }]; |
| | | @property (nonatomic, strong, readonly) RACSignal *rac_prepareForReuseSignal; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // MKAnnotationView+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Zak Remer on 3/31/15. |
| | | // Copyright (c) 2015 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "MKAnnotationView+RACSignalSupport.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "NSObject+RACSelectorSignal.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "RACUnit.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @implementation MKAnnotationView (RACSignalSupport) |
| | | |
| | | - (RACSignal *)rac_prepareForReuseSignal { |
| | | RACSignal *signal = objc_getAssociatedObject(self, _cmd); |
| | | if (signal != nil) return signal; |
| | | |
| | | signal = [[[self |
| | | rac_signalForSelector:@selector(prepareForReuse)] |
| | | mapReplace:RACUnit.defaultUnit] |
| | | setNameWithFormat:@"%@ -rac_prepareForReuseSignal", self.rac_description]; |
| | | |
| | | objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | return signal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSArray+RACSequenceAdditions.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSequence; |
| | | |
| | | @interface NSArray (RACSequenceAdditions) |
| | | |
| | | /// Creates and returns a sequence corresponding to the receiver. |
| | | /// |
| | | /// Mutating the receiver will not affect the sequence after it's been created. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_sequence; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSArray+RACSequenceAdditions.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "NSArray+RACSequenceAdditions.h" |
| | | #import "RACArraySequence.h" |
| | | |
| | | @implementation NSArray (RACSequenceAdditions) |
| | | |
| | | - (RACSequence *)rac_sequence { |
| | | return [RACArraySequence sequenceWithArray:self offset:0]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSData+RACSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/11/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACScheduler; |
| | | @class RACSignal; |
| | | |
| | | @interface NSData (RACSupport) |
| | | |
| | | // Read the data at the URL using -[NSData initWithContentsOfURL:options:error:]. |
| | | // Sends the data or the error. |
| | | // |
| | | // scheduler - cannot be nil. |
| | | + (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSData+RACSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/11/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSData+RACSupport.h" |
| | | #import "RACReplaySubject.h" |
| | | #import "RACScheduler.h" |
| | | |
| | | @implementation NSData (RACSupport) |
| | | |
| | | + (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler { |
| | | NSCParameterAssert(scheduler != nil); |
| | | |
| | | RACReplaySubject *subject = [RACReplaySubject subject]; |
| | | [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ options: %lu scheduler: %@", URL, (unsigned long)options, scheduler]; |
| | | |
| | | [scheduler schedule:^{ |
| | | NSError *error = nil; |
| | | NSData *data = [[NSData alloc] initWithContentsOfURL:URL options:options error:&error]; |
| | | if (data == nil) { |
| | | [subject sendError:error]; |
| | | } else { |
| | | [subject sendNext:data]; |
| | | [subject sendCompleted]; |
| | | } |
| | | }]; |
| | | |
| | | return subject; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSDictionary+RACSequenceAdditions.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSequence; |
| | | |
| | | @interface NSDictionary (RACSequenceAdditions) |
| | | |
| | | /// Creates and returns a sequence of RACTuple key/value pairs. The key will be |
| | | /// the first element in the tuple, and the value will be the second. |
| | | /// |
| | | /// Mutating the receiver will not affect the sequence after it's been created. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_sequence; |
| | | |
| | | /// Creates and returns a sequence corresponding to the keys in the receiver. |
| | | /// |
| | | /// Mutating the receiver will not affect the sequence after it's been created. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_keySequence; |
| | | |
| | | /// Creates and returns a sequence corresponding to the values in the receiver. |
| | | /// |
| | | /// Mutating the receiver will not affect the sequence after it's been created. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_valueSequence; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSDictionary+RACSequenceAdditions.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "NSDictionary+RACSequenceAdditions.h" |
| | | #import "NSArray+RACSequenceAdditions.h" |
| | | #import "RACSequence.h" |
| | | #import "RACTuple.h" |
| | | |
| | | @implementation NSDictionary (RACSequenceAdditions) |
| | | |
| | | - (RACSequence *)rac_sequence { |
| | | NSDictionary *immutableDict = [self copy]; |
| | | |
| | | // TODO: First class support for dictionary sequences. |
| | | return [immutableDict.allKeys.rac_sequence map:^(id key) { |
| | | id value = immutableDict[key]; |
| | | return RACTuplePack(key, value); |
| | | }]; |
| | | } |
| | | |
| | | - (RACSequence *)rac_keySequence { |
| | | return self.allKeys.rac_sequence; |
| | | } |
| | | |
| | | - (RACSequence *)rac_valueSequence { |
| | | return self.allValues.rac_sequence; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSEnumerator+RACSequenceAdditions.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 07/01/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSequence; |
| | | |
| | | @interface NSEnumerator (RACSequenceAdditions) |
| | | |
| | | /// Creates and returns a sequence corresponding to the receiver. |
| | | /// |
| | | /// The receiver is exhausted lazily as the sequence is enumerated. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_sequence; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSEnumerator+RACSequenceAdditions.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 07/01/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSEnumerator+RACSequenceAdditions.h" |
| | | #import "RACSequence.h" |
| | | |
| | | @implementation NSEnumerator (RACSequenceAdditions) |
| | | |
| | | - (RACSequence *)rac_sequence { |
| | | return [RACSequence sequenceWithHeadBlock:^{ |
| | | return [self nextObject]; |
| | | } tailBlock:^{ |
| | | return self.rac_sequence; |
| | | }]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSFileHandle+RACSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/10/12. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | @interface NSFileHandle (RACSupport) |
| | | |
| | | // Read any available data in the background and send it. Completes when data |
| | | // length is <= 0. |
| | | - (RACSignal *)rac_readInBackground; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSFileHandle+RACSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/10/12. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "NSFileHandle+RACSupport.h" |
| | | #import "NSNotificationCenter+RACSupport.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "RACReplaySubject.h" |
| | | #import "RACDisposable.h" |
| | | |
| | | @implementation NSFileHandle (RACSupport) |
| | | |
| | | - (RACSignal *)rac_readInBackground { |
| | | RACReplaySubject *subject = [RACReplaySubject subject]; |
| | | [subject setNameWithFormat:@"%@ -rac_readInBackground", self.rac_description]; |
| | | |
| | | RACSignal *dataNotification = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:NSFileHandleReadCompletionNotification object:self] map:^(NSNotification *note) { |
| | | return note.userInfo[NSFileHandleNotificationDataItem]; |
| | | }]; |
| | | |
| | | __block RACDisposable *subscription = [dataNotification subscribeNext:^(NSData *data) { |
| | | if (data.length > 0) { |
| | | [subject sendNext:data]; |
| | | [self readInBackgroundAndNotify]; |
| | | } else { |
| | | [subject sendCompleted]; |
| | | [subscription dispose]; |
| | | } |
| | | }]; |
| | | |
| | | [self readInBackgroundAndNotify]; |
| | | |
| | | return subject; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSIndexSet+RACSequenceAdditions.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Sergey Gavrilyuk on 12/17/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSequence; |
| | | |
| | | @interface NSIndexSet (RACSequenceAdditions) |
| | | |
| | | /// Creates and returns a sequence of indexes (as `NSNumber`s) corresponding to |
| | | /// the receiver. |
| | | /// |
| | | /// Mutating the receiver will not affect the sequence after it's been created. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_sequence; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSIndexSet+RACSequenceAdditions.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Sergey Gavrilyuk on 12/17/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSIndexSet+RACSequenceAdditions.h" |
| | | #import "RACIndexSetSequence.h" |
| | | |
| | | @implementation NSIndexSet (RACSequenceAdditions) |
| | | |
| | | - (RACSequence *)rac_sequence { |
| | | return [RACIndexSetSequence sequenceWithIndexSet:self]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSInvocation+RACTypeParsing.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/17/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACTuple; |
| | | |
| | | // A private category of methods to handle wrapping and unwrapping of values. |
| | | @interface NSInvocation (RACTypeParsing) |
| | | |
| | | // Sets the argument for the invocation at the given index by unboxing the given |
| | | // object based on the type signature of the argument. |
| | | // |
| | | // This does not support C arrays or unions. |
| | | // |
| | | // Note that calling this on a char * or const char * argument can cause all |
| | | // arguments to be retained. |
| | | // |
| | | // object - The object to unbox and set as the argument. |
| | | // index - The index of the argument to set. |
| | | - (void)rac_setArgument:(id)object atIndex:(NSUInteger)index; |
| | | |
| | | // Gets the argument for the invocation at the given index based on the |
| | | // invocation's method signature. The value is then wrapped in the appropriate |
| | | // object type. |
| | | // |
| | | // This does not support C arrays or unions. |
| | | // |
| | | // index - The index of the argument to get. |
| | | // |
| | | // Returns the argument of the invocation, wrapped in an object. |
| | | - (id)rac_argumentAtIndex:(NSUInteger)index; |
| | | |
| | | // Arguments tuple for the invocation. |
| | | // |
| | | // The arguments tuple excludes implicit variables `self` and `_cmd`. |
| | | // |
| | | // See -rac_argumentAtIndex: and -rac_setArgumentAtIndex: for further |
| | | // description of the underlying behavior. |
| | | @property (nonatomic, copy) RACTuple *rac_argumentsTuple; |
| | | |
| | | // Gets the return value from the invocation based on the invocation's method |
| | | // signature. The value is then wrapped in the appropriate object type. |
| | | // |
| | | // This does not support C arrays or unions. |
| | | // |
| | | // Returns the return value of the invocation, wrapped in an object. Voids are |
| | | // returned as `RACUnit.defaultUnit`. |
| | | - (id)rac_returnValue; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSInvocation+RACTypeParsing.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/17/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSInvocation+RACTypeParsing.h" |
| | | #import "RACTuple.h" |
| | | #import "RACUnit.h" |
| | | #import <CoreGraphics/CoreGraphics.h> |
| | | |
| | | @implementation NSInvocation (RACTypeParsing) |
| | | |
| | | - (void)rac_setArgument:(id)object atIndex:(NSUInteger)index { |
| | | #define PULL_AND_SET(type, selector) \ |
| | | do { \ |
| | | type val = [object selector]; \ |
| | | [self setArgument:&val atIndex:(NSInteger)index]; \ |
| | | } while (0) |
| | | |
| | | const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; |
| | | // Skip const type qualifier. |
| | | if (argType[0] == 'r') { |
| | | argType++; |
| | | } |
| | | |
| | | if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { |
| | | [self setArgument:&object atIndex:(NSInteger)index]; |
| | | } else if (strcmp(argType, @encode(char)) == 0) { |
| | | PULL_AND_SET(char, charValue); |
| | | } else if (strcmp(argType, @encode(int)) == 0) { |
| | | PULL_AND_SET(int, intValue); |
| | | } else if (strcmp(argType, @encode(short)) == 0) { |
| | | PULL_AND_SET(short, shortValue); |
| | | } else if (strcmp(argType, @encode(long)) == 0) { |
| | | PULL_AND_SET(long, longValue); |
| | | } else if (strcmp(argType, @encode(long long)) == 0) { |
| | | PULL_AND_SET(long long, longLongValue); |
| | | } else if (strcmp(argType, @encode(unsigned char)) == 0) { |
| | | PULL_AND_SET(unsigned char, unsignedCharValue); |
| | | } else if (strcmp(argType, @encode(unsigned int)) == 0) { |
| | | PULL_AND_SET(unsigned int, unsignedIntValue); |
| | | } else if (strcmp(argType, @encode(unsigned short)) == 0) { |
| | | PULL_AND_SET(unsigned short, unsignedShortValue); |
| | | } else if (strcmp(argType, @encode(unsigned long)) == 0) { |
| | | PULL_AND_SET(unsigned long, unsignedLongValue); |
| | | } else if (strcmp(argType, @encode(unsigned long long)) == 0) { |
| | | PULL_AND_SET(unsigned long long, unsignedLongLongValue); |
| | | } else if (strcmp(argType, @encode(float)) == 0) { |
| | | PULL_AND_SET(float, floatValue); |
| | | } else if (strcmp(argType, @encode(double)) == 0) { |
| | | PULL_AND_SET(double, doubleValue); |
| | | } else if (strcmp(argType, @encode(BOOL)) == 0) { |
| | | PULL_AND_SET(BOOL, boolValue); |
| | | } else if (strcmp(argType, @encode(char *)) == 0) { |
| | | const char *cString = [object UTF8String]; |
| | | [self setArgument:&cString atIndex:(NSInteger)index]; |
| | | [self retainArguments]; |
| | | } else if (strcmp(argType, @encode(void (^)(void))) == 0) { |
| | | [self setArgument:&object atIndex:(NSInteger)index]; |
| | | } else { |
| | | NSCParameterAssert([object isKindOfClass:NSValue.class]); |
| | | |
| | | NSUInteger valueSize = 0; |
| | | NSGetSizeAndAlignment([object objCType], &valueSize, NULL); |
| | | |
| | | #if DEBUG |
| | | NSUInteger argSize = 0; |
| | | NSGetSizeAndAlignment(argType, &argSize, NULL); |
| | | NSCAssert(valueSize == argSize, @"Value size does not match argument size in -rac_setArgument: %@ atIndex: %lu", object, (unsigned long)index); |
| | | #endif |
| | | |
| | | unsigned char valueBytes[valueSize]; |
| | | [object getValue:valueBytes]; |
| | | |
| | | [self setArgument:valueBytes atIndex:(NSInteger)index]; |
| | | } |
| | | |
| | | #undef PULL_AND_SET |
| | | } |
| | | |
| | | - (id)rac_argumentAtIndex:(NSUInteger)index { |
| | | #define WRAP_AND_RETURN(type) \ |
| | | do { \ |
| | | type val = 0; \ |
| | | [self getArgument:&val atIndex:(NSInteger)index]; \ |
| | | return @(val); \ |
| | | } while (0) |
| | | |
| | | const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; |
| | | // Skip const type qualifier. |
| | | if (argType[0] == 'r') { |
| | | argType++; |
| | | } |
| | | |
| | | if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { |
| | | __autoreleasing id returnObj; |
| | | [self getArgument:&returnObj atIndex:(NSInteger)index]; |
| | | return returnObj; |
| | | } else if (strcmp(argType, @encode(char)) == 0) { |
| | | WRAP_AND_RETURN(char); |
| | | } else if (strcmp(argType, @encode(int)) == 0) { |
| | | WRAP_AND_RETURN(int); |
| | | } else if (strcmp(argType, @encode(short)) == 0) { |
| | | WRAP_AND_RETURN(short); |
| | | } else if (strcmp(argType, @encode(long)) == 0) { |
| | | WRAP_AND_RETURN(long); |
| | | } else if (strcmp(argType, @encode(long long)) == 0) { |
| | | WRAP_AND_RETURN(long long); |
| | | } else if (strcmp(argType, @encode(unsigned char)) == 0) { |
| | | WRAP_AND_RETURN(unsigned char); |
| | | } else if (strcmp(argType, @encode(unsigned int)) == 0) { |
| | | WRAP_AND_RETURN(unsigned int); |
| | | } else if (strcmp(argType, @encode(unsigned short)) == 0) { |
| | | WRAP_AND_RETURN(unsigned short); |
| | | } else if (strcmp(argType, @encode(unsigned long)) == 0) { |
| | | WRAP_AND_RETURN(unsigned long); |
| | | } else if (strcmp(argType, @encode(unsigned long long)) == 0) { |
| | | WRAP_AND_RETURN(unsigned long long); |
| | | } else if (strcmp(argType, @encode(float)) == 0) { |
| | | WRAP_AND_RETURN(float); |
| | | } else if (strcmp(argType, @encode(double)) == 0) { |
| | | WRAP_AND_RETURN(double); |
| | | } else if (strcmp(argType, @encode(BOOL)) == 0) { |
| | | WRAP_AND_RETURN(BOOL); |
| | | } else if (strcmp(argType, @encode(char *)) == 0) { |
| | | WRAP_AND_RETURN(const char *); |
| | | } else if (strcmp(argType, @encode(void (^)(void))) == 0) { |
| | | __unsafe_unretained id block = nil; |
| | | [self getArgument:&block atIndex:(NSInteger)index]; |
| | | return [block copy]; |
| | | } else { |
| | | NSUInteger valueSize = 0; |
| | | NSGetSizeAndAlignment(argType, &valueSize, NULL); |
| | | |
| | | unsigned char valueBytes[valueSize]; |
| | | [self getArgument:valueBytes atIndex:(NSInteger)index]; |
| | | |
| | | return [NSValue valueWithBytes:valueBytes objCType:argType]; |
| | | } |
| | | |
| | | return nil; |
| | | |
| | | #undef WRAP_AND_RETURN |
| | | } |
| | | |
| | | - (RACTuple *)rac_argumentsTuple { |
| | | NSUInteger numberOfArguments = self.methodSignature.numberOfArguments; |
| | | NSMutableArray *argumentsArray = [NSMutableArray arrayWithCapacity:numberOfArguments - 2]; |
| | | for (NSUInteger index = 2; index < numberOfArguments; index++) { |
| | | [argumentsArray addObject:[self rac_argumentAtIndex:index] ?: RACTupleNil.tupleNil]; |
| | | } |
| | | |
| | | return [RACTuple tupleWithObjectsFromArray:argumentsArray]; |
| | | } |
| | | |
| | | - (void)setRac_argumentsTuple:(RACTuple *)arguments { |
| | | NSCAssert(arguments.count == self.methodSignature.numberOfArguments - 2, @"Number of supplied arguments (%lu), does not match the number expected by the signature (%lu)", (unsigned long)arguments.count, (unsigned long)self.methodSignature.numberOfArguments - 2); |
| | | |
| | | NSUInteger index = 2; |
| | | for (id arg in arguments) { |
| | | [self rac_setArgument:(arg == RACTupleNil.tupleNil ? nil : arg) atIndex:index]; |
| | | index++; |
| | | } |
| | | } |
| | | |
| | | - (id)rac_returnValue { |
| | | #define WRAP_AND_RETURN(type) \ |
| | | do { \ |
| | | type val = 0; \ |
| | | [self getReturnValue:&val]; \ |
| | | return @(val); \ |
| | | } while (0) |
| | | |
| | | const char *returnType = self.methodSignature.methodReturnType; |
| | | // Skip const type qualifier. |
| | | if (returnType[0] == 'r') { |
| | | returnType++; |
| | | } |
| | | |
| | | if (strcmp(returnType, @encode(id)) == 0 || strcmp(returnType, @encode(Class)) == 0 || strcmp(returnType, @encode(void (^)(void))) == 0) { |
| | | __autoreleasing id returnObj; |
| | | [self getReturnValue:&returnObj]; |
| | | return returnObj; |
| | | } else if (strcmp(returnType, @encode(char)) == 0) { |
| | | WRAP_AND_RETURN(char); |
| | | } else if (strcmp(returnType, @encode(int)) == 0) { |
| | | WRAP_AND_RETURN(int); |
| | | } else if (strcmp(returnType, @encode(short)) == 0) { |
| | | WRAP_AND_RETURN(short); |
| | | } else if (strcmp(returnType, @encode(long)) == 0) { |
| | | WRAP_AND_RETURN(long); |
| | | } else if (strcmp(returnType, @encode(long long)) == 0) { |
| | | WRAP_AND_RETURN(long long); |
| | | } else if (strcmp(returnType, @encode(unsigned char)) == 0) { |
| | | WRAP_AND_RETURN(unsigned char); |
| | | } else if (strcmp(returnType, @encode(unsigned int)) == 0) { |
| | | WRAP_AND_RETURN(unsigned int); |
| | | } else if (strcmp(returnType, @encode(unsigned short)) == 0) { |
| | | WRAP_AND_RETURN(unsigned short); |
| | | } else if (strcmp(returnType, @encode(unsigned long)) == 0) { |
| | | WRAP_AND_RETURN(unsigned long); |
| | | } else if (strcmp(returnType, @encode(unsigned long long)) == 0) { |
| | | WRAP_AND_RETURN(unsigned long long); |
| | | } else if (strcmp(returnType, @encode(float)) == 0) { |
| | | WRAP_AND_RETURN(float); |
| | | } else if (strcmp(returnType, @encode(double)) == 0) { |
| | | WRAP_AND_RETURN(double); |
| | | } else if (strcmp(returnType, @encode(BOOL)) == 0) { |
| | | WRAP_AND_RETURN(BOOL); |
| | | } else if (strcmp(returnType, @encode(char *)) == 0) { |
| | | WRAP_AND_RETURN(const char *); |
| | | } else if (strcmp(returnType, @encode(void)) == 0) { |
| | | return RACUnit.defaultUnit; |
| | | } else { |
| | | NSUInteger valueSize = 0; |
| | | NSGetSizeAndAlignment(returnType, &valueSize, NULL); |
| | | |
| | | unsigned char valueBytes[valueSize]; |
| | | [self getReturnValue:valueBytes]; |
| | | |
| | | return [NSValue valueWithBytes:valueBytes objCType:returnType]; |
| | | } |
| | | |
| | | return nil; |
| | | |
| | | #undef WRAP_AND_RETURN |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSNotificationCenter+RACSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/10/12. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | @interface NSNotificationCenter (RACSupport) |
| | | |
| | | // Sends the NSNotification every time the notification is posted. |
| | | - (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSNotificationCenter+RACSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/10/12. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "NSNotificationCenter+RACSupport.h" |
| | | #import "RACEXTScope.h" |
| | | #import "RACSignal.h" |
| | | #import "RACSubscriber.h" |
| | | #import "RACDisposable.h" |
| | | |
| | | @implementation NSNotificationCenter (RACSupport) |
| | | |
| | | - (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object { |
| | | @unsafeify(object); |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | @strongify(object); |
| | | id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) { |
| | | [subscriber sendNext:note]; |
| | | }]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [self removeObserver:observer]; |
| | | }]; |
| | | }] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACDeallocating.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Kazuo Koga on 2013/03/15. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACCompoundDisposable; |
| | | @class RACDisposable; |
| | | @class RACSignal; |
| | | |
| | | @interface NSObject (RACDeallocating) |
| | | |
| | | /// The compound disposable which will be disposed of when the receiver is |
| | | /// deallocated. |
| | | @property (atomic, readonly, strong) RACCompoundDisposable *rac_deallocDisposable; |
| | | |
| | | /// Returns a signal that will complete immediately before the receiver is fully |
| | | /// deallocated. If already deallocated when the signal is subscribed to, |
| | | /// a `completed` event will be sent immediately. |
| | | - (RACSignal *)rac_willDeallocSignal; |
| | | |
| | | @end |
| | | |
| | | @interface NSObject (RACDeallocatingDeprecated) |
| | | |
| | | - (RACSignal *)rac_didDeallocSignal __attribute__((deprecated("Use -rac_willDeallocSignal"))); |
| | | |
| | | - (void)rac_addDeallocDisposable:(RACDisposable *)disposable __attribute__((deprecated("Add disposables to -rac_deallocDisposable instead"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACDeallocating.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Kazuo Koga on 2013/03/15. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACReplaySubject.h" |
| | | #import <objc/message.h> |
| | | #import <objc/runtime.h> |
| | | |
| | | static const void *RACObjectCompoundDisposable = &RACObjectCompoundDisposable; |
| | | |
| | | static NSMutableSet *swizzledClasses() { |
| | | static dispatch_once_t onceToken; |
| | | static NSMutableSet *swizzledClasses = nil; |
| | | dispatch_once(&onceToken, ^{ |
| | | swizzledClasses = [[NSMutableSet alloc] init]; |
| | | }); |
| | | |
| | | return swizzledClasses; |
| | | } |
| | | |
| | | static void swizzleDeallocIfNeeded(Class classToSwizzle) { |
| | | @synchronized (swizzledClasses()) { |
| | | NSString *className = NSStringFromClass(classToSwizzle); |
| | | if ([swizzledClasses() containsObject:className]) return; |
| | | |
| | | SEL deallocSelector = sel_registerName("dealloc"); |
| | | |
| | | __block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL; |
| | | |
| | | id newDealloc = ^(__unsafe_unretained id self) { |
| | | RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable); |
| | | [compoundDisposable dispose]; |
| | | |
| | | if (originalDealloc == NULL) { |
| | | struct objc_super superInfo = { |
| | | .receiver = self, |
| | | .super_class = class_getSuperclass(classToSwizzle) |
| | | }; |
| | | |
| | | void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper; |
| | | msgSend(&superInfo, deallocSelector); |
| | | } else { |
| | | originalDealloc(self, deallocSelector); |
| | | } |
| | | }; |
| | | |
| | | IMP newDeallocIMP = imp_implementationWithBlock(newDealloc); |
| | | |
| | | if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) { |
| | | // The class already contains a method implementation. |
| | | Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector); |
| | | |
| | | // We need to store original implementation before setting new implementation |
| | | // in case method is called at the time of setting. |
| | | originalDealloc = (__typeof__(originalDealloc))method_getImplementation(deallocMethod); |
| | | |
| | | // We need to store original implementation again, in case it just changed. |
| | | originalDealloc = (__typeof__(originalDealloc))method_setImplementation(deallocMethod, newDeallocIMP); |
| | | } |
| | | |
| | | [swizzledClasses() addObject:className]; |
| | | } |
| | | } |
| | | |
| | | @implementation NSObject (RACDeallocating) |
| | | |
| | | - (RACSignal *)rac_willDeallocSignal { |
| | | RACSignal *signal = objc_getAssociatedObject(self, _cmd); |
| | | if (signal != nil) return signal; |
| | | |
| | | RACReplaySubject *subject = [RACReplaySubject subject]; |
| | | |
| | | [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| | | [subject sendCompleted]; |
| | | }]]; |
| | | |
| | | objc_setAssociatedObject(self, _cmd, subject, OBJC_ASSOCIATION_RETAIN); |
| | | |
| | | return subject; |
| | | } |
| | | |
| | | - (RACCompoundDisposable *)rac_deallocDisposable { |
| | | @synchronized (self) { |
| | | RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable); |
| | | if (compoundDisposable != nil) return compoundDisposable; |
| | | |
| | | swizzleDeallocIfNeeded(self.class); |
| | | |
| | | compoundDisposable = [RACCompoundDisposable compoundDisposable]; |
| | | objc_setAssociatedObject(self, RACObjectCompoundDisposable, compoundDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | |
| | | return compoundDisposable; |
| | | } |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation NSObject (RACDeallocatingDeprecated) |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | |
| | | - (RACSignal *)rac_didDeallocSignal { |
| | | RACSubject *subject = [RACSubject subject]; |
| | | |
| | | RACScopedDisposable *disposable = [[RACDisposable |
| | | disposableWithBlock:^{ |
| | | [subject sendCompleted]; |
| | | }] |
| | | asScopedDisposable]; |
| | | |
| | | objc_setAssociatedObject(self, (__bridge void *)disposable, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | return subject; |
| | | } |
| | | |
| | | - (void)rac_addDeallocDisposable:(RACDisposable *)disposable { |
| | | [self.rac_deallocDisposable addDisposable:disposable]; |
| | | } |
| | | |
| | | #pragma clang diagnostic pop |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACDescription.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-05-13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | // A private category providing a terser but faster alternative to -description. |
| | | @interface NSObject (RACDescription) |
| | | |
| | | // A simplified description of the receiver, which does not invoke -description |
| | | // (and thus should be much faster in many cases). |
| | | // |
| | | // This is for debugging purposes only, and will return a constant string |
| | | // unless the RAC_DEBUG_SIGNAL_NAMES environment variable is set. |
| | | - (NSString *)rac_description; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACDescription.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-05-13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSObject+RACDescription.h" |
| | | #import "RACTuple.h" |
| | | |
| | | @implementation NSObject (RACDescription) |
| | | |
| | | - (NSString *)rac_description { |
| | | if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { |
| | | return [[NSString alloc] initWithFormat:@"<%@: %p>", self.class, self]; |
| | | } else { |
| | | return @"(description skipped)"; |
| | | } |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation NSValue (RACDescription) |
| | | |
| | | - (NSString *)rac_description { |
| | | return self.description; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation NSString (RACDescription) |
| | | |
| | | - (NSString *)rac_description { |
| | | return self.description; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACTuple (RACDescription) |
| | | |
| | | - (NSString *)rac_description { |
| | | if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { |
| | | return self.allObjects.description; |
| | | } else { |
| | | return @"(description skipped)"; |
| | | } |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACKVOWrapper.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 10/11/11. |
| | | // Copyright (c) 2011 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACDisposable; |
| | | @class RACKVOTrampoline; |
| | | |
| | | // A private category providing a block based interface to KVO. |
| | | @interface NSObject (RACKVOWrapper) |
| | | |
| | | // Adds the given block as the callbacks for when the key path changes. |
| | | // |
| | | // Unlike direct KVO observation, this handles deallocation of `weak` properties |
| | | // by generating an appropriate notification. This will only occur if there is |
| | | // an `@property` declaration visible in the observed class, with the `weak` |
| | | // memory management attribute. |
| | | // |
| | | // The observation does not need to be explicitly removed. It will be removed |
| | | // when the observer or the receiver deallocate. |
| | | // |
| | | // keyPath - The key path to observe. Must not be nil. |
| | | // options - The KVO observation options. |
| | | // observer - The object that requested the observation. May be nil. |
| | | // block - The block called when the value at the key path changes. It is |
| | | // passed the current value of the key path and the extended KVO |
| | | // change dictionary including RAC-specific keys and values. Must not |
| | | // be nil. |
| | | // |
| | | // Returns a disposable that can be used to stop the observation. |
| | | - (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer block:(void (^)(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent))block; |
| | | |
| | | @end |
| | | |
| | | typedef void (^RACKVOBlock)(id target, id observer, NSDictionary *change); |
| | | |
| | | @interface NSObject (RACKVOWrapperDeprecated) |
| | | |
| | | - (RACKVOTrampoline *)rac_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block __attribute((deprecated("Use rac_observeKeyPath:options:observer:block: instead."))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACKVOWrapper.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 10/11/11. |
| | | // Copyright (c) 2011 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "NSObject+RACKVOWrapper.h" |
| | | #import "RACEXTRuntimeExtensions.h" |
| | | #import "RACEXTScope.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSString+RACKeyPathUtilities.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACKVOTrampoline.h" |
| | | #import "RACSerialDisposable.h" |
| | | |
| | | @implementation NSObject (RACKVOWrapper) |
| | | |
| | | - (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver block:(void (^)(id, NSDictionary *, BOOL, BOOL))block { |
| | | NSCParameterAssert(block != nil); |
| | | NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); |
| | | |
| | | keyPath = [keyPath copy]; |
| | | |
| | | NSObject *strongObserver = weakObserver; |
| | | |
| | | NSArray *keyPathComponents = keyPath.rac_keyPathComponents; |
| | | BOOL keyPathHasOneComponent = (keyPathComponents.count == 1); |
| | | NSString *keyPathHead = keyPathComponents[0]; |
| | | NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent; |
| | | |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | // The disposable that groups all disposal necessary to clean up the callbacks |
| | | // added to the value of the first key path component. |
| | | RACSerialDisposable *firstComponentSerialDisposable = [RACSerialDisposable serialDisposableWithDisposable:[RACCompoundDisposable compoundDisposable]]; |
| | | RACCompoundDisposable * (^firstComponentDisposable)(void) = ^{ |
| | | return (RACCompoundDisposable *)firstComponentSerialDisposable.disposable; |
| | | }; |
| | | |
| | | [disposable addDisposable:firstComponentSerialDisposable]; |
| | | |
| | | BOOL shouldAddDeallocObserver = NO; |
| | | |
| | | objc_property_t property = class_getProperty(object_getClass(self), keyPathHead.UTF8String); |
| | | if (property != NULL) { |
| | | rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property); |
| | | if (attributes != NULL) { |
| | | @onExit { |
| | | free(attributes); |
| | | }; |
| | | |
| | | BOOL isObject = attributes->objectClass != nil || strstr(attributes->type, @encode(id)) == attributes->type; |
| | | BOOL isProtocol = attributes->objectClass == NSClassFromString(@"Protocol"); |
| | | BOOL isBlock = strcmp(attributes->type, @encode(void(^)())) == 0; |
| | | BOOL isWeak = attributes->weak; |
| | | |
| | | // If this property isn't actually an object (or is a Class object), |
| | | // no point in observing the deallocation of the wrapper returned by |
| | | // KVC. |
| | | // |
| | | // If this property is an object, but not declared `weak`, we |
| | | // don't need to watch for it spontaneously being set to nil. |
| | | // |
| | | // Attempting to observe non-weak properties will result in |
| | | // broken behavior for dynamic getters, so don't even try. |
| | | shouldAddDeallocObserver = isObject && isWeak && !isBlock && !isProtocol; |
| | | } |
| | | } |
| | | |
| | | // Adds the callback block to the value's deallocation. Also adds the logic to |
| | | // clean up the callback to the firstComponentDisposable. |
| | | void (^addDeallocObserverToPropertyValue)(NSObject *) = ^(NSObject *value) { |
| | | if (!shouldAddDeallocObserver) return; |
| | | |
| | | // If a key path value is the observer, commonly when a key path begins |
| | | // with "self", we prevent deallocation triggered callbacks for any such key |
| | | // path components. Thus, the observer's deallocation is not considered a |
| | | // change to the key path. |
| | | if (value == weakObserver) return; |
| | | |
| | | NSDictionary *change = @{ |
| | | NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), |
| | | NSKeyValueChangeNewKey: NSNull.null, |
| | | }; |
| | | |
| | | RACCompoundDisposable *valueDisposable = value.rac_deallocDisposable; |
| | | RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ |
| | | block(nil, change, YES, keyPathHasOneComponent); |
| | | }]; |
| | | |
| | | [valueDisposable addDisposable:deallocDisposable]; |
| | | [firstComponentDisposable() addDisposable:[RACDisposable disposableWithBlock:^{ |
| | | [valueDisposable removeDisposable:deallocDisposable]; |
| | | }]]; |
| | | }; |
| | | |
| | | // Adds the callback block to the remaining path components on the value. Also |
| | | // adds the logic to clean up the callbacks to the firstComponentDisposable. |
| | | void (^addObserverToValue)(NSObject *) = ^(NSObject *value) { |
| | | RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:weakObserver block:block]; |
| | | [firstComponentDisposable() addDisposable:observerDisposable]; |
| | | }; |
| | | |
| | | // Observe only the first key path component, when the value changes clean up |
| | | // the callbacks on the old value, add callbacks to the new value and call the |
| | | // callback block as needed. |
| | | // |
| | | // Note this does not use NSKeyValueObservingOptionInitial so this only |
| | | // handles changes to the value, callbacks to the initial value must be added |
| | | // separately. |
| | | NSKeyValueObservingOptions trampolineOptions = (options | NSKeyValueObservingOptionPrior) & ~NSKeyValueObservingOptionInitial; |
| | | RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:strongObserver keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) { |
| | | // If this is a prior notification, clean up all the callbacks added to the |
| | | // previous value and call the callback block. Everything else is deferred |
| | | // until after we get the notification after the change. |
| | | if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { |
| | | [firstComponentDisposable() dispose]; |
| | | |
| | | if ((options & NSKeyValueObservingOptionPrior) != 0) { |
| | | block([trampolineTarget valueForKeyPath:keyPath], change, NO, keyPathHasOneComponent); |
| | | } |
| | | |
| | | return; |
| | | } |
| | | |
| | | // From here the notification is not prior. |
| | | NSObject *value = [trampolineTarget valueForKey:keyPathHead]; |
| | | |
| | | // If the value has changed but is nil, there is no need to add callbacks to |
| | | // it, just call the callback block. |
| | | if (value == nil) { |
| | | block(nil, change, NO, keyPathHasOneComponent); |
| | | return; |
| | | } |
| | | |
| | | // From here the notification is not prior and the value is not nil. |
| | | |
| | | // Create a new firstComponentDisposable while getting rid of the old one at |
| | | // the same time, in case this is being called concurrently. |
| | | RACDisposable *oldFirstComponentDisposable = [firstComponentSerialDisposable swapInDisposable:[RACCompoundDisposable compoundDisposable]]; |
| | | [oldFirstComponentDisposable dispose]; |
| | | |
| | | addDeallocObserverToPropertyValue(value); |
| | | |
| | | // If there are no further key path components, there is no need to add the |
| | | // other callbacks, just call the callback block with the value itself. |
| | | if (keyPathHasOneComponent) { |
| | | block(value, change, NO, keyPathHasOneComponent); |
| | | return; |
| | | } |
| | | |
| | | // The value has changed, is not nil, and there are more key path components |
| | | // to consider. Add the callbacks to the value for the remaining key path |
| | | // components and call the callback block with the current value of the full |
| | | // key path. |
| | | addObserverToValue(value); |
| | | block([value valueForKeyPath:keyPathTail], change, NO, keyPathHasOneComponent); |
| | | }]; |
| | | |
| | | // Stop the KVO observation when this one is disposed of. |
| | | [disposable addDisposable:trampoline]; |
| | | |
| | | // Add the callbacks to the initial value if needed. |
| | | NSObject *value = [self valueForKey:keyPathHead]; |
| | | if (value != nil) { |
| | | addDeallocObserverToPropertyValue(value); |
| | | |
| | | if (!keyPathHasOneComponent) { |
| | | addObserverToValue(value); |
| | | } |
| | | } |
| | | |
| | | // Call the block with the initial value if needed. |
| | | if ((options & NSKeyValueObservingOptionInitial) != 0) { |
| | | id initialValue = [self valueForKeyPath:keyPath]; |
| | | NSDictionary *initialChange = @{ |
| | | NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), |
| | | NSKeyValueChangeNewKey: initialValue ?: NSNull.null, |
| | | }; |
| | | block(initialValue, initialChange, NO, keyPathHasOneComponent); |
| | | } |
| | | |
| | | |
| | | RACCompoundDisposable *observerDisposable = strongObserver.rac_deallocDisposable; |
| | | RACCompoundDisposable *selfDisposable = self.rac_deallocDisposable; |
| | | // Dispose of this observation if the receiver or the observer deallocate. |
| | | [observerDisposable addDisposable:disposable]; |
| | | [selfDisposable addDisposable:disposable]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [disposable dispose]; |
| | | [observerDisposable removeDisposable:disposable]; |
| | | [selfDisposable removeDisposable:disposable]; |
| | | }]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation NSObject (RACKVOWrapperDeprecated) |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | |
| | | - (RACKVOTrampoline *)rac_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block { |
| | | return [[RACKVOTrampoline alloc] initWithTarget:self observer:observer keyPath:keyPath options:options block:block]; |
| | | } |
| | | |
| | | #pragma clang diagnostic pop |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACLifting.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 10/13/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | @interface NSObject (RACLifting) |
| | | |
| | | /// Lifts the selector on the receiver into the reactive world. The selector will |
| | | /// be invoked whenever any signal argument sends a value, but only after each |
| | | /// signal has sent an initial value. |
| | | /// |
| | | /// It will replay the most recently sent value to new subscribers. |
| | | /// |
| | | /// This does not support C arrays or unions. |
| | | /// |
| | | /// selector - The selector on self to invoke. |
| | | /// firstSignal - The signal corresponding to the first method argument. This |
| | | /// must not be nil. |
| | | /// ... - A list of RACSignals corresponding to the remaining arguments. |
| | | /// There must be a non-nil signal for each method argument. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// [button rac_liftSelector:@selector(setTitleColor:forState:) withSignals:textColorSignal, [RACSignal return:@(UIControlStateNormal)], nil]; |
| | | /// |
| | | /// Returns a signal which sends the return value from each invocation of the |
| | | /// selector. If the selector returns void, it instead sends RACUnit.defaultUnit. |
| | | /// It completes only after all the signal arguments complete. |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... NS_REQUIRES_NIL_TERMINATION; |
| | | |
| | | /// Like -rac_liftSelector:withSignals:, but accepts an array instead of |
| | | /// a variadic list of arguments. |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals; |
| | | |
| | | /// Like -rac_liftSelector:withSignals:, but accepts a signal sending tuples of |
| | | /// arguments instead of a variadic list of arguments. |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withSignalOfArguments:(RACSignal *)arguments; |
| | | |
| | | @end |
| | | |
| | | @interface NSObject (RACLiftingDeprecated) |
| | | |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withObjects:(id)arg, ... __attribute__((deprecated("Use -rac_liftSelector:withSignals: instead"))); |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withObjectsFromArray:(NSArray *)args __attribute__((deprecated("Use -rac_liftSelector:withSignalsFromArray: instead"))); |
| | | - (RACSignal *)rac_liftBlock:(id)block withArguments:(id)arg, ... NS_REQUIRES_NIL_TERMINATION __attribute__((deprecated("Use +combineLatest:reduce: instead"))); |
| | | - (RACSignal *)rac_liftBlock:(id)block withArgumentsFromArray:(NSArray *)args __attribute__((deprecated("Use +combineLatest:reduce: instead"))); |
| | | |
| | | @end |
| | | |
| | | @interface NSObject (RACLiftingUnavailable) |
| | | |
| | | - (instancetype)rac_lift __attribute__((unavailable("Use -rac_liftSelector:withSignals: instead"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACLifting.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 10/13/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSObject+RACLifting.h" |
| | | #import "RACEXTScope.h" |
| | | #import "NSInvocation+RACTypeParsing.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "RACTuple.h" |
| | | |
| | | @implementation NSObject (RACLifting) |
| | | |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withSignalOfArguments:(RACSignal *)arguments { |
| | | NSCParameterAssert(selector != NULL); |
| | | NSCParameterAssert(arguments != nil); |
| | | |
| | | @unsafeify(self); |
| | | |
| | | NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; |
| | | NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector)); |
| | | |
| | | return [[[[arguments |
| | | takeUntil:self.rac_willDeallocSignal] |
| | | map:^(RACTuple *arguments) { |
| | | @strongify(self); |
| | | |
| | | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; |
| | | invocation.selector = selector; |
| | | invocation.rac_argumentsTuple = arguments; |
| | | [invocation invokeWithTarget:self]; |
| | | |
| | | return invocation.rac_returnValue; |
| | | }] |
| | | replayLast] |
| | | setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsOfArguments: %@", self.rac_description, sel_getName(selector), arguments]; |
| | | } |
| | | |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals { |
| | | NSCParameterAssert(signals != nil); |
| | | NSCParameterAssert(signals.count > 0); |
| | | |
| | | NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; |
| | | NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector)); |
| | | |
| | | NSUInteger numberOfArguments __attribute__((unused)) = methodSignature.numberOfArguments - 2; |
| | | NSCAssert(numberOfArguments == signals.count, @"Wrong number of signals for %@ (expected %lu, got %lu)", NSStringFromSelector(selector), (unsigned long)numberOfArguments, (unsigned long)signals.count); |
| | | |
| | | return [[self |
| | | rac_liftSelector:selector withSignalOfArguments:[RACSignal combineLatest:signals]] |
| | | setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsFromArray: %@", self.rac_description, sel_getName(selector), signals]; |
| | | } |
| | | |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... { |
| | | NSCParameterAssert(firstSignal != nil); |
| | | |
| | | NSMutableArray *signals = [NSMutableArray array]; |
| | | |
| | | va_list args; |
| | | va_start(args, firstSignal); |
| | | for (id currentSignal = firstSignal; currentSignal != nil; currentSignal = va_arg(args, id)) { |
| | | NSCAssert([currentSignal isKindOfClass:RACSignal.class], @"Argument %@ is not a RACSignal", currentSignal); |
| | | |
| | | [signals addObject:currentSignal]; |
| | | } |
| | | va_end(args); |
| | | |
| | | return [[self |
| | | rac_liftSelector:selector withSignalsFromArray:signals] |
| | | setNameWithFormat:@"%@ -rac_liftSelector: %s withSignals: %@", self.rac_description, sel_getName(selector), signals]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation NSObject (RACLiftingDeprecated) |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | |
| | | static NSArray *RACMapArgumentsToSignals(NSArray *args) { |
| | | NSMutableArray *mappedArgs = [NSMutableArray array]; |
| | | for (id arg in args) { |
| | | if ([arg isEqual:RACTupleNil.tupleNil]) { |
| | | [mappedArgs addObject:[RACSignal return:nil]]; |
| | | } else if ([arg isKindOfClass:RACSignal.class]) { |
| | | [mappedArgs addObject:arg]; |
| | | } else { |
| | | [mappedArgs addObject:[RACSignal return:arg]]; |
| | | } |
| | | } |
| | | |
| | | return mappedArgs; |
| | | } |
| | | |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withObjects:(id)arg, ... { |
| | | NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; |
| | | NSMutableArray *arguments = [NSMutableArray array]; |
| | | |
| | | va_list args; |
| | | va_start(args, arg); |
| | | for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) { |
| | | id currentObject = (i == 2 ? arg : va_arg(args, id)); |
| | | [arguments addObject:currentObject ?: RACTupleNil.tupleNil]; |
| | | } |
| | | |
| | | va_end(args); |
| | | return [self rac_liftSelector:selector withObjectsFromArray:arguments]; |
| | | } |
| | | |
| | | - (RACSignal *)rac_liftSelector:(SEL)selector withObjectsFromArray:(NSArray *)args { |
| | | return [self rac_liftSelector:selector withSignalsFromArray:RACMapArgumentsToSignals(args)]; |
| | | } |
| | | |
| | | - (RACSignal *)rac_liftBlock:(id)block withArguments:(id)arg, ... { |
| | | NSMutableArray *arguments = [NSMutableArray array]; |
| | | |
| | | va_list args; |
| | | va_start(args, arg); |
| | | for (id currentObject = arg; currentObject != nil; currentObject = va_arg(args, id)) { |
| | | [arguments addObject:currentObject]; |
| | | } |
| | | |
| | | va_end(args); |
| | | return [self rac_liftBlock:block withArgumentsFromArray:arguments]; |
| | | } |
| | | |
| | | - (RACSignal *)rac_liftBlock:(id)block withArgumentsFromArray:(NSArray *)args { |
| | | return [[[[RACSignal |
| | | combineLatest:RACMapArgumentsToSignals(args)] |
| | | reduceEach:block] |
| | | takeUntil:self.rac_willDeallocSignal] |
| | | replayLast]; |
| | | } |
| | | |
| | | #pragma clang diagnostic pop |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACPropertySubscribing.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/2/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "RACmetamacros.h" |
| | | |
| | | /// Creates a signal which observes `KEYPATH` on `TARGET` for changes. |
| | | /// |
| | | /// In either case, the observation continues until `TARGET` _or self_ is |
| | | /// deallocated. If any intermediate object is deallocated instead, it will be |
| | | /// assumed to have been set to nil. |
| | | /// |
| | | /// Make sure to `@strongify(self)` when using this macro within a block! The |
| | | /// macro will _always_ reference `self`, which can silently introduce a retain |
| | | /// cycle within a block. As a result, you should make sure that `self` is a weak |
| | | /// reference (e.g., created by `@weakify` and `@strongify`) before the |
| | | /// expression that uses `RACObserve`. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// // Observes self, and doesn't stop until self is deallocated. |
| | | /// RACSignal *selfSignal = RACObserve(self, arrayController.items); |
| | | /// |
| | | /// // Observes the array controller, and stops when self _or_ the array |
| | | /// // controller is deallocated. |
| | | /// RACSignal *arrayControllerSignal = RACObserve(self.arrayController, items); |
| | | /// |
| | | /// // Observes obj.arrayController, and stops when self _or_ the array |
| | | /// // controller is deallocated. |
| | | /// RACSignal *signal2 = RACObserve(obj.arrayController, items); |
| | | /// |
| | | /// @weakify(self); |
| | | /// RACSignal *signal3 = [anotherSignal flattenMap:^(NSArrayController *arrayController) { |
| | | /// // Avoids a retain cycle because of RACObserve implicitly referencing |
| | | /// // self. |
| | | /// @strongify(self); |
| | | /// return RACObserve(arrayController, items); |
| | | /// }]; |
| | | /// |
| | | /// Returns a signal which sends the current value of the key path on |
| | | /// subscription, then sends the new value every time it changes, and sends |
| | | /// completed if self or observer is deallocated. |
| | | #define RACObserve(TARGET, KEYPATH) \ |
| | | ({ \ |
| | | __weak id target_ = (TARGET); \ |
| | | [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \ |
| | | }) |
| | | |
| | | @class RACDisposable; |
| | | @class RACSignal; |
| | | |
| | | @interface NSObject (RACPropertySubscribing) |
| | | |
| | | /// Creates a signal to observe the value at the given key path. |
| | | /// |
| | | /// The initial value is sent on subscription, the subsequent values are sent |
| | | /// from whichever thread the change occured on, even if it doesn't have a valid |
| | | /// scheduler. |
| | | /// |
| | | /// Returns a signal that immediately sends the receiver's current value at the |
| | | /// given keypath, then any changes thereafter. |
| | | - (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer; |
| | | |
| | | /// Creates a signal to observe the changes of the given key path. |
| | | /// |
| | | /// The initial value is sent on subscription, the subsequent values are sent |
| | | /// from whichever thread the change occured on, even if it doesn't have a valid |
| | | /// scheduler. |
| | | /// |
| | | /// Returns a signal that sends tuples containing the current value at the key |
| | | /// path and the change dictionary for each KVO callback. |
| | | - (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer; |
| | | |
| | | @end |
| | | |
| | | #define RACAble(...) \ |
| | | metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ |
| | | (_RACAbleObject(self, __VA_ARGS__)) \ |
| | | (_RACAbleObject(__VA_ARGS__)) |
| | | |
| | | #define _RACAbleObject(object, property) [object rac_signalForKeyPath:@keypath(object, property) observer:self] |
| | | |
| | | #define RACAbleWithStart(...) \ |
| | | metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ |
| | | (_RACAbleWithStartObject(self, __VA_ARGS__)) \ |
| | | (_RACAbleWithStartObject(__VA_ARGS__)) |
| | | |
| | | #define _RACAbleWithStartObject(object, property) [object rac_signalWithStartingValueForKeyPath:@keypath(object, property) observer:self] |
| | | |
| | | @interface NSObject (RACPropertySubscribingDeprecated) |
| | | |
| | | + (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); |
| | | + (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); |
| | | + (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesAndChangesForKeyPath:options:observer: instead."))); |
| | | - (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); |
| | | - (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); |
| | | - (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal __attribute__((deprecated("Use -[RACSignal setKeyPath:onObject:] instead"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACPropertySubscribing.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/2/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSObject+RACPropertySubscribing.h" |
| | | #import "RACEXTScope.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "NSObject+RACKVOWrapper.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACKVOTrampoline.h" |
| | | #import "RACSubscriber.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "RACTuple.h" |
| | | #import <libkern/OSAtomic.h> |
| | | |
| | | @implementation NSObject (RACPropertySubscribing) |
| | | |
| | | - (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer { |
| | | return [[[self |
| | | rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer] |
| | | map:^(RACTuple *value) { |
| | | // -map: because it doesn't require the block trampoline that -reduceEach: uses |
| | | return value[0]; |
| | | }] |
| | | setNameWithFormat:@"RACObserve(%@, %@)", self.rac_description, keyPath]; |
| | | } |
| | | |
| | | - (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver { |
| | | NSObject *strongObserver = weakObserver; |
| | | keyPath = [keyPath copy]; |
| | | |
| | | NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init]; |
| | | objectLock.name = @"org.reactivecocoa.ReactiveCocoa.NSObjectRACPropertySubscribing"; |
| | | |
| | | __weak NSObject *weakSelf = self; |
| | | |
| | | RACSignal *deallocSignal = [[RACSignal |
| | | zip:@[ |
| | | self.rac_willDeallocSignal, |
| | | strongObserver.rac_willDeallocSignal ?: [RACSignal never] |
| | | ]] |
| | | doCompleted:^{ |
| | | // Forces deallocation to wait if the object variables are currently |
| | | // being read on another thread. |
| | | [objectLock lock]; |
| | | @onExit { |
| | | [objectLock unlock]; |
| | | }; |
| | | }]; |
| | | |
| | | return [[[RACSignal |
| | | createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| | | // Hold onto the lock the whole time we're setting up the KVO |
| | | // observation, because any resurrection that might be caused by our |
| | | // retaining below must be balanced out by the time -dealloc returns |
| | | // (if another thread is waiting on the lock above). |
| | | [objectLock lock]; |
| | | @onExit { |
| | | [objectLock unlock]; |
| | | }; |
| | | |
| | | __strong NSObject *observer __attribute__((objc_precise_lifetime)) = weakObserver; |
| | | __strong NSObject *self __attribute__((objc_precise_lifetime)) = weakSelf; |
| | | |
| | | if (self == nil) { |
| | | [subscriber sendCompleted]; |
| | | return nil; |
| | | } |
| | | |
| | | return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { |
| | | [subscriber sendNext:RACTuplePack(value, change)]; |
| | | }]; |
| | | }] |
| | | takeUntil:deallocSignal] |
| | | setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", self.rac_description, keyPath, (unsigned long)options, strongObserver.rac_description]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | static RACSignal *signalWithoutChangesFor(Class class, NSObject *object, NSString *keyPath, NSKeyValueObservingOptions options, NSObject *observer) { |
| | | NSCParameterAssert(object != nil); |
| | | NSCParameterAssert(keyPath != nil); |
| | | NSCParameterAssert(observer != nil); |
| | | |
| | | keyPath = [keyPath copy]; |
| | | |
| | | @unsafeify(object); |
| | | |
| | | return [[class |
| | | rac_signalWithChangesFor:object keyPath:keyPath options:options observer:observer] |
| | | map:^(NSDictionary *change) { |
| | | @strongify(object); |
| | | return [object valueForKeyPath:keyPath]; |
| | | }]; |
| | | } |
| | | |
| | | @implementation NSObject (RACPropertySubscribingDeprecated) |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | |
| | | + (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer { |
| | | return signalWithoutChangesFor(self, object, keyPath, 0, observer); |
| | | } |
| | | |
| | | + (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer { |
| | | return signalWithoutChangesFor(self, object, keyPath, NSKeyValueObservingOptionInitial, observer); |
| | | } |
| | | |
| | | + (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer { |
| | | @unsafeify(observer, object); |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | |
| | | @strongify(observer, object); |
| | | RACKVOTrampoline *KVOTrampoline = [object rac_addObserver:observer forKeyPath:keyPath options:options block:^(id target, id observer, NSDictionary *change) { |
| | | [subscriber sendNext:change]; |
| | | }]; |
| | | |
| | | @weakify(subscriber); |
| | | RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ |
| | | @strongify(subscriber); |
| | | [KVOTrampoline dispose]; |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | [observer.rac_deallocDisposable addDisposable:deallocDisposable]; |
| | | [object.rac_deallocDisposable addDisposable:deallocDisposable]; |
| | | |
| | | RACCompoundDisposable *observerDisposable = observer.rac_deallocDisposable; |
| | | RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable; |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [observerDisposable removeDisposable:deallocDisposable]; |
| | | [objectDisposable removeDisposable:deallocDisposable]; |
| | | [KVOTrampoline dispose]; |
| | | }]; |
| | | }] setNameWithFormat:@"RACAble(%@, %@)", object.rac_description, keyPath]; |
| | | } |
| | | |
| | | - (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { |
| | | return [self.class rac_signalFor:self keyPath:keyPath observer:observer]; |
| | | } |
| | | |
| | | - (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { |
| | | return [self.class rac_signalWithStartingValueFor:self keyPath:keyPath observer:observer]; |
| | | } |
| | | |
| | | - (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal { |
| | | return [signal setKeyPath:keyPath onObject:self]; |
| | | } |
| | | |
| | | #pragma clang diagnostic pop |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACSelectorSignal.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/18/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | /// The domain for any errors originating from -rac_signalForSelector:. |
| | | extern NSString * const RACSelectorSignalErrorDomain; |
| | | |
| | | /// -rac_signalForSelector: was going to add a new method implementation for |
| | | /// `selector`, but another thread added an implementation before it was able to. |
| | | /// |
| | | /// This will _not_ occur for cases where a method implementation exists before |
| | | /// -rac_signalForSelector: is invoked. |
| | | extern const NSInteger RACSelectorSignalErrorMethodSwizzlingRace; |
| | | |
| | | @interface NSObject (RACSelectorSignal) |
| | | |
| | | /// Creates a signal associated with the receiver, which will send a tuple of the |
| | | /// method's arguments each time the given selector is invoked. |
| | | /// |
| | | /// If the selector is already implemented on the receiver, the existing |
| | | /// implementation will be invoked _before_ the signal fires. |
| | | /// |
| | | /// If the selector is not yet implemented on the receiver, the injected |
| | | /// implementation will have a `void` return type and accept only object |
| | | /// arguments. Invoking the added implementation with non-object values, or |
| | | /// expecting a return value, will result in undefined behavior. |
| | | /// |
| | | /// This is useful for changing an event or delegate callback into a signal. For |
| | | /// example, on an NSView: |
| | | /// |
| | | /// [[view rac_signalForSelector:@selector(mouseDown:)] subscribeNext:^(RACTuple *args) { |
| | | /// NSEvent *event = args.first; |
| | | /// NSLog(@"mouse button pressed: %@", event); |
| | | /// }]; |
| | | /// |
| | | /// selector - The selector for whose invocations are to be observed. If it |
| | | /// doesn't exist, it will be implemented to accept object arguments |
| | | /// and return void. This cannot have C arrays or unions as arguments |
| | | /// or C arrays, unions, structs, complex or vector types as return |
| | | /// type. |
| | | /// |
| | | /// Returns a signal which will send a tuple of arguments upon each invocation of |
| | | /// the selector, then completes when the receiver is deallocated. `next` events |
| | | /// will be sent synchronously from the thread that invoked the method. If |
| | | /// a runtime call fails, the signal will send an error in the |
| | | /// RACSelectorSignalErrorDomain. |
| | | - (RACSignal *)rac_signalForSelector:(SEL)selector; |
| | | |
| | | /// Behaves like -rac_signalForSelector:, but if the selector is not yet |
| | | /// implemented on the receiver, its method signature is looked up within |
| | | /// `protocol`, and may accept non-object arguments. |
| | | /// |
| | | /// If the selector is not yet implemented and has a return value, the injected |
| | | /// method will return all zero bits (equal to `nil`, `NULL`, 0, 0.0f, etc.). |
| | | /// |
| | | /// selector - The selector for whose invocations are to be observed. If it |
| | | /// doesn't exist, it will be implemented using information from |
| | | /// `protocol`, and may accept non-object arguments and return |
| | | /// a value. This cannot have C arrays or unions as arguments or |
| | | /// return type. |
| | | /// protocol - The protocol in which `selector` is declared. This will be used |
| | | /// for type information if the selector is not already implemented on |
| | | /// the receiver. This must not be `NULL`, and `selector` must exist |
| | | /// in this protocol. |
| | | /// |
| | | /// Returns a signal which will send a tuple of arguments on each invocation of |
| | | /// the selector, or an error in RACSelectorSignalErrorDomain if a runtime |
| | | /// call fails. |
| | | - (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSObject+RACSelectorSignal.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/18/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSObject+RACSelectorSignal.h" |
| | | #import "RACEXTRuntimeExtensions.h" |
| | | #import "NSInvocation+RACTypeParsing.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACObjCRuntime.h" |
| | | #import "RACSubject.h" |
| | | #import "RACTuple.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import <objc/message.h> |
| | | #import <objc/runtime.h> |
| | | |
| | | NSString * const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain"; |
| | | const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1; |
| | | |
| | | static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_"; |
| | | static NSString * const RACSubclassSuffix = @"_RACSelectorSignal"; |
| | | static void *RACSubclassAssociationKey = &RACSubclassAssociationKey; |
| | | |
| | | static NSMutableSet *swizzledClasses() { |
| | | static NSMutableSet *set; |
| | | static dispatch_once_t pred; |
| | | |
| | | dispatch_once(&pred, ^{ |
| | | set = [[NSMutableSet alloc] init]; |
| | | }); |
| | | |
| | | return set; |
| | | } |
| | | |
| | | @implementation NSObject (RACSelectorSignal) |
| | | |
| | | static BOOL RACForwardInvocation(id self, NSInvocation *invocation) { |
| | | SEL aliasSelector = RACAliasForSelector(invocation.selector); |
| | | RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); |
| | | |
| | | Class class = object_getClass(invocation.target); |
| | | BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector]; |
| | | if (respondsToAlias) { |
| | | invocation.selector = aliasSelector; |
| | | [invocation invoke]; |
| | | } |
| | | |
| | | if (subject == nil) return respondsToAlias; |
| | | |
| | | [subject sendNext:invocation.rac_argumentsTuple]; |
| | | return YES; |
| | | } |
| | | |
| | | static void RACSwizzleForwardInvocation(Class class) { |
| | | SEL forwardInvocationSEL = @selector(forwardInvocation:); |
| | | Method forwardInvocationMethod = class_getInstanceMethod(class, forwardInvocationSEL); |
| | | |
| | | // Preserve any existing implementation of -forwardInvocation:. |
| | | void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL; |
| | | if (forwardInvocationMethod != NULL) { |
| | | originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod); |
| | | } |
| | | |
| | | // Set up a new version of -forwardInvocation:. |
| | | // |
| | | // If the selector has been passed to -rac_signalForSelector:, invoke |
| | | // the aliased method, and forward the arguments to any attached signals. |
| | | // |
| | | // If the selector has not been passed to -rac_signalForSelector:, |
| | | // invoke any existing implementation of -forwardInvocation:. If there |
| | | // was no existing implementation, throw an unrecognized selector |
| | | // exception. |
| | | id newForwardInvocation = ^(id self, NSInvocation *invocation) { |
| | | BOOL matched = RACForwardInvocation(self, invocation); |
| | | if (matched) return; |
| | | |
| | | if (originalForwardInvocation == NULL) { |
| | | [self doesNotRecognizeSelector:invocation.selector]; |
| | | } else { |
| | | originalForwardInvocation(self, forwardInvocationSEL, invocation); |
| | | } |
| | | }; |
| | | |
| | | class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@"); |
| | | } |
| | | |
| | | static void RACSwizzleRespondsToSelector(Class class) { |
| | | SEL respondsToSelectorSEL = @selector(respondsToSelector:); |
| | | |
| | | // Preserve existing implementation of -respondsToSelector:. |
| | | Method respondsToSelectorMethod = class_getInstanceMethod(class, respondsToSelectorSEL); |
| | | BOOL (*originalRespondsToSelector)(id, SEL, SEL) = (__typeof__(originalRespondsToSelector))method_getImplementation(respondsToSelectorMethod); |
| | | |
| | | // Set up a new version of -respondsToSelector: that returns YES for methods |
| | | // added by -rac_signalForSelector:. |
| | | // |
| | | // If the selector has a method defined on the receiver's actual class, and |
| | | // if that method's implementation is _objc_msgForward, then returns whether |
| | | // the instance has a signal for the selector. |
| | | // Otherwise, call the original -respondsToSelector:. |
| | | id newRespondsToSelector = ^ BOOL (id self, SEL selector) { |
| | | Method method = rac_getImmediateInstanceMethod(class, selector); |
| | | |
| | | if (method != NULL && method_getImplementation(method) == _objc_msgForward) { |
| | | SEL aliasSelector = RACAliasForSelector(selector); |
| | | if (objc_getAssociatedObject(self, aliasSelector) != nil) return YES; |
| | | } |
| | | |
| | | return originalRespondsToSelector(self, respondsToSelectorSEL, selector); |
| | | }; |
| | | |
| | | class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod)); |
| | | } |
| | | |
| | | static void RACSwizzleGetClass(Class class, Class statedClass) { |
| | | SEL selector = @selector(class); |
| | | Method method = class_getInstanceMethod(class, selector); |
| | | IMP newIMP = imp_implementationWithBlock(^(id self) { |
| | | return statedClass; |
| | | }); |
| | | class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method)); |
| | | } |
| | | |
| | | static void RACSwizzleMethodSignatureForSelector(Class class) { |
| | | IMP newIMP = imp_implementationWithBlock(^(id self, SEL selector) { |
| | | // Don't send the -class message to the receiver because we've changed |
| | | // that to return the original class. |
| | | Class actualClass = object_getClass(self); |
| | | Method method = class_getInstanceMethod(actualClass, selector); |
| | | if (method == NULL) { |
| | | // Messages that the original class dynamically implements fall |
| | | // here. |
| | | // |
| | | // Call the original class' -methodSignatureForSelector:. |
| | | struct objc_super target = { |
| | | .super_class = class_getSuperclass(class), |
| | | .receiver = self, |
| | | }; |
| | | NSMethodSignature * (*messageSend)(struct objc_super *, SEL, SEL) = (__typeof__(messageSend))objc_msgSendSuper; |
| | | return messageSend(&target, @selector(methodSignatureForSelector:), selector); |
| | | } |
| | | |
| | | char const *encoding = method_getTypeEncoding(method); |
| | | return [NSMethodSignature signatureWithObjCTypes:encoding]; |
| | | }); |
| | | |
| | | SEL selector = @selector(methodSignatureForSelector:); |
| | | Method methodSignatureForSelectorMethod = class_getInstanceMethod(class, selector); |
| | | class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(methodSignatureForSelectorMethod)); |
| | | } |
| | | |
| | | // It's hard to tell which struct return types use _objc_msgForward, and |
| | | // which use _objc_msgForward_stret instead, so just exclude all struct, array, |
| | | // union, complex and vector return types. |
| | | static void RACCheckTypeEncoding(const char *typeEncoding) { |
| | | #if !NS_BLOCK_ASSERTIONS |
| | | // Some types, including vector types, are not encoded. In these cases the |
| | | // signature starts with the size of the argument frame. |
| | | NSCAssert(*typeEncoding < '1' || *typeEncoding > '9', @"unknown method return type not supported in type encoding: %s", typeEncoding); |
| | | NSCAssert(strstr(typeEncoding, "(") != typeEncoding, @"union method return type not supported"); |
| | | NSCAssert(strstr(typeEncoding, "{") != typeEncoding, @"struct method return type not supported"); |
| | | NSCAssert(strstr(typeEncoding, "[") != typeEncoding, @"array method return type not supported"); |
| | | NSCAssert(strstr(typeEncoding, @encode(_Complex float)) != typeEncoding, @"complex float method return type not supported"); |
| | | NSCAssert(strstr(typeEncoding, @encode(_Complex double)) != typeEncoding, @"complex double method return type not supported"); |
| | | NSCAssert(strstr(typeEncoding, @encode(_Complex long double)) != typeEncoding, @"complex long double method return type not supported"); |
| | | |
| | | #endif // !NS_BLOCK_ASSERTIONS |
| | | } |
| | | |
| | | static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) { |
| | | SEL aliasSelector = RACAliasForSelector(selector); |
| | | |
| | | @synchronized (self) { |
| | | RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); |
| | | if (subject != nil) return subject; |
| | | |
| | | Class class = RACSwizzleClass(self); |
| | | NSCAssert(class != nil, @"Could not swizzle class of %@", self); |
| | | |
| | | subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", self.rac_description, sel_getName(selector)]; |
| | | objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN); |
| | | |
| | | [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| | | [subject sendCompleted]; |
| | | }]]; |
| | | |
| | | Method targetMethod = class_getInstanceMethod(class, selector); |
| | | if (targetMethod == NULL) { |
| | | const char *typeEncoding; |
| | | if (protocol == NULL) { |
| | | typeEncoding = RACSignatureForUndefinedSelector(selector); |
| | | } else { |
| | | // Look for the selector as an optional instance method. |
| | | struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); |
| | | |
| | | if (methodDescription.name == NULL) { |
| | | // Then fall back to looking for a required instance |
| | | // method. |
| | | methodDescription = protocol_getMethodDescription(protocol, selector, YES, YES); |
| | | NSCAssert(methodDescription.name != NULL, @"Selector %@ does not exist in <%s>", NSStringFromSelector(selector), protocol_getName(protocol)); |
| | | } |
| | | |
| | | typeEncoding = methodDescription.types; |
| | | } |
| | | |
| | | RACCheckTypeEncoding(typeEncoding); |
| | | |
| | | // Define the selector to call -forwardInvocation:. |
| | | if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) { |
| | | NSDictionary *userInfo = @{ |
| | | NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@", nil), NSStringFromSelector(selector), class], |
| | | NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Invoke -rac_signalForSelector: again to override the implementation.", nil) |
| | | }; |
| | | |
| | | return [RACSignal error:[NSError errorWithDomain:RACSelectorSignalErrorDomain code:RACSelectorSignalErrorMethodSwizzlingRace userInfo:userInfo]]; |
| | | } |
| | | } else if (method_getImplementation(targetMethod) != _objc_msgForward) { |
| | | // Make a method alias for the existing method implementation. |
| | | const char *typeEncoding = method_getTypeEncoding(targetMethod); |
| | | |
| | | RACCheckTypeEncoding(typeEncoding); |
| | | |
| | | BOOL addedAlias __attribute__((unused)) = class_addMethod(class, aliasSelector, method_getImplementation(targetMethod), typeEncoding); |
| | | NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), class); |
| | | |
| | | // Redefine the selector to call -forwardInvocation:. |
| | | class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod)); |
| | | } |
| | | |
| | | return subject; |
| | | } |
| | | } |
| | | |
| | | static SEL RACAliasForSelector(SEL originalSelector) { |
| | | NSString *selectorName = NSStringFromSelector(originalSelector); |
| | | return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]); |
| | | } |
| | | |
| | | static const char *RACSignatureForUndefinedSelector(SEL selector) { |
| | | const char *name = sel_getName(selector); |
| | | NSMutableString *signature = [NSMutableString stringWithString:@"v@:"]; |
| | | |
| | | while ((name = strchr(name, ':')) != NULL) { |
| | | [signature appendString:@"@"]; |
| | | name++; |
| | | } |
| | | |
| | | return signature.UTF8String; |
| | | } |
| | | |
| | | static Class RACSwizzleClass(NSObject *self) { |
| | | Class statedClass = self.class; |
| | | Class baseClass = object_getClass(self); |
| | | |
| | | // The "known dynamic subclass" is the subclass generated by RAC. |
| | | // It's stored as an associated object on every instance that's already |
| | | // been swizzled, so that even if something else swizzles the class of |
| | | // this instance, we can still access the RAC generated subclass. |
| | | Class knownDynamicSubclass = objc_getAssociatedObject(self, RACSubclassAssociationKey); |
| | | if (knownDynamicSubclass != Nil) return knownDynamicSubclass; |
| | | |
| | | NSString *className = NSStringFromClass(baseClass); |
| | | |
| | | if (statedClass != baseClass) { |
| | | // If the class is already lying about what it is, it's probably a KVO |
| | | // dynamic subclass or something else that we shouldn't subclass |
| | | // ourselves. |
| | | // |
| | | // Just swizzle -forwardInvocation: in-place. Since the object's class |
| | | // was almost certainly dynamically changed, we shouldn't see another of |
| | | // these classes in the hierarchy. |
| | | // |
| | | // Additionally, swizzle -respondsToSelector: because the default |
| | | // implementation may be ignorant of methods added to this class. |
| | | @synchronized (swizzledClasses()) { |
| | | if (![swizzledClasses() containsObject:className]) { |
| | | RACSwizzleForwardInvocation(baseClass); |
| | | RACSwizzleRespondsToSelector(baseClass); |
| | | RACSwizzleGetClass(baseClass, statedClass); |
| | | RACSwizzleGetClass(object_getClass(baseClass), statedClass); |
| | | RACSwizzleMethodSignatureForSelector(baseClass); |
| | | [swizzledClasses() addObject:className]; |
| | | } |
| | | } |
| | | |
| | | return baseClass; |
| | | } |
| | | |
| | | const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String; |
| | | Class subclass = objc_getClass(subclassName); |
| | | |
| | | if (subclass == nil) { |
| | | subclass = [RACObjCRuntime createClass:subclassName inheritingFromClass:baseClass]; |
| | | if (subclass == nil) return nil; |
| | | |
| | | RACSwizzleForwardInvocation(subclass); |
| | | RACSwizzleRespondsToSelector(subclass); |
| | | |
| | | RACSwizzleGetClass(subclass, statedClass); |
| | | RACSwizzleGetClass(object_getClass(subclass), statedClass); |
| | | |
| | | RACSwizzleMethodSignatureForSelector(subclass); |
| | | |
| | | objc_registerClassPair(subclass); |
| | | } |
| | | |
| | | object_setClass(self, subclass); |
| | | objc_setAssociatedObject(self, RACSubclassAssociationKey, subclass, OBJC_ASSOCIATION_ASSIGN); |
| | | return subclass; |
| | | } |
| | | |
| | | - (RACSignal *)rac_signalForSelector:(SEL)selector { |
| | | NSCParameterAssert(selector != NULL); |
| | | |
| | | return NSObjectRACSignalForSelector(self, selector, NULL); |
| | | } |
| | | |
| | | - (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol { |
| | | NSCParameterAssert(selector != NULL); |
| | | NSCParameterAssert(protocol != NULL); |
| | | |
| | | return NSObjectRACSignalForSelector(self, selector, protocol); |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSOrderedSet+RACSequenceAdditions.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSequence; |
| | | |
| | | @interface NSOrderedSet (RACSequenceAdditions) |
| | | |
| | | /// Creates and returns a sequence corresponding to the receiver. |
| | | /// |
| | | /// Mutating the receiver will not affect the sequence after it's been created. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_sequence; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSOrderedSet+RACSequenceAdditions.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "NSOrderedSet+RACSequenceAdditions.h" |
| | | #import "NSArray+RACSequenceAdditions.h" |
| | | |
| | | @implementation NSOrderedSet (RACSequenceAdditions) |
| | | |
| | | - (RACSequence *)rac_sequence { |
| | | // TODO: First class support for ordered set sequences. |
| | | return self.array.rac_sequence; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSSet+RACSequenceAdditions.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSequence; |
| | | |
| | | @interface NSSet (RACSequenceAdditions) |
| | | |
| | | /// Creates and returns a sequence corresponding to the receiver. |
| | | /// |
| | | /// Mutating the receiver will not affect the sequence after it's been created. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_sequence; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSSet+RACSequenceAdditions.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "NSSet+RACSequenceAdditions.h" |
| | | #import "NSArray+RACSequenceAdditions.h" |
| | | |
| | | @implementation NSSet (RACSequenceAdditions) |
| | | |
| | | - (RACSequence *)rac_sequence { |
| | | // TODO: First class support for set sequences. |
| | | return self.allObjects.rac_sequence; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSString+RACKeyPathUtilities.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 05/05/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | // A private category of methods to extract parts of a key path. |
| | | @interface NSString (RACKeyPathUtilities) |
| | | |
| | | // Returns an array of the components of the receiver. |
| | | // |
| | | // Calling this method on a string that isn't a key path is considered undefined |
| | | // behavior. |
| | | - (NSArray *)rac_keyPathComponents; |
| | | |
| | | // Returns a key path with all the components of the receiver except for the |
| | | // last one. |
| | | // |
| | | // Calling this method on a string that isn't a key path is considered undefined |
| | | // behavior. |
| | | - (NSString *)rac_keyPathByDeletingLastKeyPathComponent; |
| | | |
| | | // Returns a key path with all the components of the receiver expect for the |
| | | // first one. |
| | | // |
| | | // Calling this method on a string that isn't a key path is considered undefined |
| | | // behavior. |
| | | - (NSString *)rac_keyPathByDeletingFirstKeyPathComponent; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSString+RACKeyPathUtilities.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 05/05/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSString+RACKeyPathUtilities.h" |
| | | |
| | | @implementation NSString (RACKeyPathUtilities) |
| | | |
| | | - (NSArray *)rac_keyPathComponents { |
| | | if (self.length == 0) { |
| | | return nil; |
| | | } |
| | | return [self componentsSeparatedByString:@"."]; |
| | | } |
| | | |
| | | - (NSString *)rac_keyPathByDeletingLastKeyPathComponent { |
| | | NSUInteger lastDotIndex = [self rangeOfString:@"." options:NSBackwardsSearch].location; |
| | | if (lastDotIndex == NSNotFound) { |
| | | return nil; |
| | | } |
| | | return [self substringToIndex:lastDotIndex]; |
| | | } |
| | | |
| | | - (NSString *)rac_keyPathByDeletingFirstKeyPathComponent { |
| | | NSUInteger firstDotIndex = [self rangeOfString:@"."].location; |
| | | if (firstDotIndex == NSNotFound) { |
| | | return nil; |
| | | } |
| | | return [self substringFromIndex:firstDotIndex + 1]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSString+RACSequenceAdditions.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSequence; |
| | | |
| | | @interface NSString (RACSequenceAdditions) |
| | | |
| | | /// Creates and returns a sequence containing strings corresponding to each |
| | | /// composed character sequence in the receiver. |
| | | /// |
| | | /// Mutating the receiver will not affect the sequence after it's been created. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_sequence; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSString+RACSequenceAdditions.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "NSString+RACSequenceAdditions.h" |
| | | #import "RACStringSequence.h" |
| | | |
| | | @implementation NSString (RACSequenceAdditions) |
| | | |
| | | - (RACSequence *)rac_sequence { |
| | | return [RACStringSequence sequenceWithString:self offset:0]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSString+RACSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/11/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACScheduler; |
| | | @class RACSignal; |
| | | |
| | | @interface NSString (RACSupport) |
| | | |
| | | // Reads in the contents of the file using +[NSString stringWithContentsOfURL:usedEncoding:error:]. |
| | | // Note that encoding won't be valid until the signal completes successfully. |
| | | // |
| | | // scheduler - cannot be nil. |
| | | + (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSString+RACSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/11/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSString+RACSupport.h" |
| | | #import "RACReplaySubject.h" |
| | | #import "RACScheduler.h" |
| | | |
| | | @implementation NSString (RACSupport) |
| | | |
| | | + (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler { |
| | | NSCParameterAssert(scheduler != nil); |
| | | |
| | | RACReplaySubject *subject = [RACReplaySubject subject]; |
| | | [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ usedEncoding:scheduler: %@", URL, scheduler]; |
| | | |
| | | [scheduler schedule:^{ |
| | | NSError *error = nil; |
| | | NSString *string = [NSString stringWithContentsOfURL:URL usedEncoding:encoding error:&error]; |
| | | if (string == nil) { |
| | | [subject sendError:error]; |
| | | } else { |
| | | [subject sendNext:string]; |
| | | [subject sendCompleted]; |
| | | } |
| | | }]; |
| | | |
| | | return subject; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSURLConnection+RACSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-01. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | @interface NSURLConnection (RACSupport) |
| | | |
| | | // Lazily loads data for the given request in the background. |
| | | // |
| | | // request - The URL request to load. This must not be nil. |
| | | // |
| | | // Returns a signal which will begin loading the request upon each subscription, |
| | | // then send a `RACTuple` of the received `NSURLResponse` and downloaded |
| | | // `NSData`, and complete on a background thread. If any errors occur, the |
| | | // returned signal will error out. |
| | | + (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSURLConnection+RACSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-01. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSURLConnection+RACSupport.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACSignal.h" |
| | | #import "RACSubscriber.h" |
| | | #import "RACTuple.h" |
| | | |
| | | @implementation NSURLConnection (RACSupport) |
| | | |
| | | + (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request { |
| | | NSCParameterAssert(request != nil); |
| | | |
| | | return [[RACSignal |
| | | createSignal:^(id<RACSubscriber> subscriber) { |
| | | NSOperationQueue *queue = [[NSOperationQueue alloc] init]; |
| | | queue.name = @"org.reactivecocoa.ReactiveCocoa.NSURLConnectionRACSupport"; |
| | | |
| | | [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { |
| | | // The docs say that `nil` data means an error occurred, but |
| | | // `nil` responses can also occur in practice (circumstances |
| | | // unknown). Consider either to be an error. |
| | | // |
| | | // Note that _empty_ data is not necessarily erroneous, as there |
| | | // may be headers but no HTTP body. |
| | | if (response == nil || data == nil) { |
| | | [subscriber sendError:error]; |
| | | } else { |
| | | [subscriber sendNext:RACTuplePack(response, data)]; |
| | | [subscriber sendCompleted]; |
| | | } |
| | | }]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | // It's not clear if this will actually cancel the connection, |
| | | // but we can at least prevent _some_ unnecessary work -- |
| | | // without writing all the code for a proper delegate, which |
| | | // doesn't really belong in RAC. |
| | | queue.suspended = YES; |
| | | [queue cancelAllOperations]; |
| | | }]; |
| | | }] |
| | | setNameWithFormat:@"+rac_sendAsynchronousRequest: %@", request]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSUserDefaults+RACSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Matt Diephouse on 12/19/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACChannelTerminal; |
| | | |
| | | @interface NSUserDefaults (RACSupport) |
| | | |
| | | /// Creates and returns a terminal for binding the user defaults key. |
| | | /// |
| | | /// **Note:** The value in the user defaults is *asynchronously* updated with |
| | | /// values sent to the channel. |
| | | /// |
| | | /// key - The user defaults key to create the channel terminal for. |
| | | /// |
| | | /// Returns a channel terminal that sends the value of the user defaults key |
| | | /// upon subscription, sends an updated value whenever the default changes, and |
| | | /// updates the default asynchronously with values it receives. |
| | | - (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // NSUserDefaults+RACSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Matt Diephouse on 12/19/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "NSUserDefaults+RACSupport.h" |
| | | #import "RACEXTScope.h" |
| | | #import "NSNotificationCenter+RACSupport.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "RACChannel.h" |
| | | #import "RACScheduler.h" |
| | | #import "RACSignal+Operations.h" |
| | | |
| | | @implementation NSUserDefaults (RACSupport) |
| | | |
| | | - (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key { |
| | | RACChannel *channel = [RACChannel new]; |
| | | |
| | | RACScheduler *scheduler = [RACScheduler scheduler]; |
| | | __block BOOL ignoreNextValue = NO; |
| | | |
| | | @weakify(self); |
| | | [[[[[[[NSNotificationCenter.defaultCenter |
| | | rac_addObserverForName:NSUserDefaultsDidChangeNotification object:self] |
| | | map:^(id _) { |
| | | @strongify(self); |
| | | return [self objectForKey:key]; |
| | | }] |
| | | startWith:[self objectForKey:key]] |
| | | // Don't send values that were set on the other side of the terminal. |
| | | filter:^ BOOL (id _) { |
| | | if (RACScheduler.currentScheduler == scheduler && ignoreNextValue) { |
| | | ignoreNextValue = NO; |
| | | return NO; |
| | | } |
| | | return YES; |
| | | }] |
| | | distinctUntilChanged] |
| | | takeUntil:self.rac_willDeallocSignal] |
| | | subscribe:channel.leadingTerminal]; |
| | | |
| | | [[channel.leadingTerminal |
| | | deliverOn:scheduler] |
| | | subscribeNext:^(id value) { |
| | | @strongify(self); |
| | | ignoreNextValue = YES; |
| | | [self setObject:value forKey:key]; |
| | | }]; |
| | | |
| | | return channel.followingTerminal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACArraySequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSequence.h" |
| | | |
| | | // Private class that adapts an array to the RACSequence interface. |
| | | @interface RACArraySequence : RACSequence |
| | | |
| | | // Returns a sequence for enumerating over the given array, starting from the |
| | | // given offset. The array will be copied to prevent mutation. |
| | | + (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACArraySequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "RACArraySequence.h" |
| | | |
| | | @interface RACArraySequence () |
| | | |
| | | // Redeclared from the superclass and marked deprecated to prevent using `array` |
| | | // where `backingArray` is intended. |
| | | @property (nonatomic, copy, readonly) NSArray *array __attribute__((deprecated)); |
| | | |
| | | // The array being sequenced. |
| | | @property (nonatomic, copy, readonly) NSArray *backingArray; |
| | | |
| | | // The index in the array from which the sequence starts. |
| | | @property (nonatomic, assign, readonly) NSUInteger offset; |
| | | |
| | | @end |
| | | |
| | | @implementation RACArraySequence |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset { |
| | | NSCParameterAssert(offset <= array.count); |
| | | |
| | | if (offset == array.count) return self.empty; |
| | | |
| | | RACArraySequence *seq = [[self alloc] init]; |
| | | seq->_backingArray = [array copy]; |
| | | seq->_offset = offset; |
| | | return seq; |
| | | } |
| | | |
| | | #pragma mark RACSequence |
| | | |
| | | - (id)head { |
| | | return self.backingArray[self.offset]; |
| | | } |
| | | |
| | | - (RACSequence *)tail { |
| | | RACSequence *sequence = [self.class sequenceWithArray:self.backingArray offset:self.offset + 1]; |
| | | sequence.name = self.name; |
| | | return sequence; |
| | | } |
| | | |
| | | #pragma mark NSFastEnumeration |
| | | |
| | | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id[])stackbuf count:(NSUInteger)len { |
| | | NSCParameterAssert(len > 0); |
| | | |
| | | if (state->state >= self.backingArray.count) { |
| | | // Enumeration has completed. |
| | | return 0; |
| | | } |
| | | |
| | | if (state->state == 0) { |
| | | state->state = self.offset; |
| | | |
| | | // Since a sequence doesn't mutate, this just needs to be set to |
| | | // something non-NULL. |
| | | state->mutationsPtr = state->extra; |
| | | } |
| | | |
| | | state->itemsPtr = stackbuf; |
| | | |
| | | NSUInteger startIndex = state->state; |
| | | NSUInteger index = 0; |
| | | |
| | | for (id value in self.backingArray) { |
| | | // Constructing an index set for -enumerateObjectsAtIndexes: can actually be |
| | | // slower than just skipping the items we don't care about. |
| | | if (index < startIndex) { |
| | | ++index; |
| | | continue; |
| | | } |
| | | |
| | | stackbuf[index - startIndex] = value; |
| | | |
| | | ++index; |
| | | if (index - startIndex >= len) break; |
| | | } |
| | | |
| | | NSCAssert(index > startIndex, @"Final index (%lu) should be greater than start index (%lu)", (unsigned long)index, (unsigned long)startIndex); |
| | | |
| | | state->state = index; |
| | | return index - startIndex; |
| | | } |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | - (NSArray *)array { |
| | | return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)]; |
| | | } |
| | | #pragma clang diagnostic pop |
| | | |
| | | #pragma mark NSCoding |
| | | |
| | | - (id)initWithCoder:(NSCoder *)coder { |
| | | self = [super initWithCoder:coder]; |
| | | if (self == nil) return nil; |
| | | |
| | | _backingArray = [coder decodeObjectForKey:@"array"]; |
| | | _offset = 0; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (void)encodeWithCoder:(NSCoder *)coder { |
| | | // Encoding is handled in RACSequence. |
| | | [super encodeWithCoder:coder]; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | return [NSString stringWithFormat:@"<%@: %p>{ name = %@, array = %@ }", self.class, self, self.name, self.backingArray]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACBehaviorSubject.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/16/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSubject.h" |
| | | |
| | | /// A behavior subject sends the last value it received when it is subscribed to. |
| | | @interface RACBehaviorSubject : RACSubject |
| | | |
| | | /// Creates a new behavior subject with a default value. If it hasn't received |
| | | /// any values when it gets subscribed to, it sends the default value. |
| | | + (instancetype)behaviorSubjectWithDefaultValue:(id)value; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACBehaviorSubject.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/16/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACBehaviorSubject.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACScheduler+Private.h" |
| | | |
| | | @interface RACBehaviorSubject () |
| | | |
| | | // This property should only be used while synchronized on self. |
| | | @property (nonatomic, strong) id currentValue; |
| | | |
| | | @end |
| | | |
| | | @implementation RACBehaviorSubject |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)behaviorSubjectWithDefaultValue:(id)value { |
| | | RACBehaviorSubject *subject = [self subject]; |
| | | subject.currentValue = value; |
| | | return subject; |
| | | } |
| | | |
| | | #pragma mark RACSignal |
| | | |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { |
| | | RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; |
| | | |
| | | RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ |
| | | @synchronized (self) { |
| | | [subscriber sendNext:self.currentValue]; |
| | | } |
| | | }]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [subscriptionDisposable dispose]; |
| | | [schedulingDisposable dispose]; |
| | | }]; |
| | | } |
| | | |
| | | #pragma mark RACSubscriber |
| | | |
| | | - (void)sendNext:(id)value { |
| | | @synchronized (self) { |
| | | self.currentValue = value; |
| | | [super sendNext:value]; |
| | | } |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACBlockTrampoline.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 10/21/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACTuple; |
| | | |
| | | // A private class that allows a limited type of dynamic block invocation. |
| | | @interface RACBlockTrampoline : NSObject |
| | | |
| | | // Invokes the given block with the given arguments. All of the block's |
| | | // argument types must be objects and it must be typed to return an object. |
| | | // |
| | | // At this time, it only supports blocks that take up to 15 arguments. Any more |
| | | // is just cray. |
| | | // |
| | | // block - The block to invoke. Must accept as many arguments as are given in |
| | | // the arguments array. Cannot be nil. |
| | | // arguments - The arguments with which to invoke the block. `RACTupleNil`s will |
| | | // be passed as nils. |
| | | // |
| | | // Returns the return value of invoking the block. |
| | | + (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACBlockTrampoline.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 10/21/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACBlockTrampoline.h" |
| | | #import "RACTuple.h" |
| | | |
| | | @interface RACBlockTrampoline () |
| | | @property (nonatomic, readonly, copy) id block; |
| | | @end |
| | | |
| | | @implementation RACBlockTrampoline |
| | | |
| | | #pragma mark API |
| | | |
| | | - (id)initWithBlock:(id)block { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _block = [block copy]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | + (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | RACBlockTrampoline *trampoline = [[self alloc] initWithBlock:block]; |
| | | return [trampoline invokeWithArguments:arguments]; |
| | | } |
| | | |
| | | - (id)invokeWithArguments:(RACTuple *)arguments { |
| | | SEL selector = [self selectorForArgumentCount:arguments.count]; |
| | | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]]; |
| | | invocation.selector = selector; |
| | | invocation.target = self; |
| | | |
| | | for (NSUInteger i = 0; i < arguments.count; i++) { |
| | | id arg = arguments[i]; |
| | | NSInteger argIndex = (NSInteger)(i + 2); |
| | | [invocation setArgument:&arg atIndex:argIndex]; |
| | | } |
| | | |
| | | [invocation invoke]; |
| | | |
| | | __unsafe_unretained id returnVal; |
| | | [invocation getReturnValue:&returnVal]; |
| | | return returnVal; |
| | | } |
| | | |
| | | - (SEL)selectorForArgumentCount:(NSUInteger)count { |
| | | NSCParameterAssert(count > 0); |
| | | |
| | | switch (count) { |
| | | case 0: return NULL; |
| | | case 1: return @selector(performWith:); |
| | | case 2: return @selector(performWith::); |
| | | case 3: return @selector(performWith:::); |
| | | case 4: return @selector(performWith::::); |
| | | case 5: return @selector(performWith:::::); |
| | | case 6: return @selector(performWith::::::); |
| | | case 7: return @selector(performWith:::::::); |
| | | case 8: return @selector(performWith::::::::); |
| | | case 9: return @selector(performWith:::::::::); |
| | | case 10: return @selector(performWith::::::::::); |
| | | case 11: return @selector(performWith:::::::::::); |
| | | case 12: return @selector(performWith::::::::::::); |
| | | case 13: return @selector(performWith:::::::::::::); |
| | | case 14: return @selector(performWith::::::::::::::); |
| | | case 15: return @selector(performWith:::::::::::::::); |
| | | } |
| | | |
| | | NSCAssert(NO, @"The argument count is too damn high! Only blocks of up to 15 arguments are currently supported."); |
| | | return NULL; |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 { |
| | | id (^block)(id) = self.block; |
| | | return block(obj1); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 { |
| | | id (^block)(id, id) = self.block; |
| | | return block(obj1, obj2); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 { |
| | | id (^block)(id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 { |
| | | id (^block)(id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 { |
| | | id (^block)(id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 { |
| | | id (^block)(id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 { |
| | | id (^block)(id, id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 { |
| | | id (^block)(id, id, id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 { |
| | | id (^block)(id, id, id, id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 { |
| | | id (^block)(id, id, id, id, id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 { |
| | | id (^block)(id, id, id, id, id, id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 { |
| | | id (^block)(id, id, id, id, id, id, id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 :(id)obj13 { |
| | | id (^block)(id, id, id, id, id, id, id, id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12, obj13); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 :(id)obj13 :(id)obj14 { |
| | | id (^block)(id, id, id, id, id, id, id, id, id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12, obj13, obj14); |
| | | } |
| | | |
| | | - (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 :(id)obj13 :(id)obj14 :(id)obj15 { |
| | | id (^block)(id, id, id, id, id, id, id, id, id, id, id, id, id, id, id) = self.block; |
| | | return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12, obj13, obj14, obj15); |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACChannel.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 01/01/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSignal.h" |
| | | #import "RACSubscriber.h" |
| | | |
| | | @class RACChannelTerminal; |
| | | |
| | | /// A two-way channel. |
| | | /// |
| | | /// Conceptually, RACChannel can be thought of as a bidirectional connection, |
| | | /// composed of two controllable signals that work in parallel. |
| | | /// |
| | | /// For example, when connecting between a view and a model: |
| | | /// |
| | | /// Model View |
| | | /// `leadingTerminal` ------> `followingTerminal` |
| | | /// `leadingTerminal` <------ `followingTerminal` |
| | | /// |
| | | /// The initial value of the model and all future changes to it are _sent on_ the |
| | | /// `leadingTerminal`, and _received by_ subscribers of the `followingTerminal`. |
| | | /// |
| | | /// Likewise, whenever the user changes the value of the view, that value is sent |
| | | /// on the `followingTerminal`, and received in the model from the |
| | | /// `leadingTerminal`. However, the initial value of the view is not received |
| | | /// from the `leadingTerminal` (only future changes). |
| | | @interface RACChannel : NSObject |
| | | |
| | | /// The terminal which "leads" the channel, by sending its latest value |
| | | /// immediately to new subscribers of the `followingTerminal`. |
| | | /// |
| | | /// New subscribers to this terminal will not receive a starting value, but will |
| | | /// receive all future values that are sent to the `followingTerminal`. |
| | | @property (nonatomic, strong, readonly) RACChannelTerminal *leadingTerminal; |
| | | |
| | | /// The terminal which "follows" the lead of the other terminal, only sending |
| | | /// _future_ values to the subscribers of the `leadingTerminal`. |
| | | /// |
| | | /// The latest value sent to the `leadingTerminal` (if any) will be sent |
| | | /// immediately to new subscribers of this terminal, and then all future values |
| | | /// as well. |
| | | @property (nonatomic, strong, readonly) RACChannelTerminal *followingTerminal; |
| | | |
| | | @end |
| | | |
| | | /// Represents one end of a RACChannel. |
| | | /// |
| | | /// An terminal is similar to a socket or pipe -- it represents one end of |
| | | /// a connection (the RACChannel, in this case). Values sent to this terminal |
| | | /// will _not_ be received by its subscribers. Instead, the values will be sent |
| | | /// to the subscribers of the RACChannel's _other_ terminal. |
| | | /// |
| | | /// For example, when using the `followingTerminal`, _sent_ values can only be |
| | | /// _received_ from the `leadingTerminal`, and vice versa. |
| | | /// |
| | | /// To make it easy to terminate a RACChannel, `error` and `completed` events |
| | | /// sent to either terminal will be received by the subscribers of _both_ |
| | | /// terminals. |
| | | /// |
| | | /// Do not instantiate this class directly. Create a RACChannel instead. |
| | | @interface RACChannelTerminal : RACSignal <RACSubscriber> |
| | | |
| | | - (id)init __attribute__((unavailable("Instantiate a RACChannel instead"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACChannel.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 01/01/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACChannel.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACReplaySubject.h" |
| | | #import "RACSignal+Operations.h" |
| | | |
| | | @interface RACChannelTerminal () |
| | | |
| | | // The values for this terminal. |
| | | @property (nonatomic, strong, readonly) RACSignal *values; |
| | | |
| | | // A subscriber will will send values to the other terminal. |
| | | @property (nonatomic, strong, readonly) id<RACSubscriber> otherTerminal; |
| | | |
| | | - (id)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal; |
| | | |
| | | @end |
| | | |
| | | @implementation RACChannel |
| | | |
| | | - (id)init { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | // We don't want any starting value from the leadingSubject, but we do want |
| | | // error and completion to be replayed. |
| | | RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0] setNameWithFormat:@"leadingSubject"]; |
| | | RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"followingSubject"]; |
| | | |
| | | // Propagate errors and completion to everything. |
| | | [[leadingSubject ignoreValues] subscribe:followingSubject]; |
| | | [[followingSubject ignoreValues] subscribe:leadingSubject]; |
| | | |
| | | _leadingTerminal = [[[RACChannelTerminal alloc] initWithValues:leadingSubject otherTerminal:followingSubject] setNameWithFormat:@"leadingTerminal"]; |
| | | _followingTerminal = [[[RACChannelTerminal alloc] initWithValues:followingSubject otherTerminal:leadingSubject] setNameWithFormat:@"followingTerminal"]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACChannelTerminal |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal { |
| | | NSCParameterAssert(values != nil); |
| | | NSCParameterAssert(otherTerminal != nil); |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _values = values; |
| | | _otherTerminal = otherTerminal; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark RACSignal |
| | | |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { |
| | | return [self.values subscribe:subscriber]; |
| | | } |
| | | |
| | | #pragma mark <RACSubscriber> |
| | | |
| | | - (void)sendNext:(id)value { |
| | | [self.otherTerminal sendNext:value]; |
| | | } |
| | | |
| | | - (void)sendError:(NSError *)error { |
| | | [self.otherTerminal sendError:error]; |
| | | } |
| | | |
| | | - (void)sendCompleted { |
| | | [self.otherTerminal sendCompleted]; |
| | | } |
| | | |
| | | - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { |
| | | [self.otherTerminal didSubscribeWithDisposable:disposable]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACCommand.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/3/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | /// The domain for errors originating within `RACCommand`. |
| | | extern NSString * const RACCommandErrorDomain; |
| | | |
| | | /// -execute: was invoked while the command was disabled. |
| | | extern const NSInteger RACCommandErrorNotEnabled; |
| | | |
| | | /// A `userInfo` key for an error, associated with the `RACCommand` that the |
| | | /// error originated from. |
| | | /// |
| | | /// This is included only when the error code is `RACCommandErrorNotEnabled`. |
| | | extern NSString * const RACUnderlyingCommandErrorKey; |
| | | |
| | | /// A command is a signal triggered in response to some action, typically |
| | | /// UI-related. |
| | | @interface RACCommand : NSObject |
| | | |
| | | /// A signal of the signals returned by successful invocations of -execute: |
| | | /// (i.e., while the receiver is `enabled`). |
| | | /// |
| | | /// Errors will be automatically caught upon the inner signals, and sent upon |
| | | /// `errors` instead. If you _want_ to receive inner errors, use -execute: or |
| | | /// -[RACSignal materialize]. |
| | | /// |
| | | /// Only executions that begin _after_ subscription will be sent upon this |
| | | /// signal. All inner signals will arrive upon the main thread. |
| | | @property (nonatomic, strong, readonly) RACSignal *executionSignals; |
| | | |
| | | /// A signal of whether this command is currently executing. |
| | | /// |
| | | /// This will send YES whenever -execute: is invoked and the created signal has |
| | | /// not yet terminated. Once all executions have terminated, `executing` will |
| | | /// send NO. |
| | | /// |
| | | /// This signal will send its current value upon subscription, and then all |
| | | /// future values on the main thread. |
| | | @property (nonatomic, strong, readonly) RACSignal *executing; |
| | | |
| | | /// A signal of whether this command is able to execute. |
| | | /// |
| | | /// This will send NO if: |
| | | /// |
| | | /// - The command was created with an `enabledSignal`, and NO is sent upon that |
| | | /// signal, or |
| | | /// - `allowsConcurrentExecution` is NO and the command has started executing. |
| | | /// |
| | | /// Once the above conditions are no longer met, the signal will send YES. |
| | | /// |
| | | /// This signal will send its current value upon subscription, and then all |
| | | /// future values on the main thread. |
| | | @property (nonatomic, strong, readonly) RACSignal *enabled; |
| | | |
| | | /// Forwards any errors that occur within signals returned by -execute:. |
| | | /// |
| | | /// When an error occurs on a signal returned from -execute:, this signal will |
| | | /// send the associated NSError value as a `next` event (since an `error` event |
| | | /// would terminate the stream). |
| | | /// |
| | | /// After subscription, this signal will send all future errors on the main |
| | | /// thread. |
| | | @property (nonatomic, strong, readonly) RACSignal *errors; |
| | | |
| | | /// Whether the command allows multiple executions to proceed concurrently. |
| | | /// |
| | | /// The default value for this property is NO. |
| | | @property (atomic, assign) BOOL allowsConcurrentExecution; |
| | | |
| | | /// Invokes -initWithEnabled:signalBlock: with a nil `enabledSignal`. |
| | | - (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock; |
| | | |
| | | /// Initializes a command that is conditionally enabled. |
| | | /// |
| | | /// This is the designated initializer for this class. |
| | | /// |
| | | /// enabledSignal - A signal of BOOLs which indicate whether the command should |
| | | /// be enabled. `enabled` will be based on the latest value sent |
| | | /// from this signal. Before any values are sent, `enabled` will |
| | | /// default to YES. This argument may be nil. |
| | | /// signalBlock - A block which will map each input value (passed to -execute:) |
| | | /// to a signal of work. The returned signal will be multicasted |
| | | /// to a replay subject, sent on `executionSignals`, then |
| | | /// subscribed to synchronously. Neither the block nor the |
| | | /// returned signal may be nil. |
| | | - (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock; |
| | | |
| | | /// If the receiver is enabled, this method will: |
| | | /// |
| | | /// 1. Invoke the `signalBlock` given at the time of initialization. |
| | | /// 2. Multicast the returned signal to a RACReplaySubject. |
| | | /// 3. Send the multicasted signal on `executionSignals`. |
| | | /// 4. Subscribe (connect) to the original signal on the main thread. |
| | | /// |
| | | /// input - The input value to pass to the receiver's `signalBlock`. This may be |
| | | /// nil. |
| | | /// |
| | | /// Returns the multicasted signal, after subscription. If the receiver is not |
| | | /// enabled, returns a signal that will send an error with code |
| | | /// RACCommandErrorNotEnabled. |
| | | - (RACSignal *)execute:(id)input; |
| | | |
| | | @end |
| | | |
| | | @interface RACCommand (Unavailable) |
| | | |
| | | @property (atomic, readonly) BOOL canExecute __attribute__((unavailable("Use the 'enabled' signal instead"))); |
| | | |
| | | + (instancetype)command __attribute__((unavailable("Use -initWithSignalBlock: instead"))); |
| | | + (instancetype)commandWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead"))); |
| | | - (id)initWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead"))); |
| | | - (RACSignal *)addSignalBlock:(RACSignal * (^)(id value))signalBlock __attribute__((unavailable("Pass the signalBlock to -initWithSignalBlock: instead"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACCommand.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/3/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACCommand.h" |
| | | #import "RACEXTScope.h" |
| | | #import "NSArray+RACSequenceAdditions.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "NSObject+RACPropertySubscribing.h" |
| | | #import "RACMulticastConnection.h" |
| | | #import "RACReplaySubject.h" |
| | | #import "RACScheduler.h" |
| | | #import "RACSequence.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import <libkern/OSAtomic.h> |
| | | |
| | | NSString * const RACCommandErrorDomain = @"RACCommandErrorDomain"; |
| | | NSString * const RACUnderlyingCommandErrorKey = @"RACUnderlyingCommandErrorKey"; |
| | | |
| | | const NSInteger RACCommandErrorNotEnabled = 1; |
| | | |
| | | @interface RACCommand () { |
| | | // The mutable array backing `activeExecutionSignals`. |
| | | // |
| | | // This should only be used while synchronized on `self`. |
| | | NSMutableArray *_activeExecutionSignals; |
| | | |
| | | // Atomic backing variable for `allowsConcurrentExecution`. |
| | | volatile uint32_t _allowsConcurrentExecution; |
| | | } |
| | | |
| | | // An array of signals representing in-flight executions, in the order they |
| | | // began. |
| | | // |
| | | // This property is KVO-compliant. |
| | | @property (atomic, copy, readonly) NSArray *activeExecutionSignals; |
| | | |
| | | // `enabled`, but without a hop to the main thread. |
| | | // |
| | | // Values from this signal may arrive on any thread. |
| | | @property (nonatomic, strong, readonly) RACSignal *immediateEnabled; |
| | | |
| | | // The signal block that the receiver was initialized with. |
| | | @property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input); |
| | | |
| | | // Adds a signal to `activeExecutionSignals` and generates a KVO notification. |
| | | - (void)addActiveExecutionSignal:(RACSignal *)signal; |
| | | |
| | | // Removes a signal from `activeExecutionSignals` and generates a KVO |
| | | // notification. |
| | | - (void)removeActiveExecutionSignal:(RACSignal *)signal; |
| | | |
| | | @end |
| | | |
| | | @implementation RACCommand |
| | | |
| | | #pragma mark Properties |
| | | |
| | | - (BOOL)allowsConcurrentExecution { |
| | | return _allowsConcurrentExecution != 0; |
| | | } |
| | | |
| | | - (void)setAllowsConcurrentExecution:(BOOL)allowed { |
| | | [self willChangeValueForKey:@keypath(self.allowsConcurrentExecution)]; |
| | | |
| | | if (allowed) { |
| | | OSAtomicOr32Barrier(1, &_allowsConcurrentExecution); |
| | | } else { |
| | | OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution); |
| | | } |
| | | |
| | | [self didChangeValueForKey:@keypath(self.allowsConcurrentExecution)]; |
| | | } |
| | | |
| | | - (NSArray *)activeExecutionSignals { |
| | | @synchronized (self) { |
| | | return [_activeExecutionSignals copy]; |
| | | } |
| | | } |
| | | |
| | | - (void)addActiveExecutionSignal:(RACSignal *)signal { |
| | | NSCParameterAssert([signal isKindOfClass:RACSignal.class]); |
| | | |
| | | @synchronized (self) { |
| | | // The KVO notification has to be generated while synchronized, because |
| | | // it depends on the index remaining consistent. |
| | | NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:_activeExecutionSignals.count]; |
| | | [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; |
| | | [_activeExecutionSignals addObject:signal]; |
| | | [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; |
| | | } |
| | | } |
| | | |
| | | - (void)removeActiveExecutionSignal:(RACSignal *)signal { |
| | | NSCParameterAssert([signal isKindOfClass:RACSignal.class]); |
| | | |
| | | @synchronized (self) { |
| | | // The indexes have to be calculated and the notification generated |
| | | // while synchronized, because they depend on the indexes remaining |
| | | // consistent. |
| | | NSIndexSet *indexes = [_activeExecutionSignals indexesOfObjectsPassingTest:^ BOOL (RACSignal *obj, NSUInteger index, BOOL *stop) { |
| | | return obj == signal; |
| | | }]; |
| | | |
| | | if (indexes.count == 0) return; |
| | | |
| | | [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; |
| | | [_activeExecutionSignals removeObjectsAtIndexes:indexes]; |
| | | [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; |
| | | } |
| | | } |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)init { |
| | | NSCAssert(NO, @"Use -initWithSignalBlock: instead"); |
| | | return nil; |
| | | } |
| | | |
| | | - (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock { |
| | | return [self initWithEnabled:nil signalBlock:signalBlock]; |
| | | } |
| | | |
| | | - (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock { |
| | | NSCParameterAssert(signalBlock != nil); |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _activeExecutionSignals = [[NSMutableArray alloc] init]; |
| | | _signalBlock = [signalBlock copy]; |
| | | |
| | | // A signal of additions to `activeExecutionSignals`. |
| | | RACSignal *newActiveExecutionSignals = [[[[[self |
| | | rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil] |
| | | reduceEach:^(id _, NSDictionary *change) { |
| | | NSArray *signals = change[NSKeyValueChangeNewKey]; |
| | | if (signals == nil) return [RACSignal empty]; |
| | | |
| | | return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler]; |
| | | }] |
| | | concat] |
| | | publish] |
| | | autoconnect]; |
| | | |
| | | _executionSignals = [[[newActiveExecutionSignals |
| | | map:^(RACSignal *signal) { |
| | | return [signal catchTo:[RACSignal empty]]; |
| | | }] |
| | | deliverOn:RACScheduler.mainThreadScheduler] |
| | | setNameWithFormat:@"%@ -executionSignals", self]; |
| | | |
| | | // `errors` needs to be multicasted so that it picks up all |
| | | // `activeExecutionSignals` that are added. |
| | | // |
| | | // In other words, if someone subscribes to `errors` _after_ an execution |
| | | // has started, it should still receive any error from that execution. |
| | | RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals |
| | | flattenMap:^(RACSignal *signal) { |
| | | return [[signal |
| | | ignoreValues] |
| | | catch:^(NSError *error) { |
| | | return [RACSignal return:error]; |
| | | }]; |
| | | }] |
| | | deliverOn:RACScheduler.mainThreadScheduler] |
| | | publish]; |
| | | |
| | | _errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self]; |
| | | [errorsConnection connect]; |
| | | |
| | | RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) { |
| | | return @(activeSignals.count > 0); |
| | | }]; |
| | | |
| | | _executing = [[[[[immediateExecuting |
| | | deliverOn:RACScheduler.mainThreadScheduler] |
| | | // This is useful before the first value arrives on the main thread. |
| | | startWith:@NO] |
| | | distinctUntilChanged] |
| | | replayLast] |
| | | setNameWithFormat:@"%@ -executing", self]; |
| | | |
| | | RACSignal *moreExecutionsAllowed = [RACSignal |
| | | if:RACObserve(self, allowsConcurrentExecution) |
| | | then:[RACSignal return:@YES] |
| | | else:[immediateExecuting not]]; |
| | | |
| | | if (enabledSignal == nil) { |
| | | enabledSignal = [RACSignal return:@YES]; |
| | | } else { |
| | | enabledSignal = [[[enabledSignal |
| | | startWith:@YES] |
| | | takeUntil:self.rac_willDeallocSignal] |
| | | replayLast]; |
| | | } |
| | | |
| | | _immediateEnabled = [[RACSignal |
| | | combineLatest:@[ enabledSignal, moreExecutionsAllowed ]] |
| | | and]; |
| | | |
| | | _enabled = [[[[[self.immediateEnabled |
| | | take:1] |
| | | concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]] |
| | | distinctUntilChanged] |
| | | replayLast] |
| | | setNameWithFormat:@"%@ -enabled", self]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark Execution |
| | | |
| | | - (RACSignal *)execute:(id)input { |
| | | // `immediateEnabled` is guaranteed to send a value upon subscription, so |
| | | // -first is acceptable here. |
| | | BOOL enabled = [[self.immediateEnabled first] boolValue]; |
| | | if (!enabled) { |
| | | NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{ |
| | | NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil), |
| | | RACUnderlyingCommandErrorKey: self |
| | | }]; |
| | | |
| | | return [RACSignal error:error]; |
| | | } |
| | | |
| | | RACSignal *signal = self.signalBlock(input); |
| | | NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input); |
| | | |
| | | // We subscribe to the signal on the main thread so that it occurs _after_ |
| | | // -addActiveExecutionSignal: completes below. |
| | | // |
| | | // This means that `executing` and `enabled` will send updated values before |
| | | // the signal actually starts performing work. |
| | | RACMulticastConnection *connection = [[signal |
| | | subscribeOn:RACScheduler.mainThreadScheduler] |
| | | multicast:[RACReplaySubject subject]]; |
| | | |
| | | @weakify(self); |
| | | |
| | | [self addActiveExecutionSignal:connection.signal]; |
| | | [connection.signal subscribeError:^(NSError *error) { |
| | | @strongify(self); |
| | | [self removeActiveExecutionSignal:connection.signal]; |
| | | } completed:^{ |
| | | @strongify(self); |
| | | [self removeActiveExecutionSignal:connection.signal]; |
| | | }]; |
| | | |
| | | [connection connect]; |
| | | return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]]; |
| | | } |
| | | |
| | | #pragma mark NSKeyValueObserving |
| | | |
| | | + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { |
| | | // Generate all KVO notifications manually to avoid the performance impact |
| | | // of unnecessary swizzling. |
| | | return NO; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACCompoundDisposable.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/30/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACDisposable.h" |
| | | |
| | | /// A disposable of disposables. When it is disposed, it disposes of all its |
| | | /// contained disposables. |
| | | /// |
| | | /// If -addDisposable: is called after the compound disposable has been disposed |
| | | /// of, the given disposable is immediately disposed. This allows a compound |
| | | /// disposable to act as a stand-in for a disposable that will be delivered |
| | | /// asynchronously. |
| | | @interface RACCompoundDisposable : RACDisposable |
| | | |
| | | /// Creates and returns a new compound disposable. |
| | | + (instancetype)compoundDisposable; |
| | | |
| | | /// Creates and returns a new compound disposable containing the given |
| | | /// disposables. |
| | | + (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables; |
| | | |
| | | /// Adds the given disposable. If the receiving disposable has already been |
| | | /// disposed of, the given disposable is disposed immediately. |
| | | /// |
| | | /// This method is thread-safe. |
| | | /// |
| | | /// disposable - The disposable to add. This may be nil, in which case nothing |
| | | /// happens. |
| | | - (void)addDisposable:(RACDisposable *)disposable; |
| | | |
| | | /// Removes the specified disposable from the compound disposable (regardless of |
| | | /// its disposed status), or does nothing if it's not in the compound disposable. |
| | | /// |
| | | /// This is mainly useful for limiting the memory usage of the compound |
| | | /// disposable for long-running operations. |
| | | /// |
| | | /// This method is thread-safe. |
| | | /// |
| | | /// disposable - The disposable to remove. This argument may be nil (to make the |
| | | /// use of weak references easier). |
| | | - (void)removeDisposable:(RACDisposable *)disposable; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACCompoundDisposable.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/30/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACCompoundDisposableProvider.h" |
| | | #import <libkern/OSAtomic.h> |
| | | |
| | | // The number of child disposables for which space will be reserved directly in |
| | | // `RACCompoundDisposable`. |
| | | // |
| | | // This number has been empirically determined to provide a good tradeoff |
| | | // between performance, memory usage, and `RACCompoundDisposable` instance size |
| | | // in a moderately complex GUI application. |
| | | // |
| | | // Profile any change! |
| | | #define RACCompoundDisposableInlineCount 2 |
| | | |
| | | static CFMutableArrayRef RACCreateDisposablesArray(void) { |
| | | // Compare values using only pointer equality. |
| | | CFArrayCallBacks callbacks = kCFTypeArrayCallBacks; |
| | | callbacks.equal = NULL; |
| | | |
| | | return CFArrayCreateMutable(NULL, 0, &callbacks); |
| | | } |
| | | |
| | | @interface RACCompoundDisposable () { |
| | | // Used for synchronization. |
| | | OSSpinLock _spinLock; |
| | | |
| | | #if RACCompoundDisposableInlineCount |
| | | // A fast array to the first N of the receiver's disposables. |
| | | // |
| | | // Once this is full, `_disposables` will be created and used for additional |
| | | // disposables. |
| | | // |
| | | // This array should only be manipulated while _spinLock is held. |
| | | RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount]; |
| | | #endif |
| | | |
| | | // Contains the receiver's disposables. |
| | | // |
| | | // This array should only be manipulated while _spinLock is held. If |
| | | // `_disposed` is YES, this may be NULL. |
| | | CFMutableArrayRef _disposables; |
| | | |
| | | // Whether the receiver has already been disposed. |
| | | // |
| | | // This ivar should only be accessed while _spinLock is held. |
| | | BOOL _disposed; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACCompoundDisposable |
| | | |
| | | #pragma mark Properties |
| | | |
| | | - (BOOL)isDisposed { |
| | | OSSpinLockLock(&_spinLock); |
| | | BOOL disposed = _disposed; |
| | | OSSpinLockUnlock(&_spinLock); |
| | | |
| | | return disposed; |
| | | } |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)compoundDisposable { |
| | | return [[self alloc] initWithDisposables:nil]; |
| | | } |
| | | |
| | | + (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables { |
| | | return [[self alloc] initWithDisposables:disposables]; |
| | | } |
| | | |
| | | - (id)initWithDisposables:(NSArray *)otherDisposables { |
| | | self = [self init]; |
| | | if (self == nil) return nil; |
| | | |
| | | #if RACCompoundDisposableInlineCount |
| | | [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) { |
| | | _inlineDisposables[index] = disposable; |
| | | |
| | | // Stop after this iteration if we've reached the end of the inlined |
| | | // array. |
| | | if (index == RACCompoundDisposableInlineCount - 1) *stop = YES; |
| | | }]; |
| | | #endif |
| | | |
| | | if (otherDisposables.count > RACCompoundDisposableInlineCount) { |
| | | _disposables = RACCreateDisposablesArray(); |
| | | |
| | | CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount); |
| | | CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range); |
| | | } |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (id)initWithBlock:(void (^)(void))block { |
| | | RACDisposable *disposable = [RACDisposable disposableWithBlock:block]; |
| | | return [self initWithDisposables:@[ disposable ]]; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | #if RACCompoundDisposableInlineCount |
| | | for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { |
| | | _inlineDisposables[i] = nil; |
| | | } |
| | | #endif |
| | | |
| | | if (_disposables != NULL) { |
| | | CFRelease(_disposables); |
| | | _disposables = NULL; |
| | | } |
| | | } |
| | | |
| | | #pragma mark Addition and Removal |
| | | |
| | | - (void)addDisposable:(RACDisposable *)disposable { |
| | | NSCParameterAssert(disposable != self); |
| | | if (disposable == nil || disposable.disposed) return; |
| | | |
| | | BOOL shouldDispose = NO; |
| | | |
| | | OSSpinLockLock(&_spinLock); |
| | | { |
| | | if (_disposed) { |
| | | shouldDispose = YES; |
| | | } else { |
| | | #if RACCompoundDisposableInlineCount |
| | | for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { |
| | | if (_inlineDisposables[i] == nil) { |
| | | _inlineDisposables[i] = disposable; |
| | | goto foundSlot; |
| | | } |
| | | } |
| | | #endif |
| | | |
| | | if (_disposables == NULL) _disposables = RACCreateDisposablesArray(); |
| | | CFArrayAppendValue(_disposables, (__bridge void *)disposable); |
| | | |
| | | if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) { |
| | | RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); |
| | | } |
| | | |
| | | #if RACCompoundDisposableInlineCount |
| | | foundSlot:; |
| | | #endif |
| | | } |
| | | } |
| | | OSSpinLockUnlock(&_spinLock); |
| | | |
| | | // Performed outside of the lock in case the compound disposable is used |
| | | // recursively. |
| | | if (shouldDispose) [disposable dispose]; |
| | | } |
| | | |
| | | - (void)removeDisposable:(RACDisposable *)disposable { |
| | | if (disposable == nil) return; |
| | | |
| | | OSSpinLockLock(&_spinLock); |
| | | { |
| | | if (!_disposed) { |
| | | #if RACCompoundDisposableInlineCount |
| | | for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { |
| | | if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil; |
| | | } |
| | | #endif |
| | | |
| | | if (_disposables != NULL) { |
| | | CFIndex count = CFArrayGetCount(_disposables); |
| | | for (CFIndex i = count - 1; i >= 0; i--) { |
| | | const void *item = CFArrayGetValueAtIndex(_disposables, i); |
| | | if (item == (__bridge void *)disposable) { |
| | | CFArrayRemoveValueAtIndex(_disposables, i); |
| | | } |
| | | } |
| | | |
| | | if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) { |
| | | RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | OSSpinLockUnlock(&_spinLock); |
| | | } |
| | | |
| | | #pragma mark RACDisposable |
| | | |
| | | static void disposeEach(const void *value, void *context) { |
| | | RACDisposable *disposable = (__bridge id)value; |
| | | [disposable dispose]; |
| | | } |
| | | |
| | | - (void)dispose { |
| | | #if RACCompoundDisposableInlineCount |
| | | RACDisposable *inlineCopy[RACCompoundDisposableInlineCount]; |
| | | #endif |
| | | |
| | | CFArrayRef remainingDisposables = NULL; |
| | | |
| | | OSSpinLockLock(&_spinLock); |
| | | { |
| | | _disposed = YES; |
| | | |
| | | #if RACCompoundDisposableInlineCount |
| | | for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { |
| | | inlineCopy[i] = _inlineDisposables[i]; |
| | | _inlineDisposables[i] = nil; |
| | | } |
| | | #endif |
| | | |
| | | remainingDisposables = _disposables; |
| | | _disposables = NULL; |
| | | } |
| | | OSSpinLockUnlock(&_spinLock); |
| | | |
| | | #if RACCompoundDisposableInlineCount |
| | | // Dispose outside of the lock in case the compound disposable is used |
| | | // recursively. |
| | | for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { |
| | | [inlineCopy[i] dispose]; |
| | | } |
| | | #endif |
| | | |
| | | if (remainingDisposables == NULL) return; |
| | | |
| | | CFIndex count = CFArrayGetCount(remainingDisposables); |
| | | CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL); |
| | | CFRelease(remainingDisposables); |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | provider RACCompoundDisposable { |
| | | probe added(char *compoundDisposable, char *disposable, long newTotal); |
| | | probe removed(char *compoundDisposable, char *disposable, long newTotal); |
| | | }; |
New file |
| | |
| | | // |
| | | // RACDelegateProxy.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Cody Krieger on 5/19/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | // A private delegate object suitable for using |
| | | // -rac_signalForSelector:fromProtocol: upon. |
| | | @interface RACDelegateProxy : NSObject |
| | | |
| | | // The delegate to which messages should be forwarded if not handled by |
| | | // any -signalForSelector: applications. |
| | | @property (nonatomic, unsafe_unretained) id rac_proxiedDelegate; |
| | | |
| | | // Creates a delegate proxy capable of responding to selectors from `protocol`. |
| | | - (instancetype)initWithProtocol:(Protocol *)protocol; |
| | | |
| | | // Calls -rac_signalForSelector:fromProtocol: using the `protocol` specified |
| | | // during initialization. |
| | | - (RACSignal *)signalForSelector:(SEL)selector; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACDelegateProxy.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Cody Krieger on 5/19/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACDelegateProxy.h" |
| | | #import "NSObject+RACSelectorSignal.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @interface RACDelegateProxy () { |
| | | // Declared as an ivar to avoid method naming conflicts. |
| | | Protocol *_protocol; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACDelegateProxy |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (instancetype)initWithProtocol:(Protocol *)protocol { |
| | | NSCParameterAssert(protocol != NULL); |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | class_addProtocol(self.class, protocol); |
| | | |
| | | _protocol = protocol; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark API |
| | | |
| | | - (RACSignal *)signalForSelector:(SEL)selector { |
| | | return [self rac_signalForSelector:selector fromProtocol:_protocol]; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (BOOL)isProxy { |
| | | return YES; |
| | | } |
| | | |
| | | - (void)forwardInvocation:(NSInvocation *)invocation { |
| | | [invocation invokeWithTarget:self.rac_proxiedDelegate]; |
| | | } |
| | | |
| | | - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { |
| | | // Look for the selector as an optional instance method. |
| | | struct objc_method_description methodDescription = protocol_getMethodDescription(_protocol, selector, NO, YES); |
| | | |
| | | if (methodDescription.name == NULL) { |
| | | // Then fall back to looking for a required instance |
| | | // method. |
| | | methodDescription = protocol_getMethodDescription(_protocol, selector, YES, YES); |
| | | if (methodDescription.name == NULL) return [super methodSignatureForSelector:selector]; |
| | | } |
| | | |
| | | return [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; |
| | | } |
| | | |
| | | - (BOOL)respondsToSelector:(SEL)selector { |
| | | // Add the delegate to the autorelease pool, so it doesn't get deallocated |
| | | // between this method call and -forwardInvocation:. |
| | | __autoreleasing id delegate = self.rac_proxiedDelegate; |
| | | if ([delegate respondsToSelector:selector]) return YES; |
| | | |
| | | return [super respondsToSelector:selector]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACDisposable.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/16/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACScopedDisposable; |
| | | |
| | | /// A disposable encapsulates the work necessary to tear down and cleanup a |
| | | /// subscription. |
| | | @interface RACDisposable : NSObject |
| | | |
| | | /// Whether the receiver has been disposed. |
| | | /// |
| | | /// Use of this property is discouraged, since it may be set to `YES` |
| | | /// concurrently at any time. |
| | | /// |
| | | /// This property is not KVO-compliant. |
| | | @property (atomic, assign, getter = isDisposed, readonly) BOOL disposed; |
| | | |
| | | + (instancetype)disposableWithBlock:(void (^)(void))block; |
| | | |
| | | /// Performs the disposal work. Can be called multiple times, though subsequent |
| | | /// calls won't do anything. |
| | | - (void)dispose; |
| | | |
| | | /// Returns a new disposable which will dispose of this disposable when it gets |
| | | /// dealloc'd. |
| | | - (RACScopedDisposable *)asScopedDisposable; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACDisposable.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/16/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACDisposable.h" |
| | | #import "RACScopedDisposable.h" |
| | | #import <libkern/OSAtomic.h> |
| | | |
| | | @interface RACDisposable () { |
| | | // A copied block of type void (^)(void) containing the logic for disposal, |
| | | // a pointer to `self` if no logic should be performed upon disposal, or |
| | | // NULL if the receiver is already disposed. |
| | | // |
| | | // This should only be used atomically. |
| | | void * volatile _disposeBlock; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACDisposable |
| | | |
| | | #pragma mark Properties |
| | | |
| | | - (BOOL)isDisposed { |
| | | return _disposeBlock == NULL; |
| | | } |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)init { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _disposeBlock = (__bridge void *)self; |
| | | OSMemoryBarrier(); |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (id)initWithBlock:(void (^)(void))block { |
| | | NSCParameterAssert(block != nil); |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _disposeBlock = (void *)CFBridgingRetain([block copy]); |
| | | OSMemoryBarrier(); |
| | | |
| | | return self; |
| | | } |
| | | |
| | | + (instancetype)disposableWithBlock:(void (^)(void))block { |
| | | return [[self alloc] initWithBlock:block]; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return; |
| | | |
| | | CFRelease(_disposeBlock); |
| | | _disposeBlock = NULL; |
| | | } |
| | | |
| | | #pragma mark Disposal |
| | | |
| | | - (void)dispose { |
| | | void (^disposeBlock)(void) = NULL; |
| | | |
| | | while (YES) { |
| | | void *blockPtr = _disposeBlock; |
| | | if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) { |
| | | if (blockPtr != (__bridge void *)self) { |
| | | disposeBlock = CFBridgingRelease(blockPtr); |
| | | } |
| | | |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (disposeBlock != nil) disposeBlock(); |
| | | } |
| | | |
| | | #pragma mark Scoped Disposables |
| | | |
| | | - (RACScopedDisposable *)asScopedDisposable { |
| | | return [RACScopedDisposable scopedDisposableWithDisposable:self]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACDynamicSequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSequence.h" |
| | | |
| | | // Private class that implements a sequence dynamically using blocks. |
| | | @interface RACDynamicSequence : RACSequence |
| | | |
| | | // Returns a sequence which evaluates `dependencyBlock` only once, the first |
| | | // time either `headBlock` or `tailBlock` is evaluated. The result of |
| | | // `dependencyBlock` will be passed into `headBlock` and `tailBlock` when |
| | | // invoked. |
| | | + (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACDynamicSequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "RACDynamicSequence.h" |
| | | #import <libkern/OSAtomic.h> |
| | | |
| | | // Determines how RACDynamicSequences will be deallocated before the next one is |
| | | // shifted onto the autorelease pool. |
| | | // |
| | | // This avoids stack overflows when deallocating long chains of dynamic |
| | | // sequences. |
| | | #define DEALLOC_OVERFLOW_GUARD 100 |
| | | |
| | | @interface RACDynamicSequence () { |
| | | // The value for the "head" property, if it's been evaluated already. |
| | | // |
| | | // Because it's legal for head to be nil, this ivar is valid any time |
| | | // headBlock is nil. |
| | | // |
| | | // This ivar should only be accessed while synchronized on self. |
| | | id _head; |
| | | |
| | | // The value for the "tail" property, if it's been evaluated already. |
| | | // |
| | | // Because it's legal for tail to be nil, this ivar is valid any time |
| | | // tailBlock is nil. |
| | | // |
| | | // This ivar should only be accessed while synchronized on self. |
| | | RACSequence *_tail; |
| | | |
| | | // The result of an evaluated `dependencyBlock`. |
| | | // |
| | | // This ivar is valid any time `hasDependency` is YES and `dependencyBlock` |
| | | // is nil. |
| | | // |
| | | // This ivar should only be accessed while synchronized on self. |
| | | id _dependency; |
| | | } |
| | | |
| | | // A block used to evaluate head. This should be set to nil after `_head` has been |
| | | // initialized. |
| | | // |
| | | // This is marked `strong` instead of `copy` because of some bizarre block |
| | | // copying bug. See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/506. |
| | | // |
| | | // The signature of this block varies based on the value of `hasDependency`: |
| | | // |
| | | // - If YES, this block is of type `id (^)(id)`. |
| | | // - If NO, this block is of type `id (^)(void)`. |
| | | // |
| | | // This property should only be accessed while synchronized on self. |
| | | @property (nonatomic, strong) id headBlock; |
| | | |
| | | // A block used to evaluate tail. This should be set to nil after `_tail` has been |
| | | // initialized. |
| | | // |
| | | // This is marked `strong` instead of `copy` because of some bizarre block |
| | | // copying bug. See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/506. |
| | | // |
| | | // The signature of this block varies based on the value of `hasDependency`: |
| | | // |
| | | // - If YES, this block is of type `RACSequence * (^)(id)`. |
| | | // - If NO, this block is of type `RACSequence * (^)(void)`. |
| | | // |
| | | // This property should only be accessed while synchronized on self. |
| | | @property (nonatomic, strong) id tailBlock; |
| | | |
| | | // Whether the receiver was initialized with a `dependencyBlock`. |
| | | // |
| | | // This property should only be accessed while synchronized on self. |
| | | @property (nonatomic, assign) BOOL hasDependency; |
| | | |
| | | // A dependency which must be evaluated before `headBlock` and `tailBlock`. This |
| | | // should be set to nil after `_dependency` and `dependencyBlockExecuted` have |
| | | // been set. |
| | | // |
| | | // This is marked `strong` instead of `copy` because of some bizarre block |
| | | // copying bug. See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/506. |
| | | // |
| | | // This property should only be accessed while synchronized on self. |
| | | @property (nonatomic, strong) id (^dependencyBlock)(void); |
| | | |
| | | @end |
| | | |
| | | @implementation RACDynamicSequence |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock { |
| | | NSCParameterAssert(headBlock != nil); |
| | | |
| | | RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; |
| | | seq.headBlock = [headBlock copy]; |
| | | seq.tailBlock = [tailBlock copy]; |
| | | seq.hasDependency = NO; |
| | | return seq; |
| | | } |
| | | |
| | | + (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock { |
| | | NSCParameterAssert(dependencyBlock != nil); |
| | | NSCParameterAssert(headBlock != nil); |
| | | |
| | | RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; |
| | | seq.headBlock = [headBlock copy]; |
| | | seq.tailBlock = [tailBlock copy]; |
| | | seq.dependencyBlock = [dependencyBlock copy]; |
| | | seq.hasDependency = YES; |
| | | return seq; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | static volatile int32_t directDeallocCount = 0; |
| | | |
| | | if (OSAtomicIncrement32(&directDeallocCount) >= DEALLOC_OVERFLOW_GUARD) { |
| | | OSAtomicAdd32(-DEALLOC_OVERFLOW_GUARD, &directDeallocCount); |
| | | |
| | | // Put this sequence's tail onto the autorelease pool so we stop |
| | | // recursing. |
| | | __autoreleasing RACSequence *tail __attribute__((unused)) = _tail; |
| | | } |
| | | |
| | | _tail = nil; |
| | | } |
| | | |
| | | #pragma mark RACSequence |
| | | |
| | | - (id)head { |
| | | @synchronized (self) { |
| | | id untypedHeadBlock = self.headBlock; |
| | | if (untypedHeadBlock == nil) return _head; |
| | | |
| | | if (self.hasDependency) { |
| | | if (self.dependencyBlock != nil) { |
| | | _dependency = self.dependencyBlock(); |
| | | self.dependencyBlock = nil; |
| | | } |
| | | |
| | | id (^headBlock)(id) = untypedHeadBlock; |
| | | _head = headBlock(_dependency); |
| | | } else { |
| | | id (^headBlock)(void) = untypedHeadBlock; |
| | | _head = headBlock(); |
| | | } |
| | | |
| | | self.headBlock = nil; |
| | | return _head; |
| | | } |
| | | } |
| | | |
| | | - (RACSequence *)tail { |
| | | @synchronized (self) { |
| | | id untypedTailBlock = self.tailBlock; |
| | | if (untypedTailBlock == nil) return _tail; |
| | | |
| | | if (self.hasDependency) { |
| | | if (self.dependencyBlock != nil) { |
| | | _dependency = self.dependencyBlock(); |
| | | self.dependencyBlock = nil; |
| | | } |
| | | |
| | | RACSequence * (^tailBlock)(id) = untypedTailBlock; |
| | | _tail = tailBlock(_dependency); |
| | | } else { |
| | | RACSequence * (^tailBlock)(void) = untypedTailBlock; |
| | | _tail = tailBlock(); |
| | | } |
| | | |
| | | if (_tail.name == nil) _tail.name = self.name; |
| | | |
| | | self.tailBlock = nil; |
| | | return _tail; |
| | | } |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | id head = @"(unresolved)"; |
| | | id tail = @"(unresolved)"; |
| | | |
| | | @synchronized (self) { |
| | | if (self.headBlock == nil) head = _head; |
| | | if (self.tailBlock == nil) { |
| | | tail = _tail; |
| | | if (tail == self) tail = @"(self)"; |
| | | } |
| | | } |
| | | |
| | | return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@, tail = %@ }", self.class, self, self.name, head, tail]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACDynamicSignal.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-10. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSignal.h" |
| | | |
| | | // A private `RACSignal` subclasses that implements its subscription behavior |
| | | // using a block. |
| | | @interface RACDynamicSignal : RACSignal |
| | | |
| | | + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACDynamicSignal.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-10. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACDynamicSignal.h" |
| | | #import "RACEXTScope.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACPassthroughSubscriber.h" |
| | | #import "RACScheduler+Private.h" |
| | | #import "RACSubscriber.h" |
| | | #import <libkern/OSAtomic.h> |
| | | |
| | | @interface RACDynamicSignal () |
| | | |
| | | // The block to invoke for each subscriber. |
| | | @property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber); |
| | | |
| | | @end |
| | | |
| | | @implementation RACDynamicSignal |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { |
| | | RACDynamicSignal *signal = [[self alloc] init]; |
| | | signal->_didSubscribe = [didSubscribe copy]; |
| | | return [signal setNameWithFormat:@"+createSignal:"]; |
| | | } |
| | | |
| | | #pragma mark Managing Subscribers |
| | | |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { |
| | | NSCParameterAssert(subscriber != nil); |
| | | |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; |
| | | |
| | | if (self.didSubscribe != NULL) { |
| | | RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ |
| | | RACDisposable *innerDisposable = self.didSubscribe(subscriber); |
| | | [disposable addDisposable:innerDisposable]; |
| | | }]; |
| | | |
| | | [disposable addDisposable:schedulingDisposable]; |
| | | } |
| | | |
| | | return disposable; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACEagerSequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 02/01/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACArraySequence.h" |
| | | |
| | | // Private class that implements an eager sequence. |
| | | @interface RACEagerSequence : RACArraySequence |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACEagerSequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 02/01/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACEagerSequence.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "RACArraySequence.h" |
| | | |
| | | @implementation RACEagerSequence |
| | | |
| | | #pragma mark RACStream |
| | | |
| | | + (instancetype)return:(id)value { |
| | | return [[self sequenceWithArray:@[ value ] offset:0] setNameWithFormat:@"+return: %@", [value rac_description]]; |
| | | } |
| | | |
| | | - (instancetype)bind:(RACStreamBindBlock (^)(void))block { |
| | | NSCParameterAssert(block != nil); |
| | | RACStreamBindBlock bindBlock = block(); |
| | | NSArray *currentArray = self.array; |
| | | NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:currentArray.count]; |
| | | |
| | | for (id value in currentArray) { |
| | | BOOL stop = NO; |
| | | RACSequence *boundValue = (id)bindBlock(value, &stop); |
| | | if (boundValue == nil) break; |
| | | |
| | | for (id x in boundValue) { |
| | | [resultArray addObject:x]; |
| | | } |
| | | |
| | | if (stop) break; |
| | | } |
| | | |
| | | return [[self.class sequenceWithArray:resultArray offset:0] setNameWithFormat:@"[%@] -bind:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)concat:(RACSequence *)sequence { |
| | | NSCParameterAssert(sequence != nil); |
| | | NSCParameterAssert([sequence isKindOfClass:RACSequence.class]); |
| | | |
| | | NSArray *array = [self.array arrayByAddingObjectsFromArray:sequence.array]; |
| | | return [[self.class sequenceWithArray:array offset:0] setNameWithFormat:@"[%@] -concat: %@", self.name, sequence]; |
| | | } |
| | | |
| | | #pragma mark Extended methods |
| | | |
| | | - (RACSequence *)eagerSequence { |
| | | return self; |
| | | } |
| | | |
| | | - (RACSequence *)lazySequence { |
| | | return [RACArraySequence sequenceWithArray:self.array offset:0]; |
| | | } |
| | | |
| | | - (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *rest))reduce { |
| | | return [super foldRightWithStart:start reduce:^(id first, RACSequence *rest) { |
| | | return reduce(first, rest.eagerSequence); |
| | | }]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACEmptySequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSequence.h" |
| | | |
| | | // Private class representing an empty sequence. |
| | | @interface RACEmptySequence : RACSequence |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACEmptySequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "RACEmptySequence.h" |
| | | |
| | | @implementation RACEmptySequence |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)empty { |
| | | static id singleton; |
| | | static dispatch_once_t pred; |
| | | |
| | | dispatch_once(&pred, ^{ |
| | | singleton = [[self alloc] init]; |
| | | }); |
| | | |
| | | return singleton; |
| | | } |
| | | |
| | | #pragma mark RACSequence |
| | | |
| | | - (id)head { |
| | | return nil; |
| | | } |
| | | |
| | | - (RACSequence *)tail { |
| | | return nil; |
| | | } |
| | | |
| | | - (RACSequence *)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence { |
| | | return passthroughSequence ?: self; |
| | | } |
| | | |
| | | #pragma mark NSCoding |
| | | |
| | | - (Class)classForCoder { |
| | | // Empty sequences should be encoded as themselves, not array sequences. |
| | | return self.class; |
| | | } |
| | | |
| | | - (id)initWithCoder:(NSCoder *)coder { |
| | | // Return the singleton. |
| | | return self.class.empty; |
| | | } |
| | | |
| | | - (void)encodeWithCoder:(NSCoder *)coder { |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | return [NSString stringWithFormat:@"<%@: %p>{ name = %@ }", self.class, self, self.name]; |
| | | } |
| | | |
| | | - (NSUInteger)hash { |
| | | // This hash isn't ideal, but it's better than -[RACSequence hash], which |
| | | // would just be zero because we have no head. |
| | | return (NSUInteger)(__bridge void *)self; |
| | | } |
| | | |
| | | - (BOOL)isEqual:(RACSequence *)seq { |
| | | return (self == seq); |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACEmptySignal.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-10. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSignal.h" |
| | | |
| | | // A private `RACSignal` subclasses that synchronously sends completed to any |
| | | // subscribers. |
| | | @interface RACEmptySignal : RACSignal |
| | | |
| | | + (RACSignal *)empty; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACEmptySignal.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-10. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACEmptySignal.h" |
| | | #import "RACScheduler+Private.h" |
| | | #import "RACSubscriber.h" |
| | | |
| | | @implementation RACEmptySignal |
| | | |
| | | #pragma mark Properties |
| | | |
| | | // Only allow this signal's name to be customized in DEBUG, since it's |
| | | // a singleton in release builds (see +empty). |
| | | - (void)setName:(NSString *)name { |
| | | #ifdef DEBUG |
| | | [super setName:name]; |
| | | #endif |
| | | } |
| | | |
| | | - (NSString *)name { |
| | | #ifdef DEBUG |
| | | return super.name; |
| | | #else |
| | | return @"+empty"; |
| | | #endif |
| | | } |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (RACSignal *)empty { |
| | | #ifdef DEBUG |
| | | // Create multiple instances of this class in DEBUG so users can set custom |
| | | // names on each. |
| | | return [[[self alloc] init] setNameWithFormat:@"+empty"]; |
| | | #else |
| | | static id singleton; |
| | | static dispatch_once_t pred; |
| | | |
| | | dispatch_once(&pred, ^{ |
| | | singleton = [[self alloc] init]; |
| | | }); |
| | | |
| | | return singleton; |
| | | #endif |
| | | } |
| | | |
| | | #pragma mark Subscription |
| | | |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { |
| | | NSCParameterAssert(subscriber != nil); |
| | | |
| | | return [RACScheduler.subscriptionScheduler schedule:^{ |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACErrorSignal.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-10. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSignal.h" |
| | | |
| | | // A private `RACSignal` subclasses that synchronously sends an error to any |
| | | // subscribers. |
| | | @interface RACErrorSignal : RACSignal |
| | | |
| | | + (RACSignal *)error:(NSError *)error; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACErrorSignal.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-10. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACErrorSignal.h" |
| | | #import "RACScheduler+Private.h" |
| | | #import "RACSubscriber.h" |
| | | |
| | | @interface RACErrorSignal () |
| | | |
| | | // The error to send upon subscription. |
| | | @property (nonatomic, strong, readonly) NSError *error; |
| | | |
| | | @end |
| | | |
| | | @implementation RACErrorSignal |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (RACSignal *)error:(NSError *)error { |
| | | RACErrorSignal *signal = [[self alloc] init]; |
| | | signal->_error = error; |
| | | |
| | | #ifdef DEBUG |
| | | [signal setNameWithFormat:@"+error: %@", error]; |
| | | #else |
| | | signal.name = @"+error:"; |
| | | #endif |
| | | |
| | | return signal; |
| | | } |
| | | |
| | | #pragma mark Subscription |
| | | |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { |
| | | NSCParameterAssert(subscriber != nil); |
| | | |
| | | return [RACScheduler.subscriptionScheduler schedule:^{ |
| | | [subscriber sendError:self.error]; |
| | | }]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACEvent.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-01-07. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | /// Describes the type of a RACEvent. |
| | | /// |
| | | /// RACEventTypeCompleted - A `completed` event. |
| | | /// RACEventTypeError - An `error` event. |
| | | /// RACEventTypeNext - A `next` event. |
| | | typedef enum : NSUInteger { |
| | | RACEventTypeCompleted, |
| | | RACEventTypeError, |
| | | RACEventTypeNext |
| | | } RACEventType; |
| | | |
| | | /// Represents an event sent by a RACSignal. |
| | | /// |
| | | /// This corresponds to the `Notification` class in Rx. |
| | | @interface RACEvent : NSObject <NSCopying> |
| | | |
| | | /// Returns a singleton RACEvent representing the `completed` event. |
| | | + (instancetype)completedEvent; |
| | | |
| | | /// Returns a new event of type RACEventTypeError, containing the given error. |
| | | + (instancetype)eventWithError:(NSError *)error; |
| | | |
| | | /// Returns a new event of type RACEventTypeNext, containing the given value. |
| | | + (instancetype)eventWithValue:(id)value; |
| | | |
| | | /// The type of event represented by the receiver. |
| | | @property (nonatomic, assign, readonly) RACEventType eventType; |
| | | |
| | | /// Returns whether the receiver is of type RACEventTypeCompleted or |
| | | /// RACEventTypeError. |
| | | @property (nonatomic, getter = isFinished, assign, readonly) BOOL finished; |
| | | |
| | | /// The error associated with an event of type RACEventTypeError. This will be |
| | | /// nil for all other event types. |
| | | @property (nonatomic, strong, readonly) NSError *error; |
| | | |
| | | /// The value associated with an event of type RACEventTypeNext. This will be |
| | | /// nil for all other event types. |
| | | @property (nonatomic, strong, readonly) id value; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACEvent.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-01-07. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACEvent.h" |
| | | |
| | | @interface RACEvent () |
| | | |
| | | // An object associated with this event. This will be used for the error and |
| | | // value properties. |
| | | @property (nonatomic, strong, readonly) id object; |
| | | |
| | | // Initializes the receiver with the given type and object. |
| | | - (id)initWithEventType:(RACEventType)type object:(id)object; |
| | | |
| | | @end |
| | | |
| | | @implementation RACEvent |
| | | |
| | | #pragma mark Properties |
| | | |
| | | - (BOOL)isFinished { |
| | | return self.eventType == RACEventTypeCompleted || self.eventType == RACEventTypeError; |
| | | } |
| | | |
| | | - (NSError *)error { |
| | | return (self.eventType == RACEventTypeError ? self.object : nil); |
| | | } |
| | | |
| | | - (id)value { |
| | | return (self.eventType == RACEventTypeNext ? self.object : nil); |
| | | } |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)completedEvent { |
| | | static dispatch_once_t pred; |
| | | static id singleton; |
| | | |
| | | dispatch_once(&pred, ^{ |
| | | singleton = [[self alloc] initWithEventType:RACEventTypeCompleted object:nil]; |
| | | }); |
| | | |
| | | return singleton; |
| | | } |
| | | |
| | | + (instancetype)eventWithError:(NSError *)error { |
| | | return [[self alloc] initWithEventType:RACEventTypeError object:error]; |
| | | } |
| | | |
| | | + (instancetype)eventWithValue:(id)value { |
| | | return [[self alloc] initWithEventType:RACEventTypeNext object:value]; |
| | | } |
| | | |
| | | - (id)initWithEventType:(RACEventType)type object:(id)object { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _eventType = type; |
| | | _object = object; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark NSCopying |
| | | |
| | | - (id)copyWithZone:(NSZone *)zone { |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | NSString *eventDescription = nil; |
| | | |
| | | switch (self.eventType) { |
| | | case RACEventTypeCompleted: |
| | | eventDescription = @"completed"; |
| | | break; |
| | | |
| | | case RACEventTypeError: |
| | | eventDescription = [NSString stringWithFormat:@"error = %@", self.object]; |
| | | break; |
| | | |
| | | case RACEventTypeNext: |
| | | eventDescription = [NSString stringWithFormat:@"next = %@", self.object]; |
| | | break; |
| | | |
| | | default: |
| | | NSCAssert(NO, @"Unrecognized event type: %i", (int)self.eventType); |
| | | } |
| | | |
| | | return [NSString stringWithFormat:@"<%@: %p>{ %@ }", self.class, self, eventDescription]; |
| | | } |
| | | |
| | | - (NSUInteger)hash { |
| | | return self.eventType ^ [self.object hash]; |
| | | } |
| | | |
| | | - (BOOL)isEqual:(id)event { |
| | | if (event == self) return YES; |
| | | if (![event isKindOfClass:RACEvent.class]) return NO; |
| | | if (self.eventType != [event eventType]) return NO; |
| | | |
| | | // Catches the nil case too. |
| | | return self.object == [event object] || [self.object isEqual:[event object]]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACGroupedSignal.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/2/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSubject.h" |
| | | |
| | | /// A grouped signal is used by -[RACSignal groupBy:transform:]. |
| | | @interface RACGroupedSignal : RACSubject |
| | | |
| | | /// The key shared by the group. |
| | | @property (nonatomic, readonly, copy) id<NSCopying> key; |
| | | |
| | | + (instancetype)signalWithKey:(id<NSCopying>)key; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACGroupedSignal.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 5/2/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACGroupedSignal.h" |
| | | |
| | | @interface RACGroupedSignal () |
| | | @property (nonatomic, copy) id<NSCopying> key; |
| | | @end |
| | | |
| | | @implementation RACGroupedSignal |
| | | |
| | | #pragma mark API |
| | | |
| | | + (instancetype)signalWithKey:(id<NSCopying>)key { |
| | | RACGroupedSignal *subject = [self subject]; |
| | | subject.key = key; |
| | | return subject; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACImmediateScheduler.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/30/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACScheduler.h" |
| | | |
| | | // A private scheduler which immediately executes its scheduled blocks. |
| | | @interface RACImmediateScheduler : RACScheduler |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACImmediateScheduler.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/30/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACImmediateScheduler.h" |
| | | #import "RACScheduler+Private.h" |
| | | |
| | | @implementation RACImmediateScheduler |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)init { |
| | | return [super initWithName:@"com.ReactiveCocoa.RACScheduler.immediateScheduler"]; |
| | | } |
| | | |
| | | #pragma mark RACScheduler |
| | | |
| | | - (RACDisposable *)schedule:(void (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | block(); |
| | | return nil; |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { |
| | | NSCParameterAssert(date != nil); |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | [NSThread sleepUntilDate:date]; |
| | | block(); |
| | | |
| | | return nil; |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { |
| | | NSCAssert(NO, @"+[RACScheduler immediateScheduler] does not support %@.", NSStringFromSelector(_cmd)); |
| | | return nil; |
| | | } |
| | | |
| | | - (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock { |
| | | for (__block NSUInteger remaining = 1; remaining > 0; remaining--) { |
| | | recursiveBlock(^{ |
| | | remaining++; |
| | | }); |
| | | } |
| | | |
| | | return nil; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACIndexSetSequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Sergey Gavrilyuk on 12/18/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSequence.h" |
| | | |
| | | // Private class that adapts an array to the RACSequence interface. |
| | | @interface RACIndexSetSequence : RACSequence |
| | | |
| | | + (instancetype)sequenceWithIndexSet:(NSIndexSet *)indexSet; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACIndexSetSequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Sergey Gavrilyuk on 12/18/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACIndexSetSequence.h" |
| | | |
| | | @interface RACIndexSetSequence () |
| | | |
| | | // A buffer holding the `NSUInteger` values to enumerate over. |
| | | // |
| | | // This is mostly used for memory management. Most access should go through |
| | | // `indexes` instead. |
| | | @property (nonatomic, strong, readonly) NSData *data; |
| | | |
| | | // The indexes that this sequence should enumerate. |
| | | @property (nonatomic, readonly) const NSUInteger *indexes; |
| | | |
| | | // The number of indexes to enumerate. |
| | | @property (nonatomic, readonly) NSUInteger count; |
| | | |
| | | @end |
| | | |
| | | @implementation RACIndexSetSequence |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)sequenceWithIndexSet:(NSIndexSet *)indexSet { |
| | | NSUInteger count = indexSet.count; |
| | | |
| | | if (count == 0) return self.empty; |
| | | |
| | | NSUInteger sizeInBytes = sizeof(NSUInteger) * count; |
| | | |
| | | NSMutableData *data = [[NSMutableData alloc] initWithCapacity:sizeInBytes]; |
| | | [indexSet getIndexes:data.mutableBytes maxCount:count inIndexRange:NULL]; |
| | | |
| | | RACIndexSetSequence *seq = [[self alloc] init]; |
| | | seq->_data = data; |
| | | seq->_indexes = data.bytes; |
| | | seq->_count = count; |
| | | return seq; |
| | | } |
| | | |
| | | + (instancetype)sequenceWithIndexSetSequence:(RACIndexSetSequence *)indexSetSequence offset:(NSUInteger)offset { |
| | | NSCParameterAssert(offset < indexSetSequence.count); |
| | | |
| | | RACIndexSetSequence *seq = [[self alloc] init]; |
| | | seq->_data = indexSetSequence.data; |
| | | seq->_indexes = indexSetSequence.indexes + offset; |
| | | seq->_count = indexSetSequence.count - offset; |
| | | return seq; |
| | | } |
| | | |
| | | #pragma mark RACSequence |
| | | |
| | | - (id)head { |
| | | return @(self.indexes[0]); |
| | | } |
| | | |
| | | - (RACSequence *)tail { |
| | | if (self.count <= 1) return [RACSequence empty]; |
| | | |
| | | return [self.class sequenceWithIndexSetSequence:self offset:1]; |
| | | } |
| | | |
| | | #pragma mark NSFastEnumeration |
| | | |
| | | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id[])stackbuf count:(NSUInteger)len { |
| | | NSCParameterAssert(len > 0); |
| | | |
| | | if (state->state >= self.count) { |
| | | // Enumeration has completed. |
| | | return 0; |
| | | } |
| | | |
| | | if (state->state == 0) { |
| | | // Enumeration begun, mark the mutation flag. |
| | | state->mutationsPtr = state->extra; |
| | | } |
| | | |
| | | state->itemsPtr = stackbuf; |
| | | |
| | | unsigned long index = 0; |
| | | while (index < MIN(self.count - state->state, len)) { |
| | | stackbuf[index] = @(self.indexes[index + state->state]); |
| | | ++index; |
| | | } |
| | | |
| | | state->state += index; |
| | | return index; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | NSMutableString *indexesStr = [NSMutableString string]; |
| | | |
| | | for (unsigned int i = 0; i < self.count; ++i) { |
| | | if (i > 0) [indexesStr appendString:@", "]; |
| | | |
| | | [indexesStr appendFormat:@"%lu", (unsigned long)self.indexes[i]]; |
| | | } |
| | | |
| | | return [NSString stringWithFormat:@"<%@: %p>{ name = %@, indexes = %@ }", self.class, self, self.name, indexesStr]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACKVOChannel.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 27/12/2012. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACChannel.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "RACmetamacros.h" |
| | | |
| | | /// Creates a RACKVOChannel to the given key path. When the targeted object |
| | | /// deallocates, the channel will complete. |
| | | /// |
| | | /// If RACChannelTo() is used as an expression, it returns a RACChannelTerminal that |
| | | /// can be used to watch the specified property for changes, and set new values |
| | | /// for it. The terminal will start with the property's current value upon |
| | | /// subscription. |
| | | /// |
| | | /// If RACChannelTo() is used on the left-hand side of an assignment, there must a |
| | | /// RACChannelTerminal on the right-hand side of the assignment. The two will be |
| | | /// subscribed to one another: the property's value is immediately set to the |
| | | /// value of the channel terminal on the right-hand side, and subsequent changes |
| | | /// to either terminal will be reflected on the other. |
| | | /// |
| | | /// There are two different versions of this macro: |
| | | /// |
| | | /// - RACChannelTo(TARGET, KEYPATH, NILVALUE) will create a channel to the `KEYPATH` |
| | | /// of `TARGET`. If the terminal is ever sent a `nil` value, the property will |
| | | /// be set to `NILVALUE` instead. `NILVALUE` may itself be `nil` for object |
| | | /// properties, but an NSValue should be used for primitive properties, to |
| | | /// avoid an exception if `nil` is sent (which might occur if an intermediate |
| | | /// object is set to `nil`). |
| | | /// - RACChannelTo(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to |
| | | /// `nil`. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// RACChannelTerminal *integerChannel = RACChannelTo(self, integerProperty, @42); |
| | | /// |
| | | /// // Sets self.integerProperty to 5. |
| | | /// [integerChannel sendNext:@5]; |
| | | /// |
| | | /// // Logs the current value of self.integerProperty, and all future changes. |
| | | /// [integerChannel subscribeNext:^(id value) { |
| | | /// NSLog(@"value: %@", value); |
| | | /// }]; |
| | | /// |
| | | /// // Binds properties to each other, taking the initial value from the right |
| | | /// side. |
| | | /// RACChannelTo(view, objectProperty) = RACChannelTo(model, objectProperty); |
| | | /// RACChannelTo(view, integerProperty, @2) = RACChannelTo(model, integerProperty, @10); |
| | | #define RACChannelTo(TARGET, ...) \ |
| | | metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ |
| | | (RACChannelTo_(TARGET, __VA_ARGS__, nil)) \ |
| | | (RACChannelTo_(TARGET, __VA_ARGS__)) |
| | | |
| | | /// Do not use this directly. Use the RACChannelTo macro above. |
| | | #define RACChannelTo_(TARGET, KEYPATH, NILVALUE) \ |
| | | [[RACKVOChannel alloc] initWithTarget:(TARGET) keyPath:@keypath(TARGET, KEYPATH) nilValue:(NILVALUE)][@keypath(RACKVOChannel.new, followingTerminal)] |
| | | |
| | | /// A RACChannel that observes a KVO-compliant key path for changes. |
| | | @interface RACKVOChannel : RACChannel |
| | | |
| | | /// Initializes a channel that will observe the given object and key path. |
| | | /// |
| | | /// The current value of the key path, and future KVO notifications for the given |
| | | /// key path, will be sent to subscribers of the channel's `followingTerminal`. |
| | | /// Values sent to the `followingTerminal` will be set at the given key path using |
| | | /// key-value coding. |
| | | /// |
| | | /// When the target object deallocates, the channel will complete. Signal errors |
| | | /// are considered undefined behavior. |
| | | /// |
| | | /// This is the designated initializer for this class. |
| | | /// |
| | | /// target - The object to bind to. |
| | | /// keyPath - The key path to observe and set the value of. |
| | | /// nilValue - The value to set at the key path whenever a `nil` value is |
| | | /// received. This may be nil when connecting to object properties, but |
| | | /// an NSValue should be used for primitive properties, to avoid an |
| | | /// exception if `nil` is received (which might occur if an intermediate |
| | | /// object is set to `nil`). |
| | | - (id)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue; |
| | | |
| | | - (id)init __attribute__((unavailable("Use -initWithTarget:keyPath:nilValue: instead"))); |
| | | |
| | | @end |
| | | |
| | | /// Methods needed for the convenience macro. Do not call explicitly. |
| | | @interface RACKVOChannel (RACChannelTo) |
| | | |
| | | - (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key; |
| | | - (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACKVOChannel.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 27/12/2012. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACKVOChannel.h" |
| | | #import "RACEXTScope.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACKVOWrapper.h" |
| | | #import "NSString+RACKeyPathUtilities.h" |
| | | #import "RACChannel.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACSignal+Operations.h" |
| | | |
| | | // Key for the array of RACKVOChannel's additional thread local |
| | | // data in the thread dictionary. |
| | | static NSString * const RACKVOChannelDataDictionaryKey = @"RACKVOChannelKey"; |
| | | |
| | | // Wrapper class for additional thread local data. |
| | | @interface RACKVOChannelData : NSObject |
| | | |
| | | // The flag used to ignore updates the channel itself has triggered. |
| | | @property (nonatomic, assign) BOOL ignoreNextUpdate; |
| | | |
| | | // A pointer to the owner of the data. Only use this for pointer comparison, |
| | | // never as an object reference. |
| | | @property (nonatomic, assign) void *owner; |
| | | |
| | | + (instancetype)dataForChannel:(RACKVOChannel *)channel; |
| | | |
| | | @end |
| | | |
| | | @interface RACKVOChannel () |
| | | |
| | | // The object whose key path the channel is wrapping. |
| | | @property (atomic, weak) NSObject *target; |
| | | |
| | | // The key path the channel is wrapping. |
| | | @property (nonatomic, copy, readonly) NSString *keyPath; |
| | | |
| | | // Returns the existing thread local data container or nil if none exists. |
| | | @property (nonatomic, strong, readonly) RACKVOChannelData *currentThreadData; |
| | | |
| | | // Creates the thread local data container for the channel. |
| | | - (void)createCurrentThreadData; |
| | | |
| | | // Destroy the thread local data container for the channel. |
| | | - (void)destroyCurrentThreadData; |
| | | |
| | | @end |
| | | |
| | | @implementation RACKVOChannel |
| | | |
| | | #pragma mark Properties |
| | | |
| | | - (RACKVOChannelData *)currentThreadData { |
| | | NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; |
| | | |
| | | for (RACKVOChannelData *data in dataArray) { |
| | | if (data.owner == (__bridge void *)self) return data; |
| | | } |
| | | |
| | | return nil; |
| | | } |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue { |
| | | NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); |
| | | |
| | | NSObject *strongTarget = target; |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _target = target; |
| | | _keyPath = [keyPath copy]; |
| | | |
| | | [self.leadingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -leadingTerminal", target, keyPath, nilValue]; |
| | | [self.followingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -followingTerminal", target, keyPath, nilValue]; |
| | | |
| | | if (strongTarget == nil) { |
| | | [self.leadingTerminal sendCompleted]; |
| | | return self; |
| | | } |
| | | |
| | | // Observe the key path on target for changes and forward the changes to the |
| | | // terminal. |
| | | // |
| | | // Intentionally capturing `self` strongly in the blocks below, so the |
| | | // channel object stays alive while observing. |
| | | RACDisposable *observationDisposable = [strongTarget rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { |
| | | // If the change wasn't triggered by deallocation, only affects the last |
| | | // path component, and ignoreNextUpdate is set, then it was triggered by |
| | | // this channel and should not be forwarded. |
| | | if (!causedByDealloc && affectedOnlyLastComponent && self.currentThreadData.ignoreNextUpdate) { |
| | | [self destroyCurrentThreadData]; |
| | | return; |
| | | } |
| | | |
| | | [self.leadingTerminal sendNext:value]; |
| | | }]; |
| | | |
| | | NSString *keyPathByDeletingLastKeyPathComponent = keyPath.rac_keyPathByDeletingLastKeyPathComponent; |
| | | NSArray *keyPathComponents = keyPath.rac_keyPathComponents; |
| | | NSUInteger keyPathComponentsCount = keyPathComponents.count; |
| | | NSString *lastKeyPathComponent = keyPathComponents.lastObject; |
| | | |
| | | // Update the value of the property with the values received. |
| | | [[self.leadingTerminal |
| | | finally:^{ |
| | | [observationDisposable dispose]; |
| | | }] |
| | | subscribeNext:^(id x) { |
| | | // Check the value of the second to last key path component. Since the |
| | | // channel can only update the value of a property on an object, and not |
| | | // update intermediate objects, it can only update the value of the whole |
| | | // key path if this object is not nil. |
| | | NSObject *object = (keyPathComponentsCount > 1 ? [self.target valueForKeyPath:keyPathByDeletingLastKeyPathComponent] : self.target); |
| | | if (object == nil) return; |
| | | |
| | | // Set the ignoreNextUpdate flag before setting the value so this channel |
| | | // ignores the value in the subsequent -didChangeValueForKey: callback. |
| | | [self createCurrentThreadData]; |
| | | self.currentThreadData.ignoreNextUpdate = YES; |
| | | |
| | | [object setValue:x ?: nilValue forKey:lastKeyPathComponent]; |
| | | } error:^(NSError *error) { |
| | | NSCAssert(NO, @"Received error in %@: %@", self, error); |
| | | |
| | | // Log the error if we're running with assertions disabled. |
| | | NSLog(@"Received error in %@: %@", self, error); |
| | | }]; |
| | | |
| | | // Capture `self` weakly for the target's deallocation disposable, so we can |
| | | // freely deallocate if we complete before then. |
| | | @weakify(self); |
| | | |
| | | [strongTarget.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| | | @strongify(self); |
| | | [self.leadingTerminal sendCompleted]; |
| | | self.target = nil; |
| | | }]]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (void)createCurrentThreadData { |
| | | NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; |
| | | if (dataArray == nil) { |
| | | dataArray = [NSMutableArray array]; |
| | | NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey] = dataArray; |
| | | [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; |
| | | return; |
| | | } |
| | | |
| | | for (RACKVOChannelData *data in dataArray) { |
| | | if (data.owner == (__bridge void *)self) return; |
| | | } |
| | | |
| | | [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; |
| | | } |
| | | |
| | | - (void)destroyCurrentThreadData { |
| | | NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; |
| | | NSUInteger index = [dataArray indexOfObjectPassingTest:^ BOOL (RACKVOChannelData *data, NSUInteger idx, BOOL *stop) { |
| | | return data.owner == (__bridge void *)self; |
| | | }]; |
| | | |
| | | if (index != NSNotFound) [dataArray removeObjectAtIndex:index]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACKVOChannel (RACChannelTo) |
| | | |
| | | - (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key { |
| | | NSCParameterAssert(key != nil); |
| | | |
| | | RACChannelTerminal *terminal = [self valueForKey:key]; |
| | | NSCAssert([terminal isKindOfClass:RACChannelTerminal.class], @"Key \"%@\" does not identify a channel terminal", key); |
| | | |
| | | return terminal; |
| | | } |
| | | |
| | | - (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key { |
| | | NSCParameterAssert(otherTerminal != nil); |
| | | |
| | | RACChannelTerminal *selfTerminal = [self objectForKeyedSubscript:key]; |
| | | [otherTerminal subscribe:selfTerminal]; |
| | | [[selfTerminal skip:1] subscribe:otherTerminal]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACKVOChannelData |
| | | |
| | | + (instancetype)dataForChannel:(RACKVOChannel *)channel { |
| | | RACKVOChannelData *data = [[self alloc] init]; |
| | | data->_owner = (__bridge void *)channel; |
| | | return data; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACKVOProxy.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Richard Speyer on 4/10/14. |
| | | // Copyright (c) 2014 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | /// A singleton that can act as a proxy between a KVO observation and a RAC |
| | | /// subscriber, in order to protect against KVO lifetime issues. |
| | | @interface RACKVOProxy : NSObject |
| | | |
| | | /// Returns the singleton KVO proxy object. |
| | | + (instancetype)sharedProxy; |
| | | |
| | | /// Registers an observer with the proxy, such that when the proxy receives a |
| | | /// KVO change with the given context, it forwards it to the observer. |
| | | /// |
| | | /// observer - True observer of the KVO change. Must not be nil. |
| | | /// context - Arbitrary context object used to differentiate multiple |
| | | /// observations of the same keypath. Must be unique, cannot be nil. |
| | | - (void)addObserver:(__weak NSObject *)observer forContext:(void *)context; |
| | | |
| | | /// Removes an observer from the proxy. Parameters must match those passed to |
| | | /// addObserver:forContext:. |
| | | /// |
| | | /// observer - True observer of the KVO change. Must not be nil. |
| | | /// context - Arbitrary context object used to differentiate multiple |
| | | /// observations of the same keypath. Must be unique, cannot be nil. |
| | | - (void)removeObserver:(NSObject *)observer forContext:(void *)context; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACKVOProxy.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Richard Speyer on 4/10/14. |
| | | // Copyright (c) 2014 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACKVOProxy.h" |
| | | |
| | | @interface RACKVOProxy() |
| | | |
| | | @property (strong, nonatomic, readonly) NSMapTable *trampolines; |
| | | @property (strong, nonatomic, readonly) dispatch_queue_t queue; |
| | | |
| | | @end |
| | | |
| | | @implementation RACKVOProxy |
| | | |
| | | + (instancetype)sharedProxy { |
| | | static RACKVOProxy *proxy; |
| | | static dispatch_once_t onceToken; |
| | | |
| | | dispatch_once(&onceToken, ^{ |
| | | proxy = [[self alloc] init]; |
| | | }); |
| | | |
| | | return proxy; |
| | | } |
| | | |
| | | - (instancetype)init { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _queue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.RACKVOProxy", DISPATCH_QUEUE_SERIAL); |
| | | _trampolines = [NSMapTable strongToWeakObjectsMapTable]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (void)addObserver:(__weak NSObject *)observer forContext:(void *)context { |
| | | NSValue *valueContext = [NSValue valueWithPointer:context]; |
| | | |
| | | dispatch_sync(self.queue, ^{ |
| | | [self.trampolines setObject:observer forKey:valueContext]; |
| | | }); |
| | | } |
| | | |
| | | - (void)removeObserver:(NSObject *)observer forContext:(void *)context { |
| | | NSValue *valueContext = [NSValue valueWithPointer:context]; |
| | | |
| | | dispatch_sync(self.queue, ^{ |
| | | [self.trampolines removeObjectForKey:valueContext]; |
| | | }); |
| | | } |
| | | |
| | | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { |
| | | NSValue *valueContext = [NSValue valueWithPointer:context]; |
| | | __block NSObject *trueObserver; |
| | | |
| | | dispatch_sync(self.queue, ^{ |
| | | trueObserver = [self.trampolines objectForKey:valueContext]; |
| | | }); |
| | | |
| | | if (trueObserver != nil) { |
| | | [trueObserver observeValueForKeyPath:keyPath ofObject:object change:change context:context]; |
| | | } |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACKVOTrampoline.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 1/15/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "NSObject+RACKVOWrapper.h" |
| | | #import "RACDisposable.h" |
| | | |
| | | // A private trampoline object that represents a KVO observation. |
| | | // |
| | | // Disposing of the trampoline will stop observation. |
| | | @interface RACKVOTrampoline : RACDisposable |
| | | |
| | | // Initializes the receiver with the given parameters. |
| | | // |
| | | // target - The object whose key path should be observed. Cannot be nil. |
| | | // observer - The object that gets notified when the value at the key path |
| | | // changes. Can be nil. |
| | | // keyPath - The key path on `target` to observe. Cannot be nil. |
| | | // options - Any key value observing options to use in the observation. |
| | | // block - The block to call when the value at the observed key path changes. |
| | | // Cannot be nil. |
| | | // |
| | | // Returns the initialized object. |
| | | - (id)initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACKVOTrampoline.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 1/15/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACKVOTrampoline.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACKVOProxy.h" |
| | | |
| | | @interface RACKVOTrampoline () |
| | | |
| | | // The keypath which the trampoline is observing. |
| | | @property (nonatomic, readonly, copy) NSString *keyPath; |
| | | |
| | | // These properties should only be manipulated while synchronized on the |
| | | // receiver. |
| | | @property (nonatomic, readonly, copy) RACKVOBlock block; |
| | | @property (nonatomic, readonly, unsafe_unretained) NSObject *unsafeTarget; |
| | | @property (nonatomic, readonly, weak) NSObject *weakTarget; |
| | | @property (nonatomic, readonly, weak) NSObject *observer; |
| | | |
| | | @end |
| | | |
| | | @implementation RACKVOTrampoline |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block { |
| | | NSCParameterAssert(keyPath != nil); |
| | | NSCParameterAssert(block != nil); |
| | | |
| | | NSObject *strongTarget = target; |
| | | if (strongTarget == nil) return nil; |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _keyPath = [keyPath copy]; |
| | | |
| | | _block = [block copy]; |
| | | _weakTarget = target; |
| | | _unsafeTarget = strongTarget; |
| | | _observer = observer; |
| | | |
| | | [RACKVOProxy.sharedProxy addObserver:self forContext:(__bridge void *)self]; |
| | | [strongTarget addObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath options:options context:(__bridge void *)self]; |
| | | |
| | | [strongTarget.rac_deallocDisposable addDisposable:self]; |
| | | [self.observer.rac_deallocDisposable addDisposable:self]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | [self dispose]; |
| | | } |
| | | |
| | | #pragma mark Observation |
| | | |
| | | - (void)dispose { |
| | | NSObject *target; |
| | | NSObject *observer; |
| | | |
| | | @synchronized (self) { |
| | | _block = nil; |
| | | |
| | | // The target should still exist at this point, because we still need to |
| | | // tear down its KVO observation. Therefore, we can use the unsafe |
| | | // reference (and need to, because the weak one will have been zeroed by |
| | | // now). |
| | | target = self.unsafeTarget; |
| | | observer = self.observer; |
| | | |
| | | _unsafeTarget = nil; |
| | | _observer = nil; |
| | | } |
| | | |
| | | [target.rac_deallocDisposable removeDisposable:self]; |
| | | [observer.rac_deallocDisposable removeDisposable:self]; |
| | | |
| | | [target removeObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath context:(__bridge void *)self]; |
| | | [RACKVOProxy.sharedProxy removeObserver:self forContext:(__bridge void *)self]; |
| | | } |
| | | |
| | | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { |
| | | if (context != (__bridge void *)self) { |
| | | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; |
| | | return; |
| | | } |
| | | |
| | | RACKVOBlock block; |
| | | id observer; |
| | | id target; |
| | | |
| | | @synchronized (self) { |
| | | block = self.block; |
| | | observer = self.observer; |
| | | target = self.weakTarget; |
| | | } |
| | | |
| | | if (block == nil || target == nil) return; |
| | | |
| | | block(target, observer, change); |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACMulticastConnection+Private.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/11/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACMulticastConnection.h" |
| | | |
| | | @class RACSubject; |
| | | |
| | | @interface RACMulticastConnection () |
| | | |
| | | - (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACMulticastConnection.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/11/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACDisposable; |
| | | @class RACSignal; |
| | | |
| | | /// A multicast connection encapsulates the idea of sharing one subscription to a |
| | | /// signal to many subscribers. This is most often needed if the subscription to |
| | | /// the underlying signal involves side-effects or shouldn't be called more than |
| | | /// once. |
| | | /// |
| | | /// The multicasted signal is only subscribed to when |
| | | /// -[RACMulticastConnection connect] is called. Until that happens, no values |
| | | /// will be sent on `signal`. See -[RACMulticastConnection autoconnect] for how |
| | | /// -[RACMulticastConnection connect] can be called automatically. |
| | | /// |
| | | /// Note that you shouldn't create RACMulticastConnection manually. Instead use |
| | | /// -[RACSignal publish] or -[RACSignal multicast:]. |
| | | @interface RACMulticastConnection : NSObject |
| | | |
| | | /// The multicasted signal. |
| | | @property (nonatomic, strong, readonly) RACSignal *signal; |
| | | |
| | | /// Connect to the underlying signal by subscribing to it. Calling this multiple |
| | | /// times does nothing but return the existing connection's disposable. |
| | | /// |
| | | /// Returns the disposable for the subscription to the multicasted signal. |
| | | - (RACDisposable *)connect; |
| | | |
| | | /// Connects to the underlying signal when the returned signal is first |
| | | /// subscribed to, and disposes of the subscription to the multicasted signal |
| | | /// when the returned signal has no subscribers. |
| | | /// |
| | | /// If new subscribers show up after being disposed, they'll subscribe and then |
| | | /// be immediately disposed of. The returned signal will never re-connect to the |
| | | /// multicasted signal. |
| | | /// |
| | | /// Returns the autoconnecting signal. |
| | | - (RACSignal *)autoconnect; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACMulticastConnection.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/11/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACMulticastConnection.h" |
| | | #import "RACMulticastConnection+Private.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACSerialDisposable.h" |
| | | #import "RACSubject.h" |
| | | #import <libkern/OSAtomic.h> |
| | | |
| | | @interface RACMulticastConnection () { |
| | | RACSubject *_signal; |
| | | |
| | | // When connecting, a caller should attempt to atomically swap the value of this |
| | | // from `0` to `1`. |
| | | // |
| | | // If the swap is successful the caller is resposible for subscribing `_signal` |
| | | // to `sourceSignal` and storing the returned disposable in `serialDisposable`. |
| | | // |
| | | // If the swap is unsuccessful it means that `_sourceSignal` has already been |
| | | // connected and the caller has no action to take. |
| | | int32_t volatile _hasConnected; |
| | | } |
| | | |
| | | @property (nonatomic, readonly, strong) RACSignal *sourceSignal; |
| | | @property (strong) RACSerialDisposable *serialDisposable; |
| | | @end |
| | | |
| | | @implementation RACMulticastConnection |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject { |
| | | NSCParameterAssert(source != nil); |
| | | NSCParameterAssert(subject != nil); |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _sourceSignal = source; |
| | | _serialDisposable = [[RACSerialDisposable alloc] init]; |
| | | _signal = subject; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark Connecting |
| | | |
| | | - (RACDisposable *)connect { |
| | | BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected); |
| | | |
| | | if (shouldConnect) { |
| | | self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal]; |
| | | } |
| | | |
| | | return self.serialDisposable; |
| | | } |
| | | |
| | | - (RACSignal *)autoconnect { |
| | | __block volatile int32_t subscriberCount = 0; |
| | | |
| | | return [[RACSignal |
| | | createSignal:^(id<RACSubscriber> subscriber) { |
| | | OSAtomicIncrement32Barrier(&subscriberCount); |
| | | |
| | | RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber]; |
| | | RACDisposable *connectionDisposable = [self connect]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [subscriptionDisposable dispose]; |
| | | |
| | | if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) { |
| | | [connectionDisposable dispose]; |
| | | } |
| | | }]; |
| | | }] |
| | | setNameWithFormat:@"[%@] -autoconnect", self.signal.name]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACObjCRuntime.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Cody Krieger on 5/19/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | // A private class containing wrappers to runtime functions. |
| | | @interface RACObjCRuntime : NSObject |
| | | |
| | | // Invokes objc_allocateClassPair(). Can be called from ARC code. |
| | | + (Class)createClass:(const char *)className inheritingFromClass:(Class)superclass; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACObjCRuntime.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Cody Krieger on 5/19/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACObjCRuntime.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | #if __has_feature(objc_arc) |
| | | #error "This file must be compiled without ARC." |
| | | #endif |
| | | |
| | | @implementation RACObjCRuntime |
| | | |
| | | + (Class)createClass:(const char *)className inheritingFromClass:(Class)superclass { |
| | | return objc_allocateClassPair(superclass, className, 0); |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACPassthroughSubscriber.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-06-13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "RACSubscriber.h" |
| | | |
| | | @class RACCompoundDisposable; |
| | | @class RACSignal; |
| | | |
| | | // A private subscriber that passes through all events to another subscriber |
| | | // while not disposed. |
| | | @interface RACPassthroughSubscriber : NSObject <RACSubscriber> |
| | | |
| | | // Initializes the receiver to pass through events until disposed. |
| | | // |
| | | // subscriber - The subscriber to forward events to. This must not be nil. |
| | | // signal - The signal that will be sending events to the receiver. |
| | | // disposable - When this disposable is disposed, no more events will be |
| | | // forwarded. This must not be nil. |
| | | // |
| | | // Returns an initialized passthrough subscriber. |
| | | - (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACPassthroughSubscriber.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-06-13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACPassthroughSubscriber.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACSignal.h" |
| | | #import "RACSignalProvider.h" |
| | | |
| | | static const char *cleanedDTraceString(NSString *original) { |
| | | return [original stringByReplacingOccurrencesOfString:@"\\s+" withString:@" " options:NSRegularExpressionSearch range:NSMakeRange(0, original.length)].UTF8String; |
| | | } |
| | | |
| | | static const char *cleanedSignalDescription(RACSignal *signal) { |
| | | NSString *desc = signal.description; |
| | | |
| | | NSRange range = [desc rangeOfString:@" name:"]; |
| | | if (range.location != NSNotFound) { |
| | | desc = [desc stringByReplacingCharactersInRange:range withString:@""]; |
| | | } |
| | | |
| | | return cleanedDTraceString(desc); |
| | | } |
| | | |
| | | @interface RACPassthroughSubscriber () |
| | | |
| | | // The subscriber to which events should be forwarded. |
| | | @property (nonatomic, strong, readonly) id<RACSubscriber> innerSubscriber; |
| | | |
| | | // The signal sending events to this subscriber. |
| | | // |
| | | // This property isn't `weak` because it's only used for DTrace probes, so |
| | | // a zeroing weak reference would incur an unnecessary performance penalty in |
| | | // normal usage. |
| | | @property (nonatomic, unsafe_unretained, readonly) RACSignal *signal; |
| | | |
| | | // A disposable representing the subscription. When disposed, no further events |
| | | // should be sent to the `innerSubscriber`. |
| | | @property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; |
| | | |
| | | @end |
| | | |
| | | @implementation RACPassthroughSubscriber |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable { |
| | | NSCParameterAssert(subscriber != nil); |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _innerSubscriber = subscriber; |
| | | _signal = signal; |
| | | _disposable = disposable; |
| | | |
| | | [self.innerSubscriber didSubscribeWithDisposable:self.disposable]; |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark RACSubscriber |
| | | |
| | | - (void)sendNext:(id)value { |
| | | if (self.disposable.disposed) return; |
| | | |
| | | if (RACSIGNAL_NEXT_ENABLED()) { |
| | | RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description])); |
| | | } |
| | | |
| | | [self.innerSubscriber sendNext:value]; |
| | | } |
| | | |
| | | - (void)sendError:(NSError *)error { |
| | | if (self.disposable.disposed) return; |
| | | |
| | | if (RACSIGNAL_ERROR_ENABLED()) { |
| | | RACSIGNAL_ERROR(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString(error.description)); |
| | | } |
| | | |
| | | [self.innerSubscriber sendError:error]; |
| | | } |
| | | |
| | | - (void)sendCompleted { |
| | | if (self.disposable.disposed) return; |
| | | |
| | | if (RACSIGNAL_COMPLETED_ENABLED()) { |
| | | RACSIGNAL_COMPLETED(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description)); |
| | | } |
| | | |
| | | [self.innerSubscriber sendCompleted]; |
| | | } |
| | | |
| | | - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { |
| | | if (disposable != self.disposable) { |
| | | [self.disposable addDisposable:disposable]; |
| | | } |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACQueueScheduler+Subclass.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 6/6/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACQueueScheduler.h" |
| | | #import "RACScheduler+Subclass.h" |
| | | |
| | | /// An interface for use by GCD queue-based subclasses. |
| | | /// |
| | | /// See RACScheduler+Subclass.h for subclassing notes. |
| | | @interface RACQueueScheduler () |
| | | |
| | | /// The queue on which blocks are enqueued. |
| | | #if OS_OBJECT_HAVE_OBJC_SUPPORT |
| | | @property (nonatomic, strong, readonly) dispatch_queue_t queue; |
| | | #else |
| | | // Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :( |
| | | @property (nonatomic, assign, readonly) dispatch_queue_t queue; |
| | | #endif |
| | | |
| | | /// Initializes the receiver with the name of the scheduler and the queue which |
| | | /// the scheduler should use. |
| | | /// |
| | | /// name - The name of the scheduler. If nil, a default name will be used. |
| | | /// queue - The queue upon which the receiver should enqueue scheduled blocks. |
| | | /// This argument must not be NULL. |
| | | /// |
| | | /// Returns the initialized object. |
| | | - (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue; |
| | | |
| | | /// Converts a date into a GCD time using dispatch_walltime(). |
| | | /// |
| | | /// date - The date to convert. This must not be nil. |
| | | + (dispatch_time_t)wallTimeWithDate:(NSDate *)date; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACQueueScheduler.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/30/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACScheduler.h" |
| | | |
| | | /// An abstract scheduler which asynchronously enqueues all its work to a Grand |
| | | /// Central Dispatch queue. |
| | | /// |
| | | /// Because RACQueueScheduler is abstract, it should not be instantiated |
| | | /// directly. Create a subclass using the `RACQueueScheduler+Subclass.h` |
| | | /// interface and use that instead. |
| | | @interface RACQueueScheduler : RACScheduler |
| | | @end |
New file |
| | |
| | | // |
| | | // RACQueueScheduler.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/30/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACQueueScheduler.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACQueueScheduler+Subclass.h" |
| | | #import "RACScheduler+Private.h" |
| | | |
| | | @implementation RACQueueScheduler |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue { |
| | | NSCParameterAssert(queue != NULL); |
| | | |
| | | self = [super initWithName:name]; |
| | | if (self == nil) return nil; |
| | | |
| | | _queue = queue; |
| | | #if !OS_OBJECT_HAVE_OBJC_SUPPORT |
| | | dispatch_retain(_queue); |
| | | #endif |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #if !OS_OBJECT_HAVE_OBJC_SUPPORT |
| | | |
| | | - (void)dealloc { |
| | | if (_queue != NULL) { |
| | | dispatch_release(_queue); |
| | | _queue = NULL; |
| | | } |
| | | } |
| | | |
| | | #endif |
| | | |
| | | #pragma mark Date Conversions |
| | | |
| | | + (dispatch_time_t)wallTimeWithDate:(NSDate *)date { |
| | | NSCParameterAssert(date != nil); |
| | | |
| | | double seconds = 0; |
| | | double frac = modf(date.timeIntervalSince1970, &seconds); |
| | | |
| | | struct timespec walltime = { |
| | | .tv_sec = (time_t)fmin(fmax(seconds, LONG_MIN), LONG_MAX), |
| | | .tv_nsec = (long)fmin(fmax(frac * NSEC_PER_SEC, LONG_MIN), LONG_MAX) |
| | | }; |
| | | |
| | | return dispatch_walltime(&walltime, 0); |
| | | } |
| | | |
| | | #pragma mark RACScheduler |
| | | |
| | | - (RACDisposable *)schedule:(void (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | RACDisposable *disposable = [[RACDisposable alloc] init]; |
| | | |
| | | dispatch_async(self.queue, ^{ |
| | | if (disposable.disposed) return; |
| | | [self performAsCurrentScheduler:block]; |
| | | }); |
| | | |
| | | return disposable; |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { |
| | | NSCParameterAssert(date != nil); |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | RACDisposable *disposable = [[RACDisposable alloc] init]; |
| | | |
| | | dispatch_after([self.class wallTimeWithDate:date], self.queue, ^{ |
| | | if (disposable.disposed) return; |
| | | [self performAsCurrentScheduler:block]; |
| | | }); |
| | | |
| | | return disposable; |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { |
| | | NSCParameterAssert(date != nil); |
| | | NSCParameterAssert(interval > 0.0 && interval < INT64_MAX / NSEC_PER_SEC); |
| | | NSCParameterAssert(leeway >= 0.0 && leeway < INT64_MAX / NSEC_PER_SEC); |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | uint64_t intervalInNanoSecs = (uint64_t)(interval * NSEC_PER_SEC); |
| | | uint64_t leewayInNanoSecs = (uint64_t)(leeway * NSEC_PER_SEC); |
| | | |
| | | dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); |
| | | dispatch_source_set_timer(timer, [self.class wallTimeWithDate:date], intervalInNanoSecs, leewayInNanoSecs); |
| | | dispatch_source_set_event_handler(timer, block); |
| | | dispatch_resume(timer); |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | dispatch_source_cancel(timer); |
| | | }]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACReplaySubject.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/14/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSubject.h" |
| | | |
| | | extern const NSUInteger RACReplaySubjectUnlimitedCapacity; |
| | | |
| | | /// A replay subject saves the values it is sent (up to its defined capacity) |
| | | /// and resends those to new subscribers. It will also replay an error or |
| | | /// completion. |
| | | @interface RACReplaySubject : RACSubject |
| | | |
| | | /// Creates a new replay subject with the given capacity. A capacity of |
| | | /// RACReplaySubjectUnlimitedCapacity means values are never trimmed. |
| | | + (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACReplaySubject.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/14/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACReplaySubject.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACScheduler+Private.h" |
| | | #import "RACSubscriber.h" |
| | | #import "RACTuple.h" |
| | | |
| | | const NSUInteger RACReplaySubjectUnlimitedCapacity = NSUIntegerMax; |
| | | |
| | | @interface RACReplaySubject () |
| | | |
| | | @property (nonatomic, assign, readonly) NSUInteger capacity; |
| | | |
| | | // These properties should only be modified while synchronized on self. |
| | | @property (nonatomic, strong, readonly) NSMutableArray *valuesReceived; |
| | | @property (nonatomic, assign) BOOL hasCompleted; |
| | | @property (nonatomic, assign) BOOL hasError; |
| | | @property (nonatomic, strong) NSError *error; |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation RACReplaySubject |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity { |
| | | return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity]; |
| | | } |
| | | |
| | | - (instancetype)init { |
| | | return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity]; |
| | | } |
| | | |
| | | - (instancetype)initWithCapacity:(NSUInteger)capacity { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _capacity = capacity; |
| | | _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]); |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark RACSignal |
| | | |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { |
| | | RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ |
| | | @synchronized (self) { |
| | | for (id value in self.valuesReceived) { |
| | | if (compoundDisposable.disposed) return; |
| | | |
| | | [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)]; |
| | | } |
| | | |
| | | if (compoundDisposable.disposed) return; |
| | | |
| | | if (self.hasCompleted) { |
| | | [subscriber sendCompleted]; |
| | | } else if (self.hasError) { |
| | | [subscriber sendError:self.error]; |
| | | } else { |
| | | RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; |
| | | [compoundDisposable addDisposable:subscriptionDisposable]; |
| | | } |
| | | } |
| | | }]; |
| | | |
| | | [compoundDisposable addDisposable:schedulingDisposable]; |
| | | |
| | | return compoundDisposable; |
| | | } |
| | | |
| | | #pragma mark RACSubscriber |
| | | |
| | | - (void)sendNext:(id)value { |
| | | @synchronized (self) { |
| | | [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil]; |
| | | [super sendNext:value]; |
| | | |
| | | if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) { |
| | | [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)sendCompleted { |
| | | @synchronized (self) { |
| | | self.hasCompleted = YES; |
| | | [super sendCompleted]; |
| | | } |
| | | } |
| | | |
| | | - (void)sendError:(NSError *)e { |
| | | @synchronized (self) { |
| | | self.hasError = YES; |
| | | self.error = e; |
| | | [super sendError:e]; |
| | | } |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACReturnSignal.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-10. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSignal.h" |
| | | |
| | | // A private `RACSignal` subclasses that synchronously sends a value to any |
| | | // subscribers, then completes. |
| | | @interface RACReturnSignal : RACSignal |
| | | |
| | | + (RACSignal *)return:(id)value; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACReturnSignal.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-10-10. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACReturnSignal.h" |
| | | #import "RACScheduler+Private.h" |
| | | #import "RACSubscriber.h" |
| | | #import "RACUnit.h" |
| | | |
| | | @interface RACReturnSignal () |
| | | |
| | | // The value to send upon subscription. |
| | | @property (nonatomic, strong, readonly) id value; |
| | | |
| | | @end |
| | | |
| | | @implementation RACReturnSignal |
| | | |
| | | #pragma mark Properties |
| | | |
| | | // Only allow this signal's name to be customized in DEBUG, since it's |
| | | // potentially a singleton in release builds (see +return:). |
| | | - (void)setName:(NSString *)name { |
| | | #ifdef DEBUG |
| | | [super setName:name]; |
| | | #endif |
| | | } |
| | | |
| | | - (NSString *)name { |
| | | #ifdef DEBUG |
| | | return super.name; |
| | | #else |
| | | return @"+return:"; |
| | | #endif |
| | | } |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (RACSignal *)return:(id)value { |
| | | #ifndef DEBUG |
| | | // In release builds, use singletons for two very common cases. |
| | | if (value == RACUnit.defaultUnit) { |
| | | static RACReturnSignal *unitSingleton; |
| | | static dispatch_once_t unitPred; |
| | | |
| | | dispatch_once(&unitPred, ^{ |
| | | unitSingleton = [[self alloc] init]; |
| | | unitSingleton->_value = RACUnit.defaultUnit; |
| | | }); |
| | | |
| | | return unitSingleton; |
| | | } else if (value == nil) { |
| | | static RACReturnSignal *nilSingleton; |
| | | static dispatch_once_t nilPred; |
| | | |
| | | dispatch_once(&nilPred, ^{ |
| | | nilSingleton = [[self alloc] init]; |
| | | nilSingleton->_value = nil; |
| | | }); |
| | | |
| | | return nilSingleton; |
| | | } |
| | | #endif |
| | | |
| | | RACReturnSignal *signal = [[self alloc] init]; |
| | | signal->_value = value; |
| | | |
| | | #ifdef DEBUG |
| | | [signal setNameWithFormat:@"+return: %@", value]; |
| | | #endif |
| | | |
| | | return signal; |
| | | } |
| | | |
| | | #pragma mark Subscription |
| | | |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { |
| | | NSCParameterAssert(subscriber != nil); |
| | | |
| | | return [RACScheduler.subscriptionScheduler schedule:^{ |
| | | [subscriber sendNext:self.value]; |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACScheduler+Private.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/29/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACScheduler.h" |
| | | |
| | | // The thread-specific current scheduler key. |
| | | extern NSString * const RACSchedulerCurrentSchedulerKey; |
| | | |
| | | // A private interface for internal RAC use only. |
| | | @interface RACScheduler () |
| | | |
| | | // A dedicated scheduler that fills two requirements: |
| | | // |
| | | // 1. By the time subscription happens, we need a valid +currentScheduler. |
| | | // 2. Subscription should happen as soon as possible. |
| | | // |
| | | // To fulfill those two, if we already have a valid +currentScheduler, it |
| | | // immediately executes scheduled blocks. If we don't, it will execute scheduled |
| | | // blocks with a private background scheduler. |
| | | + (instancetype)subscriptionScheduler; |
| | | |
| | | // Initializes the receiver with the given name. |
| | | // |
| | | // name - The name of the scheduler. If nil, a default name will be used. |
| | | // |
| | | // Returns the initialized object. |
| | | - (id)initWithName:(NSString *)name; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACScheduler.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by MiÄ·elis Vindavs on 5/27/14. |
| | | // Copyright (c) 2014 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "RACScheduler.h" |
| | | |
| | | /// An interface for use by subclasses. |
| | | /// |
| | | /// Subclasses should use `-performAsCurrentScheduler:` to do the actual block |
| | | /// invocation so that +[RACScheduler currentScheduler] behaves as expected. |
| | | /// |
| | | /// **Note that RACSchedulers are expected to be serial**. Subclasses must honor |
| | | /// that contract. See `RACTargetQueueScheduler` for a queue-based scheduler |
| | | /// which will enforce the serialization guarantee. |
| | | @interface RACScheduler () |
| | | |
| | | /// Performs the given block with the receiver as the current scheduler for |
| | | /// its thread. This should only be called by subclasses to perform their |
| | | /// scheduled blocks. |
| | | /// |
| | | /// block - The block to execute. Cannot be NULL. |
| | | - (void)performAsCurrentScheduler:(void (^)(void))block; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACScheduler.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/16/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | /// The priority for the scheduler. |
| | | /// |
| | | /// RACSchedulerPriorityHigh - High priority. |
| | | /// RACSchedulerPriorityDefault - Default priority. |
| | | /// RACSchedulerPriorityLow - Low priority. |
| | | /// RACSchedulerPriorityBackground - Background priority. |
| | | typedef enum : long { |
| | | RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH, |
| | | RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT, |
| | | RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW, |
| | | RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND, |
| | | } RACSchedulerPriority; |
| | | |
| | | /// Scheduled with -scheduleRecursiveBlock:, this type of block is passed a block |
| | | /// with which it can call itself recursively. |
| | | typedef void (^RACSchedulerRecursiveBlock)(void (^reschedule)(void)); |
| | | |
| | | @class RACDisposable; |
| | | |
| | | /// Schedulers are used to control when and where work is performed. |
| | | @interface RACScheduler : NSObject |
| | | |
| | | /// A singleton scheduler that immediately executes the blocks it is given. |
| | | /// |
| | | /// **Note:** Unlike most other schedulers, this does not set the current |
| | | /// scheduler. There may still be a valid +currentScheduler if this is used |
| | | /// within a block scheduled on a different scheduler. |
| | | + (RACScheduler *)immediateScheduler; |
| | | |
| | | /// A singleton scheduler that executes blocks in the main thread. |
| | | + (RACScheduler *)mainThreadScheduler; |
| | | |
| | | /// Creates and returns a new background scheduler with the given priority and |
| | | /// name. The name is for debug and instrumentation purposes only. |
| | | /// |
| | | /// Scheduler creation is cheap. It's unnecessary to save the result of this |
| | | /// method call unless you want to serialize some actions on the same background |
| | | /// scheduler. |
| | | + (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name; |
| | | |
| | | /// Invokes +schedulerWithPriority:name: with a default name. |
| | | + (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority; |
| | | |
| | | /// Invokes +schedulerWithPriority: with RACSchedulerPriorityDefault. |
| | | + (RACScheduler *)scheduler; |
| | | |
| | | /// The current scheduler. This will only be valid when used from within a |
| | | /// -[RACScheduler schedule:] block or when on the main thread. |
| | | + (RACScheduler *)currentScheduler; |
| | | |
| | | /// Schedule the given block for execution on the scheduler. |
| | | /// |
| | | /// Scheduled blocks will be executed in the order in which they were scheduled. |
| | | /// |
| | | /// block - The block to schedule for execution. Cannot be nil. |
| | | /// |
| | | /// Returns a disposable which can be used to cancel the scheduled block before |
| | | /// it begins executing, or nil if cancellation is not supported. |
| | | - (RACDisposable *)schedule:(void (^)(void))block; |
| | | |
| | | /// Schedule the given block for execution on the scheduler at or after |
| | | /// a specific time. |
| | | /// |
| | | /// Note that blocks scheduled for a certain time will not preempt any other |
| | | /// scheduled work that is executing at the time. |
| | | /// |
| | | /// When invoked on the +immediateScheduler, the calling thread **will block** |
| | | /// until the specified time. |
| | | /// |
| | | /// date - The earliest time at which `block` should begin executing. The block |
| | | /// may not execute immediately at this time, whether due to system load |
| | | /// or another block on the scheduler currently being run. Cannot be nil. |
| | | /// block - The block to schedule for execution. Cannot be nil. |
| | | /// |
| | | /// Returns a disposable which can be used to cancel the scheduled block before |
| | | /// it begins executing, or nil if cancellation is not supported. |
| | | - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block; |
| | | |
| | | /// Schedule the given block for execution on the scheduler after the delay. |
| | | /// |
| | | /// Converts the delay into an NSDate, then invokes `-after:schedule:`. |
| | | - (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block; |
| | | |
| | | /// Reschedule the given block at a particular interval, starting at a specific |
| | | /// time, and with a given leeway for deferral. |
| | | /// |
| | | /// Note that blocks scheduled for a certain time will not preempt any other |
| | | /// scheduled work that is executing at the time. |
| | | /// |
| | | /// Regardless of the value of `leeway`, the given block may not execute exactly |
| | | /// at `when` or exactly on successive intervals, whether due to system load or |
| | | /// because another block is currently being run on the scheduler. |
| | | /// |
| | | /// It is considered undefined behavior to invoke this method on the |
| | | /// +immediateScheduler. |
| | | /// |
| | | /// date - The earliest time at which `block` should begin executing. The |
| | | /// block may not execute immediately at this time, whether due to |
| | | /// system load or another block on the scheduler currently being |
| | | /// run. Cannot be nil. |
| | | /// interval - The interval at which the block should be rescheduled, starting |
| | | /// from `date`. This will use the system wall clock, to avoid |
| | | /// skew when the computer goes to sleep. |
| | | /// leeway - A hint to the system indicating the number of seconds that each |
| | | /// scheduling can be deferred. Note that this is just a hint, and |
| | | /// there may be some additional latency no matter what. |
| | | /// block - The block to repeatedly schedule for execution. Cannot be nil. |
| | | /// |
| | | /// Returns a disposable which can be used to cancel the automatic scheduling and |
| | | /// rescheduling, or nil if cancellation is not supported. |
| | | - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block; |
| | | |
| | | /// Schedule the given recursive block for execution on the scheduler. The |
| | | /// scheduler will automatically flatten any recursive scheduling into iteration |
| | | /// instead, so this can be used without issue for blocks that may keep invoking |
| | | /// themselves forever. |
| | | /// |
| | | /// Scheduled blocks will be executed in the order in which they were scheduled. |
| | | /// |
| | | /// recursiveBlock - The block to schedule for execution. When invoked, the |
| | | /// recursive block will be passed a `void (^)(void)` block |
| | | /// which will reschedule the recursive block at the end of the |
| | | /// receiver's queue. This passed-in block will automatically |
| | | /// skip scheduling if the scheduling of the `recursiveBlock` |
| | | /// was disposed in the meantime. |
| | | /// |
| | | /// Returns a disposable which can be used to cancel the scheduled block before |
| | | /// it begins executing, or to stop it from rescheduling if it's already begun |
| | | /// execution. |
| | | - (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock; |
| | | |
| | | @end |
| | | |
| | | @interface RACScheduler (Deprecated) |
| | | |
| | | + (RACScheduler *)schedulerWithQueue:(dispatch_queue_t)queue name:(NSString *)name __attribute__((deprecated("Use -[RACTargetQueueScheduler initWithName:targetQueue:] instead."))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACScheduler.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/16/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACScheduler.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACImmediateScheduler.h" |
| | | #import "RACScheduler+Private.h" |
| | | #import "RACSubscriptionScheduler.h" |
| | | #import "RACTargetQueueScheduler.h" |
| | | |
| | | // The key for the thread-specific current scheduler. |
| | | NSString * const RACSchedulerCurrentSchedulerKey = @"RACSchedulerCurrentSchedulerKey"; |
| | | |
| | | @interface RACScheduler () |
| | | @property (nonatomic, readonly, copy) NSString *name; |
| | | @end |
| | | |
| | | @implementation RACScheduler |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.name]; |
| | | } |
| | | |
| | | #pragma mark Initializers |
| | | |
| | | - (id)initWithName:(NSString *)name { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | if (name == nil) { |
| | | _name = [NSString stringWithFormat:@"com.ReactiveCocoa.%@.anonymousScheduler", self.class]; |
| | | } else { |
| | | _name = [name copy]; |
| | | } |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark Schedulers |
| | | |
| | | + (instancetype)immediateScheduler { |
| | | static dispatch_once_t onceToken; |
| | | static RACScheduler *immediateScheduler; |
| | | dispatch_once(&onceToken, ^{ |
| | | immediateScheduler = [[RACImmediateScheduler alloc] init]; |
| | | }); |
| | | |
| | | return immediateScheduler; |
| | | } |
| | | |
| | | + (instancetype)mainThreadScheduler { |
| | | static dispatch_once_t onceToken; |
| | | static RACScheduler *mainThreadScheduler; |
| | | dispatch_once(&onceToken, ^{ |
| | | mainThreadScheduler = [[RACTargetQueueScheduler alloc] initWithName:@"com.ReactiveCocoa.RACScheduler.mainThreadScheduler" targetQueue:dispatch_get_main_queue()]; |
| | | }); |
| | | |
| | | return mainThreadScheduler; |
| | | } |
| | | |
| | | + (instancetype)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name { |
| | | return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)]; |
| | | } |
| | | |
| | | + (instancetype)schedulerWithPriority:(RACSchedulerPriority)priority { |
| | | return [self schedulerWithPriority:priority name:@"com.ReactiveCocoa.RACScheduler.backgroundScheduler"]; |
| | | } |
| | | |
| | | + (instancetype)scheduler { |
| | | return [self schedulerWithPriority:RACSchedulerPriorityDefault]; |
| | | } |
| | | |
| | | + (instancetype)subscriptionScheduler { |
| | | static dispatch_once_t onceToken; |
| | | static RACScheduler *subscriptionScheduler; |
| | | dispatch_once(&onceToken, ^{ |
| | | subscriptionScheduler = [[RACSubscriptionScheduler alloc] init]; |
| | | }); |
| | | |
| | | return subscriptionScheduler; |
| | | } |
| | | |
| | | + (BOOL)isOnMainThread { |
| | | return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread]; |
| | | } |
| | | |
| | | + (instancetype)currentScheduler { |
| | | RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey]; |
| | | if (scheduler != nil) return scheduler; |
| | | if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler; |
| | | |
| | | return nil; |
| | | } |
| | | |
| | | #pragma mark Scheduling |
| | | |
| | | - (RACDisposable *)schedule:(void (^)(void))block { |
| | | NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); |
| | | return nil; |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { |
| | | NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); |
| | | return nil; |
| | | } |
| | | |
| | | - (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block { |
| | | return [self after:[NSDate dateWithTimeIntervalSinceNow:delay] schedule:block]; |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { |
| | | NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); |
| | | return nil; |
| | | } |
| | | |
| | | - (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock { |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable]; |
| | | return disposable; |
| | | } |
| | | |
| | | - (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable { |
| | | @autoreleasepool { |
| | | RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable]; |
| | | [disposable addDisposable:selfDisposable]; |
| | | |
| | | __weak RACDisposable *weakSelfDisposable = selfDisposable; |
| | | |
| | | RACDisposable *schedulingDisposable = [self schedule:^{ |
| | | @autoreleasepool { |
| | | // At this point, we've been invoked, so our disposable is now useless. |
| | | [disposable removeDisposable:weakSelfDisposable]; |
| | | } |
| | | |
| | | if (disposable.disposed) return; |
| | | |
| | | void (^reallyReschedule)(void) = ^{ |
| | | if (disposable.disposed) return; |
| | | [self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable]; |
| | | }; |
| | | |
| | | // Protects the variables below. |
| | | // |
| | | // This doesn't actually need to be __block qualified, but Clang |
| | | // complains otherwise. :C |
| | | __block NSLock *lock = [[NSLock alloc] init]; |
| | | lock.name = [NSString stringWithFormat:@"%@ %s", self, sel_getName(_cmd)]; |
| | | |
| | | __block NSUInteger rescheduleCount = 0; |
| | | |
| | | // Set to YES once synchronous execution has finished. Further |
| | | // rescheduling should occur immediately (rather than being |
| | | // flattened). |
| | | __block BOOL rescheduleImmediately = NO; |
| | | |
| | | @autoreleasepool { |
| | | recursiveBlock(^{ |
| | | [lock lock]; |
| | | BOOL immediate = rescheduleImmediately; |
| | | if (!immediate) ++rescheduleCount; |
| | | [lock unlock]; |
| | | |
| | | if (immediate) reallyReschedule(); |
| | | }); |
| | | } |
| | | |
| | | [lock lock]; |
| | | NSUInteger synchronousCount = rescheduleCount; |
| | | rescheduleImmediately = YES; |
| | | [lock unlock]; |
| | | |
| | | for (NSUInteger i = 0; i < synchronousCount; i++) { |
| | | reallyReschedule(); |
| | | } |
| | | }]; |
| | | |
| | | [selfDisposable addDisposable:schedulingDisposable]; |
| | | } |
| | | } |
| | | |
| | | - (void)performAsCurrentScheduler:(void (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | // If we're using a concurrent queue, we could end up in here concurrently, |
| | | // in which case we *don't* want to clear the current scheduler immediately |
| | | // after our block is done executing, but only *after* all our concurrent |
| | | // invocations are done. |
| | | |
| | | RACScheduler *previousScheduler = RACScheduler.currentScheduler; |
| | | NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; |
| | | |
| | | @autoreleasepool { |
| | | block(); |
| | | } |
| | | |
| | | if (previousScheduler != nil) { |
| | | NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; |
| | | } else { |
| | | [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; |
| | | } |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACScheduler (Deprecated) |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | |
| | | + (instancetype)schedulerWithQueue:(dispatch_queue_t)queue name:(NSString *)name { |
| | | NSCParameterAssert(queue != NULL); |
| | | |
| | | return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:queue]; |
| | | } |
| | | |
| | | #pragma clang diagnostic pop |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACScopedDisposable.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/28/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACDisposable.h" |
| | | |
| | | /// A disposable that calls its own -dispose when it is dealloc'd. |
| | | @interface RACScopedDisposable : RACDisposable |
| | | |
| | | /// Creates a new scoped disposable that will also dispose of the given |
| | | /// disposable when it is dealloc'd. |
| | | + (instancetype)scopedDisposableWithDisposable:(RACDisposable *)disposable; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACScopedDisposable.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/28/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACScopedDisposable.h" |
| | | |
| | | @implementation RACScopedDisposable |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)scopedDisposableWithDisposable:(RACDisposable *)disposable { |
| | | return [self disposableWithBlock:^{ |
| | | [disposable dispose]; |
| | | }]; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | [self dispose]; |
| | | } |
| | | |
| | | #pragma mark RACDisposable |
| | | |
| | | - (RACScopedDisposable *)asScopedDisposable { |
| | | // totally already are |
| | | return self; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "RACStream.h" |
| | | |
| | | @class RACScheduler; |
| | | @class RACSignal; |
| | | |
| | | /// Represents an immutable sequence of values. Unless otherwise specified, the |
| | | /// sequences' values are evaluated lazily on demand. Like Cocoa collections, |
| | | /// sequences cannot contain nil. |
| | | /// |
| | | /// Most inherited RACStream methods that accept a block will execute the block |
| | | /// _at most_ once for each value that is evaluated in the returned sequence. |
| | | /// Side effects are subject to the behavior described in |
| | | /// +sequenceWithHeadBlock:tailBlock:. |
| | | /// |
| | | /// Implemented as a class cluster. A minimal implementation for a subclass |
| | | /// consists simply of -head and -tail. |
| | | @interface RACSequence : RACStream <NSCoding, NSCopying, NSFastEnumeration> |
| | | |
| | | /// The first object in the sequence, or nil if the sequence is empty. |
| | | /// |
| | | /// Subclasses must provide an implementation of this method. |
| | | @property (nonatomic, strong, readonly) id head; |
| | | |
| | | /// All but the first object in the sequence, or nil if the sequence is empty. |
| | | /// |
| | | /// Subclasses must provide an implementation of this method. |
| | | @property (nonatomic, strong, readonly) RACSequence *tail; |
| | | |
| | | /// Evaluates the full sequence to produce an equivalently-sized array. |
| | | @property (nonatomic, copy, readonly) NSArray *array; |
| | | |
| | | /// Returns an enumerator of all objects in the sequence. |
| | | @property (nonatomic, copy, readonly) NSEnumerator *objectEnumerator; |
| | | |
| | | /// Converts a sequence into an eager sequence. |
| | | /// |
| | | /// An eager sequence fully evaluates all of its values immediately. Sequences |
| | | /// derived from an eager sequence will also be eager. |
| | | /// |
| | | /// Returns a new eager sequence, or the receiver if the sequence is already |
| | | /// eager. |
| | | @property (nonatomic, copy, readonly) RACSequence *eagerSequence; |
| | | |
| | | /// Converts a sequence into a lazy sequence. |
| | | /// |
| | | /// A lazy sequence evaluates its values on demand, as they are accessed. |
| | | /// Sequences derived from a lazy sequence will also be lazy. |
| | | /// |
| | | /// Returns a new lazy sequence, or the receiver if the sequence is already lazy. |
| | | @property (nonatomic, copy, readonly) RACSequence *lazySequence; |
| | | |
| | | /// Invokes -signalWithScheduler: with a new RACScheduler. |
| | | - (RACSignal *)signal; |
| | | |
| | | /// Evaluates the full sequence on the given scheduler. |
| | | /// |
| | | /// Each item is evaluated in its own scheduled block, such that control of the |
| | | /// scheduler is yielded between each value. |
| | | /// |
| | | /// Returns a signal which sends the receiver's values on the given scheduler as |
| | | /// they're evaluated. |
| | | - (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler; |
| | | |
| | | /// Applies a left fold to the sequence. |
| | | /// |
| | | /// This is the same as iterating the sequence along with a provided start value. |
| | | /// This uses a constant amount of memory. A left fold is left-associative so in |
| | | /// the sequence [1,2,3] the block would applied in the following order: |
| | | /// reduce(reduce(reduce(start, 1), 2), 3) |
| | | /// |
| | | /// start - The starting value for the fold. Used as `accumulator` for the |
| | | /// first fold. |
| | | /// reduce - The block used to combine the accumulated value and the next value. |
| | | /// Cannot be nil. |
| | | /// |
| | | /// Returns a reduced value. |
| | | - (id)foldLeftWithStart:(id)start reduce:(id (^)(id accumulator, id value))reduce; |
| | | |
| | | /// Applies a right fold to the sequence. |
| | | /// |
| | | /// A right fold is equivalent to recursion on the list. The block is evaluated |
| | | /// from the right to the left in list. It is right associative so it's applied |
| | | /// to the rightmost elements first. For example, in the sequence [1,2,3] the |
| | | /// block is applied in the order: |
| | | /// reduce(1, reduce(2, reduce(3, start))) |
| | | /// |
| | | /// start - The starting value for the fold. |
| | | /// reduce - The block used to combine the accumulated value and the next head. |
| | | /// The block is given the accumulated value and the value of the rest |
| | | /// of the computation (result of the recursion). This is computed when |
| | | /// you retrieve its value using `rest.head`. This allows you to |
| | | /// prevent unnecessary computation by not accessing `rest.head` if you |
| | | /// don't need to. |
| | | /// |
| | | /// Returns a reduced value. |
| | | - (id)foldRightWithStart:(id)start reduce:(id (^)(id first, RACSequence *rest))reduce; |
| | | |
| | | /// Check if any value in sequence passes the block. |
| | | /// |
| | | /// block - The block predicate used to check each item. Cannot be nil. |
| | | /// |
| | | /// Returns a boolean indiciating if any value in the sequence passed. |
| | | - (BOOL)any:(BOOL (^)(id value))block; |
| | | |
| | | /// Check if all values in the sequence pass the block. |
| | | /// |
| | | /// block - The block predicate used to check each item. Cannot be nil. |
| | | /// |
| | | /// Returns a boolean indicating if all values in the sequence passed. |
| | | - (BOOL)all:(BOOL (^)(id value))block; |
| | | |
| | | /// Returns the first object that passes the block. |
| | | /// |
| | | /// block - The block predicate used to check each item. Cannot be nil. |
| | | /// |
| | | /// Returns an object that passes the block or nil if no objects passed. |
| | | - (id)objectPassingTest:(BOOL (^)(id value))block; |
| | | |
| | | /// Creates a sequence that dynamically generates its values. |
| | | /// |
| | | /// headBlock - Invoked the first time -head is accessed. |
| | | /// tailBlock - Invoked the first time -tail is accessed. |
| | | /// |
| | | /// The results from each block are memoized, so each block will be invoked at |
| | | /// most once, no matter how many times the head and tail properties of the |
| | | /// sequence are accessed. |
| | | /// |
| | | /// Any side effects in `headBlock` or `tailBlock` should be thread-safe, since |
| | | /// the sequence may be evaluated at any time from any thread. Not only that, but |
| | | /// -tail may be accessed before -head, or both may be accessed simultaneously. |
| | | /// As noted above, side effects will only be triggered the _first_ time -head or |
| | | /// -tail is invoked. |
| | | /// |
| | | /// Returns a sequence that lazily invokes the given blocks to provide head and |
| | | /// tail. `headBlock` must not be nil. |
| | | + (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock; |
| | | |
| | | @end |
| | | |
| | | @interface RACSequence (Deprecated) |
| | | |
| | | - (id)foldLeftWithStart:(id)start combine:(id (^)(id accumulator, id value))combine __attribute__((deprecated("Renamed to -foldLeftWithStart:reduce:"))); |
| | | - (id)foldRightWithStart:(id)start combine:(id (^)(id first, RACSequence *rest))combine __attribute__((deprecated("Renamed to -foldRightWithStart:reduce:"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSequence.h" |
| | | #import "RACArraySequence.h" |
| | | #import "RACDynamicSequence.h" |
| | | #import "RACEagerSequence.h" |
| | | #import "RACEmptySequence.h" |
| | | #import "RACScheduler.h" |
| | | #import "RACSignal.h" |
| | | #import "RACSubscriber.h" |
| | | #import "RACTuple.h" |
| | | #import "RACUnarySequence.h" |
| | | |
| | | // An enumerator over sequences. |
| | | @interface RACSequenceEnumerator : NSEnumerator |
| | | |
| | | // The sequence the enumerator is enumerating. |
| | | // |
| | | // This will change as the enumerator is exhausted. This property should only be |
| | | // accessed while synchronized on self. |
| | | @property (nonatomic, strong) RACSequence *sequence; |
| | | |
| | | @end |
| | | |
| | | @interface RACSequence () |
| | | |
| | | // Performs one iteration of lazy binding, passing through values from `current` |
| | | // until the sequence is exhausted, then recursively binding the remaining |
| | | // values in the receiver. |
| | | // |
| | | // Returns a new sequence which contains `current`, followed by the combined |
| | | // result of all applications of `block` to the remaining values in the receiver. |
| | | - (instancetype)bind:(RACStreamBindBlock)block passingThroughValuesFromSequence:(RACSequence *)current; |
| | | |
| | | @end |
| | | |
| | | @implementation RACSequenceEnumerator |
| | | |
| | | - (id)nextObject { |
| | | id object = nil; |
| | | |
| | | @synchronized (self) { |
| | | object = self.sequence.head; |
| | | self.sequence = self.sequence.tail; |
| | | } |
| | | |
| | | return object; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACSequence |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock { |
| | | return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"]; |
| | | } |
| | | |
| | | #pragma mark Class cluster primitives |
| | | |
| | | - (id)head { |
| | | NSCAssert(NO, @"%s must be overridden by subclasses", __func__); |
| | | return nil; |
| | | } |
| | | |
| | | - (RACSequence *)tail { |
| | | NSCAssert(NO, @"%s must be overridden by subclasses", __func__); |
| | | return nil; |
| | | } |
| | | |
| | | #pragma mark RACStream |
| | | |
| | | + (instancetype)empty { |
| | | return RACEmptySequence.empty; |
| | | } |
| | | |
| | | + (instancetype)return:(id)value { |
| | | return [RACUnarySequence return:value]; |
| | | } |
| | | |
| | | - (instancetype)bind:(RACStreamBindBlock (^)(void))block { |
| | | RACStreamBindBlock bindBlock = block(); |
| | | return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence { |
| | | // Store values calculated in the dependency here instead, avoiding any kind |
| | | // of temporary collection and boxing. |
| | | // |
| | | // This relies on the implementation of RACDynamicSequence synchronizing |
| | | // access to its head, tail, and dependency, and we're only doing it because |
| | | // we really need the performance. |
| | | __block RACSequence *valuesSeq = self; |
| | | __block RACSequence *current = passthroughSequence; |
| | | __block BOOL stop = NO; |
| | | |
| | | RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id { |
| | | while (current.head == nil) { |
| | | if (stop) return nil; |
| | | |
| | | // We've exhausted the current sequence, create a sequence from the |
| | | // next value. |
| | | id value = valuesSeq.head; |
| | | |
| | | if (value == nil) { |
| | | // We've exhausted all the sequences. |
| | | stop = YES; |
| | | return nil; |
| | | } |
| | | |
| | | current = (id)bindBlock(value, &stop); |
| | | if (current == nil) { |
| | | stop = YES; |
| | | return nil; |
| | | } |
| | | |
| | | valuesSeq = valuesSeq.tail; |
| | | } |
| | | |
| | | NSCAssert([current isKindOfClass:RACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current); |
| | | return nil; |
| | | } headBlock:^(id _) { |
| | | return current.head; |
| | | } tailBlock:^ id (id _) { |
| | | if (stop) return nil; |
| | | |
| | | return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail]; |
| | | }]; |
| | | |
| | | sequence.name = self.name; |
| | | return sequence; |
| | | } |
| | | |
| | | - (instancetype)concat:(RACStream *)stream { |
| | | NSCParameterAssert(stream != nil); |
| | | |
| | | return [[[RACArraySequence sequenceWithArray:@[ self, stream ] offset:0] |
| | | flatten] |
| | | setNameWithFormat:@"[%@] -concat: %@", self.name, stream]; |
| | | } |
| | | |
| | | - (instancetype)zipWith:(RACSequence *)sequence { |
| | | NSCParameterAssert(sequence != nil); |
| | | |
| | | return [[RACSequence |
| | | sequenceWithHeadBlock:^ id { |
| | | if (self.head == nil || sequence.head == nil) return nil; |
| | | return RACTuplePack(self.head, sequence.head); |
| | | } tailBlock:^ id { |
| | | if (self.tail == nil || [[RACSequence empty] isEqual:self.tail]) return nil; |
| | | if (sequence.tail == nil || [[RACSequence empty] isEqual:sequence.tail]) return nil; |
| | | |
| | | return [self.tail zipWith:sequence.tail]; |
| | | }] |
| | | setNameWithFormat:@"[%@] -zipWith: %@", self.name, sequence]; |
| | | } |
| | | |
| | | #pragma mark Extended methods |
| | | |
| | | - (NSArray *)array { |
| | | NSMutableArray *array = [NSMutableArray array]; |
| | | for (id obj in self) { |
| | | [array addObject:obj]; |
| | | } |
| | | |
| | | return [array copy]; |
| | | } |
| | | |
| | | - (NSEnumerator *)objectEnumerator { |
| | | RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init]; |
| | | enumerator.sequence = self; |
| | | return enumerator; |
| | | } |
| | | |
| | | - (RACSignal *)signal { |
| | | return [[self signalWithScheduler:[RACScheduler scheduler]] setNameWithFormat:@"[%@] -signal", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | __block RACSequence *sequence = self; |
| | | |
| | | return [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) { |
| | | if (sequence.head == nil) { |
| | | [subscriber sendCompleted]; |
| | | return; |
| | | } |
| | | |
| | | [subscriber sendNext:sequence.head]; |
| | | |
| | | sequence = sequence.tail; |
| | | reschedule(); |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -signalWithScheduler: %@", self.name, scheduler]; |
| | | } |
| | | |
| | | - (id)foldLeftWithStart:(id)start reduce:(id (^)(id, id))reduce { |
| | | NSCParameterAssert(reduce != NULL); |
| | | |
| | | if (self.head == nil) return start; |
| | | |
| | | for (id value in self) { |
| | | start = reduce(start, value); |
| | | } |
| | | |
| | | return start; |
| | | } |
| | | |
| | | - (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *))reduce { |
| | | NSCParameterAssert(reduce != NULL); |
| | | |
| | | if (self.head == nil) return start; |
| | | |
| | | RACSequence *rest = [RACSequence sequenceWithHeadBlock:^{ |
| | | return [self.tail foldRightWithStart:start reduce:reduce]; |
| | | } tailBlock:nil]; |
| | | |
| | | return reduce(self.head, rest); |
| | | } |
| | | |
| | | - (BOOL)any:(BOOL (^)(id))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | return [self objectPassingTest:block] != nil; |
| | | } |
| | | |
| | | - (BOOL)all:(BOOL (^)(id))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) { |
| | | return @(accumulator.boolValue && block(value)); |
| | | }]; |
| | | |
| | | return result.boolValue; |
| | | } |
| | | |
| | | - (id)objectPassingTest:(BOOL (^)(id))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | return [self filter:block].head; |
| | | } |
| | | |
| | | - (RACSequence *)eagerSequence { |
| | | return [RACEagerSequence sequenceWithArray:self.array offset:0]; |
| | | } |
| | | |
| | | - (RACSequence *)lazySequence { |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark NSCopying |
| | | |
| | | - (id)copyWithZone:(NSZone *)zone { |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark NSCoding |
| | | |
| | | - (Class)classForCoder { |
| | | // Most sequences should be archived as RACArraySequences. |
| | | return RACArraySequence.class; |
| | | } |
| | | |
| | | - (id)initWithCoder:(NSCoder *)coder { |
| | | if (![self isKindOfClass:RACArraySequence.class]) return [[RACArraySequence alloc] initWithCoder:coder]; |
| | | |
| | | // Decoding is handled in RACArraySequence. |
| | | return [super init]; |
| | | } |
| | | |
| | | - (void)encodeWithCoder:(NSCoder *)coder { |
| | | [coder encodeObject:self.array forKey:@"array"]; |
| | | } |
| | | |
| | | #pragma mark NSFastEnumeration |
| | | |
| | | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len { |
| | | if (state->state == ULONG_MAX) { |
| | | // Enumeration has completed. |
| | | return 0; |
| | | } |
| | | |
| | | // We need to traverse the sequence itself on repeated calls to this |
| | | // method, so use the 'state' field to track the current head. |
| | | RACSequence *(^getSequence)(void) = ^{ |
| | | return (__bridge RACSequence *)(void *)state->state; |
| | | }; |
| | | |
| | | void (^setSequence)(RACSequence *) = ^(RACSequence *sequence) { |
| | | // Release the old sequence and retain the new one. |
| | | CFBridgingRelease((void *)state->state); |
| | | |
| | | state->state = (unsigned long)CFBridgingRetain(sequence); |
| | | }; |
| | | |
| | | void (^complete)(void) = ^{ |
| | | // Release any stored sequence. |
| | | setSequence(nil); |
| | | state->state = ULONG_MAX; |
| | | }; |
| | | |
| | | if (state->state == 0) { |
| | | // Since a sequence doesn't mutate, this just needs to be set to |
| | | // something non-NULL. |
| | | state->mutationsPtr = state->extra; |
| | | |
| | | setSequence(self); |
| | | } |
| | | |
| | | state->itemsPtr = stackbuf; |
| | | |
| | | NSUInteger enumeratedCount = 0; |
| | | while (enumeratedCount < len) { |
| | | RACSequence *seq = getSequence(); |
| | | |
| | | // Because the objects in a sequence may be generated lazily, we want to |
| | | // prevent them from being released until the enumerator's used them. |
| | | __autoreleasing id obj = seq.head; |
| | | if (obj == nil) { |
| | | complete(); |
| | | break; |
| | | } |
| | | |
| | | stackbuf[enumeratedCount++] = obj; |
| | | |
| | | if (seq.tail == nil) { |
| | | complete(); |
| | | break; |
| | | } |
| | | |
| | | setSequence(seq.tail); |
| | | } |
| | | |
| | | return enumeratedCount; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSUInteger)hash { |
| | | return [self.head hash]; |
| | | } |
| | | |
| | | - (BOOL)isEqual:(RACSequence *)seq { |
| | | if (self == seq) return YES; |
| | | if (![seq isKindOfClass:RACSequence.class]) return NO; |
| | | |
| | | for (id<NSObject> selfObj in self) { |
| | | id<NSObject> seqObj = seq.head; |
| | | |
| | | // Handles the nil case too. |
| | | if (![seqObj isEqual:selfObj]) return NO; |
| | | |
| | | seq = seq.tail; |
| | | } |
| | | |
| | | // self is now depleted -- the argument should be too. |
| | | return (seq.head == nil); |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACSequence (Deprecated) |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | |
| | | - (id)foldLeftWithStart:(id)start combine:(id (^)(id accumulator, id value))combine { |
| | | return [self foldLeftWithStart:start reduce:combine]; |
| | | } |
| | | |
| | | - (id)foldRightWithStart:(id)start combine:(id (^)(id first, RACSequence *rest))combine { |
| | | return [self foldRightWithStart:start reduce:combine]; |
| | | } |
| | | |
| | | #pragma clang diagnostic pop |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSerialDisposable.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-07-22. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACDisposable.h" |
| | | |
| | | /// A disposable that contains exactly one other disposable and allows it to be |
| | | /// swapped out atomically. |
| | | @interface RACSerialDisposable : RACDisposable |
| | | |
| | | /// The inner disposable managed by the serial disposable. |
| | | /// |
| | | /// This property is thread-safe for reading and writing. However, if you want to |
| | | /// read the current value _and_ write a new one atomically, use |
| | | /// -swapInDisposable: instead. |
| | | /// |
| | | /// Disposing of the receiver will also dispose of the current disposable set for |
| | | /// this property, then set the property to nil. If any new disposable is set |
| | | /// after the receiver is disposed, it will be disposed immediately and this |
| | | /// property will remain set to nil. |
| | | @property (atomic, strong) RACDisposable *disposable; |
| | | |
| | | /// Creates a serial disposable which will wrap the given disposable. |
| | | /// |
| | | /// disposable - The value to set for `disposable`. This may be nil. |
| | | /// |
| | | /// Returns a RACSerialDisposable, or nil if an error occurs. |
| | | + (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable; |
| | | |
| | | /// Atomically swaps the receiver's `disposable` for `newDisposable`. |
| | | /// |
| | | /// newDisposable - The new value for `disposable`. If the receiver has already |
| | | /// been disposed, this disposable will be too, and `disposable` |
| | | /// will remain set to nil. This argument may be nil. |
| | | /// |
| | | /// Returns the previous value for the `disposable` property. |
| | | - (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSerialDisposable.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-07-22. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSerialDisposable.h" |
| | | #import <libkern/OSAtomic.h> |
| | | |
| | | @interface RACSerialDisposable () { |
| | | // The receiver's `disposable`. This variable must only be referenced while |
| | | // _spinLock is held. |
| | | RACDisposable * _disposable; |
| | | |
| | | // YES if the receiver has been disposed. This variable must only be modified |
| | | // while _spinLock is held. |
| | | BOOL _disposed; |
| | | |
| | | // A spinlock to protect access to _disposable and _disposed. |
| | | // |
| | | // It must be used when _disposable is mutated or retained and when _disposed |
| | | // is mutated. |
| | | OSSpinLock _spinLock; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACSerialDisposable |
| | | |
| | | #pragma mark Properties |
| | | |
| | | - (BOOL)isDisposed { |
| | | return _disposed; |
| | | } |
| | | |
| | | - (RACDisposable *)disposable { |
| | | RACDisposable *result; |
| | | |
| | | OSSpinLockLock(&_spinLock); |
| | | result = _disposable; |
| | | OSSpinLockUnlock(&_spinLock); |
| | | |
| | | return result; |
| | | } |
| | | |
| | | - (void)setDisposable:(RACDisposable *)disposable { |
| | | [self swapInDisposable:disposable]; |
| | | } |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable { |
| | | RACSerialDisposable *serialDisposable = [[self alloc] init]; |
| | | serialDisposable.disposable = disposable; |
| | | return serialDisposable; |
| | | } |
| | | |
| | | - (id)initWithBlock:(void (^)(void))block { |
| | | self = [self init]; |
| | | if (self == nil) return nil; |
| | | |
| | | self.disposable = [RACDisposable disposableWithBlock:block]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark Inner Disposable |
| | | |
| | | - (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable { |
| | | RACDisposable *existingDisposable; |
| | | BOOL alreadyDisposed; |
| | | |
| | | OSSpinLockLock(&_spinLock); |
| | | alreadyDisposed = _disposed; |
| | | if (!alreadyDisposed) { |
| | | existingDisposable = _disposable; |
| | | _disposable = newDisposable; |
| | | } |
| | | OSSpinLockUnlock(&_spinLock); |
| | | |
| | | if (alreadyDisposed) { |
| | | [newDisposable dispose]; |
| | | return nil; |
| | | } |
| | | |
| | | return existingDisposable; |
| | | } |
| | | |
| | | #pragma mark Disposal |
| | | |
| | | - (void)dispose { |
| | | RACDisposable *existingDisposable; |
| | | |
| | | OSSpinLockLock(&_spinLock); |
| | | if (!_disposed) { |
| | | existingDisposable = _disposable; |
| | | _disposed = YES; |
| | | _disposable = nil; |
| | | } |
| | | OSSpinLockUnlock(&_spinLock); |
| | | |
| | | [existingDisposable dispose]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSignal+Operations.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-09-06. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "RACSignal.h" |
| | | |
| | | /// The domain for errors originating in RACSignal operations. |
| | | extern NSString * const RACSignalErrorDomain; |
| | | |
| | | /// The error code used with -timeout:. |
| | | extern const NSInteger RACSignalErrorTimedOut; |
| | | |
| | | /// The error code used when a value passed into +switch:cases:default: does not |
| | | /// match any of the cases, and no default was given. |
| | | extern const NSInteger RACSignalErrorNoMatchingCase; |
| | | |
| | | @class RACCommand; |
| | | @class RACDisposable; |
| | | @class RACMulticastConnection; |
| | | @class RACScheduler; |
| | | @class RACSequence; |
| | | @class RACSubject; |
| | | @class RACTuple; |
| | | @protocol RACSubscriber; |
| | | |
| | | @interface RACSignal (Operations) |
| | | |
| | | /// Do the given block on `next`. This should be used to inject side effects into |
| | | /// the signal. |
| | | - (RACSignal *)doNext:(void (^)(id x))block; |
| | | |
| | | /// Do the given block on `error`. This should be used to inject side effects |
| | | /// into the signal. |
| | | - (RACSignal *)doError:(void (^)(NSError *error))block; |
| | | |
| | | /// Do the given block on `completed`. This should be used to inject side effects |
| | | /// into the signal. |
| | | - (RACSignal *)doCompleted:(void (^)(void))block; |
| | | |
| | | /// Sends `next`s only if we don't receive another `next` in `interval` seconds. |
| | | /// |
| | | /// If a `next` is received, and then another `next` is received before |
| | | /// `interval` seconds have passed, the first value is discarded. |
| | | /// |
| | | /// After `interval` seconds have passed since the most recent `next` was sent, |
| | | /// the most recent `next` is forwarded on the scheduler that the value was |
| | | /// originally received on. If +[RACScheduler currentScheduler] was nil at the |
| | | /// time, a private background scheduler is used. |
| | | /// |
| | | /// Returns a signal which sends throttled and delayed `next` events. Completion |
| | | /// and errors are always forwarded immediately. |
| | | - (RACSignal *)throttle:(NSTimeInterval)interval; |
| | | |
| | | /// Throttles `next`s for which `predicate` returns YES. |
| | | /// |
| | | /// When `predicate` returns YES for a `next`: |
| | | /// |
| | | /// 1. If another `next` is received before `interval` seconds have passed, the |
| | | /// prior value is discarded. This happens regardless of whether the new |
| | | /// value will be throttled. |
| | | /// 2. After `interval` seconds have passed since the value was originally |
| | | /// received, it will be forwarded on the scheduler that it was received |
| | | /// upon. If +[RACScheduler currentScheduler] was nil at the time, a private |
| | | /// background scheduler is used. |
| | | /// |
| | | /// When `predicate` returns NO for a `next`, it is forwarded immediately, |
| | | /// without any throttling. |
| | | /// |
| | | /// interval - The number of seconds for which to buffer the latest value that |
| | | /// passes `predicate`. |
| | | /// predicate - Passed each `next` from the receiver, this block returns |
| | | /// whether the given value should be throttled. This argument must |
| | | /// not be nil. |
| | | /// |
| | | /// Returns a signal which sends `next` events, throttled when `predicate` |
| | | /// returns YES. Completion and errors are always forwarded immediately. |
| | | - (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate; |
| | | |
| | | /// Forwards `next` and `completed` events after delaying for `interval` seconds |
| | | /// on the current scheduler (on which the events were delivered). |
| | | /// |
| | | /// If +[RACScheduler currentScheduler] is nil when `next` or `completed` is |
| | | /// received, a private background scheduler is used. |
| | | /// |
| | | /// Returns a signal which sends delayed `next` and `completed` events. Errors |
| | | /// are always forwarded immediately. |
| | | - (RACSignal *)delay:(NSTimeInterval)interval; |
| | | |
| | | /// Resubscribes when the signal completes. |
| | | - (RACSignal *)repeat; |
| | | |
| | | /// Executes the given block each time a subscription is created. |
| | | /// |
| | | /// block - A block which defines the subscription side effects. Cannot be `nil`. |
| | | /// |
| | | /// Example: |
| | | /// |
| | | /// // Write new file, with backup. |
| | | /// [[[[fileManager |
| | | /// rac_createFileAtPath:path contents:data] |
| | | /// initially:^{ |
| | | /// // 2. Second, backup current file |
| | | /// [fileManager moveItemAtPath:path toPath:backupPath error:nil]; |
| | | /// }] |
| | | /// initially:^{ |
| | | /// // 1. First, acquire write lock. |
| | | /// [writeLock lock]; |
| | | /// }] |
| | | /// finally:^{ |
| | | /// [writeLock unlock]; |
| | | /// }]; |
| | | /// |
| | | /// Returns a signal that passes through all events of the receiver, plus |
| | | /// introduces side effects which occur prior to any subscription side effects |
| | | /// of the receiver. |
| | | - (RACSignal *)initially:(void (^)(void))block; |
| | | |
| | | /// Executes the given block when the signal completes or errors. |
| | | - (RACSignal *)finally:(void (^)(void))block; |
| | | |
| | | /// Divides the receiver's `next`s into buffers which deliver every `interval` |
| | | /// seconds. |
| | | /// |
| | | /// interval - The interval in which values are grouped into one buffer. |
| | | /// scheduler - The scheduler upon which the returned signal will deliver its |
| | | /// values. This must not be nil or +[RACScheduler |
| | | /// immediateScheduler]. |
| | | /// |
| | | /// Returns a signal which sends RACTuples of the buffered values at each |
| | | /// interval on `scheduler`. When the receiver completes, any currently-buffered |
| | | /// values will be sent immediately. |
| | | - (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; |
| | | |
| | | /// Collects all receiver's `next`s into a NSArray. Nil values will be converted |
| | | /// to NSNull. |
| | | /// |
| | | /// This corresponds to the `ToArray` method in Rx. |
| | | /// |
| | | /// Returns a signal which sends a single NSArray when the receiver completes |
| | | /// successfully. |
| | | - (RACSignal *)collect; |
| | | |
| | | /// Takes the last `count` `next`s after the receiving signal completes. |
| | | - (RACSignal *)takeLast:(NSUInteger)count; |
| | | |
| | | /// Combines the latest values from the receiver and the given signal into |
| | | /// RACTuples, once both have sent at least one `next`. |
| | | /// |
| | | /// Any additional `next`s will result in a new RACTuple with the latest values |
| | | /// from both signals. |
| | | /// |
| | | /// signal - The signal to combine with. This argument must not be nil. |
| | | /// |
| | | /// Returns a signal which sends RACTuples of the combined values, forwards any |
| | | /// `error` events, and completes when both input signals complete. |
| | | - (RACSignal *)combineLatestWith:(RACSignal *)signal; |
| | | |
| | | /// Combines the latest values from the given signals into RACTuples, once all |
| | | /// the signals have sent at least one `next`. |
| | | /// |
| | | /// Any additional `next`s will result in a new RACTuple with the latest values |
| | | /// from all signals. |
| | | /// |
| | | /// signals - The signals to combine. If this collection is empty, the returned |
| | | /// signal will immediately complete upon subscription. |
| | | /// |
| | | /// Returns a signal which sends RACTuples of the combined values, forwards any |
| | | /// `error` events, and completes when all input signals complete. |
| | | + (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals; |
| | | |
| | | /// Combines signals using +combineLatest:, then reduces the resulting tuples |
| | | /// into a single value using -reduceEach:. |
| | | /// |
| | | /// signals - The signals to combine. If this collection is empty, the |
| | | /// returned signal will immediately complete upon subscription. |
| | | /// reduceBlock - The block which reduces the latest values from all the |
| | | /// signals into one value. It must take as many arguments as the |
| | | /// number of signals given. Each argument will be an object |
| | | /// argument. The return value must be an object. This argument |
| | | /// must not be nil. |
| | | /// |
| | | /// Example: |
| | | /// |
| | | /// [RACSignal combineLatest:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) { |
| | | /// return [NSString stringWithFormat:@"%@: %@", string, number]; |
| | | /// }]; |
| | | /// |
| | | /// Returns a signal which sends the results from each invocation of |
| | | /// `reduceBlock`. |
| | | + (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock; |
| | | |
| | | /// Merges the receiver and the given signal with `+merge:` and returns the |
| | | /// resulting signal. |
| | | - (RACSignal *)merge:(RACSignal *)signal; |
| | | |
| | | /// Sends the latest `next` from any of the signals. |
| | | /// |
| | | /// Returns a signal that passes through values from each of the given signals, |
| | | /// and sends `completed` when all of them complete. If any signal sends an error, |
| | | /// the returned signal sends `error` immediately. |
| | | + (RACSignal *)merge:(id<NSFastEnumeration>)signals; |
| | | |
| | | /// Merges the signals sent by the receiver into a flattened signal, but only |
| | | /// subscribes to `maxConcurrent` number of signals at a time. New signals are |
| | | /// queued and subscribed to as other signals complete. |
| | | /// |
| | | /// If an error occurs on any of the signals, it is sent on the returned signal. |
| | | /// It completes only after the receiver and all sent signals have completed. |
| | | /// |
| | | /// This corresponds to `Merge<TSource>(IObservable<IObservable<TSource>>, Int32)` |
| | | /// in Rx. |
| | | /// |
| | | /// maxConcurrent - the maximum number of signals to subscribe to at a |
| | | /// time. If 0, it subscribes to an unlimited number of |
| | | /// signals. |
| | | - (RACSignal *)flatten:(NSUInteger)maxConcurrent; |
| | | |
| | | /// Ignores all `next`s from the receiver, waits for the receiver to complete, |
| | | /// then subscribes to a new signal. |
| | | /// |
| | | /// block - A block which will create or obtain a new signal to subscribe to, |
| | | /// executed only after the receiver completes. This block must not be |
| | | /// nil, and it must not return a nil signal. |
| | | /// |
| | | /// Returns a signal which will pass through the events of the signal created in |
| | | /// `block`. If the receiver errors out, the returned signal will error as well. |
| | | - (RACSignal *)then:(RACSignal * (^)(void))block; |
| | | |
| | | /// Concats the inner signals of a signal of signals. |
| | | - (RACSignal *)concat; |
| | | |
| | | /// Aggregates the `next` values of the receiver into a single combined value. |
| | | /// |
| | | /// The algorithm proceeds as follows: |
| | | /// |
| | | /// 1. `start` is passed into the block as the `running` value, and the first |
| | | /// element of the receiver is passed into the block as the `next` value. |
| | | /// 2. The result of the invocation (`running`) and the next element of the |
| | | /// receiver (`next`) is passed into `reduceBlock`. |
| | | /// 3. Steps 2 and 3 are repeated until all values have been processed. |
| | | /// 4. The last result of `reduceBlock` is sent on the returned signal. |
| | | /// |
| | | /// This method is similar to -scanWithStart:reduce:, except that only the |
| | | /// final result is sent on the returned signal. |
| | | /// |
| | | /// start - The value to be combined with the first element of the |
| | | /// receiver. This value may be `nil`. |
| | | /// reduceBlock - The block that describes how to combine values of the |
| | | /// receiver. If the receiver is empty, this block will never be |
| | | /// invoked. Cannot be nil. |
| | | /// |
| | | /// Returns a signal that will send the aggregated value when the receiver |
| | | /// completes, then itself complete. If the receiver never sends any values, |
| | | /// `start` will be sent instead. |
| | | - (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock; |
| | | |
| | | /// Aggregates the `next` values of the receiver into a single combined value. |
| | | /// This is indexed version of -aggregateWithStart:reduce:. |
| | | /// |
| | | /// start - The value to be combined with the first element of the |
| | | /// receiver. This value may be `nil`. |
| | | /// reduceBlock - The block that describes how to combine values of the |
| | | /// receiver. This block takes zero-based index value as the last |
| | | /// parameter. If the receiver is empty, this block will never be |
| | | /// invoked. Cannot be nil. |
| | | /// |
| | | /// Returns a signal that will send the aggregated value when the receiver |
| | | /// completes, then itself complete. If the receiver never sends any values, |
| | | /// `start` will be sent instead. |
| | | - (RACSignal *)aggregateWithStart:(id)start reduceWithIndex:(id (^)(id running, id next, NSUInteger index))reduceBlock; |
| | | |
| | | /// Aggregates the `next` values of the receiver into a single combined value. |
| | | /// |
| | | /// This invokes `startFactory` block on each subscription, then calls |
| | | /// -aggregateWithStart:reduce: with the return value of the block as start value. |
| | | /// |
| | | /// startFactory - The block that returns start value which will be combined |
| | | /// with the first element of the receiver. Cannot be nil. |
| | | /// reduceBlock - The block that describes how to combine values of the |
| | | /// receiver. If the receiver is empty, this block will never be |
| | | /// invoked. Cannot be nil. |
| | | /// |
| | | /// Returns a signal that will send the aggregated value when the receiver |
| | | /// completes, then itself complete. If the receiver never sends any values, |
| | | /// the return value of `startFactory` will be sent instead. |
| | | - (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory reduce:(id (^)(id running, id next))reduceBlock; |
| | | |
| | | /// Invokes -setKeyPath:onObject:nilValue: with `nil` for the nil value. |
| | | /// |
| | | /// WARNING: Under certain conditions, this method is known to be thread-unsafe. |
| | | /// See the description in -setKeyPath:onObject:nilValue:. |
| | | - (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object; |
| | | |
| | | /// Binds the receiver to an object, automatically setting the given key path on |
| | | /// every `next`. When the signal completes, the binding is automatically |
| | | /// disposed of. |
| | | /// |
| | | /// WARNING: Under certain conditions, this method is known to be thread-unsafe. |
| | | /// A crash can result if `object` is deallocated concurrently on |
| | | /// another thread within a window of time between a value being sent |
| | | /// on this signal and immediately prior to the invocation of |
| | | /// -setValue:forKeyPath:, which sets the property. To prevent this, |
| | | /// ensure `object` is deallocated on the same thread the receiver |
| | | /// sends on, or ensure that the returned disposable is disposed of |
| | | /// before `object` deallocates. |
| | | /// See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/1184 |
| | | /// |
| | | /// Sending an error on the signal is considered undefined behavior, and will |
| | | /// generate an assertion failure in Debug builds. |
| | | /// |
| | | /// A given key on an object should only have one active signal bound to it at any |
| | | /// given time. Binding more than one signal to the same property is considered |
| | | /// undefined behavior. |
| | | /// |
| | | /// keyPath - The key path to update with `next`s from the receiver. |
| | | /// object - The object that `keyPath` is relative to. |
| | | /// nilValue - The value to set at the key path whenever `nil` is sent by the |
| | | /// receiver. This may be nil when binding to object properties, but |
| | | /// an NSValue should be used for primitive properties, to avoid an |
| | | /// exception if `nil` is sent (which might occur if an intermediate |
| | | /// object is set to `nil`). |
| | | /// |
| | | /// Returns a disposable which can be used to terminate the binding. |
| | | - (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue; |
| | | |
| | | /// Sends NSDate.date every `interval` seconds. |
| | | /// |
| | | /// interval - The time interval in seconds at which the current time is sent. |
| | | /// scheduler - The scheduler upon which the current NSDate should be sent. This |
| | | /// must not be nil or +[RACScheduler immediateScheduler]. |
| | | /// |
| | | /// Returns a signal that sends the current date/time every `interval` on |
| | | /// `scheduler`. |
| | | + (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; |
| | | |
| | | /// Sends NSDate.date at intervals of at least `interval` seconds, up to |
| | | /// approximately `interval` + `leeway` seconds. |
| | | /// |
| | | /// The created signal will defer sending each `next` for at least `interval` |
| | | /// seconds, and for an additional amount of time up to `leeway` seconds in the |
| | | /// interest of performance or power consumption. Note that some additional |
| | | /// latency is to be expected, even when specifying a `leeway` of 0. |
| | | /// |
| | | /// interval - The base interval between `next`s. |
| | | /// scheduler - The scheduler upon which the current NSDate should be sent. This |
| | | /// must not be nil or +[RACScheduler immediateScheduler]. |
| | | /// leeway - The maximum amount of additional time the `next` can be deferred. |
| | | /// |
| | | /// Returns a signal that sends the current date/time at intervals of at least |
| | | /// `interval seconds` up to approximately `interval` + `leeway` seconds on |
| | | /// `scheduler`. |
| | | + (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway; |
| | | |
| | | /// Takes `next`s until the `signalTrigger` sends `next` or `completed`. |
| | | /// |
| | | /// Returns a signal which passes through all events from the receiver until |
| | | /// `signalTrigger` sends `next` or `completed`, at which point the returned signal |
| | | /// will send `completed`. |
| | | - (RACSignal *)takeUntil:(RACSignal *)signalTrigger; |
| | | |
| | | /// Takes `next`s until the `replacement` sends an event. |
| | | /// |
| | | /// replacement - The signal which replaces the receiver as soon as it sends an |
| | | /// event. |
| | | /// |
| | | /// Returns a signal which passes through `next`s and `error` from the receiver |
| | | /// until `replacement` sends an event, at which point the returned signal will |
| | | /// send that event and switch to passing through events from `replacement` |
| | | /// instead, regardless of whether the receiver has sent events already. |
| | | - (RACSignal *)takeUntilReplacement:(RACSignal *)replacement; |
| | | |
| | | /// Subscribes to the returned signal when an error occurs. |
| | | - (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock; |
| | | |
| | | /// Subscribes to the given signal when an error occurs. |
| | | - (RACSignal *)catchTo:(RACSignal *)signal; |
| | | |
| | | /// Runs `tryBlock` against each of the receiver's values, passing values |
| | | /// until `tryBlock` returns NO, or the receiver completes. |
| | | /// |
| | | /// tryBlock - An action to run against each of the receiver's values. |
| | | /// The block should return YES to indicate that the action was |
| | | /// successful. This block must not be nil. |
| | | /// |
| | | /// Example: |
| | | /// |
| | | /// // The returned signal will send an error if data values cannot be |
| | | /// // written to `someFileURL`. |
| | | /// [signal try:^(NSData *data, NSError **errorPtr) { |
| | | /// return [data writeToURL:someFileURL options:NSDataWritingAtomic error:errorPtr]; |
| | | /// }]; |
| | | /// |
| | | /// Returns a signal which passes through all the values of the receiver. If |
| | | /// `tryBlock` fails for any value, the returned signal will error using the |
| | | /// `NSError` passed out from the block. |
| | | - (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock; |
| | | |
| | | /// Runs `mapBlock` against each of the receiver's values, mapping values until |
| | | /// `mapBlock` returns nil, or the receiver completes. |
| | | /// |
| | | /// mapBlock - An action to map each of the receiver's values. The block should |
| | | /// return a non-nil value to indicate that the action was successful. |
| | | /// This block must not be nil. |
| | | /// |
| | | /// Example: |
| | | /// |
| | | /// // The returned signal will send an error if data cannot be read from |
| | | /// // `fileURL`. |
| | | /// [signal tryMap:^(NSURL *fileURL, NSError **errorPtr) { |
| | | /// return [NSData dataWithContentsOfURL:fileURL options:0 error:errorPtr]; |
| | | /// }]; |
| | | /// |
| | | /// Returns a signal which transforms all the values of the receiver. If |
| | | /// `mapBlock` returns nil for any value, the returned signal will error using |
| | | /// the `NSError` passed out from the block. |
| | | - (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock; |
| | | |
| | | /// Returns the first `next`. Note that this is a blocking call. |
| | | - (id)first; |
| | | |
| | | /// Returns the first `next` or `defaultValue` if the signal completes or errors |
| | | /// without sending a `next`. Note that this is a blocking call. |
| | | - (id)firstOrDefault:(id)defaultValue; |
| | | |
| | | /// Returns the first `next` or `defaultValue` if the signal completes or errors |
| | | /// without sending a `next`. If an error occurs success will be NO and error |
| | | /// will be populated. Note that this is a blocking call. |
| | | /// |
| | | /// Both success and error may be NULL. |
| | | - (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error; |
| | | |
| | | /// Blocks the caller and waits for the signal to complete. |
| | | /// |
| | | /// error - If not NULL, set to any error that occurs. |
| | | /// |
| | | /// Returns whether the signal completed successfully. If NO, `error` will be set |
| | | /// to the error that occurred. |
| | | - (BOOL)waitUntilCompleted:(NSError **)error; |
| | | |
| | | /// Defers creation of a signal until the signal's actually subscribed to. |
| | | /// |
| | | /// This can be used to effectively turn a hot signal into a cold signal. |
| | | + (RACSignal *)defer:(RACSignal * (^)(void))block; |
| | | |
| | | /// Every time the receiver sends a new RACSignal, subscribes and sends `next`s and |
| | | /// `error`s only for that signal. |
| | | /// |
| | | /// The receiver must be a signal of signals. |
| | | /// |
| | | /// Returns a signal which passes through `next`s and `error`s from the latest |
| | | /// signal sent by the receiver, and sends `completed` when both the receiver and |
| | | /// the last sent signal complete. |
| | | - (RACSignal *)switchToLatest; |
| | | |
| | | /// Switches between the signals in `cases` as well as `defaultSignal` based on |
| | | /// the latest value sent by `signal`. |
| | | /// |
| | | /// signal - A signal of objects used as keys in the `cases` dictionary. |
| | | /// This argument must not be nil. |
| | | /// cases - A dictionary that has signals as values. This argument must |
| | | /// not be nil. A RACTupleNil key in this dictionary will match |
| | | /// nil `next` events that are received on `signal`. |
| | | /// defaultSignal - The signal to pass through after `signal` sends a value for |
| | | /// which `cases` does not contain a signal. If nil, any |
| | | /// unmatched values will result in |
| | | /// a RACSignalErrorNoMatchingCase error. |
| | | /// |
| | | /// Returns a signal which passes through `next`s and `error`s from one of the |
| | | /// the signals in `cases` or `defaultSignal`, and sends `completed` when both |
| | | /// `signal` and the last used signal complete. If no `defaultSignal` is given, |
| | | /// an unmatched `next` will result in an error on the returned signal. |
| | | + (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal; |
| | | |
| | | /// Switches between `trueSignal` and `falseSignal` based on the latest value |
| | | /// sent by `boolSignal`. |
| | | /// |
| | | /// boolSignal - A signal of BOOLs determining whether `trueSignal` or |
| | | /// `falseSignal` should be active. This argument must not be nil. |
| | | /// trueSignal - The signal to pass through after `boolSignal` has sent YES. |
| | | /// This argument must not be nil. |
| | | /// falseSignal - The signal to pass through after `boolSignal` has sent NO. This |
| | | /// argument must not be nil. |
| | | /// |
| | | /// Returns a signal which passes through `next`s and `error`s from `trueSignal` |
| | | /// and/or `falseSignal`, and sends `completed` when both `boolSignal` and the |
| | | /// last switched signal complete. |
| | | + (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal; |
| | | |
| | | /// Adds every `next` to an array. Nils are represented by NSNulls. Note that |
| | | /// this is a blocking call. |
| | | /// |
| | | /// **This is not the same as the `ToArray` method in Rx.** See -collect for |
| | | /// that behavior instead. |
| | | /// |
| | | /// Returns the array of `next` values, or nil if an error occurs. |
| | | - (NSArray *)toArray; |
| | | |
| | | /// Adds every `next` to a sequence. Nils are represented by NSNulls. |
| | | /// |
| | | /// This corresponds to the `ToEnumerable` method in Rx. |
| | | /// |
| | | /// Returns a sequence which provides values from the signal as they're sent. |
| | | /// Trying to retrieve a value from the sequence which has not yet been sent will |
| | | /// block. |
| | | @property (nonatomic, strong, readonly) RACSequence *sequence; |
| | | |
| | | /// Creates and returns a multicast connection. This allows you to share a single |
| | | /// subscription to the underlying signal. |
| | | - (RACMulticastConnection *)publish; |
| | | |
| | | /// Creates and returns a multicast connection that pushes values into the given |
| | | /// subject. This allows you to share a single subscription to the underlying |
| | | /// signal. |
| | | - (RACMulticastConnection *)multicast:(RACSubject *)subject; |
| | | |
| | | /// Multicasts the signal to a RACReplaySubject of unlimited capacity, and |
| | | /// immediately connects to the resulting RACMulticastConnection. |
| | | /// |
| | | /// Returns the connected, multicasted signal. |
| | | - (RACSignal *)replay; |
| | | |
| | | /// Multicasts the signal to a RACReplaySubject of capacity 1, and immediately |
| | | /// connects to the resulting RACMulticastConnection. |
| | | /// |
| | | /// Returns the connected, multicasted signal. |
| | | - (RACSignal *)replayLast; |
| | | |
| | | /// Multicasts the signal to a RACReplaySubject of unlimited capacity, and |
| | | /// lazily connects to the resulting RACMulticastConnection. |
| | | /// |
| | | /// This means the returned signal will subscribe to the multicasted signal only |
| | | /// when the former receives its first subscription. |
| | | /// |
| | | /// Returns the lazily connected, multicasted signal. |
| | | - (RACSignal *)replayLazily; |
| | | |
| | | /// Sends an error after `interval` seconds if the source doesn't complete |
| | | /// before then. |
| | | /// |
| | | /// The error will be in the RACSignalErrorDomain and have a code of |
| | | /// RACSignalErrorTimedOut. |
| | | /// |
| | | /// interval - The number of seconds after which the signal should error out. |
| | | /// scheduler - The scheduler upon which any timeout error should be sent. This |
| | | /// must not be nil or +[RACScheduler immediateScheduler]. |
| | | /// |
| | | /// Returns a signal that passes through the receiver's events, until the stream |
| | | /// finishes or times out, at which point an error will be sent on `scheduler`. |
| | | - (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; |
| | | |
| | | /// Creates and returns a signal that delivers its events on the given scheduler. |
| | | /// Any side effects of the receiver will still be performed on the original |
| | | /// thread. |
| | | /// |
| | | /// This is ideal when the signal already performs its work on the desired |
| | | /// thread, but you want to handle its events elsewhere. |
| | | /// |
| | | /// This corresponds to the `ObserveOn` method in Rx. |
| | | - (RACSignal *)deliverOn:(RACScheduler *)scheduler; |
| | | |
| | | /// Creates and returns a signal that executes its side effects and delivers its |
| | | /// events on the given scheduler. |
| | | /// |
| | | /// Use of this operator should be avoided whenever possible, because the |
| | | /// receiver's side effects may not be safe to run on another thread. If you just |
| | | /// want to receive the signal's events on `scheduler`, use -deliverOn: instead. |
| | | - (RACSignal *)subscribeOn:(RACScheduler *)scheduler; |
| | | |
| | | /// Creates and returns a signal that delivers its events on the main thread. |
| | | /// If events are already being sent on the main thread, they may be passed on |
| | | /// without delay. An event will instead be queued for later delivery on the main |
| | | /// thread if sent on another thread, or if a previous event is already being |
| | | /// processed, or has been queued. |
| | | /// |
| | | /// Any side effects of the receiver will still be performed on the original |
| | | /// thread. |
| | | /// |
| | | /// This can be used when a signal will cause UI updates, to avoid potential |
| | | /// flicker caused by delayed delivery of events, such as the first event from |
| | | /// a RACObserve at view instantiation. |
| | | - (RACSignal *)deliverOnMainThread; |
| | | |
| | | /// Groups each received object into a group, as determined by calling `keyBlock` |
| | | /// with that object. The object sent is transformed by calling `transformBlock` |
| | | /// with the object. If `transformBlock` is nil, it sends the original object. |
| | | /// |
| | | /// The returned signal is a signal of RACGroupedSignal. |
| | | - (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock transform:(id (^)(id object))transformBlock; |
| | | |
| | | /// Calls -[RACSignal groupBy:keyBlock transform:nil]. |
| | | - (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock; |
| | | |
| | | /// Sends an [NSNumber numberWithBool:YES] if the receiving signal sends any |
| | | /// objects. |
| | | - (RACSignal *)any; |
| | | |
| | | /// Sends an [NSNumber numberWithBool:YES] if the receiving signal sends any |
| | | /// objects that pass `predicateBlock`. |
| | | /// |
| | | /// predicateBlock - cannot be nil. |
| | | - (RACSignal *)any:(BOOL (^)(id object))predicateBlock; |
| | | |
| | | /// Sends an [NSNumber numberWithBool:YES] if all the objects the receiving |
| | | /// signal sends pass `predicateBlock`. |
| | | /// |
| | | /// predicateBlock - cannot be nil. |
| | | - (RACSignal *)all:(BOOL (^)(id object))predicateBlock; |
| | | |
| | | /// Resubscribes to the receiving signal if an error occurs, up until it has |
| | | /// retried the given number of times. |
| | | /// |
| | | /// retryCount - if 0, it keeps retrying until it completes. |
| | | - (RACSignal *)retry:(NSInteger)retryCount; |
| | | |
| | | /// Resubscribes to the receiving signal if an error occurs. |
| | | - (RACSignal *)retry; |
| | | |
| | | /// Sends the latest value from the receiver only when `sampler` sends a value. |
| | | /// The returned signal could repeat values if `sampler` fires more often than |
| | | /// the receiver. Values from `sampler` are ignored before the receiver sends |
| | | /// its first value. |
| | | /// |
| | | /// sampler - The signal that controls when the latest value from the receiver |
| | | /// is sent. Cannot be nil. |
| | | - (RACSignal *)sample:(RACSignal *)sampler; |
| | | |
| | | /// Ignores all `next`s from the receiver. |
| | | /// |
| | | /// Returns a signal which only passes through `error` or `completed` events from |
| | | /// the receiver. |
| | | - (RACSignal *)ignoreValues; |
| | | |
| | | /// Converts each of the receiver's events into a RACEvent object. |
| | | /// |
| | | /// Returns a signal which sends the receiver's events as RACEvents, and |
| | | /// completes after the receiver sends `completed` or `error`. |
| | | - (RACSignal *)materialize; |
| | | |
| | | /// Converts each RACEvent in the receiver back into "real" RACSignal events. |
| | | /// |
| | | /// Returns a signal which sends `next` for each value RACEvent, `error` for each |
| | | /// error RACEvent, and `completed` for each completed RACEvent. |
| | | - (RACSignal *)dematerialize; |
| | | |
| | | /// Inverts each NSNumber-wrapped BOOL sent by the receiver. It will assert if |
| | | /// the receiver sends anything other than NSNumbers. |
| | | /// |
| | | /// Returns a signal of inverted NSNumber-wrapped BOOLs. |
| | | - (RACSignal *)not; |
| | | |
| | | /// Performs a boolean AND on all of the RACTuple of NSNumbers in sent by the receiver. |
| | | /// |
| | | /// Asserts if the receiver sends anything other than a RACTuple of one or more NSNumbers. |
| | | /// |
| | | /// Returns a signal that applies AND to each NSNumber in the tuple. |
| | | - (RACSignal *)and; |
| | | |
| | | /// Performs a boolean OR on all of the RACTuple of NSNumbers in sent by the receiver. |
| | | /// |
| | | /// Asserts if the receiver sends anything other than a RACTuple of one or more NSNumbers. |
| | | /// |
| | | /// Returns a signal that applies OR to each NSNumber in the tuple. |
| | | - (RACSignal *)or; |
| | | |
| | | /// Sends the result of calling the block with arguments as packed in each RACTuple |
| | | /// sent by the receiver. |
| | | /// |
| | | /// The receiver must send tuple values, where the first element of the tuple is |
| | | /// a block, taking a number of parameters equal to the count of the remaining |
| | | /// elements of the tuple, and returning an object. Each block must take at least |
| | | /// one argument, so each tuple must contain at least 2 elements. |
| | | /// |
| | | /// Example: |
| | | /// |
| | | /// RACSignal *adder = [RACSignal return:^(NSNumber *a, NSNumber *b) { |
| | | /// return @(a.intValue + b.intValue); |
| | | /// }]; |
| | | /// RACSignal *sums = [[RACSignal |
| | | /// combineLatest:@[ adder, as, bs ]] |
| | | /// reduceApply]; |
| | | /// |
| | | /// Returns a signal of the result of applying the first element of each tuple |
| | | /// to the remaining elements. |
| | | - (RACSignal *)reduceApply; |
| | | |
| | | @end |
| | | |
| | | @interface RACSignal (OperationsDeprecated) |
| | | |
| | | - (RACSignal *)windowWithStart:(RACSignal *)openSignal close:(RACSignal * (^)(RACSignal *start))closeBlock __attribute__((deprecated("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587"))); |
| | | - (RACSignal *)buffer:(NSUInteger)bufferCount __attribute__((deprecated("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587"))); |
| | | - (RACSignal *)let:(RACSignal * (^)(RACSignal *sharedSignal))letBlock __attribute__((deprecated("Use -publish instead"))); |
| | | + (RACSignal *)interval:(NSTimeInterval)interval __attribute__((deprecated("Use +interval:onScheduler: instead"))); |
| | | + (RACSignal *)interval:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway __attribute__((deprecated("Use +interval:onScheduler:withLeeway: instead"))); |
| | | - (RACSignal *)bufferWithTime:(NSTimeInterval)interval __attribute__((deprecated("Use -bufferWithTime:onScheduler: instead"))); |
| | | - (RACSignal *)timeout:(NSTimeInterval)interval __attribute__((deprecated("Use -timeout:onScheduler: instead"))); |
| | | - (RACDisposable *)toProperty:(NSString *)keyPath onObject:(NSObject *)object __attribute__((deprecated("Renamed to -setKeyPath:onObject:"))); |
| | | - (RACSignal *)ignoreElements __attribute__((deprecated("Renamed to -ignoreValues"))); |
| | | - (RACSignal *)sequenceNext:(RACSignal * (^)(void))block __attribute__((deprecated("Renamed to -then:"))); |
| | | - (RACSignal *)aggregateWithStart:(id)start combine:(id (^)(id running, id next))combineBlock __attribute__((deprecated("Renamed to -aggregateWithStart:reduce:"))); |
| | | - (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory combine:(id (^)(id running, id next))combineBlock __attribute__((deprecated("Renamed to -aggregateWithStartFactory:reduce:"))); |
| | | - (RACDisposable *)executeCommand:(RACCommand *)command __attribute__((deprecated("Use -flattenMap: or -subscribeNext: instead"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSignal+Operations.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-09-06. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSignal+Operations.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "RACBlockTrampoline.h" |
| | | #import "RACCommand.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACEvent.h" |
| | | #import "RACGroupedSignal.h" |
| | | #import "RACMulticastConnection+Private.h" |
| | | #import "RACReplaySubject.h" |
| | | #import "RACScheduler.h" |
| | | #import "RACSerialDisposable.h" |
| | | #import "RACSignalSequence.h" |
| | | #import "RACStream+Private.h" |
| | | #import "RACSubject.h" |
| | | #import "RACSubscriber+Private.h" |
| | | #import "RACSubscriber.h" |
| | | #import "RACTuple.h" |
| | | #import "RACUnit.h" |
| | | #import <libkern/OSAtomic.h> |
| | | #import <objc/runtime.h> |
| | | |
| | | NSString * const RACSignalErrorDomain = @"RACSignalErrorDomain"; |
| | | |
| | | const NSInteger RACSignalErrorTimedOut = 1; |
| | | const NSInteger RACSignalErrorNoMatchingCase = 2; |
| | | |
| | | // Subscribes to the given signal with the given blocks. |
| | | // |
| | | // If the signal errors or completes, the corresponding block is invoked. If the |
| | | // disposable passed to the block is _not_ disposed, then the signal is |
| | | // subscribed to again. |
| | | static RACDisposable *subscribeForever (RACSignal *signal, void (^next)(id), void (^error)(NSError *, RACDisposable *), void (^completed)(RACDisposable *)) { |
| | | next = [next copy]; |
| | | error = [error copy]; |
| | | completed = [completed copy]; |
| | | |
| | | RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | RACSchedulerRecursiveBlock recursiveBlock = ^(void (^recurse)(void)) { |
| | | RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable]; |
| | | [compoundDisposable addDisposable:selfDisposable]; |
| | | |
| | | __weak RACDisposable *weakSelfDisposable = selfDisposable; |
| | | |
| | | RACDisposable *subscriptionDisposable = [signal subscribeNext:next error:^(NSError *e) { |
| | | @autoreleasepool { |
| | | error(e, compoundDisposable); |
| | | [compoundDisposable removeDisposable:weakSelfDisposable]; |
| | | } |
| | | |
| | | recurse(); |
| | | } completed:^{ |
| | | @autoreleasepool { |
| | | completed(compoundDisposable); |
| | | [compoundDisposable removeDisposable:weakSelfDisposable]; |
| | | } |
| | | |
| | | recurse(); |
| | | }]; |
| | | |
| | | [selfDisposable addDisposable:subscriptionDisposable]; |
| | | }; |
| | | |
| | | // Subscribe once immediately, and then use recursive scheduling for any |
| | | // further resubscriptions. |
| | | recursiveBlock(^{ |
| | | RACScheduler *recursiveScheduler = RACScheduler.currentScheduler ?: [RACScheduler scheduler]; |
| | | |
| | | RACDisposable *schedulingDisposable = [recursiveScheduler scheduleRecursiveBlock:recursiveBlock]; |
| | | [compoundDisposable addDisposable:schedulingDisposable]; |
| | | }); |
| | | |
| | | return compoundDisposable; |
| | | } |
| | | |
| | | @implementation RACSignal (Operations) |
| | | |
| | | - (RACSignal *)doNext:(void (^)(id x))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | return [self subscribeNext:^(id x) { |
| | | block(x); |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -doNext:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)doError:(void (^)(NSError *error))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | return [self subscribeNext:^(id x) { |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | block(error); |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -doError:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)doCompleted:(void (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | return [self subscribeNext:^(id x) { |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | block(); |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -doCompleted:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)throttle:(NSTimeInterval)interval { |
| | | return [[self throttle:interval valuesPassingTest:^(id _) { |
| | | return YES; |
| | | }] setNameWithFormat:@"[%@] -throttle: %f", self.name, (double)interval]; |
| | | } |
| | | |
| | | - (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate { |
| | | NSCParameterAssert(interval >= 0); |
| | | NSCParameterAssert(predicate != nil); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | // We may never use this scheduler, but we need to set it up ahead of |
| | | // time so that our scheduled blocks are run serially if we do. |
| | | RACScheduler *scheduler = [RACScheduler scheduler]; |
| | | |
| | | // Information about any currently-buffered `next` event. |
| | | __block id nextValue = nil; |
| | | __block BOOL hasNextValue = NO; |
| | | RACSerialDisposable *nextDisposable = [[RACSerialDisposable alloc] init]; |
| | | |
| | | void (^flushNext)(BOOL send) = ^(BOOL send) { |
| | | @synchronized (compoundDisposable) { |
| | | [nextDisposable.disposable dispose]; |
| | | |
| | | if (!hasNextValue) return; |
| | | if (send) [subscriber sendNext:nextValue]; |
| | | |
| | | nextValue = nil; |
| | | hasNextValue = NO; |
| | | } |
| | | }; |
| | | |
| | | RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { |
| | | RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler; |
| | | BOOL shouldThrottle = predicate(x); |
| | | |
| | | @synchronized (compoundDisposable) { |
| | | flushNext(NO); |
| | | if (!shouldThrottle) { |
| | | [subscriber sendNext:x]; |
| | | return; |
| | | } |
| | | |
| | | nextValue = x; |
| | | hasNextValue = YES; |
| | | nextDisposable.disposable = [delayScheduler afterDelay:interval schedule:^{ |
| | | flushNext(YES); |
| | | }]; |
| | | } |
| | | } error:^(NSError *error) { |
| | | [compoundDisposable dispose]; |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | flushNext(YES); |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | [compoundDisposable addDisposable:subscriptionDisposable]; |
| | | return compoundDisposable; |
| | | }] setNameWithFormat:@"[%@] -throttle: %f valuesPassingTest:", self.name, (double)interval]; |
| | | } |
| | | |
| | | - (RACSignal *)delay:(NSTimeInterval)interval { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | // We may never use this scheduler, but we need to set it up ahead of |
| | | // time so that our scheduled blocks are run serially if we do. |
| | | RACScheduler *scheduler = [RACScheduler scheduler]; |
| | | |
| | | void (^schedule)(dispatch_block_t) = ^(dispatch_block_t block) { |
| | | RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler; |
| | | RACDisposable *schedulerDisposable = [delayScheduler afterDelay:interval schedule:block]; |
| | | [disposable addDisposable:schedulerDisposable]; |
| | | }; |
| | | |
| | | RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { |
| | | schedule(^{ |
| | | [subscriber sendNext:x]; |
| | | }); |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | schedule(^{ |
| | | [subscriber sendCompleted]; |
| | | }); |
| | | }]; |
| | | |
| | | [disposable addDisposable:subscriptionDisposable]; |
| | | return disposable; |
| | | }] setNameWithFormat:@"[%@] -delay: %f", self.name, (double)interval]; |
| | | } |
| | | |
| | | - (RACSignal *)repeat { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | return subscribeForever(self, |
| | | ^(id x) { |
| | | [subscriber sendNext:x]; |
| | | }, |
| | | ^(NSError *error, RACDisposable *disposable) { |
| | | [disposable dispose]; |
| | | [subscriber sendError:error]; |
| | | }, |
| | | ^(RACDisposable *disposable) { |
| | | // Resubscribe. |
| | | }); |
| | | }] setNameWithFormat:@"[%@] -repeat", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock { |
| | | NSCParameterAssert(catchBlock != NULL); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACSerialDisposable *catchDisposable = [[RACSerialDisposable alloc] init]; |
| | | |
| | | RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | RACSignal *signal = catchBlock(error); |
| | | NSCAssert(signal != nil, @"Expected non-nil signal from catch block on %@", self); |
| | | catchDisposable.disposable = [signal subscribe:subscriber]; |
| | | } completed:^{ |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [catchDisposable dispose]; |
| | | [subscriptionDisposable dispose]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -catch:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)catchTo:(RACSignal *)signal { |
| | | return [[self catch:^(NSError *error) { |
| | | return signal; |
| | | }] setNameWithFormat:@"[%@] -catchTo: %@", self.name, signal]; |
| | | } |
| | | |
| | | - (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock { |
| | | NSCParameterAssert(tryBlock != NULL); |
| | | |
| | | return [[self flattenMap:^(id value) { |
| | | NSError *error = nil; |
| | | BOOL passed = tryBlock(value, &error); |
| | | return (passed ? [RACSignal return:value] : [RACSignal error:error]); |
| | | }] setNameWithFormat:@"[%@] -try:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock { |
| | | NSCParameterAssert(mapBlock != NULL); |
| | | |
| | | return [[self flattenMap:^(id value) { |
| | | NSError *error = nil; |
| | | id mappedValue = mapBlock(value, &error); |
| | | return (mappedValue == nil ? [RACSignal error:error] : [RACSignal return:mappedValue]); |
| | | }] setNameWithFormat:@"[%@] -tryMap:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)initially:(void (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | return [[RACSignal defer:^{ |
| | | block(); |
| | | return self; |
| | | }] setNameWithFormat:@"[%@] -initially:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)finally:(void (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | return [[[self |
| | | doError:^(NSError *error) { |
| | | block(); |
| | | }] |
| | | doCompleted:^{ |
| | | block(); |
| | | }] |
| | | setNameWithFormat:@"[%@] -finally:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { |
| | | NSCParameterAssert(scheduler != nil); |
| | | NSCParameterAssert(scheduler != RACScheduler.immediateScheduler); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACSerialDisposable *timerDisposable = [[RACSerialDisposable alloc] init]; |
| | | NSMutableArray *values = [NSMutableArray array]; |
| | | |
| | | void (^flushValues)() = ^{ |
| | | @synchronized (values) { |
| | | [timerDisposable.disposable dispose]; |
| | | |
| | | if (values.count == 0) return; |
| | | |
| | | RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:values]; |
| | | [values removeAllObjects]; |
| | | [subscriber sendNext:tuple]; |
| | | } |
| | | }; |
| | | |
| | | RACDisposable *selfDisposable = [self subscribeNext:^(id x) { |
| | | @synchronized (values) { |
| | | if (values.count == 0) { |
| | | timerDisposable.disposable = [scheduler afterDelay:interval schedule:flushValues]; |
| | | } |
| | | |
| | | [values addObject:x ?: RACTupleNil.tupleNil]; |
| | | } |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | flushValues(); |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [selfDisposable dispose]; |
| | | [timerDisposable dispose]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -bufferWithTime: %f onScheduler: %@", self.name, (double)interval, scheduler]; |
| | | } |
| | | |
| | | - (RACSignal *)collect { |
| | | return [[self aggregateWithStartFactory:^{ |
| | | return [[NSMutableArray alloc] init]; |
| | | } reduce:^(NSMutableArray *collectedValues, id x) { |
| | | [collectedValues addObject:(x ?: NSNull.null)]; |
| | | return collectedValues; |
| | | }] setNameWithFormat:@"[%@] -collect", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)takeLast:(NSUInteger)count { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | NSMutableArray *valuesTaken = [NSMutableArray arrayWithCapacity:count]; |
| | | return [self subscribeNext:^(id x) { |
| | | [valuesTaken addObject:x ? : RACTupleNil.tupleNil]; |
| | | |
| | | while (valuesTaken.count > count) { |
| | | [valuesTaken removeObjectAtIndex:0]; |
| | | } |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | for (id value in valuesTaken) { |
| | | [subscriber sendNext:value == RACTupleNil.tupleNil ? nil : value]; |
| | | } |
| | | |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -takeLast: %lu", self.name, (unsigned long)count]; |
| | | } |
| | | |
| | | - (RACSignal *)combineLatestWith:(RACSignal *)signal { |
| | | NSCParameterAssert(signal != nil); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | __block id lastSelfValue = nil; |
| | | __block BOOL selfCompleted = NO; |
| | | |
| | | __block id lastOtherValue = nil; |
| | | __block BOOL otherCompleted = NO; |
| | | |
| | | void (^sendNext)(void) = ^{ |
| | | @synchronized (disposable) { |
| | | if (lastSelfValue == nil || lastOtherValue == nil) return; |
| | | [subscriber sendNext:RACTuplePack(lastSelfValue, lastOtherValue)]; |
| | | } |
| | | }; |
| | | |
| | | RACDisposable *selfDisposable = [self subscribeNext:^(id x) { |
| | | @synchronized (disposable) { |
| | | lastSelfValue = x ?: RACTupleNil.tupleNil; |
| | | sendNext(); |
| | | } |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | @synchronized (disposable) { |
| | | selfCompleted = YES; |
| | | if (otherCompleted) [subscriber sendCompleted]; |
| | | } |
| | | }]; |
| | | |
| | | [disposable addDisposable:selfDisposable]; |
| | | |
| | | RACDisposable *otherDisposable = [signal subscribeNext:^(id x) { |
| | | @synchronized (disposable) { |
| | | lastOtherValue = x ?: RACTupleNil.tupleNil; |
| | | sendNext(); |
| | | } |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | @synchronized (disposable) { |
| | | otherCompleted = YES; |
| | | if (selfCompleted) [subscriber sendCompleted]; |
| | | } |
| | | }]; |
| | | |
| | | [disposable addDisposable:otherDisposable]; |
| | | |
| | | return disposable; |
| | | }] setNameWithFormat:@"[%@] -combineLatestWith: %@", self.name, signal]; |
| | | } |
| | | |
| | | + (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals { |
| | | return [[self join:signals block:^(RACSignal *left, RACSignal *right) { |
| | | return [left combineLatestWith:right]; |
| | | }] setNameWithFormat:@"+combineLatest: %@", signals]; |
| | | } |
| | | |
| | | + (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock { |
| | | NSCParameterAssert(reduceBlock != nil); |
| | | |
| | | RACSignal *result = [self combineLatest:signals]; |
| | | |
| | | // Although we assert this condition above, older versions of this method |
| | | // supported this argument being nil. Avoid crashing Release builds of |
| | | // apps that depended on that. |
| | | if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; |
| | | |
| | | return [result setNameWithFormat:@"+combineLatest: %@ reduce:", signals]; |
| | | } |
| | | |
| | | - (RACSignal *)merge:(RACSignal *)signal { |
| | | return [[RACSignal |
| | | merge:@[ self, signal ]] |
| | | setNameWithFormat:@"[%@] -merge: %@", self.name, signal]; |
| | | } |
| | | |
| | | + (RACSignal *)merge:(id<NSFastEnumeration>)signals { |
| | | NSMutableArray *copiedSignals = [[NSMutableArray alloc] init]; |
| | | for (RACSignal *signal in signals) { |
| | | [copiedSignals addObject:signal]; |
| | | } |
| | | |
| | | return [[[RACSignal |
| | | createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| | | for (RACSignal *signal in copiedSignals) { |
| | | [subscriber sendNext:signal]; |
| | | } |
| | | |
| | | [subscriber sendCompleted]; |
| | | return nil; |
| | | }] |
| | | flatten] |
| | | setNameWithFormat:@"+merge: %@", copiedSignals]; |
| | | } |
| | | |
| | | - (RACSignal *)flatten:(NSUInteger)maxConcurrent { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACCompoundDisposable *compoundDisposable = [[RACCompoundDisposable alloc] init]; |
| | | |
| | | // Contains disposables for the currently active subscriptions. |
| | | // |
| | | // This should only be used while synchronized on `subscriber`. |
| | | NSMutableArray *activeDisposables = [[NSMutableArray alloc] initWithCapacity:maxConcurrent]; |
| | | |
| | | // Whether the signal-of-signals has completed yet. |
| | | // |
| | | // This should only be used while synchronized on `subscriber`. |
| | | __block BOOL selfCompleted = NO; |
| | | |
| | | // Subscribes to the given signal. |
| | | __block void (^subscribeToSignal)(RACSignal *); |
| | | |
| | | // Weak reference to the above, to avoid a leak. |
| | | __weak __block void (^recur)(RACSignal *); |
| | | |
| | | // Sends completed to the subscriber if all signals are finished. |
| | | // |
| | | // This should only be used while synchronized on `subscriber`. |
| | | void (^completeIfAllowed)(void) = ^{ |
| | | if (selfCompleted && activeDisposables.count == 0) { |
| | | [subscriber sendCompleted]; |
| | | |
| | | // A strong reference is held to `subscribeToSignal` until completion, |
| | | // preventing it from deallocating early. |
| | | subscribeToSignal = nil; |
| | | } |
| | | }; |
| | | |
| | | // The signals waiting to be started. |
| | | // |
| | | // This array should only be used while synchronized on `subscriber`. |
| | | NSMutableArray *queuedSignals = [NSMutableArray array]; |
| | | |
| | | recur = subscribeToSignal = ^(RACSignal *signal) { |
| | | RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; |
| | | |
| | | @synchronized (subscriber) { |
| | | [compoundDisposable addDisposable:serialDisposable]; |
| | | [activeDisposables addObject:serialDisposable]; |
| | | } |
| | | |
| | | serialDisposable.disposable = [signal subscribeNext:^(id x) { |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | __strong void (^subscribeToSignal)(RACSignal *) = recur; |
| | | RACSignal *nextSignal; |
| | | |
| | | @synchronized (subscriber) { |
| | | [compoundDisposable removeDisposable:serialDisposable]; |
| | | [activeDisposables removeObjectIdenticalTo:serialDisposable]; |
| | | |
| | | if (queuedSignals.count == 0) { |
| | | completeIfAllowed(); |
| | | return; |
| | | } |
| | | |
| | | nextSignal = queuedSignals[0]; |
| | | [queuedSignals removeObjectAtIndex:0]; |
| | | } |
| | | |
| | | subscribeToSignal(nextSignal); |
| | | }]; |
| | | }; |
| | | |
| | | [compoundDisposable addDisposable:[self subscribeNext:^(RACSignal *signal) { |
| | | if (signal == nil) return; |
| | | |
| | | NSCAssert([signal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", signal); |
| | | |
| | | @synchronized (subscriber) { |
| | | if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) { |
| | | [queuedSignals addObject:signal]; |
| | | |
| | | // If we need to wait, skip subscribing to this |
| | | // signal. |
| | | return; |
| | | } |
| | | } |
| | | |
| | | subscribeToSignal(signal); |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | @synchronized (subscriber) { |
| | | selfCompleted = YES; |
| | | completeIfAllowed(); |
| | | } |
| | | }]]; |
| | | |
| | | return compoundDisposable; |
| | | }] setNameWithFormat:@"[%@] -flatten: %lu", self.name, (unsigned long)maxConcurrent]; |
| | | } |
| | | |
| | | - (RACSignal *)then:(RACSignal * (^)(void))block { |
| | | NSCParameterAssert(block != nil); |
| | | |
| | | return [[[self |
| | | ignoreValues] |
| | | concat:[RACSignal defer:block]] |
| | | setNameWithFormat:@"[%@] -then:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)concat { |
| | | return [[self flatten:1] setNameWithFormat:@"[%@] -concat", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory reduce:(id (^)(id running, id next))reduceBlock { |
| | | NSCParameterAssert(startFactory != NULL); |
| | | NSCParameterAssert(reduceBlock != NULL); |
| | | |
| | | return [[RACSignal defer:^{ |
| | | return [self aggregateWithStart:startFactory() reduce:reduceBlock]; |
| | | }] setNameWithFormat:@"[%@] -aggregateWithStartFactory:reduce:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock { |
| | | return [[self |
| | | aggregateWithStart:start |
| | | reduceWithIndex:^(id running, id next, NSUInteger index) { |
| | | return reduceBlock(running, next); |
| | | }] |
| | | setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduce:", self.name, [start rac_description]]; |
| | | } |
| | | |
| | | - (RACSignal *)aggregateWithStart:(id)start reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock { |
| | | return [[[[self |
| | | scanWithStart:start reduceWithIndex:reduceBlock] |
| | | startWith:start] |
| | | takeLast:1] |
| | | setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduceWithIndex:", self.name, [start rac_description]]; |
| | | } |
| | | |
| | | - (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object { |
| | | return [self setKeyPath:keyPath onObject:object nilValue:nil]; |
| | | } |
| | | |
| | | - (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue { |
| | | NSCParameterAssert(keyPath != nil); |
| | | NSCParameterAssert(object != nil); |
| | | |
| | | keyPath = [keyPath copy]; |
| | | |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | // Purposely not retaining 'object', since we want to tear down the binding |
| | | // when it deallocates normally. |
| | | __block void * volatile objectPtr = (__bridge void *)object; |
| | | |
| | | RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { |
| | | // Possibly spec, possibly compiler bug, but this __bridge cast does not |
| | | // result in a retain here, effectively an invisible __unsafe_unretained |
| | | // qualifier. Using objc_precise_lifetime gives the __strong reference |
| | | // desired. The explicit use of __strong is strictly defensive. |
| | | __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr; |
| | | [object setValue:x ?: nilValue forKeyPath:keyPath]; |
| | | } error:^(NSError *error) { |
| | | __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr; |
| | | |
| | | NSCAssert(NO, @"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error); |
| | | |
| | | // Log the error if we're running with assertions disabled. |
| | | NSLog(@"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error); |
| | | |
| | | [disposable dispose]; |
| | | } completed:^{ |
| | | [disposable dispose]; |
| | | }]; |
| | | |
| | | [disposable addDisposable:subscriptionDisposable]; |
| | | |
| | | #if DEBUG |
| | | static void *bindingsKey = &bindingsKey; |
| | | NSMutableDictionary *bindings; |
| | | |
| | | @synchronized (object) { |
| | | bindings = objc_getAssociatedObject(object, bindingsKey); |
| | | if (bindings == nil) { |
| | | bindings = [NSMutableDictionary dictionary]; |
| | | objc_setAssociatedObject(object, bindingsKey, bindings, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | } |
| | | |
| | | @synchronized (bindings) { |
| | | NSCAssert(bindings[keyPath] == nil, @"Signal %@ is already bound to key path \"%@\" on object %@, adding signal %@ is undefined behavior", [bindings[keyPath] nonretainedObjectValue], keyPath, object, self); |
| | | |
| | | bindings[keyPath] = [NSValue valueWithNonretainedObject:self]; |
| | | } |
| | | #endif |
| | | |
| | | RACDisposable *clearPointerDisposable = [RACDisposable disposableWithBlock:^{ |
| | | #if DEBUG |
| | | @synchronized (bindings) { |
| | | [bindings removeObjectForKey:keyPath]; |
| | | } |
| | | #endif |
| | | |
| | | while (YES) { |
| | | void *ptr = objectPtr; |
| | | if (OSAtomicCompareAndSwapPtrBarrier(ptr, NULL, &objectPtr)) { |
| | | break; |
| | | } |
| | | } |
| | | }]; |
| | | |
| | | [disposable addDisposable:clearPointerDisposable]; |
| | | |
| | | [object.rac_deallocDisposable addDisposable:disposable]; |
| | | |
| | | RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable; |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [objectDisposable removeDisposable:disposable]; |
| | | [disposable dispose]; |
| | | }]; |
| | | } |
| | | |
| | | + (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { |
| | | return [[RACSignal interval:interval onScheduler:scheduler withLeeway:0.0] setNameWithFormat:@"+interval: %f onScheduler: %@", (double)interval, scheduler]; |
| | | } |
| | | |
| | | + (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway { |
| | | NSCParameterAssert(scheduler != nil); |
| | | NSCParameterAssert(scheduler != RACScheduler.immediateScheduler); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | return [scheduler after:[NSDate dateWithTimeIntervalSinceNow:interval] repeatingEvery:interval withLeeway:leeway schedule:^{ |
| | | [subscriber sendNext:[NSDate date]]; |
| | | }]; |
| | | }] setNameWithFormat:@"+interval: %f onScheduler: %@ withLeeway: %f", (double)interval, scheduler, (double)leeway]; |
| | | } |
| | | |
| | | - (RACSignal *)takeUntil:(RACSignal *)signalTrigger { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | void (^triggerCompletion)(void) = ^{ |
| | | [disposable dispose]; |
| | | [subscriber sendCompleted]; |
| | | }; |
| | | |
| | | RACDisposable *triggerDisposable = [signalTrigger subscribeNext:^(id _) { |
| | | triggerCompletion(); |
| | | } completed:^{ |
| | | triggerCompletion(); |
| | | }]; |
| | | |
| | | [disposable addDisposable:triggerDisposable]; |
| | | |
| | | if (!disposable.disposed) { |
| | | RACDisposable *selfDisposable = [self subscribeNext:^(id x) { |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [disposable dispose]; |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | [disposable addDisposable:selfDisposable]; |
| | | } |
| | | |
| | | return disposable; |
| | | }] setNameWithFormat:@"[%@] -takeUntil: %@", self.name, signalTrigger]; |
| | | } |
| | | |
| | | - (RACSignal *)takeUntilReplacement:(RACSignal *)replacement { |
| | | return [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; |
| | | |
| | | RACDisposable *replacementDisposable = [replacement subscribeNext:^(id x) { |
| | | [selfDisposable dispose]; |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [selfDisposable dispose]; |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [selfDisposable dispose]; |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | if (!selfDisposable.disposed) { |
| | | selfDisposable.disposable = [[self |
| | | concat:[RACSignal never]] |
| | | subscribe:subscriber]; |
| | | } |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [selfDisposable dispose]; |
| | | [replacementDisposable dispose]; |
| | | }]; |
| | | }]; |
| | | } |
| | | |
| | | - (RACSignal *)switchToLatest { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACMulticastConnection *connection = [self publish]; |
| | | |
| | | RACDisposable *subscriptionDisposable = [[connection.signal |
| | | flattenMap:^(RACSignal *x) { |
| | | NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x); |
| | | |
| | | // -concat:[RACSignal never] prevents completion of the receiver from |
| | | // prematurely terminating the inner signal. |
| | | return [x takeUntil:[connection.signal concat:[RACSignal never]]]; |
| | | }] |
| | | subscribe:subscriber]; |
| | | |
| | | RACDisposable *connectionDisposable = [connection connect]; |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [subscriptionDisposable dispose]; |
| | | [connectionDisposable dispose]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -switchToLatest", self.name]; |
| | | } |
| | | |
| | | + (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal { |
| | | NSCParameterAssert(signal != nil); |
| | | NSCParameterAssert(cases != nil); |
| | | |
| | | for (id key in cases) { |
| | | id value __attribute__((unused)) = cases[key]; |
| | | NSCAssert([value isKindOfClass:RACSignal.class], @"Expected all cases to be RACSignals, %@ isn't", value); |
| | | } |
| | | |
| | | NSDictionary *copy = [cases copy]; |
| | | |
| | | return [[[signal |
| | | map:^(id key) { |
| | | if (key == nil) key = RACTupleNil.tupleNil; |
| | | |
| | | RACSignal *signal = copy[key] ?: defaultSignal; |
| | | if (signal == nil) { |
| | | NSString *description = [NSString stringWithFormat:NSLocalizedString(@"No matching signal found for value %@", @""), key]; |
| | | return [RACSignal error:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorNoMatchingCase userInfo:@{ NSLocalizedDescriptionKey: description }]]; |
| | | } |
| | | |
| | | return signal; |
| | | }] |
| | | switchToLatest] |
| | | setNameWithFormat:@"+switch: %@ cases: %@ default: %@", signal, cases, defaultSignal]; |
| | | } |
| | | |
| | | + (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal { |
| | | NSCParameterAssert(boolSignal != nil); |
| | | NSCParameterAssert(trueSignal != nil); |
| | | NSCParameterAssert(falseSignal != nil); |
| | | |
| | | return [[[boolSignal |
| | | map:^(NSNumber *value) { |
| | | NSCAssert([value isKindOfClass:NSNumber.class], @"Expected %@ to send BOOLs, not %@", boolSignal, value); |
| | | |
| | | return (value.boolValue ? trueSignal : falseSignal); |
| | | }] |
| | | switchToLatest] |
| | | setNameWithFormat:@"+if: %@ then: %@ else: %@", boolSignal, trueSignal, falseSignal]; |
| | | } |
| | | |
| | | - (id)first { |
| | | return [self firstOrDefault:nil]; |
| | | } |
| | | |
| | | - (id)firstOrDefault:(id)defaultValue { |
| | | return [self firstOrDefault:defaultValue success:NULL error:NULL]; |
| | | } |
| | | |
| | | - (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error { |
| | | NSCondition *condition = [[NSCondition alloc] init]; |
| | | condition.name = [NSString stringWithFormat:@"[%@] -firstOrDefault: %@ success:error:", self.name, defaultValue]; |
| | | |
| | | __block id value = defaultValue; |
| | | __block BOOL done = NO; |
| | | |
| | | // Ensures that we don't pass values across thread boundaries by reference. |
| | | __block NSError *localError; |
| | | __block BOOL localSuccess; |
| | | |
| | | [[self take:1] subscribeNext:^(id x) { |
| | | [condition lock]; |
| | | |
| | | value = x; |
| | | localSuccess = YES; |
| | | |
| | | done = YES; |
| | | [condition broadcast]; |
| | | [condition unlock]; |
| | | } error:^(NSError *e) { |
| | | [condition lock]; |
| | | |
| | | if (!done) { |
| | | localSuccess = NO; |
| | | localError = e; |
| | | |
| | | done = YES; |
| | | [condition broadcast]; |
| | | } |
| | | |
| | | [condition unlock]; |
| | | } completed:^{ |
| | | [condition lock]; |
| | | |
| | | localSuccess = YES; |
| | | |
| | | done = YES; |
| | | [condition broadcast]; |
| | | [condition unlock]; |
| | | }]; |
| | | |
| | | [condition lock]; |
| | | while (!done) { |
| | | [condition wait]; |
| | | } |
| | | |
| | | if (success != NULL) *success = localSuccess; |
| | | if (error != NULL) *error = localError; |
| | | |
| | | [condition unlock]; |
| | | return value; |
| | | } |
| | | |
| | | - (BOOL)waitUntilCompleted:(NSError **)error { |
| | | BOOL success = NO; |
| | | |
| | | [[[self |
| | | ignoreValues] |
| | | setNameWithFormat:@"[%@] -waitUntilCompleted:", self.name] |
| | | firstOrDefault:nil success:&success error:error]; |
| | | |
| | | return success; |
| | | } |
| | | |
| | | + (RACSignal *)defer:(RACSignal * (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | return [block() subscribe:subscriber]; |
| | | }] setNameWithFormat:@"+defer:"]; |
| | | } |
| | | |
| | | - (NSArray *)toArray { |
| | | return [[[self collect] first] copy]; |
| | | } |
| | | |
| | | - (RACSequence *)sequence { |
| | | return [[RACSignalSequence sequenceWithSignal:self] setNameWithFormat:@"[%@] -sequence", self.name]; |
| | | } |
| | | |
| | | - (RACMulticastConnection *)publish { |
| | | RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name]; |
| | | RACMulticastConnection *connection = [self multicast:subject]; |
| | | return connection; |
| | | } |
| | | |
| | | - (RACMulticastConnection *)multicast:(RACSubject *)subject { |
| | | [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name]; |
| | | RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject]; |
| | | return connection; |
| | | } |
| | | |
| | | - (RACSignal *)replay { |
| | | RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name]; |
| | | |
| | | RACMulticastConnection *connection = [self multicast:subject]; |
| | | [connection connect]; |
| | | |
| | | return connection.signal; |
| | | } |
| | | |
| | | - (RACSignal *)replayLast { |
| | | RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name]; |
| | | |
| | | RACMulticastConnection *connection = [self multicast:subject]; |
| | | [connection connect]; |
| | | |
| | | return connection.signal; |
| | | } |
| | | |
| | | - (RACSignal *)replayLazily { |
| | | RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]]; |
| | | return [[RACSignal |
| | | defer:^{ |
| | | [connection connect]; |
| | | return connection.signal; |
| | | }] |
| | | setNameWithFormat:@"[%@] -replayLazily", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { |
| | | NSCParameterAssert(scheduler != nil); |
| | | NSCParameterAssert(scheduler != RACScheduler.immediateScheduler); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | RACDisposable *timeoutDisposable = [scheduler afterDelay:interval schedule:^{ |
| | | [disposable dispose]; |
| | | [subscriber sendError:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorTimedOut userInfo:nil]]; |
| | | }]; |
| | | |
| | | [disposable addDisposable:timeoutDisposable]; |
| | | |
| | | RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [disposable dispose]; |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [disposable dispose]; |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | [disposable addDisposable:subscriptionDisposable]; |
| | | return disposable; |
| | | }] setNameWithFormat:@"[%@] -timeout: %f onScheduler: %@", self.name, (double)interval, scheduler]; |
| | | } |
| | | |
| | | - (RACSignal *)deliverOn:(RACScheduler *)scheduler { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | return [self subscribeNext:^(id x) { |
| | | [scheduler schedule:^{ |
| | | [subscriber sendNext:x]; |
| | | }]; |
| | | } error:^(NSError *error) { |
| | | [scheduler schedule:^{ |
| | | [subscriber sendError:error]; |
| | | }]; |
| | | } completed:^{ |
| | | [scheduler schedule:^{ |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -deliverOn: %@", self.name, scheduler]; |
| | | } |
| | | |
| | | - (RACSignal *)subscribeOn:(RACScheduler *)scheduler { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | RACDisposable *schedulingDisposable = [scheduler schedule:^{ |
| | | RACDisposable *subscriptionDisposable = [self subscribe:subscriber]; |
| | | |
| | | [disposable addDisposable:subscriptionDisposable]; |
| | | }]; |
| | | |
| | | [disposable addDisposable:schedulingDisposable]; |
| | | return disposable; |
| | | }] setNameWithFormat:@"[%@] -subscribeOn: %@", self.name, scheduler]; |
| | | } |
| | | |
| | | - (RACSignal *)deliverOnMainThread { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | __block volatile int32_t queueLength = 0; |
| | | |
| | | void (^performOnMainThread)(dispatch_block_t) = ^(dispatch_block_t block) { |
| | | int32_t queued = OSAtomicIncrement32(&queueLength); |
| | | if (NSThread.isMainThread && queued == 1) { |
| | | block(); |
| | | OSAtomicDecrement32(&queueLength); |
| | | } else { |
| | | dispatch_async(dispatch_get_main_queue(), ^{ |
| | | block(); |
| | | OSAtomicDecrement32(&queueLength); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | return [self subscribeNext:^(id x) { |
| | | performOnMainThread(^{ |
| | | [subscriber sendNext:x]; |
| | | }); |
| | | } error:^(NSError *error) { |
| | | performOnMainThread(^{ |
| | | [subscriber sendError:error]; |
| | | }); |
| | | } completed:^{ |
| | | performOnMainThread(^{ |
| | | [subscriber sendCompleted]; |
| | | }); |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -deliverOnMainThread", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock transform:(id (^)(id object))transformBlock { |
| | | NSCParameterAssert(keyBlock != NULL); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | NSMutableDictionary *groups = [NSMutableDictionary dictionary]; |
| | | NSMutableArray *orderedGroups = [NSMutableArray array]; |
| | | |
| | | return [self subscribeNext:^(id x) { |
| | | id<NSCopying> key = keyBlock(x); |
| | | RACGroupedSignal *groupSubject = nil; |
| | | @synchronized(groups) { |
| | | groupSubject = groups[key]; |
| | | if (groupSubject == nil) { |
| | | groupSubject = [RACGroupedSignal signalWithKey:key]; |
| | | groups[key] = groupSubject; |
| | | [orderedGroups addObject:groupSubject]; |
| | | [subscriber sendNext:groupSubject]; |
| | | } |
| | | } |
| | | |
| | | [groupSubject sendNext:transformBlock != NULL ? transformBlock(x) : x]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | |
| | | [orderedGroups makeObjectsPerformSelector:@selector(sendError:) withObject:error]; |
| | | } completed:^{ |
| | | [subscriber sendCompleted]; |
| | | |
| | | [orderedGroups makeObjectsPerformSelector:@selector(sendCompleted)]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -groupBy:transform:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock { |
| | | return [[self groupBy:keyBlock transform:nil] setNameWithFormat:@"[%@] -groupBy:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)any { |
| | | return [[self any:^(id x) { |
| | | return YES; |
| | | }] setNameWithFormat:@"[%@] -any", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)any:(BOOL (^)(id object))predicateBlock { |
| | | NSCParameterAssert(predicateBlock != NULL); |
| | | |
| | | return [[[self materialize] bind:^{ |
| | | return ^(RACEvent *event, BOOL *stop) { |
| | | if (event.finished) { |
| | | *stop = YES; |
| | | return [RACSignal return:@NO]; |
| | | } |
| | | |
| | | if (predicateBlock(event.value)) { |
| | | *stop = YES; |
| | | return [RACSignal return:@YES]; |
| | | } |
| | | |
| | | return [RACSignal empty]; |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -any:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)all:(BOOL (^)(id object))predicateBlock { |
| | | NSCParameterAssert(predicateBlock != NULL); |
| | | |
| | | return [[[self materialize] bind:^{ |
| | | return ^(RACEvent *event, BOOL *stop) { |
| | | if (event.eventType == RACEventTypeCompleted) { |
| | | *stop = YES; |
| | | return [RACSignal return:@YES]; |
| | | } |
| | | |
| | | if (event.eventType == RACEventTypeError || !predicateBlock(event.value)) { |
| | | *stop = YES; |
| | | return [RACSignal return:@NO]; |
| | | } |
| | | |
| | | return [RACSignal empty]; |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -all:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)retry:(NSInteger)retryCount { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | __block NSInteger currentRetryCount = 0; |
| | | return subscribeForever(self, |
| | | ^(id x) { |
| | | [subscriber sendNext:x]; |
| | | }, |
| | | ^(NSError *error, RACDisposable *disposable) { |
| | | if (retryCount == 0 || currentRetryCount < retryCount) { |
| | | // Resubscribe. |
| | | currentRetryCount++; |
| | | return; |
| | | } |
| | | |
| | | [disposable dispose]; |
| | | [subscriber sendError:error]; |
| | | }, |
| | | ^(RACDisposable *disposable) { |
| | | [disposable dispose]; |
| | | [subscriber sendCompleted]; |
| | | }); |
| | | }] setNameWithFormat:@"[%@] -retry: %lu", self.name, (unsigned long)retryCount]; |
| | | } |
| | | |
| | | - (RACSignal *)retry { |
| | | return [[self retry:0] setNameWithFormat:@"[%@] -retry", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)sample:(RACSignal *)sampler { |
| | | NSCParameterAssert(sampler != nil); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | NSLock *lock = [[NSLock alloc] init]; |
| | | __block id lastValue; |
| | | __block BOOL hasValue = NO; |
| | | |
| | | RACSerialDisposable *samplerDisposable = [[RACSerialDisposable alloc] init]; |
| | | RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { |
| | | [lock lock]; |
| | | hasValue = YES; |
| | | lastValue = x; |
| | | [lock unlock]; |
| | | } error:^(NSError *error) { |
| | | [samplerDisposable dispose]; |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [samplerDisposable dispose]; |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | samplerDisposable.disposable = [sampler subscribeNext:^(id _) { |
| | | BOOL shouldSend = NO; |
| | | id value; |
| | | [lock lock]; |
| | | shouldSend = hasValue; |
| | | value = lastValue; |
| | | [lock unlock]; |
| | | |
| | | if (shouldSend) { |
| | | [subscriber sendNext:value]; |
| | | } |
| | | } error:^(NSError *error) { |
| | | [sourceDisposable dispose]; |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [sourceDisposable dispose]; |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [samplerDisposable dispose]; |
| | | [sourceDisposable dispose]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -sample: %@", self.name, sampler]; |
| | | } |
| | | |
| | | - (RACSignal *)ignoreValues { |
| | | return [[self filter:^(id _) { |
| | | return NO; |
| | | }] setNameWithFormat:@"[%@] -ignoreValues", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)materialize { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | return [self subscribeNext:^(id x) { |
| | | [subscriber sendNext:[RACEvent eventWithValue:x]]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendNext:[RACEvent eventWithError:error]]; |
| | | [subscriber sendCompleted]; |
| | | } completed:^{ |
| | | [subscriber sendNext:RACEvent.completedEvent]; |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -materialize", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)dematerialize { |
| | | return [[self bind:^{ |
| | | return ^(RACEvent *event, BOOL *stop) { |
| | | switch (event.eventType) { |
| | | case RACEventTypeCompleted: |
| | | *stop = YES; |
| | | return [RACSignal empty]; |
| | | |
| | | case RACEventTypeError: |
| | | *stop = YES; |
| | | return [RACSignal error:event.error]; |
| | | |
| | | case RACEventTypeNext: |
| | | return [RACSignal return:event.value]; |
| | | } |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -dematerialize", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)not { |
| | | return [[self map:^(NSNumber *value) { |
| | | NSCAssert([value isKindOfClass:NSNumber.class], @"-not must only be used on a signal of NSNumbers. Instead, got: %@", value); |
| | | |
| | | return @(!value.boolValue); |
| | | }] setNameWithFormat:@"[%@] -not", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)and { |
| | | return [[self map:^(RACTuple *tuple) { |
| | | NSCAssert([tuple isKindOfClass:RACTuple.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple); |
| | | NSCAssert(tuple.count > 0, @"-and must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple"); |
| | | |
| | | return @([tuple.rac_sequence all:^(NSNumber *number) { |
| | | NSCAssert([number isKindOfClass:NSNumber.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple); |
| | | |
| | | return number.boolValue; |
| | | }]); |
| | | }] setNameWithFormat:@"[%@] -and", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)or { |
| | | return [[self map:^(RACTuple *tuple) { |
| | | NSCAssert([tuple isKindOfClass:RACTuple.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple); |
| | | NSCAssert(tuple.count > 0, @"-or must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple"); |
| | | |
| | | return @([tuple.rac_sequence any:^(NSNumber *number) { |
| | | NSCAssert([number isKindOfClass:NSNumber.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple); |
| | | |
| | | return number.boolValue; |
| | | }]); |
| | | }] setNameWithFormat:@"[%@] -or", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)reduceApply { |
| | | return [[self map:^(RACTuple *tuple) { |
| | | NSCAssert([tuple isKindOfClass:RACTuple.class], @"-reduceApply must only be used on a signal of RACTuples. Instead, received: %@", tuple); |
| | | NSCAssert(tuple.count > 1, @"-reduceApply must only be used on a signal of RACTuples, with at least a block in tuple[0] and its first argument in tuple[1]"); |
| | | |
| | | // We can't use -array, because we need to preserve RACTupleNil |
| | | NSMutableArray *tupleArray = [NSMutableArray arrayWithCapacity:tuple.count]; |
| | | for (id val in tuple) { |
| | | [tupleArray addObject:val]; |
| | | } |
| | | RACTuple *arguments = [RACTuple tupleWithObjectsFromArray:[tupleArray subarrayWithRange:NSMakeRange(1, tupleArray.count - 1)]]; |
| | | |
| | | return [RACBlockTrampoline invokeBlock:tuple[0] withArguments:arguments]; |
| | | }] setNameWithFormat:@"[%@] -reduceApply", self.name]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACSignal (OperationsDeprecated) |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | |
| | | - (RACSignal *)windowWithStart:(RACSignal *)openSignal close:(RACSignal * (^)(RACSignal *start))closeBlock { |
| | | NSCParameterAssert(openSignal != nil); |
| | | NSCParameterAssert(closeBlock != NULL); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | __block RACSubject *currentWindow = nil; |
| | | __block RACSignal *currentCloseWindow = nil; |
| | | __block RACDisposable *closeObserverDisposable = NULL; |
| | | |
| | | void (^closeCurrentWindow)(void) = ^{ |
| | | [currentWindow sendCompleted]; |
| | | currentWindow = nil; |
| | | currentCloseWindow = nil; |
| | | [closeObserverDisposable dispose], closeObserverDisposable = nil; |
| | | }; |
| | | |
| | | RACDisposable *openObserverDisposable = [openSignal subscribe:[RACSubscriber subscriberWithNext:^(id x) { |
| | | if(currentWindow == nil) { |
| | | currentWindow = [RACSubject subject]; |
| | | [subscriber sendNext:currentWindow]; |
| | | |
| | | currentCloseWindow = closeBlock(currentWindow); |
| | | closeObserverDisposable = [currentCloseWindow subscribe:[RACSubscriber subscriberWithNext:^(id x) { |
| | | closeCurrentWindow(); |
| | | } error:^(NSError *error) { |
| | | closeCurrentWindow(); |
| | | } completed:^{ |
| | | closeCurrentWindow(); |
| | | }]]; |
| | | } |
| | | } error:^(NSError *error) { |
| | | |
| | | } completed:^{ |
| | | |
| | | }]]; |
| | | |
| | | RACDisposable *selfObserverDisposable = [self subscribeNext:^(id x) { |
| | | [currentWindow sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [closeObserverDisposable dispose]; |
| | | [openObserverDisposable dispose]; |
| | | [selfObserverDisposable dispose]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -windowWithStart: %@ close:", self.name, openSignal]; |
| | | } |
| | | |
| | | - (RACSignal *)buffer:(NSUInteger)bufferCount { |
| | | NSCParameterAssert(bufferCount > 0); |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | NSMutableArray *values = [NSMutableArray arrayWithCapacity:bufferCount]; |
| | | RACSubject *windowCloseSubject = [RACSubject subject]; |
| | | |
| | | RACDisposable *closeDisposable = [windowCloseSubject subscribeNext:^(id x) { |
| | | [subscriber sendNext:[RACTuple tupleWithObjectsFromArray:values]]; |
| | | [values removeAllObjects]; |
| | | }]; |
| | | |
| | | __block RACDisposable *innerDisposable = nil; |
| | | RACDisposable *outerDisposable = [[self windowWithStart:self close:^(RACSignal *start) { |
| | | return windowCloseSubject; |
| | | }] subscribeNext:^(id x) { |
| | | innerDisposable = [x subscribeNext:^(id x) { |
| | | [values addObject:x ? : [RACTupleNil tupleNil]]; |
| | | if(values.count % bufferCount == 0) { |
| | | [windowCloseSubject sendNext:[RACUnit defaultUnit]]; |
| | | } |
| | | }]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [innerDisposable dispose]; |
| | | [outerDisposable dispose]; |
| | | [closeDisposable dispose]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -buffer: %lu", self.name, (unsigned long)bufferCount]; |
| | | } |
| | | |
| | | - (RACSignal *)let:(RACSignal * (^)(RACSignal *sharedSignal))letBlock { |
| | | NSCParameterAssert(letBlock != NULL); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACMulticastConnection *connection = [self publish]; |
| | | RACDisposable *finalDisposable = [letBlock(connection.signal) subscribeNext:^(id x) { |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | |
| | | RACDisposable *connectionDisposable = [connection connect]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [connectionDisposable dispose]; |
| | | [finalDisposable dispose]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -let:", self.name]; |
| | | } |
| | | |
| | | + (RACSignal *)interval:(NSTimeInterval)interval { |
| | | return [RACSignal interval:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]]; |
| | | } |
| | | |
| | | + (RACSignal *)interval:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway { |
| | | return [RACSignal interval:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh] withLeeway:leeway]; |
| | | } |
| | | |
| | | - (RACSignal *)timeout:(NSTimeInterval)interval { |
| | | return [self timeout:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]]; |
| | | } |
| | | |
| | | - (RACSignal *)bufferWithTime:(NSTimeInterval)interval { |
| | | return [self bufferWithTime:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]]; |
| | | } |
| | | |
| | | - (RACDisposable *)toProperty:(NSString *)keyPath onObject:(NSObject *)object { |
| | | return [self setKeyPath:keyPath onObject:object]; |
| | | } |
| | | |
| | | - (RACSignal *)ignoreElements { |
| | | return [self ignoreValues]; |
| | | } |
| | | |
| | | - (RACSignal *)sequenceNext:(RACSignal * (^)(void))block { |
| | | return [self then:block]; |
| | | } |
| | | |
| | | - (RACSignal *)aggregateWithStart:(id)start combine:(id (^)(id running, id next))combineBlock { |
| | | return [self aggregateWithStart:start reduce:combineBlock]; |
| | | } |
| | | |
| | | - (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory combine:(id (^)(id running, id next))combineBlock { |
| | | return [self aggregateWithStartFactory:startFactory reduce:combineBlock]; |
| | | } |
| | | |
| | | - (RACDisposable *)executeCommand:(RACCommand *)command { |
| | | NSCParameterAssert(command != nil); |
| | | |
| | | return [self subscribeNext:^(id x) { |
| | | [command execute:x]; |
| | | }]; |
| | | } |
| | | |
| | | #pragma clang diagnostic pop |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSignal.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/1/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "RACStream.h" |
| | | |
| | | @class RACDisposable; |
| | | @class RACScheduler; |
| | | @class RACSubject; |
| | | @protocol RACSubscriber; |
| | | |
| | | @interface RACSignal : RACStream |
| | | |
| | | /// Creates a new signal. This is the preferred way to create a new signal |
| | | /// operation or behavior. |
| | | /// |
| | | /// Events can be sent to new subscribers immediately in the `didSubscribe` |
| | | /// block, but the subscriber will not be able to dispose of the signal until |
| | | /// a RACDisposable is returned from `didSubscribe`. In the case of infinite |
| | | /// signals, this won't _ever_ happen if events are sent immediately. |
| | | /// |
| | | /// To ensure that the signal is disposable, events can be scheduled on the |
| | | /// +[RACScheduler currentScheduler] (so that they're deferred, not sent |
| | | /// immediately), or they can be sent in the background. The RACDisposable |
| | | /// returned by the `didSubscribe` block should cancel any such scheduling or |
| | | /// asynchronous work. |
| | | /// |
| | | /// didSubscribe - Called when the signal is subscribed to. The new subscriber is |
| | | /// passed in. You can then manually control the <RACSubscriber> by |
| | | /// sending it -sendNext:, -sendError:, and -sendCompleted, |
| | | /// as defined by the operation you're implementing. This block |
| | | /// should return a RACDisposable which cancels any ongoing work |
| | | /// triggered by the subscription, and cleans up any resources or |
| | | /// disposables created as part of it. When the disposable is |
| | | /// disposed of, the signal must not send any more events to the |
| | | /// `subscriber`. If no cleanup is necessary, return nil. |
| | | /// |
| | | /// **Note:** The `didSubscribe` block is called every time a new subscriber |
| | | /// subscribes. Any side effects within the block will thus execute once for each |
| | | /// subscription, not necessarily on one thread, and possibly even |
| | | /// simultaneously! |
| | | + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe; |
| | | |
| | | /// Returns a signal that immediately sends the given error. |
| | | + (RACSignal *)error:(NSError *)error; |
| | | |
| | | /// Returns a signal that never completes. |
| | | + (RACSignal *)never; |
| | | |
| | | /// Immediately schedules the given block on the given scheduler. The block is |
| | | /// given a subscriber to which it can send events. |
| | | /// |
| | | /// scheduler - The scheduler on which `block` will be scheduled and results |
| | | /// delivered. Cannot be nil. |
| | | /// block - The block to invoke. Cannot be NULL. |
| | | /// |
| | | /// Returns a signal which will send all events sent on the subscriber given to |
| | | /// `block`. All events will be sent on `scheduler` and it will replay any missed |
| | | /// events to new subscribers. |
| | | + (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block; |
| | | |
| | | /// Invokes the given block only on the first subscription. The block is given a |
| | | /// subscriber to which it can send events. |
| | | /// |
| | | /// Note that disposing of the subscription to the returned signal will *not* |
| | | /// dispose of the underlying subscription. If you need that behavior, see |
| | | /// -[RACMulticastConnection autoconnect]. The underlying subscription will never |
| | | /// be disposed of. Because of this, `block` should never return an infinite |
| | | /// signal since there would be no way of ending it. |
| | | /// |
| | | /// scheduler - The scheduler on which the block should be scheduled. Note that |
| | | /// if given +[RACScheduler immediateScheduler], the block will be |
| | | /// invoked synchronously on the first subscription. Cannot be nil. |
| | | /// block - The block to invoke on the first subscription. Cannot be NULL. |
| | | /// |
| | | /// Returns a signal which will pass through the events sent to the subscriber |
| | | /// given to `block` and replay any missed events to new subscribers. |
| | | + (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block; |
| | | |
| | | @end |
| | | |
| | | @interface RACSignal (RACStream) |
| | | |
| | | /// Returns a signal that immediately sends the given value and then completes. |
| | | + (RACSignal *)return:(id)value; |
| | | |
| | | /// Returns a signal that immediately completes. |
| | | + (RACSignal *)empty; |
| | | |
| | | /// Subscribes to `signal` when the source signal completes. |
| | | - (RACSignal *)concat:(RACSignal *)signal; |
| | | |
| | | /// Zips the values in the receiver with those of the given signal to create |
| | | /// RACTuples. |
| | | /// |
| | | /// The first `next` of each stream will be combined, then the second `next`, and |
| | | /// so forth, until either signal completes or errors. |
| | | /// |
| | | /// signal - The signal to zip with. This must not be `nil`. |
| | | /// |
| | | /// Returns a new signal of RACTuples, representing the combined values of the |
| | | /// two signals. Any error from one of the original signals will be forwarded on |
| | | /// the returned signal. |
| | | - (RACSignal *)zipWith:(RACSignal *)signal; |
| | | |
| | | @end |
| | | |
| | | @interface RACSignal (Subscription) |
| | | |
| | | /// Subscribes `subscriber` to changes on the receiver. The receiver defines which |
| | | /// events it actually sends and in what situations the events are sent. |
| | | /// |
| | | /// Subscription will always happen on a valid RACScheduler. If the |
| | | /// +[RACScheduler currentScheduler] cannot be determined at the time of |
| | | /// subscription (e.g., because the calling code is running on a GCD queue or |
| | | /// NSOperationQueue), subscription will occur on a private background scheduler. |
| | | /// On the main thread, subscriptions will always occur immediately, with a |
| | | /// +[RACScheduler currentScheduler] of +[RACScheduler mainThreadScheduler]. |
| | | /// |
| | | /// This method must be overridden by any subclasses. |
| | | /// |
| | | /// Returns nil or a disposable. You can call -[RACDisposable dispose] if you |
| | | /// need to end your subscription before it would "naturally" end, either by |
| | | /// completing or erroring. Once the disposable has been disposed, the subscriber |
| | | /// won't receive any more events from the subscription. |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber; |
| | | |
| | | /// Convenience method to subscribe to the `next` event. |
| | | /// |
| | | /// This corresponds to `IObserver<T>.OnNext` in Rx. |
| | | - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock; |
| | | |
| | | /// Convenience method to subscribe to the `next` and `completed` events. |
| | | - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock; |
| | | |
| | | /// Convenience method to subscribe to the `next`, `completed`, and `error` events. |
| | | - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; |
| | | |
| | | /// Convenience method to subscribe to `error` events. |
| | | /// |
| | | /// This corresponds to the `IObserver<T>.OnError` in Rx. |
| | | - (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock; |
| | | |
| | | /// Convenience method to subscribe to `completed` events. |
| | | /// |
| | | /// This corresponds to the `IObserver<T>.OnCompleted` in Rx. |
| | | - (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock; |
| | | |
| | | /// Convenience method to subscribe to `next` and `error` events. |
| | | - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock; |
| | | |
| | | /// Convenience method to subscribe to `error` and `completed` events. |
| | | - (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; |
| | | |
| | | @end |
| | | |
| | | /// Additional methods to assist with debugging. |
| | | @interface RACSignal (Debugging) |
| | | |
| | | /// Logs all events that the receiver sends. |
| | | - (RACSignal *)logAll; |
| | | |
| | | /// Logs each `next` that the receiver sends. |
| | | - (RACSignal *)logNext; |
| | | |
| | | /// Logs any error that the receiver sends. |
| | | - (RACSignal *)logError; |
| | | |
| | | /// Logs any `completed` event that the receiver sends. |
| | | - (RACSignal *)logCompleted; |
| | | |
| | | @end |
| | | |
| | | /// Additional methods to assist with unit testing. |
| | | /// |
| | | /// **These methods should never ship in production code.** |
| | | @interface RACSignal (Testing) |
| | | |
| | | /// Spins the main run loop for a short while, waiting for the receiver to send a `next`. |
| | | /// |
| | | /// **Because this method executes the run loop recursively, it should only be used |
| | | /// on the main thread, and only from a unit test.** |
| | | /// |
| | | /// defaultValue - Returned if the receiver completes or errors before sending |
| | | /// a `next`, or if the method times out. This argument may be |
| | | /// nil. |
| | | /// success - If not NULL, set to whether the receiver completed |
| | | /// successfully. |
| | | /// error - If not NULL, set to any error that occurred. |
| | | /// |
| | | /// Returns the first value received, or `defaultValue` if no value is received |
| | | /// before the signal finishes or the method times out. |
| | | - (id)asynchronousFirstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error; |
| | | |
| | | /// Spins the main run loop for a short while, waiting for the receiver to complete. |
| | | /// |
| | | /// **Because this method executes the run loop recursively, it should only be used |
| | | /// on the main thread, and only from a unit test.** |
| | | /// |
| | | /// error - If not NULL, set to any error that occurs. |
| | | /// |
| | | /// Returns whether the signal completed successfully before timing out. If NO, |
| | | /// `error` will be set to any error that occurred. |
| | | - (BOOL)asynchronouslyWaitUntilCompleted:(NSError **)error; |
| | | |
| | | @end |
| | | |
| | | @interface RACSignal (Deprecated) |
| | | |
| | | + (RACSignal *)start:(id (^)(BOOL *success, NSError **error))block __attribute__((deprecated("Use +startEagerlyWithScheduler:block: instead"))); |
| | | + (RACSignal *)startWithScheduler:(RACScheduler *)scheduler subjectBlock:(void (^)(RACSubject *subject))block __attribute__((deprecated("Use +startEagerlyWithScheduler:block: instead"))); |
| | | + (RACSignal *)startWithScheduler:(RACScheduler *)scheduler block:(id (^)(BOOL *success, NSError **error))block __attribute__((deprecated("Use +startEagerlyWithScheduler:block: instead"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSignal.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/15/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSignal.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACDynamicSignal.h" |
| | | #import "RACEmptySignal.h" |
| | | #import "RACErrorSignal.h" |
| | | #import "RACMulticastConnection.h" |
| | | #import "RACReplaySubject.h" |
| | | #import "RACReturnSignal.h" |
| | | #import "RACScheduler.h" |
| | | #import "RACSerialDisposable.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "RACSubject.h" |
| | | #import "RACSubscriber+Private.h" |
| | | #import "RACTuple.h" |
| | | |
| | | @implementation RACSignal |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { |
| | | return [RACDynamicSignal createSignal:didSubscribe]; |
| | | } |
| | | |
| | | + (RACSignal *)error:(NSError *)error { |
| | | return [RACErrorSignal error:error]; |
| | | } |
| | | |
| | | + (RACSignal *)never { |
| | | return [[self createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| | | return nil; |
| | | }] setNameWithFormat:@"+never"]; |
| | | } |
| | | |
| | | + (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block { |
| | | NSCParameterAssert(scheduler != nil); |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | RACSignal *signal = [self startLazilyWithScheduler:scheduler block:block]; |
| | | // Subscribe to force the lazy signal to call its block. |
| | | [[signal publish] connect]; |
| | | return [signal setNameWithFormat:@"+startEagerlyWithScheduler: %@ block:", scheduler]; |
| | | } |
| | | |
| | | + (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block { |
| | | NSCParameterAssert(scheduler != nil); |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | RACMulticastConnection *connection = [[RACSignal |
| | | createSignal:^ id (id<RACSubscriber> subscriber) { |
| | | block(subscriber); |
| | | return nil; |
| | | }] |
| | | multicast:[RACReplaySubject subject]]; |
| | | |
| | | return [[[RACSignal |
| | | createSignal:^ id (id<RACSubscriber> subscriber) { |
| | | [connection.signal subscribe:subscriber]; |
| | | [connection connect]; |
| | | return nil; |
| | | }] |
| | | subscribeOn:scheduler] |
| | | setNameWithFormat:@"+startLazilyWithScheduler: %@ block:", scheduler]; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | return [NSString stringWithFormat:@"<%@: %p> name: %@", self.class, self, self.name]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACSignal (RACStream) |
| | | |
| | | + (RACSignal *)empty { |
| | | return [RACEmptySignal empty]; |
| | | } |
| | | |
| | | + (RACSignal *)return:(id)value { |
| | | return [RACReturnSignal return:value]; |
| | | } |
| | | |
| | | - (RACSignal *)bind:(RACStreamBindBlock (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | /* |
| | | * -bind: should: |
| | | * |
| | | * 1. Subscribe to the original signal of values. |
| | | * 2. Any time the original signal sends a value, transform it using the binding block. |
| | | * 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received. |
| | | * 4. If the binding block asks the bind to terminate, complete the _original_ signal. |
| | | * 5. When _all_ signals complete, send completed to the subscriber. |
| | | * |
| | | * If any signal sends an error at any point, send that to the subscriber. |
| | | */ |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACStreamBindBlock bindingBlock = block(); |
| | | |
| | | NSMutableArray *signals = [NSMutableArray arrayWithObject:self]; |
| | | |
| | | RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) { |
| | | BOOL removeDisposable = NO; |
| | | |
| | | @synchronized (signals) { |
| | | [signals removeObject:signal]; |
| | | |
| | | if (signals.count == 0) { |
| | | [subscriber sendCompleted]; |
| | | [compoundDisposable dispose]; |
| | | } else { |
| | | removeDisposable = YES; |
| | | } |
| | | } |
| | | |
| | | if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; |
| | | }; |
| | | |
| | | void (^addSignal)(RACSignal *) = ^(RACSignal *signal) { |
| | | @synchronized (signals) { |
| | | [signals addObject:signal]; |
| | | } |
| | | |
| | | RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; |
| | | [compoundDisposable addDisposable:selfDisposable]; |
| | | |
| | | RACDisposable *disposable = [signal subscribeNext:^(id x) { |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [compoundDisposable dispose]; |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | @autoreleasepool { |
| | | completeSignal(signal, selfDisposable); |
| | | } |
| | | }]; |
| | | |
| | | selfDisposable.disposable = disposable; |
| | | }; |
| | | |
| | | @autoreleasepool { |
| | | RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; |
| | | [compoundDisposable addDisposable:selfDisposable]; |
| | | |
| | | RACDisposable *bindingDisposable = [self subscribeNext:^(id x) { |
| | | // Manually check disposal to handle synchronous errors. |
| | | if (compoundDisposable.disposed) return; |
| | | |
| | | BOOL stop = NO; |
| | | id signal = bindingBlock(x, &stop); |
| | | |
| | | @autoreleasepool { |
| | | if (signal != nil) addSignal(signal); |
| | | if (signal == nil || stop) { |
| | | [selfDisposable dispose]; |
| | | completeSignal(self, selfDisposable); |
| | | } |
| | | } |
| | | } error:^(NSError *error) { |
| | | [compoundDisposable dispose]; |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | @autoreleasepool { |
| | | completeSignal(self, selfDisposable); |
| | | } |
| | | }]; |
| | | |
| | | selfDisposable.disposable = bindingDisposable; |
| | | } |
| | | |
| | | return compoundDisposable; |
| | | }] setNameWithFormat:@"[%@] -bind:", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)concat:(RACSignal *)signal { |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; |
| | | |
| | | RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { |
| | | [subscriber sendNext:x]; |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | RACDisposable *concattedDisposable = [signal subscribe:subscriber]; |
| | | serialDisposable.disposable = concattedDisposable; |
| | | }]; |
| | | |
| | | serialDisposable.disposable = sourceDisposable; |
| | | return serialDisposable; |
| | | }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal]; |
| | | } |
| | | |
| | | - (RACSignal *)zipWith:(RACSignal *)signal { |
| | | NSCParameterAssert(signal != nil); |
| | | |
| | | return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| | | __block BOOL selfCompleted = NO; |
| | | NSMutableArray *selfValues = [NSMutableArray array]; |
| | | |
| | | __block BOOL otherCompleted = NO; |
| | | NSMutableArray *otherValues = [NSMutableArray array]; |
| | | |
| | | void (^sendCompletedIfNecessary)(void) = ^{ |
| | | @synchronized (selfValues) { |
| | | BOOL selfEmpty = (selfCompleted && selfValues.count == 0); |
| | | BOOL otherEmpty = (otherCompleted && otherValues.count == 0); |
| | | if (selfEmpty || otherEmpty) [subscriber sendCompleted]; |
| | | } |
| | | }; |
| | | |
| | | void (^sendNext)(void) = ^{ |
| | | @synchronized (selfValues) { |
| | | if (selfValues.count == 0) return; |
| | | if (otherValues.count == 0) return; |
| | | |
| | | RACTuple *tuple = RACTuplePack(selfValues[0], otherValues[0]); |
| | | [selfValues removeObjectAtIndex:0]; |
| | | [otherValues removeObjectAtIndex:0]; |
| | | |
| | | [subscriber sendNext:tuple]; |
| | | sendCompletedIfNecessary(); |
| | | } |
| | | }; |
| | | |
| | | RACDisposable *selfDisposable = [self subscribeNext:^(id x) { |
| | | @synchronized (selfValues) { |
| | | [selfValues addObject:x ?: RACTupleNil.tupleNil]; |
| | | sendNext(); |
| | | } |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | @synchronized (selfValues) { |
| | | selfCompleted = YES; |
| | | sendCompletedIfNecessary(); |
| | | } |
| | | }]; |
| | | |
| | | RACDisposable *otherDisposable = [signal subscribeNext:^(id x) { |
| | | @synchronized (selfValues) { |
| | | [otherValues addObject:x ?: RACTupleNil.tupleNil]; |
| | | sendNext(); |
| | | } |
| | | } error:^(NSError *error) { |
| | | [subscriber sendError:error]; |
| | | } completed:^{ |
| | | @synchronized (selfValues) { |
| | | otherCompleted = YES; |
| | | sendCompletedIfNecessary(); |
| | | } |
| | | }]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | [selfDisposable dispose]; |
| | | [otherDisposable dispose]; |
| | | }]; |
| | | }] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACSignal (Subscription) |
| | | |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { |
| | | NSCAssert(NO, @"This method must be overridden by subclasses"); |
| | | return nil; |
| | | } |
| | | |
| | | - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock { |
| | | NSCParameterAssert(nextBlock != NULL); |
| | | |
| | | RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL]; |
| | | return [self subscribe:o]; |
| | | } |
| | | |
| | | - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock { |
| | | NSCParameterAssert(nextBlock != NULL); |
| | | NSCParameterAssert(completedBlock != NULL); |
| | | |
| | | RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:completedBlock]; |
| | | return [self subscribe:o]; |
| | | } |
| | | |
| | | - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock { |
| | | NSCParameterAssert(nextBlock != NULL); |
| | | NSCParameterAssert(errorBlock != NULL); |
| | | NSCParameterAssert(completedBlock != NULL); |
| | | |
| | | RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock]; |
| | | return [self subscribe:o]; |
| | | } |
| | | |
| | | - (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock { |
| | | NSCParameterAssert(errorBlock != NULL); |
| | | |
| | | RACSubscriber *o = [RACSubscriber subscriberWithNext:NULL error:errorBlock completed:NULL]; |
| | | return [self subscribe:o]; |
| | | } |
| | | |
| | | - (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock { |
| | | NSCParameterAssert(completedBlock != NULL); |
| | | |
| | | RACSubscriber *o = [RACSubscriber subscriberWithNext:NULL error:NULL completed:completedBlock]; |
| | | return [self subscribe:o]; |
| | | } |
| | | |
| | | - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock { |
| | | NSCParameterAssert(nextBlock != NULL); |
| | | NSCParameterAssert(errorBlock != NULL); |
| | | |
| | | RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:NULL]; |
| | | return [self subscribe:o]; |
| | | } |
| | | |
| | | - (RACDisposable *)subscribeError:(void (^)(NSError *))errorBlock completed:(void (^)(void))completedBlock { |
| | | NSCParameterAssert(completedBlock != NULL); |
| | | NSCParameterAssert(errorBlock != NULL); |
| | | |
| | | RACSubscriber *o = [RACSubscriber subscriberWithNext:NULL error:errorBlock completed:completedBlock]; |
| | | return [self subscribe:o]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACSignal (Debugging) |
| | | |
| | | - (RACSignal *)logAll { |
| | | return [[[self logNext] logError] logCompleted]; |
| | | } |
| | | |
| | | - (RACSignal *)logNext { |
| | | return [[self doNext:^(id x) { |
| | | NSLog(@"%@ next: %@", self, x); |
| | | }] setNameWithFormat:@"%@", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)logError { |
| | | return [[self doError:^(NSError *error) { |
| | | NSLog(@"%@ error: %@", self, error); |
| | | }] setNameWithFormat:@"%@", self.name]; |
| | | } |
| | | |
| | | - (RACSignal *)logCompleted { |
| | | return [[self doCompleted:^{ |
| | | NSLog(@"%@ completed", self); |
| | | }] setNameWithFormat:@"%@", self.name]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACSignal (Testing) |
| | | |
| | | static const NSTimeInterval RACSignalAsynchronousWaitTimeout = 10; |
| | | |
| | | - (id)asynchronousFirstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error { |
| | | NSCAssert([NSThread isMainThread], @"%s should only be used from the main thread", __func__); |
| | | |
| | | __block id result = defaultValue; |
| | | __block BOOL done = NO; |
| | | |
| | | // Ensures that we don't pass values across thread boundaries by reference. |
| | | __block NSError *localError; |
| | | __block BOOL localSuccess = YES; |
| | | |
| | | [[[[self |
| | | take:1] |
| | | timeout:RACSignalAsynchronousWaitTimeout onScheduler:[RACScheduler scheduler]] |
| | | deliverOn:RACScheduler.mainThreadScheduler] |
| | | subscribeNext:^(id x) { |
| | | result = x; |
| | | done = YES; |
| | | } error:^(NSError *e) { |
| | | if (!done) { |
| | | localSuccess = NO; |
| | | localError = e; |
| | | done = YES; |
| | | } |
| | | } completed:^{ |
| | | done = YES; |
| | | }]; |
| | | |
| | | do { |
| | | [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; |
| | | } while (!done); |
| | | |
| | | if (success != NULL) *success = localSuccess; |
| | | if (error != NULL) *error = localError; |
| | | |
| | | return result; |
| | | } |
| | | |
| | | - (BOOL)asynchronouslyWaitUntilCompleted:(NSError **)error { |
| | | BOOL success = NO; |
| | | [[self ignoreValues] asynchronousFirstOrDefault:nil success:&success error:error]; |
| | | return success; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACSignal (Deprecated) |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | |
| | | + (RACSignal *)startWithScheduler:(RACScheduler *)scheduler subjectBlock:(void (^)(RACSubject *subject))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"+startWithScheduler:subjectBlock:"]; |
| | | |
| | | [scheduler schedule:^{ |
| | | block(subject); |
| | | }]; |
| | | |
| | | return subject; |
| | | } |
| | | |
| | | + (RACSignal *)start:(id (^)(BOOL *success, NSError **error))block { |
| | | return [[self startWithScheduler:[RACScheduler scheduler] block:block] setNameWithFormat:@"+start:"]; |
| | | } |
| | | |
| | | + (RACSignal *)startWithScheduler:(RACScheduler *)scheduler block:(id (^)(BOOL *success, NSError **error))block { |
| | | return [[self startWithScheduler:scheduler subjectBlock:^(id<RACSubscriber> subscriber) { |
| | | BOOL success = YES; |
| | | NSError *error = nil; |
| | | id returned = block(&success, &error); |
| | | |
| | | if (!success) { |
| | | [subscriber sendError:error]; |
| | | } else { |
| | | [subscriber sendNext:returned]; |
| | | [subscriber sendCompleted]; |
| | | } |
| | | }] setNameWithFormat:@"+startWithScheduler:block:"]; |
| | | } |
| | | |
| | | #pragma clang diagnostic pop |
| | | |
| | | @end |
New file |
| | |
| | | provider RACSignal { |
| | | probe next(char *signal, char *subscriber, char *valueDescription); |
| | | probe completed(char *signal, char *subscriber); |
| | | probe error(char *signal, char *subscriber, char *errorDescription); |
| | | }; |
New file |
| | |
| | | // |
| | | // RACSignalSequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-11-09. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSequence.h" |
| | | |
| | | @class RACSignal; |
| | | |
| | | // Private class that adapts a RACSignal to the RACSequence interface. |
| | | @interface RACSignalSequence : RACSequence |
| | | |
| | | // Returns a sequence for enumerating over the given signal. |
| | | + (RACSequence *)sequenceWithSignal:(RACSignal *)signal; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSignalSequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-11-09. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSignalSequence.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACReplaySubject.h" |
| | | #import "RACSignal+Operations.h" |
| | | |
| | | @interface RACSignalSequence () |
| | | |
| | | // Replays the signal given on initialization. |
| | | @property (nonatomic, strong, readonly) RACReplaySubject *subject; |
| | | |
| | | @end |
| | | |
| | | @implementation RACSignalSequence |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (RACSequence *)sequenceWithSignal:(RACSignal *)signal { |
| | | RACSignalSequence *seq = [[self alloc] init]; |
| | | |
| | | RACReplaySubject *subject = [RACReplaySubject subject]; |
| | | [signal subscribeNext:^(id value) { |
| | | [subject sendNext:value]; |
| | | } error:^(NSError *error) { |
| | | [subject sendError:error]; |
| | | } completed:^{ |
| | | [subject sendCompleted]; |
| | | }]; |
| | | |
| | | seq->_subject = subject; |
| | | return seq; |
| | | } |
| | | |
| | | #pragma mark RACSequence |
| | | |
| | | - (id)head { |
| | | id value = [self.subject firstOrDefault:self]; |
| | | |
| | | if (value == self) { |
| | | return nil; |
| | | } else { |
| | | return value ?: NSNull.null; |
| | | } |
| | | } |
| | | |
| | | - (RACSequence *)tail { |
| | | RACSequence *sequence = [self.class sequenceWithSignal:[self.subject skip:1]]; |
| | | sequence.name = self.name; |
| | | return sequence; |
| | | } |
| | | |
| | | - (NSArray *)array { |
| | | return self.subject.toArray; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | // Synchronously accumulate the values that have been sent so far. |
| | | NSMutableArray *values = [NSMutableArray array]; |
| | | RACDisposable *disposable = [self.subject subscribeNext:^(id value) { |
| | | @synchronized (values) { |
| | | [values addObject:value ?: NSNull.null]; |
| | | } |
| | | }]; |
| | | |
| | | [disposable dispose]; |
| | | |
| | | return [NSString stringWithFormat:@"<%@: %p>{ name = %@, values = %@ ⦠}", self.class, self, self.name, values]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACStream+Private.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-07-22. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACStream.h" |
| | | |
| | | @interface RACStream () |
| | | |
| | | // Combines a list of streams using the logic of the given block. |
| | | // |
| | | // streams - The streams to combine. |
| | | // block - An operator that combines two streams and returns a new one. The |
| | | // returned stream should contain 2-tuples of the streams' combined |
| | | // values. |
| | | // |
| | | // Returns a combined stream. |
| | | + (instancetype)join:(id<NSFastEnumeration>)streams block:(RACStream * (^)(id, id))block; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACStream.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-31. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACStream; |
| | | |
| | | /// A block which accepts a value from a RACStream and returns a new instance |
| | | /// of the same stream class. |
| | | /// |
| | | /// Setting `stop` to `YES` will cause the bind to terminate after the returned |
| | | /// value. Returning `nil` will result in immediate termination. |
| | | typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop); |
| | | |
| | | /// An abstract class representing any stream of values. |
| | | /// |
| | | /// This class represents a monad, upon which many stream-based operations can |
| | | /// be built. |
| | | /// |
| | | /// When subclassing RACStream, only the methods in the main @interface body need |
| | | /// to be overridden. |
| | | @interface RACStream : NSObject |
| | | |
| | | /// Returns an empty stream. |
| | | + (instancetype)empty; |
| | | |
| | | /// Lifts `value` into the stream monad. |
| | | /// |
| | | /// Returns a stream containing only the given value. |
| | | + (instancetype)return:(id)value; |
| | | |
| | | /// Lazily binds a block to the values in the receiver. |
| | | /// |
| | | /// This should only be used if you need to terminate the bind early, or close |
| | | /// over some state. -flattenMap: is more appropriate for all other cases. |
| | | /// |
| | | /// block - A block returning a RACStreamBindBlock. This block will be invoked |
| | | /// each time the bound stream is re-evaluated. This block must not be |
| | | /// nil or return nil. |
| | | /// |
| | | /// Returns a new stream which represents the combined result of all lazy |
| | | /// applications of `block`. |
| | | - (instancetype)bind:(RACStreamBindBlock (^)(void))block; |
| | | |
| | | /// Appends the values of `stream` to the values in the receiver. |
| | | /// |
| | | /// stream - A stream to concatenate. This must be an instance of the same |
| | | /// concrete class as the receiver, and should not be `nil`. |
| | | /// |
| | | /// Returns a new stream representing the receiver followed by `stream`. |
| | | - (instancetype)concat:(RACStream *)stream; |
| | | |
| | | /// Zips the values in the receiver with those of the given stream to create |
| | | /// RACTuples. |
| | | /// |
| | | /// The first value of each stream will be combined, then the second value, and |
| | | /// so forth, until at least one of the streams is exhausted. |
| | | /// |
| | | /// stream - The stream to zip with. This must be an instance of the same |
| | | /// concrete class as the receiver, and should not be `nil`. |
| | | /// |
| | | /// Returns a new stream of RACTuples, representing the zipped values of the |
| | | /// two streams. |
| | | - (instancetype)zipWith:(RACStream *)stream; |
| | | |
| | | @end |
| | | |
| | | /// This extension contains functionality to support naming streams for |
| | | /// debugging. |
| | | /// |
| | | /// Subclasses do not need to override the methods here. |
| | | @interface RACStream () |
| | | |
| | | /// The name of the stream. This is for debugging/human purposes only. |
| | | @property (copy) NSString *name; |
| | | |
| | | /// Sets the name of the receiver to the given format string. |
| | | /// |
| | | /// This is for debugging purposes only, and won't do anything unless the |
| | | /// RAC_DEBUG_SIGNAL_NAMES environment variable is set. |
| | | /// |
| | | /// Returns the receiver, for easy method chaining. |
| | | - (instancetype)setNameWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); |
| | | |
| | | @end |
| | | |
| | | /// Operations built on the RACStream primitives. |
| | | /// |
| | | /// These methods do not need to be overridden, although subclasses may |
| | | /// occasionally gain better performance from doing so. |
| | | @interface RACStream (Operations) |
| | | |
| | | /// Maps `block` across the values in the receiver and flattens the result. |
| | | /// |
| | | /// Note that operators applied _after_ -flattenMap: behave differently from |
| | | /// operators _within_ -flattenMap:. See the Examples section below. |
| | | /// |
| | | /// This corresponds to the `SelectMany` method in Rx. |
| | | /// |
| | | /// block - A block which accepts the values in the receiver and returns a new |
| | | /// instance of the receiver's class. Returning `nil` from this block is |
| | | /// equivalent to returning an empty signal. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// [signal flattenMap:^(id x) { |
| | | /// // Logs each time a returned signal completes. |
| | | /// return [[RACSignal return:x] logCompleted]; |
| | | /// }]; |
| | | /// |
| | | /// [[signal |
| | | /// flattenMap:^(id x) { |
| | | /// return [RACSignal return:x]; |
| | | /// }] |
| | | /// // Logs only once, when all of the signals complete. |
| | | /// logCompleted]; |
| | | /// |
| | | /// Returns a new stream which represents the combined streams resulting from |
| | | /// mapping `block`. |
| | | - (instancetype)flattenMap:(RACStream * (^)(id value))block; |
| | | |
| | | /// Flattens a stream of streams. |
| | | /// |
| | | /// This corresponds to the `Merge` method in Rx. |
| | | /// |
| | | /// Returns a stream consisting of the combined streams obtained from the |
| | | /// receiver. |
| | | - (instancetype)flatten; |
| | | |
| | | /// Maps `block` across the values in the receiver. |
| | | /// |
| | | /// This corresponds to the `Select` method in Rx. |
| | | /// |
| | | /// Returns a new stream with the mapped values. |
| | | - (instancetype)map:(id (^)(id value))block; |
| | | |
| | | /// Replaces each value in the receiver with the given object. |
| | | /// |
| | | /// Returns a new stream which includes the given object once for each value in |
| | | /// the receiver. |
| | | - (instancetype)mapReplace:(id)object; |
| | | |
| | | /// Filters out values in the receiver that don't pass the given test. |
| | | /// |
| | | /// This corresponds to the `Where` method in Rx. |
| | | /// |
| | | /// Returns a new stream with only those values that passed. |
| | | - (instancetype)filter:(BOOL (^)(id value))block; |
| | | |
| | | /// Filters out values in the receiver that equal (via -isEqual:) the provided value. |
| | | /// |
| | | /// value - The value can be `nil`, in which case it ignores `nil` values. |
| | | /// |
| | | /// Returns a new stream containing only the values which did not compare equal |
| | | /// to `value`. |
| | | - (instancetype)ignore:(id)value; |
| | | |
| | | /// Unpacks each RACTuple in the receiver and maps the values to a new value. |
| | | /// |
| | | /// reduceBlock - The block which reduces each RACTuple's values into one value. |
| | | /// It must take as many arguments as the number of tuple elements |
| | | /// to process. Each argument will be an object argument. The |
| | | /// return value must be an object. This argument cannot be nil. |
| | | /// |
| | | /// Returns a new stream of reduced tuple values. |
| | | - (instancetype)reduceEach:(id (^)())reduceBlock; |
| | | |
| | | /// Returns a stream consisting of `value`, followed by the values in the |
| | | /// receiver. |
| | | - (instancetype)startWith:(id)value; |
| | | |
| | | /// Skips the first `skipCount` values in the receiver. |
| | | /// |
| | | /// Returns the receiver after skipping the first `skipCount` values. If |
| | | /// `skipCount` is greater than the number of values in the stream, an empty |
| | | /// stream is returned. |
| | | - (instancetype)skip:(NSUInteger)skipCount; |
| | | |
| | | /// Returns a stream of the first `count` values in the receiver. If `count` is |
| | | /// greater than or equal to the number of values in the stream, a stream |
| | | /// equivalent to the receiver is returned. |
| | | - (instancetype)take:(NSUInteger)count; |
| | | |
| | | /// Zips the values in the given streams to create RACTuples. |
| | | /// |
| | | /// The first value of each stream will be combined, then the second value, and |
| | | /// so forth, until at least one of the streams is exhausted. |
| | | /// |
| | | /// streams - The streams to combine. These must all be instances of the same |
| | | /// concrete class implementing the protocol. If this collection is |
| | | /// empty, the returned stream will be empty. |
| | | /// |
| | | /// Returns a new stream containing RACTuples of the zipped values from the |
| | | /// streams. |
| | | + (instancetype)zip:(id<NSFastEnumeration>)streams; |
| | | |
| | | /// Zips streams using +zip:, then reduces the resulting tuples into a single |
| | | /// value using -reduceEach: |
| | | /// |
| | | /// streams - The streams to combine. These must all be instances of the |
| | | /// same concrete class implementing the protocol. If this |
| | | /// collection is empty, the returned stream will be empty. |
| | | /// reduceBlock - The block which reduces the values from all the streams |
| | | /// into one value. It must take as many arguments as the |
| | | /// number of streams given. Each argument will be an object |
| | | /// argument. The return value must be an object. This argument |
| | | /// must not be nil. |
| | | /// |
| | | /// Example: |
| | | /// |
| | | /// [RACStream zip:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) { |
| | | /// return [NSString stringWithFormat:@"%@: %@", string, number]; |
| | | /// }]; |
| | | /// |
| | | /// Returns a new stream containing the results from each invocation of |
| | | /// `reduceBlock`. |
| | | + (instancetype)zip:(id<NSFastEnumeration>)streams reduce:(id (^)())reduceBlock; |
| | | |
| | | /// Returns a stream obtained by concatenating `streams` in order. |
| | | + (instancetype)concat:(id<NSFastEnumeration>)streams; |
| | | |
| | | /// Combines values in the receiver from left to right using the given block. |
| | | /// |
| | | /// The algorithm proceeds as follows: |
| | | /// |
| | | /// 1. `startingValue` is passed into the block as the `running` value, and the |
| | | /// first element of the receiver is passed into the block as the `next` value. |
| | | /// 2. The result of the invocation is added to the returned stream. |
| | | /// 3. The result of the invocation (`running`) and the next element of the |
| | | /// receiver (`next`) is passed into `block`. |
| | | /// 4. Steps 2 and 3 are repeated until all values have been processed. |
| | | /// |
| | | /// startingValue - The value to be combined with the first element of the |
| | | /// receiver. This value may be `nil`. |
| | | /// reduceBlock - The block that describes how to combine values of the |
| | | /// receiver. If the receiver is empty, this block will never be |
| | | /// invoked. Cannot be nil. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// RACSequence *numbers = @[ @1, @2, @3, @4 ].rac_sequence; |
| | | /// |
| | | /// // Contains 1, 3, 6, 10 |
| | | /// RACSequence *sums = [numbers scanWithStart:@0 reduce:^(NSNumber *sum, NSNumber *next) { |
| | | /// return @(sum.integerValue + next.integerValue); |
| | | /// }]; |
| | | /// |
| | | /// Returns a new stream that consists of each application of `reduceBlock`. If the |
| | | /// receiver is empty, an empty stream is returned. |
| | | - (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))reduceBlock; |
| | | |
| | | /// Combines values in the receiver from left to right using the given block |
| | | /// which also takes zero-based index of the values. |
| | | /// |
| | | /// startingValue - The value to be combined with the first element of the |
| | | /// receiver. This value may be `nil`. |
| | | /// reduceBlock - The block that describes how to combine values of the |
| | | /// receiver. This block takes zero-based index value as the last |
| | | /// parameter. If the receiver is empty, this block will never |
| | | /// be invoked. Cannot be nil. |
| | | /// |
| | | /// Returns a new stream that consists of each application of `reduceBlock`. If the |
| | | /// receiver is empty, an empty stream is returned. |
| | | - (instancetype)scanWithStart:(id)startingValue reduceWithIndex:(id (^)(id running, id next, NSUInteger index))reduceBlock; |
| | | |
| | | /// Combines each previous and current value into one object. |
| | | /// |
| | | /// This method is similar to -scanWithStart:reduce:, but only ever operates on |
| | | /// the previous and current values (instead of the whole stream), and does not |
| | | /// pass the return value of `reduceBlock` into the next invocation of it. |
| | | /// |
| | | /// start - The value passed into `reduceBlock` as `previous` for the |
| | | /// first value. |
| | | /// reduceBlock - The block that combines the previous value and the current |
| | | /// value to create the reduced value. Cannot be nil. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// RACSequence *numbers = @[ @1, @2, @3, @4 ].rac_sequence; |
| | | /// |
| | | /// // Contains 1, 3, 5, 7 |
| | | /// RACSequence *sums = [numbers combinePreviousWithStart:@0 reduce:^(NSNumber *previous, NSNumber *next) { |
| | | /// return @(previous.integerValue + next.integerValue); |
| | | /// }]; |
| | | /// |
| | | /// Returns a new stream consisting of the return values from each application of |
| | | /// `reduceBlock`. |
| | | - (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id current))reduceBlock; |
| | | |
| | | /// Takes values until the given block returns `YES`. |
| | | /// |
| | | /// Returns a stream of the initial values in the receiver that fail `predicate`. |
| | | /// If `predicate` never returns `YES`, a stream equivalent to the receiver is |
| | | /// returned. |
| | | - (instancetype)takeUntilBlock:(BOOL (^)(id x))predicate; |
| | | |
| | | /// Takes values until the given block returns `NO`. |
| | | /// |
| | | /// Returns a stream of the initial values in the receiver that pass `predicate`. |
| | | /// If `predicate` never returns `NO`, a stream equivalent to the receiver is |
| | | /// returned. |
| | | - (instancetype)takeWhileBlock:(BOOL (^)(id x))predicate; |
| | | |
| | | /// Skips values until the given block returns `YES`. |
| | | /// |
| | | /// Returns a stream containing the values of the receiver that follow any |
| | | /// initial values failing `predicate`. If `predicate` never returns `YES`, |
| | | /// an empty stream is returned. |
| | | - (instancetype)skipUntilBlock:(BOOL (^)(id x))predicate; |
| | | |
| | | /// Skips values until the given block returns `NO`. |
| | | /// |
| | | /// Returns a stream containing the values of the receiver that follow any |
| | | /// initial values passing `predicate`. If `predicate` never returns `NO`, an |
| | | /// empty stream is returned. |
| | | - (instancetype)skipWhileBlock:(BOOL (^)(id x))predicate; |
| | | |
| | | /// Returns a stream of values for which -isEqual: returns NO when compared to the |
| | | /// previous value. |
| | | - (instancetype)distinctUntilChanged; |
| | | |
| | | @end |
| | | |
| | | @interface RACStream (Deprecated) |
| | | |
| | | - (instancetype)sequenceMany:(RACStream * (^)(void))block __attribute__((deprecated("Use -flattenMap: instead"))); |
| | | - (instancetype)scanWithStart:(id)startingValue combine:(id (^)(id running, id next))block __attribute__((deprecated("Renamed to -scanWithStart:reduce:"))); |
| | | - (instancetype)mapPreviousWithStart:(id)start reduce:(id (^)(id previous, id current))combineBlock __attribute__((deprecated("Renamed to -combinePreviousWithStart:reduce:"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACStream.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-31. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACStream.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "RACBlockTrampoline.h" |
| | | #import "RACTuple.h" |
| | | |
| | | @implementation RACStream |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)init { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | self.name = @""; |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark Abstract methods |
| | | |
| | | + (instancetype)empty { |
| | | return nil; |
| | | } |
| | | |
| | | - (instancetype)bind:(RACStreamBindBlock (^)(void))block { |
| | | return nil; |
| | | } |
| | | |
| | | + (instancetype)return:(id)value { |
| | | return nil; |
| | | } |
| | | |
| | | - (instancetype)concat:(RACStream *)stream { |
| | | return nil; |
| | | } |
| | | |
| | | - (instancetype)zipWith:(RACStream *)stream { |
| | | return nil; |
| | | } |
| | | |
| | | #pragma mark Naming |
| | | |
| | | - (instancetype)setNameWithFormat:(NSString *)format, ... { |
| | | if (getenv("RAC_DEBUG_SIGNAL_NAMES") == NULL) return self; |
| | | |
| | | NSCParameterAssert(format != nil); |
| | | |
| | | va_list args; |
| | | va_start(args, format); |
| | | |
| | | NSString *str = [[NSString alloc] initWithFormat:format arguments:args]; |
| | | va_end(args); |
| | | |
| | | self.name = str; |
| | | return self; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACStream (Operations) |
| | | |
| | | - (instancetype)flattenMap:(RACStream * (^)(id value))block { |
| | | Class class = self.class; |
| | | |
| | | return [[self bind:^{ |
| | | return ^(id value, BOOL *stop) { |
| | | id stream = block(value) ?: [class empty]; |
| | | NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream); |
| | | |
| | | return stream; |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -flattenMap:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)flatten { |
| | | __weak RACStream *stream __attribute__((unused)) = self; |
| | | return [[self flattenMap:^(id value) { |
| | | return value; |
| | | }] setNameWithFormat:@"[%@] -flatten", self.name]; |
| | | } |
| | | |
| | | - (instancetype)map:(id (^)(id value))block { |
| | | NSCParameterAssert(block != nil); |
| | | |
| | | Class class = self.class; |
| | | |
| | | return [[self flattenMap:^(id value) { |
| | | return [class return:block(value)]; |
| | | }] setNameWithFormat:@"[%@] -map:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)mapReplace:(id)object { |
| | | return [[self map:^(id _) { |
| | | return object; |
| | | }] setNameWithFormat:@"[%@] -mapReplace: %@", self.name, [object rac_description]]; |
| | | } |
| | | |
| | | - (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id next))reduceBlock { |
| | | NSCParameterAssert(reduceBlock != NULL); |
| | | return [[[self |
| | | scanWithStart:RACTuplePack(start) |
| | | reduce:^(RACTuple *previousTuple, id next) { |
| | | id value = reduceBlock(previousTuple[0], next); |
| | | return RACTuplePack(next, value); |
| | | }] |
| | | map:^(RACTuple *tuple) { |
| | | return tuple[1]; |
| | | }] |
| | | setNameWithFormat:@"[%@] -combinePreviousWithStart: %@ reduce:", self.name, [start rac_description]]; |
| | | } |
| | | |
| | | - (instancetype)filter:(BOOL (^)(id value))block { |
| | | NSCParameterAssert(block != nil); |
| | | |
| | | Class class = self.class; |
| | | |
| | | return [[self flattenMap:^ id (id value) { |
| | | if (block(value)) { |
| | | return [class return:value]; |
| | | } else { |
| | | return class.empty; |
| | | } |
| | | }] setNameWithFormat:@"[%@] -filter:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)ignore:(id)value { |
| | | return [[self filter:^ BOOL (id innerValue) { |
| | | return innerValue != value && ![innerValue isEqual:value]; |
| | | }] setNameWithFormat:@"[%@] -ignore: %@", self.name, [value rac_description]]; |
| | | } |
| | | |
| | | - (instancetype)reduceEach:(id (^)())reduceBlock { |
| | | NSCParameterAssert(reduceBlock != nil); |
| | | |
| | | __weak RACStream *stream __attribute__((unused)) = self; |
| | | return [[self map:^(RACTuple *t) { |
| | | NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t); |
| | | return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:t]; |
| | | }] setNameWithFormat:@"[%@] -reduceEach:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)startWith:(id)value { |
| | | return [[[self.class return:value] |
| | | concat:self] |
| | | setNameWithFormat:@"[%@] -startWith: %@", self.name, [value rac_description]]; |
| | | } |
| | | |
| | | - (instancetype)skip:(NSUInteger)skipCount { |
| | | Class class = self.class; |
| | | |
| | | return [[self bind:^{ |
| | | __block NSUInteger skipped = 0; |
| | | |
| | | return ^(id value, BOOL *stop) { |
| | | if (skipped >= skipCount) return [class return:value]; |
| | | |
| | | skipped++; |
| | | return class.empty; |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -skip: %lu", self.name, (unsigned long)skipCount]; |
| | | } |
| | | |
| | | - (instancetype)take:(NSUInteger)count { |
| | | Class class = self.class; |
| | | |
| | | if (count == 0) return class.empty; |
| | | |
| | | return [[self bind:^{ |
| | | __block NSUInteger taken = 0; |
| | | |
| | | return ^ id (id value, BOOL *stop) { |
| | | if (taken < count) { |
| | | ++taken; |
| | | if (taken == count) *stop = YES; |
| | | return [class return:value]; |
| | | } else { |
| | | return nil; |
| | | } |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -take: %lu", self.name, (unsigned long)count]; |
| | | } |
| | | |
| | | + (instancetype)join:(id<NSFastEnumeration>)streams block:(RACStream * (^)(id, id))block { |
| | | RACStream *current = nil; |
| | | |
| | | // Creates streams of successively larger tuples by combining the input |
| | | // streams one-by-one. |
| | | for (RACStream *stream in streams) { |
| | | // For the first stream, just wrap its values in a RACTuple. That way, |
| | | // if only one stream is given, the result is still a stream of tuples. |
| | | if (current == nil) { |
| | | current = [stream map:^(id x) { |
| | | return RACTuplePack(x); |
| | | }]; |
| | | |
| | | continue; |
| | | } |
| | | |
| | | current = block(current, stream); |
| | | } |
| | | |
| | | if (current == nil) return [self empty]; |
| | | |
| | | return [current map:^(RACTuple *xs) { |
| | | // Right now, each value is contained in its own tuple, sorta like: |
| | | // |
| | | // (((1), 2), 3) |
| | | // |
| | | // We need to unwrap all the layers and create a tuple out of the result. |
| | | NSMutableArray *values = [[NSMutableArray alloc] init]; |
| | | |
| | | while (xs != nil) { |
| | | [values insertObject:xs.last ?: RACTupleNil.tupleNil atIndex:0]; |
| | | xs = (xs.count > 1 ? xs.first : nil); |
| | | } |
| | | |
| | | return [RACTuple tupleWithObjectsFromArray:values]; |
| | | }]; |
| | | } |
| | | |
| | | + (instancetype)zip:(id<NSFastEnumeration>)streams { |
| | | return [[self join:streams block:^(RACStream *left, RACStream *right) { |
| | | return [left zipWith:right]; |
| | | }] setNameWithFormat:@"+zip: %@", streams]; |
| | | } |
| | | |
| | | + (instancetype)zip:(id<NSFastEnumeration>)streams reduce:(id (^)())reduceBlock { |
| | | NSCParameterAssert(reduceBlock != nil); |
| | | |
| | | RACStream *result = [self zip:streams]; |
| | | |
| | | // Although we assert this condition above, older versions of this method |
| | | // supported this argument being nil. Avoid crashing Release builds of |
| | | // apps that depended on that. |
| | | if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; |
| | | |
| | | return [result setNameWithFormat:@"+zip: %@ reduce:", streams]; |
| | | } |
| | | |
| | | + (instancetype)concat:(id<NSFastEnumeration>)streams { |
| | | RACStream *result = self.empty; |
| | | for (RACStream *stream in streams) { |
| | | result = [result concat:stream]; |
| | | } |
| | | |
| | | return [result setNameWithFormat:@"+concat: %@", streams]; |
| | | } |
| | | |
| | | - (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))reduceBlock { |
| | | NSCParameterAssert(reduceBlock != nil); |
| | | |
| | | return [[self |
| | | scanWithStart:startingValue |
| | | reduceWithIndex:^(id running, id next, NSUInteger index) { |
| | | return reduceBlock(running, next); |
| | | }] |
| | | setNameWithFormat:@"[%@] -scanWithStart: %@ reduce:", self.name, [startingValue rac_description]]; |
| | | } |
| | | |
| | | - (instancetype)scanWithStart:(id)startingValue reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock { |
| | | NSCParameterAssert(reduceBlock != nil); |
| | | |
| | | Class class = self.class; |
| | | |
| | | return [[self bind:^{ |
| | | __block id running = startingValue; |
| | | __block NSUInteger index = 0; |
| | | |
| | | return ^(id value, BOOL *stop) { |
| | | running = reduceBlock(running, value, index++); |
| | | return [class return:running]; |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -scanWithStart: %@ reduceWithIndex:", self.name, [startingValue rac_description]]; |
| | | } |
| | | |
| | | - (instancetype)takeUntilBlock:(BOOL (^)(id x))predicate { |
| | | NSCParameterAssert(predicate != nil); |
| | | |
| | | Class class = self.class; |
| | | |
| | | return [[self bind:^{ |
| | | return ^ id (id value, BOOL *stop) { |
| | | if (predicate(value)) return nil; |
| | | |
| | | return [class return:value]; |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -takeUntilBlock:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)takeWhileBlock:(BOOL (^)(id x))predicate { |
| | | NSCParameterAssert(predicate != nil); |
| | | |
| | | return [[self takeUntilBlock:^ BOOL (id x) { |
| | | return !predicate(x); |
| | | }] setNameWithFormat:@"[%@] -takeWhileBlock:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)skipUntilBlock:(BOOL (^)(id x))predicate { |
| | | NSCParameterAssert(predicate != nil); |
| | | |
| | | Class class = self.class; |
| | | |
| | | return [[self bind:^{ |
| | | __block BOOL skipping = YES; |
| | | |
| | | return ^ id (id value, BOOL *stop) { |
| | | if (skipping) { |
| | | if (predicate(value)) { |
| | | skipping = NO; |
| | | } else { |
| | | return class.empty; |
| | | } |
| | | } |
| | | |
| | | return [class return:value]; |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)skipWhileBlock:(BOOL (^)(id x))predicate { |
| | | NSCParameterAssert(predicate != nil); |
| | | |
| | | return [[self skipUntilBlock:^ BOOL (id x) { |
| | | return !predicate(x); |
| | | }] setNameWithFormat:@"[%@] -skipWhileBlock:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)distinctUntilChanged { |
| | | Class class = self.class; |
| | | |
| | | return [[self bind:^{ |
| | | __block id lastValue = nil; |
| | | __block BOOL initial = YES; |
| | | |
| | | return ^(id x, BOOL *stop) { |
| | | if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty]; |
| | | |
| | | initial = NO; |
| | | lastValue = x; |
| | | return [class return:x]; |
| | | }; |
| | | }] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACStream (Deprecated) |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-implementations" |
| | | |
| | | - (instancetype)sequenceMany:(RACStream * (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | return [[self flattenMap:^(id _) { |
| | | return block(); |
| | | }] setNameWithFormat:@"[%@] -sequenceMany:", self.name]; |
| | | } |
| | | |
| | | - (instancetype)scanWithStart:(id)startingValue combine:(id (^)(id running, id next))block { |
| | | return [self scanWithStart:startingValue reduce:block]; |
| | | } |
| | | |
| | | - (instancetype)mapPreviousWithStart:(id)start reduce:(id (^)(id previous, id current))combineBlock { |
| | | return [self combinePreviousWithStart:start reduce:combineBlock]; |
| | | } |
| | | |
| | | #pragma clang diagnostic pop |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACStringSequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSequence.h" |
| | | |
| | | // Private class that adapts a string to the RACSequence interface. |
| | | @interface RACStringSequence : RACSequence |
| | | |
| | | // Returns a sequence for enumerating over the given string, starting from the |
| | | // given character offset. The string will be copied to prevent mutation. |
| | | + (RACSequence *)sequenceWithString:(NSString *)string offset:(NSUInteger)offset; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACStringSequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2012-10-29. |
| | | // Copyright (c) 2012 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "RACStringSequence.h" |
| | | |
| | | @interface RACStringSequence () |
| | | |
| | | // The string being sequenced. |
| | | @property (nonatomic, copy, readonly) NSString *string; |
| | | |
| | | // The index in the string from which the sequence starts. |
| | | @property (nonatomic, assign, readonly) NSUInteger offset; |
| | | |
| | | @end |
| | | |
| | | @implementation RACStringSequence |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (RACSequence *)sequenceWithString:(NSString *)string offset:(NSUInteger)offset { |
| | | NSCParameterAssert(offset <= string.length); |
| | | |
| | | if (offset == string.length) return self.empty; |
| | | |
| | | RACStringSequence *seq = [[self alloc] init]; |
| | | seq->_string = [string copy]; |
| | | seq->_offset = offset; |
| | | return seq; |
| | | } |
| | | |
| | | #pragma mark RACSequence |
| | | |
| | | - (id)head { |
| | | return [self.string substringWithRange:NSMakeRange(self.offset, 1)]; |
| | | } |
| | | |
| | | - (RACSequence *)tail { |
| | | RACSequence *sequence = [self.class sequenceWithString:self.string offset:self.offset + 1]; |
| | | sequence.name = self.name; |
| | | return sequence; |
| | | } |
| | | |
| | | - (NSArray *)array { |
| | | NSUInteger substringLength = self.string.length - self.offset; |
| | | NSMutableArray *array = [NSMutableArray arrayWithCapacity:substringLength]; |
| | | |
| | | [self.string enumerateSubstringsInRange:NSMakeRange(self.offset, substringLength) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { |
| | | [array addObject:substring]; |
| | | }]; |
| | | |
| | | return [array copy]; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | return [NSString stringWithFormat:@"<%@: %p>{ name = %@, string = %@ }", self.class, self, self.name, [self.string substringFromIndex:self.offset]]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSubject.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/9/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSignal.h" |
| | | #import "RACSubscriber.h" |
| | | |
| | | /// A subject can be thought of as a signal that you can manually control by |
| | | /// sending next, completed, and error. |
| | | /// |
| | | /// They're most helpful in bridging the non-RAC world to RAC, since they let you |
| | | /// manually control the sending of events. |
| | | @interface RACSubject : RACSignal <RACSubscriber> |
| | | |
| | | /// Returns a new subject. |
| | | + (instancetype)subject; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSubject.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/9/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSubject.h" |
| | | #import "RACEXTScope.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACPassthroughSubscriber.h" |
| | | |
| | | @interface RACSubject () |
| | | |
| | | // Contains all current subscribers to the receiver. |
| | | // |
| | | // This should only be used while synchronized on `self`. |
| | | @property (nonatomic, strong, readonly) NSMutableArray *subscribers; |
| | | |
| | | // Contains all of the receiver's subscriptions to other signals. |
| | | @property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; |
| | | |
| | | // Enumerates over each of the receiver's `subscribers` and invokes `block` for |
| | | // each. |
| | | - (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block; |
| | | |
| | | @end |
| | | |
| | | @implementation RACSubject |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)subject { |
| | | return [[self alloc] init]; |
| | | } |
| | | |
| | | - (id)init { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _disposable = [RACCompoundDisposable compoundDisposable]; |
| | | _subscribers = [[NSMutableArray alloc] initWithCapacity:1]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | [self.disposable dispose]; |
| | | } |
| | | |
| | | #pragma mark Subscription |
| | | |
| | | - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { |
| | | NSCParameterAssert(subscriber != nil); |
| | | |
| | | RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; |
| | | subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; |
| | | |
| | | NSMutableArray *subscribers = self.subscribers; |
| | | @synchronized (subscribers) { |
| | | [subscribers addObject:subscriber]; |
| | | } |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | @synchronized (subscribers) { |
| | | // Since newer subscribers are generally shorter-lived, search |
| | | // starting from the end of the list. |
| | | NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) { |
| | | return obj == subscriber; |
| | | }]; |
| | | |
| | | if (index != NSNotFound) [subscribers removeObjectAtIndex:index]; |
| | | } |
| | | }]; |
| | | } |
| | | |
| | | - (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block { |
| | | NSArray *subscribers; |
| | | @synchronized (self.subscribers) { |
| | | subscribers = [self.subscribers copy]; |
| | | } |
| | | |
| | | for (id<RACSubscriber> subscriber in subscribers) { |
| | | block(subscriber); |
| | | } |
| | | } |
| | | |
| | | #pragma mark RACSubscriber |
| | | |
| | | - (void)sendNext:(id)value { |
| | | [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { |
| | | [subscriber sendNext:value]; |
| | | }]; |
| | | } |
| | | |
| | | - (void)sendError:(NSError *)error { |
| | | [self.disposable dispose]; |
| | | |
| | | [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { |
| | | [subscriber sendError:error]; |
| | | }]; |
| | | } |
| | | |
| | | - (void)sendCompleted { |
| | | [self.disposable dispose]; |
| | | |
| | | [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { |
| | | [subscriber sendCompleted]; |
| | | }]; |
| | | } |
| | | |
| | | - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)d { |
| | | if (d.disposed) return; |
| | | [self.disposable addDisposable:d]; |
| | | |
| | | @weakify(self, d); |
| | | [d addDisposable:[RACDisposable disposableWithBlock:^{ |
| | | @strongify(self, d); |
| | | [self.disposable removeDisposable:d]; |
| | | }]]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSubscriber+Private.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-06-13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSubscriber.h" |
| | | |
| | | // A simple block-based subscriber. |
| | | @interface RACSubscriber : NSObject <RACSubscriber> |
| | | |
| | | // Creates a new subscriber with the given blocks. |
| | | + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSubscriber.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/1/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | @class RACCompoundDisposable; |
| | | |
| | | /// Represents any object which can directly receive values from a RACSignal. |
| | | /// |
| | | /// You generally shouldn't need to implement this protocol. +[RACSignal |
| | | /// createSignal:], RACSignal's subscription methods, or RACSubject should work |
| | | /// for most uses. |
| | | /// |
| | | /// Implementors of this protocol may receive messages and values from multiple |
| | | /// threads simultaneously, and so should be thread-safe. Subscribers will also |
| | | /// be weakly referenced so implementations must allow that. |
| | | @protocol RACSubscriber <NSObject> |
| | | @required |
| | | |
| | | /// Sends the next value to subscribers. |
| | | /// |
| | | /// value - The value to send. This can be `nil`. |
| | | - (void)sendNext:(id)value; |
| | | |
| | | /// Sends the error to subscribers. |
| | | /// |
| | | /// error - The error to send. This can be `nil`. |
| | | /// |
| | | /// This terminates the subscription, and invalidates the subscriber (such that |
| | | /// it cannot subscribe to anything else in the future). |
| | | - (void)sendError:(NSError *)error; |
| | | |
| | | /// Sends completed to subscribers. |
| | | /// |
| | | /// This terminates the subscription, and invalidates the subscriber (such that |
| | | /// it cannot subscribe to anything else in the future). |
| | | - (void)sendCompleted; |
| | | |
| | | /// Sends the subscriber a disposable that represents one of its subscriptions. |
| | | /// |
| | | /// A subscriber may receive multiple disposables if it gets subscribed to |
| | | /// multiple signals; however, any error or completed events must terminate _all_ |
| | | /// subscriptions. |
| | | - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSubscriber.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/1/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSubscriber.h" |
| | | #import "RACSubscriber+Private.h" |
| | | #import "RACEXTScope.h" |
| | | #import "RACCompoundDisposable.h" |
| | | |
| | | @interface RACSubscriber () |
| | | |
| | | // These callbacks should only be accessed while synchronized on self. |
| | | @property (nonatomic, copy) void (^next)(id value); |
| | | @property (nonatomic, copy) void (^error)(NSError *error); |
| | | @property (nonatomic, copy) void (^completed)(void); |
| | | |
| | | @property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; |
| | | |
| | | @end |
| | | |
| | | @implementation RACSubscriber |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed { |
| | | RACSubscriber *subscriber = [[self alloc] init]; |
| | | |
| | | subscriber->_next = [next copy]; |
| | | subscriber->_error = [error copy]; |
| | | subscriber->_completed = [completed copy]; |
| | | |
| | | return subscriber; |
| | | } |
| | | |
| | | - (id)init { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | @unsafeify(self); |
| | | |
| | | RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{ |
| | | @strongify(self); |
| | | |
| | | @synchronized (self) { |
| | | self.next = nil; |
| | | self.error = nil; |
| | | self.completed = nil; |
| | | } |
| | | }]; |
| | | |
| | | _disposable = [RACCompoundDisposable compoundDisposable]; |
| | | [_disposable addDisposable:selfDisposable]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | [self.disposable dispose]; |
| | | } |
| | | |
| | | #pragma mark RACSubscriber |
| | | |
| | | - (void)sendNext:(id)value { |
| | | @synchronized (self) { |
| | | void (^nextBlock)(id) = [self.next copy]; |
| | | if (nextBlock == nil) return; |
| | | |
| | | nextBlock(value); |
| | | } |
| | | } |
| | | |
| | | - (void)sendError:(NSError *)e { |
| | | @synchronized (self) { |
| | | void (^errorBlock)(NSError *) = [self.error copy]; |
| | | [self.disposable dispose]; |
| | | |
| | | if (errorBlock == nil) return; |
| | | errorBlock(e); |
| | | } |
| | | } |
| | | |
| | | - (void)sendCompleted { |
| | | @synchronized (self) { |
| | | void (^completedBlock)(void) = [self.completed copy]; |
| | | [self.disposable dispose]; |
| | | |
| | | if (completedBlock == nil) return; |
| | | completedBlock(); |
| | | } |
| | | } |
| | | |
| | | - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)otherDisposable { |
| | | if (otherDisposable.disposed) return; |
| | | |
| | | RACCompoundDisposable *selfDisposable = self.disposable; |
| | | [selfDisposable addDisposable:otherDisposable]; |
| | | |
| | | @unsafeify(otherDisposable); |
| | | |
| | | // If this subscription terminates, purge its disposable to avoid unbounded |
| | | // memory growth. |
| | | [otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| | | @strongify(otherDisposable); |
| | | [selfDisposable removeDisposable:otherDisposable]; |
| | | }]]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSubscriptingAssignmentTrampoline.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 9/24/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "RACEXTKeyPathCoding.h" |
| | | |
| | | @class RACSignal; |
| | | |
| | | /// Assigns a signal to an object property, automatically setting the given key |
| | | /// path on every `next`. When the signal completes, the binding is automatically |
| | | /// disposed of. |
| | | /// |
| | | /// There are two different versions of this macro: |
| | | /// |
| | | /// - RAC(TARGET, KEYPATH, NILVALUE) will bind the `KEYPATH` of `TARGET` to the |
| | | /// given signal. If the signal ever sends a `nil` value, the property will be |
| | | /// set to `NILVALUE` instead. `NILVALUE` may itself be `nil` for object |
| | | /// properties, but an NSValue should be used for primitive properties, to |
| | | /// avoid an exception if `nil` is sent (which might occur if an intermediate |
| | | /// object is set to `nil`). |
| | | /// - RAC(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to |
| | | /// `nil`. |
| | | /// |
| | | /// See -[RACSignal setKeyPath:onObject:nilValue:] for more information about the |
| | | /// binding's semantics. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// RAC(self, objectProperty) = objectSignal; |
| | | /// RAC(self, stringProperty, @"foobar") = stringSignal; |
| | | /// RAC(self, integerProperty, @42) = integerSignal; |
| | | /// |
| | | /// WARNING: Under certain conditions, use of this macro can be thread-unsafe. |
| | | /// See the documentation of -setKeyPath:onObject:nilValue:. |
| | | #define RAC(TARGET, ...) \ |
| | | metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ |
| | | (RAC_(TARGET, __VA_ARGS__, nil)) \ |
| | | (RAC_(TARGET, __VA_ARGS__)) |
| | | |
| | | /// Do not use this directly. Use the RAC macro above. |
| | | #define RAC_(TARGET, KEYPATH, NILVALUE) \ |
| | | [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(TARGET) nilValue:(NILVALUE)][@keypath(TARGET, KEYPATH)] |
| | | |
| | | @interface RACSubscriptingAssignmentTrampoline : NSObject |
| | | |
| | | - (id)initWithTarget:(id)target nilValue:(id)nilValue; |
| | | - (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSubscriptingAssignmentTrampoline.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 9/24/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSubscriptingAssignmentTrampoline.h" |
| | | #import "RACSignal+Operations.h" |
| | | |
| | | @interface RACSubscriptingAssignmentTrampoline () |
| | | |
| | | // The object to bind to. |
| | | @property (nonatomic, strong, readonly) id target; |
| | | |
| | | // A value to use when `nil` is sent on the bound signal. |
| | | @property (nonatomic, strong, readonly) id nilValue; |
| | | |
| | | @end |
| | | |
| | | @implementation RACSubscriptingAssignmentTrampoline |
| | | |
| | | - (id)initWithTarget:(id)target nilValue:(id)nilValue { |
| | | // This is often a programmer error, but this prevents crashes if the target |
| | | // object has unexpectedly deallocated. |
| | | if (target == nil) return nil; |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _target = target; |
| | | _nilValue = nilValue; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath { |
| | | [signal setKeyPath:keyPath onObject:self.target nilValue:self.nilValue]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSubscriptionScheduler.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/30/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACScheduler.h" |
| | | |
| | | // A private scheduler used only for subscriptions. See the private |
| | | // +[RACScheduler subscriptionScheduler] method for more information. |
| | | @interface RACSubscriptionScheduler : RACScheduler |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACSubscriptionScheduler.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 11/30/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSubscriptionScheduler.h" |
| | | #import "RACScheduler+Private.h" |
| | | |
| | | @interface RACSubscriptionScheduler () |
| | | |
| | | // A private background scheduler on which to subscribe if the +currentScheduler |
| | | // is unknown. |
| | | @property (nonatomic, strong, readonly) RACScheduler *backgroundScheduler; |
| | | |
| | | @end |
| | | |
| | | @implementation RACSubscriptionScheduler |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)init { |
| | | self = [super initWithName:@"com.ReactiveCocoa.RACScheduler.subscriptionScheduler"]; |
| | | if (self == nil) return nil; |
| | | |
| | | _backgroundScheduler = [RACScheduler scheduler]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark RACScheduler |
| | | |
| | | - (RACDisposable *)schedule:(void (^)(void))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block]; |
| | | |
| | | block(); |
| | | return nil; |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { |
| | | RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; |
| | | return [scheduler after:date schedule:block]; |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { |
| | | RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; |
| | | return [scheduler after:date repeatingEvery:interval withLeeway:leeway schedule:block]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACTargetQueueScheduler.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 6/6/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACQueueScheduler.h" |
| | | |
| | | /// A scheduler that enqueues blocks on a private serial queue, targeting an |
| | | /// arbitrary GCD queue. |
| | | @interface RACTargetQueueScheduler : RACQueueScheduler |
| | | |
| | | /// Initializes the receiver with a serial queue that will target the given |
| | | /// `targetQueue`. |
| | | /// |
| | | /// name - The name of the scheduler. If nil, a default name will be used. |
| | | /// targetQueue - The queue to target. Cannot be NULL. |
| | | /// |
| | | /// Returns the initialized object. |
| | | - (id)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACTargetQueueScheduler.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 6/6/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACTargetQueueScheduler.h" |
| | | #import "RACQueueScheduler+Subclass.h" |
| | | |
| | | @implementation RACTargetQueueScheduler |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue { |
| | | NSCParameterAssert(targetQueue != NULL); |
| | | |
| | | if (name == nil) { |
| | | name = [NSString stringWithFormat:@"com.ReactiveCocoa.RACTargetQueueScheduler(%s)", dispatch_queue_get_label(targetQueue)]; |
| | | } |
| | | |
| | | dispatch_queue_t queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL); |
| | | if (queue == NULL) return nil; |
| | | |
| | | dispatch_set_target_queue(queue, targetQueue); |
| | | |
| | | return [super initWithName:name queue:queue]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACTestScheduler.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-07-06. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACScheduler.h" |
| | | |
| | | /// A special kind of scheduler that steps through virtualized time. |
| | | /// |
| | | /// This scheduler class can be used in unit tests to verify asynchronous |
| | | /// behaviors without spending significant time waiting. |
| | | /// |
| | | /// This class can be used from multiple threads, but only one thread can `step` |
| | | /// through the enqueued actions at a time. Other threads will wait while the |
| | | /// scheduled blocks are being executed. |
| | | @interface RACTestScheduler : RACScheduler |
| | | |
| | | /// Initializes a new test scheduler. |
| | | - (instancetype)init; |
| | | |
| | | /// Executes the next scheduled block, if any. |
| | | /// |
| | | /// This method will block until the scheduled action has completed. |
| | | - (void)step; |
| | | |
| | | /// Executes up to the next `ticks` scheduled blocks. |
| | | /// |
| | | /// This method will block until the scheduled actions have completed. |
| | | /// |
| | | /// ticks - The number of scheduled blocks to execute. If there aren't this many |
| | | /// blocks enqueued, all scheduled blocks are executed. |
| | | - (void)step:(NSUInteger)ticks; |
| | | |
| | | /// Executes all of the scheduled blocks on the receiver. |
| | | /// |
| | | /// This method will block until the scheduled actions have completed. |
| | | - (void)stepAll; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACTestScheduler.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-07-06. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACTestScheduler.h" |
| | | #import "RACEXTScope.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACScheduler+Private.h" |
| | | |
| | | @interface RACTestSchedulerAction : NSObject |
| | | |
| | | // The date at which the action should be executed. |
| | | // |
| | | // This absolute time will not actually be honored. This date is only used for |
| | | // comparison, to determine which block should be run _next_. |
| | | @property (nonatomic, copy, readonly) NSDate *date; |
| | | |
| | | // The scheduled block. |
| | | @property (nonatomic, copy, readonly) void (^block)(void); |
| | | |
| | | // A disposable for this action. |
| | | // |
| | | // When disposed, the action should not start executing if it hasn't already. |
| | | @property (nonatomic, strong, readonly) RACDisposable *disposable; |
| | | |
| | | // Initializes a new scheduler action. |
| | | - (id)initWithDate:(NSDate *)date block:(void (^)(void))block; |
| | | |
| | | @end |
| | | |
| | | static CFComparisonResult RACCompareScheduledActions(const void *ptr1, const void *ptr2, void *info) { |
| | | RACTestSchedulerAction *action1 = (__bridge id)ptr1; |
| | | RACTestSchedulerAction *action2 = (__bridge id)ptr2; |
| | | return CFDateCompare((__bridge CFDateRef)action1.date, (__bridge CFDateRef)action2.date, NULL); |
| | | } |
| | | |
| | | static const void *RACRetainScheduledAction(CFAllocatorRef allocator, const void *ptr) { |
| | | return CFRetain(ptr); |
| | | } |
| | | |
| | | static void RACReleaseScheduledAction(CFAllocatorRef allocator, const void *ptr) { |
| | | CFRelease(ptr); |
| | | } |
| | | |
| | | @interface RACTestScheduler () |
| | | |
| | | // All of the RACTestSchedulerActions that have been enqueued and not yet |
| | | // executed. |
| | | // |
| | | // The minimum value in the heap represents the action to execute next. |
| | | // |
| | | // This property should only be used while synchronized on self. |
| | | @property (nonatomic, assign, readonly) CFBinaryHeapRef scheduledActions; |
| | | |
| | | // The number of blocks that have been directly enqueued with -schedule: so |
| | | // far. |
| | | // |
| | | // This is used to ensure unique dates when two blocks are enqueued |
| | | // simultaneously. |
| | | // |
| | | // This property should only be used while synchronized on self. |
| | | @property (nonatomic, assign) NSUInteger numberOfDirectlyScheduledBlocks; |
| | | |
| | | @end |
| | | |
| | | @implementation RACTestScheduler |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (instancetype)init { |
| | | self = [super initWithName:@"org.reactivecocoa.ReactiveCocoa.RACTestScheduler"]; |
| | | if (self == nil) return nil; |
| | | |
| | | CFBinaryHeapCallBacks callbacks = (CFBinaryHeapCallBacks){ |
| | | .version = 0, |
| | | .retain = &RACRetainScheduledAction, |
| | | .release = &RACReleaseScheduledAction, |
| | | .copyDescription = &CFCopyDescription, |
| | | .compare = &RACCompareScheduledActions |
| | | }; |
| | | |
| | | _scheduledActions = CFBinaryHeapCreate(NULL, 0, &callbacks, NULL); |
| | | return self; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | [self stepAll]; |
| | | |
| | | if (_scheduledActions != NULL) { |
| | | CFRelease(_scheduledActions); |
| | | _scheduledActions = NULL; |
| | | } |
| | | } |
| | | |
| | | #pragma mark Execution |
| | | |
| | | - (void)step { |
| | | [self step:1]; |
| | | } |
| | | |
| | | - (void)step:(NSUInteger)ticks { |
| | | @synchronized (self) { |
| | | for (NSUInteger i = 0; i < ticks; i++) { |
| | | const void *actionPtr = NULL; |
| | | if (!CFBinaryHeapGetMinimumIfPresent(self.scheduledActions, &actionPtr)) break; |
| | | |
| | | RACTestSchedulerAction *action = (__bridge id)actionPtr; |
| | | CFBinaryHeapRemoveMinimumValue(self.scheduledActions); |
| | | |
| | | if (action.disposable.disposed) continue; |
| | | |
| | | RACScheduler *previousScheduler = RACScheduler.currentScheduler; |
| | | NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; |
| | | |
| | | action.block(); |
| | | |
| | | if (previousScheduler != nil) { |
| | | NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; |
| | | } else { |
| | | [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)stepAll { |
| | | [self step:NSUIntegerMax]; |
| | | } |
| | | |
| | | #pragma mark RACScheduler |
| | | |
| | | - (RACDisposable *)schedule:(void (^)(void))block { |
| | | NSCParameterAssert(block != nil); |
| | | |
| | | @synchronized (self) { |
| | | NSDate *uniqueDate = [NSDate dateWithTimeIntervalSinceReferenceDate:self.numberOfDirectlyScheduledBlocks]; |
| | | self.numberOfDirectlyScheduledBlocks++; |
| | | |
| | | RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:uniqueDate block:block]; |
| | | CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); |
| | | |
| | | return action.disposable; |
| | | } |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { |
| | | NSCParameterAssert(date != nil); |
| | | NSCParameterAssert(block != nil); |
| | | |
| | | @synchronized (self) { |
| | | RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:block]; |
| | | CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); |
| | | |
| | | return action.disposable; |
| | | } |
| | | } |
| | | |
| | | - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { |
| | | NSCParameterAssert(date != nil); |
| | | NSCParameterAssert(block != nil); |
| | | NSCParameterAssert(interval >= 0); |
| | | NSCParameterAssert(leeway >= 0); |
| | | |
| | | RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; |
| | | |
| | | @weakify(self); |
| | | @synchronized (self) { |
| | | __block RACDisposable *thisDisposable = nil; |
| | | |
| | | void (^reschedulingBlock)(void) = ^{ |
| | | @strongify(self); |
| | | |
| | | [compoundDisposable removeDisposable:thisDisposable]; |
| | | |
| | | // Schedule the next interval. |
| | | RACDisposable *schedulingDisposable = [self after:[date dateByAddingTimeInterval:interval] repeatingEvery:interval withLeeway:leeway schedule:block]; |
| | | [compoundDisposable addDisposable:schedulingDisposable]; |
| | | |
| | | block(); |
| | | }; |
| | | |
| | | RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:reschedulingBlock]; |
| | | CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); |
| | | |
| | | thisDisposable = action.disposable; |
| | | [compoundDisposable addDisposable:thisDisposable]; |
| | | } |
| | | |
| | | return compoundDisposable; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACTestSchedulerAction |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | - (id)initWithDate:(NSDate *)date block:(void (^)(void))block { |
| | | NSCParameterAssert(date != nil); |
| | | NSCParameterAssert(block != nil); |
| | | |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | _date = [date copy]; |
| | | _block = [block copy]; |
| | | _disposable = [[RACDisposable alloc] init]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | return [NSString stringWithFormat:@"<%@: %p>{ date: %@ }", self.class, self, self.date]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACTuple.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/12/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "RACmetamacros.h" |
| | | |
| | | @class RACSequence; |
| | | |
| | | /// Creates a new tuple with the given values. At least one value must be given. |
| | | /// Values can be nil. |
| | | #define RACTuplePack(...) \ |
| | | RACTuplePack_(__VA_ARGS__) |
| | | |
| | | /// Declares new object variables and unpacks a RACTuple into them. |
| | | /// |
| | | /// This macro should be used on the left side of an assignment, with the |
| | | /// tuple on the right side. Nothing else should appear on the same line, and the |
| | | /// macro should not be the only statement in a conditional or loop body. |
| | | /// |
| | | /// If the tuple has more values than there are variables listed, the excess |
| | | /// values are ignored. |
| | | /// |
| | | /// If the tuple has fewer values than there are variables listed, the excess |
| | | /// variables are initialized to nil. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// RACTupleUnpack(NSString *string, NSNumber *num) = [RACTuple tupleWithObjects:@"foo", @5, nil]; |
| | | /// NSLog(@"string: %@", string); |
| | | /// NSLog(@"num: %@", num); |
| | | /// |
| | | /// /* The above is equivalent to: */ |
| | | /// RACTuple *t = [RACTuple tupleWithObjects:@"foo", @5, nil]; |
| | | /// NSString *string = t[0]; |
| | | /// NSNumber *num = t[1]; |
| | | /// NSLog(@"string: %@", string); |
| | | /// NSLog(@"num: %@", num); |
| | | #define RACTupleUnpack(...) \ |
| | | RACTupleUnpack_(__VA_ARGS__) |
| | | |
| | | /// A sentinel object that represents nils in the tuple. |
| | | /// |
| | | /// It should never be necessary to create a tuple nil yourself. Just use |
| | | /// +tupleNil. |
| | | @interface RACTupleNil : NSObject <NSCopying, NSCoding> |
| | | /// A singleton instance. |
| | | + (RACTupleNil *)tupleNil; |
| | | @end |
| | | |
| | | |
| | | /// A tuple is an ordered collection of objects. It may contain nils, represented |
| | | /// by RACTupleNil. |
| | | @interface RACTuple : NSObject <NSCoding, NSCopying, NSFastEnumeration> |
| | | |
| | | @property (nonatomic, readonly) NSUInteger count; |
| | | |
| | | /// These properties all return the object at that index or nil if the number of |
| | | /// objects is less than the index. |
| | | @property (nonatomic, readonly) id first; |
| | | @property (nonatomic, readonly) id second; |
| | | @property (nonatomic, readonly) id third; |
| | | @property (nonatomic, readonly) id fourth; |
| | | @property (nonatomic, readonly) id fifth; |
| | | @property (nonatomic, readonly) id last; |
| | | |
| | | /// Creates a new tuple out of the array. Does not convert nulls to nils. |
| | | + (instancetype)tupleWithObjectsFromArray:(NSArray *)array; |
| | | |
| | | /// Creates a new tuple out of the array. If `convert` is YES, it also converts |
| | | /// every NSNull to RACTupleNil. |
| | | + (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert; |
| | | |
| | | /// Creates a new tuple with the given objects. Use RACTupleNil to represent |
| | | /// nils. |
| | | + (instancetype)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION; |
| | | |
| | | /// Returns the object at `index` or nil if the object is a RACTupleNil. Unlike |
| | | /// NSArray and friends, it's perfectly fine to ask for the object at an index |
| | | /// past the tuple's count - 1. It will simply return nil. |
| | | - (id)objectAtIndex:(NSUInteger)index; |
| | | |
| | | /// Returns an array of all the objects. RACTupleNils are converted to NSNulls. |
| | | - (NSArray *)allObjects; |
| | | |
| | | /// Appends `obj` to the receiver. |
| | | /// |
| | | /// obj - The object to add to the tuple. This argument may be nil. |
| | | /// |
| | | /// Returns a new tuple. |
| | | - (instancetype)tupleByAddingObject:(id)obj; |
| | | |
| | | @end |
| | | |
| | | @interface RACTuple (RACSequenceAdditions) |
| | | |
| | | /// Returns a sequence of all the objects. RACTupleNils are converted to NSNulls. |
| | | @property (nonatomic, copy, readonly) RACSequence *rac_sequence; |
| | | |
| | | @end |
| | | |
| | | @interface RACTuple (ObjectSubscripting) |
| | | /// Returns the object at that index or nil if the number of objects is less |
| | | /// than the index. |
| | | - (id)objectAtIndexedSubscript:(NSUInteger)idx; |
| | | @end |
| | | |
| | | /// This and everything below is for internal use only. |
| | | /// |
| | | /// See RACTuplePack() and RACTupleUnpack() instead. |
| | | #define RACTuplePack_(...) \ |
| | | ([RACTuple tupleWithObjectsFromArray:@[ metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]]) |
| | | |
| | | #define RACTuplePack_object_or_ractuplenil(INDEX, ARG) \ |
| | | (ARG) ?: RACTupleNil.tupleNil, |
| | | |
| | | #define RACTupleUnpack_(...) \ |
| | | metamacro_foreach(RACTupleUnpack_decl,, __VA_ARGS__) \ |
| | | \ |
| | | int RACTupleUnpack_state = 0; \ |
| | | \ |
| | | RACTupleUnpack_after: \ |
| | | ; \ |
| | | metamacro_foreach(RACTupleUnpack_assign,, __VA_ARGS__) \ |
| | | if (RACTupleUnpack_state != 0) RACTupleUnpack_state = 2; \ |
| | | \ |
| | | while (RACTupleUnpack_state != 2) \ |
| | | if (RACTupleUnpack_state == 1) { \ |
| | | goto RACTupleUnpack_after; \ |
| | | } else \ |
| | | for (; RACTupleUnpack_state != 1; RACTupleUnpack_state = 1) \ |
| | | [RACTupleUnpackingTrampoline trampoline][ @[ metamacro_foreach(RACTupleUnpack_value,, __VA_ARGS__) ] ] |
| | | |
| | | #define RACTupleUnpack_state metamacro_concat(RACTupleUnpack_state, __LINE__) |
| | | #define RACTupleUnpack_after metamacro_concat(RACTupleUnpack_after, __LINE__) |
| | | #define RACTupleUnpack_loop metamacro_concat(RACTupleUnpack_loop, __LINE__) |
| | | |
| | | #define RACTupleUnpack_decl_name(INDEX) \ |
| | | metamacro_concat(metamacro_concat(RACTupleUnpack, __LINE__), metamacro_concat(_var, INDEX)) |
| | | |
| | | #define RACTupleUnpack_decl(INDEX, ARG) \ |
| | | __strong id RACTupleUnpack_decl_name(INDEX); |
| | | |
| | | #define RACTupleUnpack_assign(INDEX, ARG) \ |
| | | __strong ARG = RACTupleUnpack_decl_name(INDEX); |
| | | |
| | | #define RACTupleUnpack_value(INDEX, ARG) \ |
| | | [NSValue valueWithPointer:&RACTupleUnpack_decl_name(INDEX)], |
| | | |
| | | @interface RACTupleUnpackingTrampoline : NSObject |
| | | |
| | | + (instancetype)trampoline; |
| | | - (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACTuple.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/12/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACTuple.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "RACTupleSequence.h" |
| | | |
| | | @implementation RACTupleNil |
| | | |
| | | + (RACTupleNil *)tupleNil { |
| | | static dispatch_once_t onceToken; |
| | | static RACTupleNil *tupleNil = nil; |
| | | dispatch_once(&onceToken, ^{ |
| | | tupleNil = [[self alloc] init]; |
| | | }); |
| | | |
| | | return tupleNil; |
| | | } |
| | | |
| | | #pragma mark NSCopying |
| | | |
| | | - (id)copyWithZone:(NSZone *)zone { |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark NSCoding |
| | | |
| | | - (id)initWithCoder:(NSCoder *)coder { |
| | | // Always return the singleton. |
| | | return self.class.tupleNil; |
| | | } |
| | | |
| | | - (void)encodeWithCoder:(NSCoder *)coder { |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @interface RACTuple () |
| | | @property (nonatomic, strong) NSArray *backingArray; |
| | | @end |
| | | |
| | | |
| | | @implementation RACTuple |
| | | |
| | | - (instancetype)init { |
| | | self = [super init]; |
| | | if (self == nil) return nil; |
| | | |
| | | self.backingArray = [NSArray array]; |
| | | |
| | | return self; |
| | | } |
| | | |
| | | - (NSString *)description { |
| | | return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.allObjects]; |
| | | } |
| | | |
| | | - (BOOL)isEqual:(RACTuple *)object { |
| | | if (object == self) return YES; |
| | | if (![object isKindOfClass:self.class]) return NO; |
| | | |
| | | return [self.backingArray isEqual:object.backingArray]; |
| | | } |
| | | |
| | | - (NSUInteger)hash { |
| | | return self.backingArray.hash; |
| | | } |
| | | |
| | | |
| | | #pragma mark NSFastEnumeration |
| | | |
| | | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len { |
| | | return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len]; |
| | | } |
| | | |
| | | |
| | | #pragma mark NSCopying |
| | | |
| | | - (instancetype)copyWithZone:(NSZone *)zone { |
| | | // we're immutable, bitches! |
| | | return self; |
| | | } |
| | | |
| | | |
| | | #pragma mark NSCoding |
| | | |
| | | - (id)initWithCoder:(NSCoder *)coder { |
| | | self = [self init]; |
| | | if (self == nil) return nil; |
| | | |
| | | self.backingArray = [coder decodeObjectForKey:@keypath(self.backingArray)]; |
| | | return self; |
| | | } |
| | | |
| | | - (void)encodeWithCoder:(NSCoder *)coder { |
| | | if (self.backingArray != nil) [coder encodeObject:self.backingArray forKey:@keypath(self.backingArray)]; |
| | | } |
| | | |
| | | |
| | | #pragma mark API |
| | | |
| | | + (instancetype)tupleWithObjectsFromArray:(NSArray *)array { |
| | | return [self tupleWithObjectsFromArray:array convertNullsToNils:NO]; |
| | | } |
| | | |
| | | + (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert { |
| | | RACTuple *tuple = [[self alloc] init]; |
| | | |
| | | if (convert) { |
| | | NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; |
| | | for (id object in array) { |
| | | [newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)]; |
| | | } |
| | | |
| | | tuple.backingArray = newArray; |
| | | } else { |
| | | tuple.backingArray = [array copy]; |
| | | } |
| | | |
| | | return tuple; |
| | | } |
| | | |
| | | + (instancetype)tupleWithObjects:(id)object, ... { |
| | | RACTuple *tuple = [[self alloc] init]; |
| | | |
| | | va_list args; |
| | | va_start(args, object); |
| | | |
| | | NSUInteger count = 0; |
| | | for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { |
| | | ++count; |
| | | } |
| | | |
| | | va_end(args); |
| | | |
| | | if (count == 0) { |
| | | tuple.backingArray = @[]; |
| | | return tuple; |
| | | } |
| | | |
| | | NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count]; |
| | | |
| | | va_start(args, object); |
| | | for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { |
| | | [objects addObject:currentObject]; |
| | | } |
| | | |
| | | va_end(args); |
| | | |
| | | tuple.backingArray = objects; |
| | | return tuple; |
| | | } |
| | | |
| | | - (id)objectAtIndex:(NSUInteger)index { |
| | | if (index >= self.count) return nil; |
| | | |
| | | id object = self.backingArray[index]; |
| | | return (object == RACTupleNil.tupleNil ? nil : object); |
| | | } |
| | | |
| | | - (NSArray *)allObjects { |
| | | NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:self.backingArray.count]; |
| | | for (id object in self.backingArray) { |
| | | [newArray addObject:(object == RACTupleNil.tupleNil ? NSNull.null : object)]; |
| | | } |
| | | |
| | | return newArray; |
| | | } |
| | | |
| | | - (instancetype)tupleByAddingObject:(id)obj { |
| | | NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; |
| | | return [self.class tupleWithObjectsFromArray:newArray]; |
| | | } |
| | | |
| | | - (NSUInteger)count { |
| | | return self.backingArray.count; |
| | | } |
| | | |
| | | - (id)first { |
| | | return self[0]; |
| | | } |
| | | |
| | | - (id)second { |
| | | return self[1]; |
| | | } |
| | | |
| | | - (id)third { |
| | | return self[2]; |
| | | } |
| | | |
| | | - (id)fourth { |
| | | return self[3]; |
| | | } |
| | | |
| | | - (id)fifth { |
| | | return self[4]; |
| | | } |
| | | |
| | | - (id)last { |
| | | return self[self.count - 1]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation RACTuple (RACSequenceAdditions) |
| | | |
| | | - (RACSequence *)rac_sequence { |
| | | return [RACTupleSequence sequenceWithTupleBackingArray:self.backingArray offset:0]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation RACTuple (ObjectSubscripting) |
| | | |
| | | - (id)objectAtIndexedSubscript:(NSUInteger)idx { |
| | | return [self objectAtIndex:idx]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation RACTupleUnpackingTrampoline |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)trampoline { |
| | | static dispatch_once_t onceToken; |
| | | static id trampoline = nil; |
| | | dispatch_once(&onceToken, ^{ |
| | | trampoline = [[self alloc] init]; |
| | | }); |
| | | |
| | | return trampoline; |
| | | } |
| | | |
| | | - (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables { |
| | | NSCParameterAssert(variables != nil); |
| | | |
| | | [variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) { |
| | | __strong id *ptr = (__strong id *)value.pointerValue; |
| | | *ptr = tuple[index]; |
| | | }]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACTupleSequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-05-01. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSequence.h" |
| | | |
| | | // Private class that adapts a RACTuple to the RACSequence interface. |
| | | @interface RACTupleSequence : RACSequence |
| | | |
| | | // Returns a sequence for enumerating over the given backing array (from a |
| | | // RACTuple), starting from the given offset. |
| | | + (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACTupleSequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-05-01. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACTupleSequence.h" |
| | | #import "RACTuple.h" |
| | | |
| | | @interface RACTupleSequence () |
| | | |
| | | // The array being sequenced, as taken from RACTuple.backingArray. |
| | | @property (nonatomic, strong, readonly) NSArray *tupleBackingArray; |
| | | |
| | | // The index in the array from which the sequence starts. |
| | | @property (nonatomic, assign, readonly) NSUInteger offset; |
| | | |
| | | @end |
| | | |
| | | @implementation RACTupleSequence |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset { |
| | | NSCParameterAssert(offset <= backingArray.count); |
| | | |
| | | if (offset == backingArray.count) return self.empty; |
| | | |
| | | RACTupleSequence *seq = [[self alloc] init]; |
| | | seq->_tupleBackingArray = backingArray; |
| | | seq->_offset = offset; |
| | | return seq; |
| | | } |
| | | |
| | | #pragma mark RACSequence |
| | | |
| | | - (id)head { |
| | | id object = self.tupleBackingArray[self.offset]; |
| | | return (object == RACTupleNil.tupleNil ? NSNull.null : object); |
| | | } |
| | | |
| | | - (RACSequence *)tail { |
| | | RACSequence *sequence = [self.class sequenceWithTupleBackingArray:self.tupleBackingArray offset:self.offset + 1]; |
| | | sequence.name = self.name; |
| | | return sequence; |
| | | } |
| | | |
| | | - (NSArray *)array { |
| | | NSRange range = NSMakeRange(self.offset, self.tupleBackingArray.count - self.offset); |
| | | NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:range.length]; |
| | | |
| | | [self.tupleBackingArray enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:range] options:0 usingBlock:^(id object, NSUInteger index, BOOL *stop) { |
| | | id mappedObject = (object == RACTupleNil.tupleNil ? NSNull.null : object); |
| | | [array addObject:mappedObject]; |
| | | }]; |
| | | |
| | | return array; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | return [NSString stringWithFormat:@"<%@: %p>{ name = %@, tuple = %@ }", self.class, self, self.name, self.tupleBackingArray]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACUnarySequence.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-05-01. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACSequence.h" |
| | | |
| | | // Private class representing a sequence of exactly one value. |
| | | @interface RACUnarySequence : RACSequence |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACUnarySequence.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-05-01. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACUnarySequence.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "NSObject+RACDescription.h" |
| | | |
| | | @interface RACUnarySequence () |
| | | |
| | | // The single value stored in this sequence. |
| | | @property (nonatomic, strong, readwrite) id head; |
| | | |
| | | @end |
| | | |
| | | @implementation RACUnarySequence |
| | | |
| | | #pragma mark Properties |
| | | |
| | | @synthesize head = _head; |
| | | |
| | | #pragma mark Lifecycle |
| | | |
| | | + (instancetype)return:(id)value { |
| | | RACUnarySequence *sequence = [[self alloc] init]; |
| | | sequence.head = value; |
| | | return [sequence setNameWithFormat:@"+return: %@", [value rac_description]]; |
| | | } |
| | | |
| | | #pragma mark RACSequence |
| | | |
| | | - (RACSequence *)tail { |
| | | return nil; |
| | | } |
| | | |
| | | - (instancetype)bind:(RACStreamBindBlock (^)(void))block { |
| | | RACStreamBindBlock bindBlock = block(); |
| | | BOOL stop = NO; |
| | | |
| | | RACSequence *result = (id)[bindBlock(self.head, &stop) setNameWithFormat:@"[%@] -bind:", self.name]; |
| | | return result ?: self.class.empty; |
| | | } |
| | | |
| | | #pragma mark NSCoding |
| | | |
| | | - (Class)classForCoder { |
| | | // Unary sequences should be encoded as themselves, not array sequences. |
| | | return self.class; |
| | | } |
| | | |
| | | - (id)initWithCoder:(NSCoder *)coder { |
| | | id value = [coder decodeObjectForKey:@keypath(self.head)]; |
| | | return [self.class return:value]; |
| | | } |
| | | |
| | | - (void)encodeWithCoder:(NSCoder *)coder { |
| | | if (self.head != nil) [coder encodeObject:self.head forKey:@keypath(self.head)]; |
| | | } |
| | | |
| | | #pragma mark NSObject |
| | | |
| | | - (NSString *)description { |
| | | return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@ }", self.class, self, self.name, self.head]; |
| | | } |
| | | |
| | | - (NSUInteger)hash { |
| | | return [self.head hash]; |
| | | } |
| | | |
| | | - (BOOL)isEqual:(RACUnarySequence *)seq { |
| | | if (self == seq) return YES; |
| | | if (![seq isKindOfClass:RACUnarySequence.class]) return NO; |
| | | |
| | | return self.head == seq.head || [(NSObject *)self.head isEqual:seq.head]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACUnit.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/27/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | /// A unit represents an empty value. |
| | | /// |
| | | /// It should never be necessary to create a unit yourself. Just use +defaultUnit. |
| | | @interface RACUnit : NSObject |
| | | |
| | | /// A singleton instance. |
| | | + (RACUnit *)defaultUnit; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACUnit.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/27/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACUnit.h" |
| | | |
| | | @implementation RACUnit |
| | | |
| | | #pragma mark API |
| | | |
| | | + (RACUnit *)defaultUnit { |
| | | static dispatch_once_t onceToken; |
| | | static RACUnit *defaultUnit = nil; |
| | | dispatch_once(&onceToken, ^{ |
| | | defaultUnit = [[self alloc] init]; |
| | | }); |
| | | |
| | | return defaultUnit; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACValueTransformer.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/6/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | // A private block based transformer. |
| | | @interface RACValueTransformer : NSValueTransformer |
| | | |
| | | + (instancetype)transformerWithBlock:(id (^)(id value))block; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // RACValueTransformer.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/6/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "RACValueTransformer.h" |
| | | |
| | | @interface RACValueTransformer () |
| | | @property (nonatomic, copy) id (^transformBlock)(id value); |
| | | @end |
| | | |
| | | |
| | | @implementation RACValueTransformer |
| | | |
| | | |
| | | #pragma mark NSValueTransformer |
| | | |
| | | + (BOOL)allowsReverseTransformation { |
| | | return NO; |
| | | } |
| | | |
| | | - (id)transformedValue:(id)value { |
| | | return self.transformBlock(value); |
| | | } |
| | | |
| | | |
| | | #pragma mark API |
| | | |
| | | @synthesize transformBlock; |
| | | |
| | | + (instancetype)transformerWithBlock:(id (^)(id value))block { |
| | | NSCParameterAssert(block != NULL); |
| | | |
| | | RACValueTransformer *transformer = [[self alloc] init]; |
| | | transformer.transformBlock = block; |
| | | return transformer; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // ReactiveCocoa.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 3/5/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | |
| | | //! Project version number for ReactiveCocoa. |
| | | FOUNDATION_EXPORT double ReactiveCocoaVersionNumber; |
| | | |
| | | //! Project version string for ReactiveCocoa. |
| | | FOUNDATION_EXPORT const unsigned char ReactiveCocoaVersionString[]; |
| | | |
| | | #import <ReactiveCocoa/RACEXTKeyPathCoding.h> |
| | | #import <ReactiveCocoa/RACEXTScope.h> |
| | | #import <ReactiveCocoa/NSArray+RACSequenceAdditions.h> |
| | | #import <ReactiveCocoa/NSData+RACSupport.h> |
| | | #import <ReactiveCocoa/NSDictionary+RACSequenceAdditions.h> |
| | | #import <ReactiveCocoa/NSEnumerator+RACSequenceAdditions.h> |
| | | #import <ReactiveCocoa/NSFileHandle+RACSupport.h> |
| | | #import <ReactiveCocoa/NSNotificationCenter+RACSupport.h> |
| | | #import <ReactiveCocoa/NSObject+RACDeallocating.h> |
| | | #import <ReactiveCocoa/NSObject+RACLifting.h> |
| | | #import <ReactiveCocoa/NSObject+RACPropertySubscribing.h> |
| | | #import <ReactiveCocoa/NSObject+RACSelectorSignal.h> |
| | | #import <ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.h> |
| | | #import <ReactiveCocoa/NSSet+RACSequenceAdditions.h> |
| | | #import <ReactiveCocoa/NSString+RACSequenceAdditions.h> |
| | | #import <ReactiveCocoa/NSString+RACSupport.h> |
| | | #import <ReactiveCocoa/NSIndexSet+RACSequenceAdditions.h> |
| | | #import <ReactiveCocoa/NSURLConnection+RACSupport.h> |
| | | #import <ReactiveCocoa/NSUserDefaults+RACSupport.h> |
| | | #import <ReactiveCocoa/RACBehaviorSubject.h> |
| | | #import <ReactiveCocoa/RACChannel.h> |
| | | #import <ReactiveCocoa/RACCommand.h> |
| | | #import <ReactiveCocoa/RACCompoundDisposable.h> |
| | | #import <ReactiveCocoa/RACDisposable.h> |
| | | #import <ReactiveCocoa/RACEvent.h> |
| | | #import <ReactiveCocoa/RACGroupedSignal.h> |
| | | #import <ReactiveCocoa/RACKVOChannel.h> |
| | | #import <ReactiveCocoa/RACMulticastConnection.h> |
| | | #import <ReactiveCocoa/RACQueueScheduler.h> |
| | | #import <ReactiveCocoa/RACQueueScheduler+Subclass.h> |
| | | #import <ReactiveCocoa/RACReplaySubject.h> |
| | | #import <ReactiveCocoa/RACScheduler.h> |
| | | #import <ReactiveCocoa/RACScheduler+Subclass.h> |
| | | #import <ReactiveCocoa/RACScopedDisposable.h> |
| | | #import <ReactiveCocoa/RACSequence.h> |
| | | #import <ReactiveCocoa/RACSerialDisposable.h> |
| | | #import <ReactiveCocoa/RACSignal+Operations.h> |
| | | #import <ReactiveCocoa/RACSignal.h> |
| | | #import <ReactiveCocoa/RACStream.h> |
| | | #import <ReactiveCocoa/RACSubject.h> |
| | | #import <ReactiveCocoa/RACSubscriber.h> |
| | | #import <ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h> |
| | | #import <ReactiveCocoa/RACTargetQueueScheduler.h> |
| | | #import <ReactiveCocoa/RACTestScheduler.h> |
| | | #import <ReactiveCocoa/RACTuple.h> |
| | | #import <ReactiveCocoa/RACUnit.h> |
| | | |
| | | #ifdef __IPHONE_OS_VERSION_MIN_REQUIRED |
| | | #import <ReactiveCocoa/MKAnnotationView+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UIActionSheet+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UIAlertView+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h> |
| | | #import <ReactiveCocoa/UIButton+RACCommandSupport.h> |
| | | #import <ReactiveCocoa/UICollectionReusableView+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UIControl+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UIDatePicker+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UIImagePickerController+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UIRefreshControl+RACCommandSupport.h> |
| | | #import <ReactiveCocoa/UISegmentedControl+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UISlider+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UIStepper+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UISwitch+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UITableViewCell+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UITextField+RACSignalSupport.h> |
| | | #import <ReactiveCocoa/UITextView+RACSignalSupport.h> |
| | | #elif TARGET_OS_MAC |
| | | #import <ReactiveCocoa/NSControl+RACCommandSupport.h> |
| | | #import <ReactiveCocoa/NSControl+RACTextSignalSupport.h> |
| | | #import <ReactiveCocoa/NSObject+RACAppKitBindings.h> |
| | | #import <ReactiveCocoa/NSText+RACSignalSupport.h> |
| | | #endif |
New file |
| | |
| | | // |
| | | // UIActionSheet+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Dave Lee on 2013-06-22. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACDelegateProxy; |
| | | @class RACSignal; |
| | | |
| | | @interface UIActionSheet (RACSignalSupport) |
| | | |
| | | /// A delegate proxy which will be set as the receiver's delegate when any of the |
| | | /// methods in this category are used. |
| | | @property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; |
| | | |
| | | /// Creates a signal for button clicks on the receiver. |
| | | /// |
| | | /// When this method is invoked, the `rac_delegateProxy` will become the |
| | | /// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy |
| | | /// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't |
| | | /// know how to handle. Setting the receiver's `delegate` afterward is |
| | | /// considered undefined behavior. |
| | | /// |
| | | /// Returns a signal which will send the index of the specific button clicked. |
| | | /// The signal will complete when the receiver is deallocated. |
| | | - (RACSignal *)rac_buttonClickedSignal; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIActionSheet+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Dave Lee on 2013-06-22. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UIActionSheet+RACSignalSupport.h" |
| | | #import "RACDelegateProxy.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @implementation UIActionSheet (RACSignalSupport) |
| | | |
| | | static void RACUseDelegateProxy(UIActionSheet *self) { |
| | | if (self.delegate == self.rac_delegateProxy) return; |
| | | |
| | | self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; |
| | | self.delegate = (id)self.rac_delegateProxy; |
| | | } |
| | | |
| | | - (RACDelegateProxy *)rac_delegateProxy { |
| | | RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); |
| | | if (proxy == nil) { |
| | | proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIActionSheetDelegate)]; |
| | | objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | return proxy; |
| | | } |
| | | |
| | | - (RACSignal *)rac_buttonClickedSignal { |
| | | RACSignal *signal = [[[[self.rac_delegateProxy |
| | | signalForSelector:@selector(actionSheet:clickedButtonAtIndex:)] |
| | | reduceEach:^(UIActionSheet *actionSheet, NSNumber *buttonIndex) { |
| | | return buttonIndex; |
| | | }] |
| | | takeUntil:self.rac_willDeallocSignal] |
| | | setNameWithFormat:@"%@ -rac_buttonClickedSignal", self.rac_description]; |
| | | |
| | | RACUseDelegateProxy(self); |
| | | |
| | | return signal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIAlertView+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Henrik Hodne on 6/16/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACDelegateProxy; |
| | | @class RACSignal; |
| | | |
| | | @interface UIAlertView (RACSignalSupport) |
| | | |
| | | /// A delegate proxy which will be set as the receiver's delegate when any of the |
| | | /// methods in this category are used. |
| | | @property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; |
| | | |
| | | /// Creates a signal for button clicks on the receiver. |
| | | /// |
| | | /// When this method is invoked, the `rac_delegateProxy` will become the |
| | | /// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy |
| | | /// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't |
| | | /// know how to handle. Setting the receiver's `delegate` afterward is considered |
| | | /// undefined behavior. |
| | | /// |
| | | /// Note that this signal will not send a value when the alert is dismissed |
| | | /// programatically. |
| | | /// |
| | | /// Returns a signal which will send the index of the specific button clicked. |
| | | /// The signal will complete itself when the receiver is deallocated. |
| | | - (RACSignal *)rac_buttonClickedSignal; |
| | | |
| | | /// Creates a signal for dismissal of the receiver. |
| | | /// |
| | | /// When this method is invoked, the `rac_delegateProxy` will become the |
| | | /// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy |
| | | /// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't |
| | | /// know how to handle. Setting the receiver's `delegate` afterward is considered |
| | | /// undefined behavior. |
| | | /// |
| | | /// Returns a signal which will send the index of the button associated with the |
| | | /// dismissal. The signal will complete itself when the receiver is deallocated. |
| | | - (RACSignal *)rac_willDismissSignal; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIAlertView+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Henrik Hodne on 6/16/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UIAlertView+RACSignalSupport.h" |
| | | #import "RACDelegateProxy.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @implementation UIAlertView (RACSignalSupport) |
| | | |
| | | static void RACUseDelegateProxy(UIAlertView *self) { |
| | | if (self.delegate == self.rac_delegateProxy) return; |
| | | |
| | | self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; |
| | | self.delegate = (id)self.rac_delegateProxy; |
| | | } |
| | | |
| | | - (RACDelegateProxy *)rac_delegateProxy { |
| | | RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); |
| | | if (proxy == nil) { |
| | | proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIAlertViewDelegate)]; |
| | | objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | return proxy; |
| | | } |
| | | |
| | | - (RACSignal *)rac_buttonClickedSignal { |
| | | RACSignal *signal = [[[[self.rac_delegateProxy |
| | | signalForSelector:@selector(alertView:clickedButtonAtIndex:)] |
| | | reduceEach:^(UIAlertView *alertView, NSNumber *buttonIndex) { |
| | | return buttonIndex; |
| | | }] |
| | | takeUntil:self.rac_willDeallocSignal] |
| | | setNameWithFormat:@"%@ -rac_buttonClickedSignal", self.rac_description]; |
| | | |
| | | RACUseDelegateProxy(self); |
| | | |
| | | return signal; |
| | | } |
| | | |
| | | - (RACSignal *)rac_willDismissSignal { |
| | | RACSignal *signal = [[[[self.rac_delegateProxy |
| | | signalForSelector:@selector(alertView:willDismissWithButtonIndex:)] |
| | | reduceEach:^(UIAlertView *alertView, NSNumber *buttonIndex) { |
| | | return buttonIndex; |
| | | }] |
| | | takeUntil:self.rac_willDeallocSignal] |
| | | setNameWithFormat:@"%@ -rac_willDismissSignal", self.rac_description]; |
| | | |
| | | RACUseDelegateProxy(self); |
| | | |
| | | return signal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIBarButtonItem+RACCommandSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Kyle LeNeau on 3/27/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACCommand; |
| | | |
| | | @interface UIBarButtonItem (RACCommandSupport) |
| | | |
| | | /// Sets the control's command. When the control is clicked, the command is |
| | | /// executed with the sender of the event. The control's enabledness is bound |
| | | /// to the command's `canExecute`. |
| | | /// |
| | | /// Note: this will reset the control's target and action. |
| | | @property (nonatomic, strong) RACCommand *rac_command; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIBarButtonItem+RACCommandSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Kyle LeNeau on 3/27/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UIBarButtonItem+RACCommandSupport.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "RACCommand.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | static void *UIControlRACCommandKey = &UIControlRACCommandKey; |
| | | static void *UIControlEnabledDisposableKey = &UIControlEnabledDisposableKey; |
| | | |
| | | @implementation UIBarButtonItem (RACCommandSupport) |
| | | |
| | | - (RACCommand *)rac_command { |
| | | return objc_getAssociatedObject(self, UIControlRACCommandKey); |
| | | } |
| | | |
| | | - (void)setRac_command:(RACCommand *)command { |
| | | objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | |
| | | // Check for stored signal in order to remove it and add a new one |
| | | RACDisposable *disposable = objc_getAssociatedObject(self, UIControlEnabledDisposableKey); |
| | | [disposable dispose]; |
| | | |
| | | if (command == nil) return; |
| | | |
| | | disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; |
| | | objc_setAssociatedObject(self, UIControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | |
| | | [self rac_hijackActionAndTargetIfNeeded]; |
| | | } |
| | | |
| | | - (void)rac_hijackActionAndTargetIfNeeded { |
| | | SEL hijackSelector = @selector(rac_commandPerformAction:); |
| | | if (self.target == self && self.action == hijackSelector) return; |
| | | |
| | | if (self.target != nil) NSLog(@"WARNING: UIBarButtonItem.rac_command hijacks the control's existing target and action."); |
| | | |
| | | self.target = self; |
| | | self.action = hijackSelector; |
| | | } |
| | | |
| | | - (void)rac_commandPerformAction:(id)sender { |
| | | [self.rac_command execute:sender]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIButton+RACCommandSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Ash Furrow on 2013-06-06. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACCommand; |
| | | |
| | | @interface UIButton (RACCommandSupport) |
| | | |
| | | /// Sets the button's command. When the button is clicked, the command is |
| | | /// executed with the sender of the event. The button's enabledness is bound |
| | | /// to the command's `canExecute`. |
| | | @property (nonatomic, strong) RACCommand *rac_command; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIButton+RACCommandSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Ash Furrow on 2013-06-06. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UIButton+RACCommandSupport.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "RACCommand.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | static void *UIButtonRACCommandKey = &UIButtonRACCommandKey; |
| | | static void *UIButtonEnabledDisposableKey = &UIButtonEnabledDisposableKey; |
| | | |
| | | @implementation UIButton (RACCommandSupport) |
| | | |
| | | - (RACCommand *)rac_command { |
| | | return objc_getAssociatedObject(self, UIButtonRACCommandKey); |
| | | } |
| | | |
| | | - (void)setRac_command:(RACCommand *)command { |
| | | objc_setAssociatedObject(self, UIButtonRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | |
| | | // Check for stored signal in order to remove it and add a new one |
| | | RACDisposable *disposable = objc_getAssociatedObject(self, UIButtonEnabledDisposableKey); |
| | | [disposable dispose]; |
| | | |
| | | if (command == nil) return; |
| | | |
| | | disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; |
| | | objc_setAssociatedObject(self, UIButtonEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | |
| | | [self rac_hijackActionAndTargetIfNeeded]; |
| | | } |
| | | |
| | | - (void)rac_hijackActionAndTargetIfNeeded { |
| | | SEL hijackSelector = @selector(rac_commandPerformAction:); |
| | | |
| | | for (NSString *selector in [self actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]) { |
| | | if (hijackSelector == NSSelectorFromString(selector)) { |
| | | return; |
| | | } |
| | | } |
| | | |
| | | [self addTarget:self action:hijackSelector forControlEvents:UIControlEventTouchUpInside]; |
| | | } |
| | | |
| | | - (void)rac_commandPerformAction:(id)sender { |
| | | [self.rac_command execute:sender]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UICollectionReusableView+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Kent Wong on 2013-10-04. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | // This category is only applicable to iOS >= 6.0. |
| | | @interface UICollectionReusableView (RACSignalSupport) |
| | | |
| | | /// A signal which will send a RACUnit whenever -prepareForReuse is invoked upon |
| | | /// the receiver. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// [[[self.cancelButton |
| | | /// rac_signalForControlEvents:UIControlEventTouchUpInside] |
| | | /// takeUntil:self.rac_prepareForReuseSignal] |
| | | /// subscribeNext:^(UIButton *x) { |
| | | /// // do other things |
| | | /// }]; |
| | | @property (nonatomic, strong, readonly) RACSignal *rac_prepareForReuseSignal; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UICollectionReusableView+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Kent Wong on 2013-10-04. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UICollectionReusableView+RACSignalSupport.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "NSObject+RACSelectorSignal.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "RACUnit.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @implementation UICollectionReusableView (RACSignalSupport) |
| | | |
| | | - (RACSignal *)rac_prepareForReuseSignal { |
| | | RACSignal *signal = objc_getAssociatedObject(self, _cmd); |
| | | if (signal != nil) return signal; |
| | | |
| | | signal = [[[self |
| | | rac_signalForSelector:@selector(prepareForReuse)] |
| | | mapReplace:RACUnit.defaultUnit] |
| | | setNameWithFormat:@"%@ -rac_prepareForReuseSignal", self.rac_description]; |
| | | |
| | | objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | return signal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIControl+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/17/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | @interface UIControl (RACSignalSupport) |
| | | |
| | | /// Creates and returns a signal that sends the sender of the control event |
| | | /// whenever one of the control events is triggered. |
| | | - (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIControl+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/17/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UIControl+RACSignalSupport.h" |
| | | #import "RACEXTScope.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACSignal.h" |
| | | #import "RACSubscriber.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | |
| | | @implementation UIControl (RACSignalSupport) |
| | | |
| | | - (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents { |
| | | @weakify(self); |
| | | |
| | | return [[RACSignal |
| | | createSignal:^(id<RACSubscriber> subscriber) { |
| | | @strongify(self); |
| | | |
| | | [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; |
| | | [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| | | [subscriber sendCompleted]; |
| | | }]]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | @strongify(self); |
| | | [self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; |
| | | }]; |
| | | }] |
| | | setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", self.rac_description, (unsigned long)controlEvents]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIControl+RACSignalSupportPrivate.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 06/08/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACChannelTerminal; |
| | | |
| | | @interface UIControl (RACSignalSupportPrivate) |
| | | |
| | | // Adds a RACChannel-based interface to the receiver for the given |
| | | // UIControlEvents and exposes it. |
| | | // |
| | | // controlEvents - A mask of UIControlEvents on which to send new values. |
| | | // key - The key whose value should be read and set when a control |
| | | // event fires and when a value is sent to the |
| | | // RACChannelTerminal respectively. |
| | | // nilValue - The value to be assigned to the key when `nil` is sent to the |
| | | // RACChannelTerminal. |
| | | // |
| | | // Returns a RACChannelTerminal which will send future values from the receiver, |
| | | // and update the receiver when values are sent to the terminal. |
| | | - (RACChannelTerminal *)rac_channelForControlEvents:(UIControlEvents)controlEvents key:(NSString *)key nilValue:(id)nilValue; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIControl+RACSignalSupportPrivate.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 06/08/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UIControl+RACSignalSupportPrivate.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACLifting.h" |
| | | #import "RACChannel.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "UIControl+RACSignalSupport.h" |
| | | |
| | | @implementation UIControl (RACSignalSupportPrivate) |
| | | |
| | | - (RACChannelTerminal *)rac_channelForControlEvents:(UIControlEvents)controlEvents key:(NSString *)key nilValue:(id)nilValue { |
| | | NSCParameterAssert(key.length > 0); |
| | | key = [key copy]; |
| | | RACChannel *channel = [[RACChannel alloc] init]; |
| | | |
| | | [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| | | [channel.followingTerminal sendCompleted]; |
| | | }]]; |
| | | |
| | | RACSignal *eventSignal = [[[self |
| | | rac_signalForControlEvents:controlEvents] |
| | | mapReplace:key] |
| | | takeUntil:[[channel.followingTerminal |
| | | ignoreValues] |
| | | catchTo:RACSignal.empty]]; |
| | | [[self |
| | | rac_liftSelector:@selector(valueForKey:) withSignals:eventSignal, nil] |
| | | subscribe:channel.followingTerminal]; |
| | | |
| | | RACSignal *valuesSignal = [channel.followingTerminal |
| | | map:^(id value) { |
| | | return value ?: nilValue; |
| | | }]; |
| | | [self rac_liftSelector:@selector(setValue:forKey:) withSignals:valuesSignal, [RACSignal return:key], nil]; |
| | | |
| | | return channel.leadingTerminal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIDatePicker+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACChannelTerminal; |
| | | |
| | | @interface UIDatePicker (RACSignalSupport) |
| | | |
| | | /// Creates a new RACChannel-based binding to the receiver. |
| | | /// |
| | | /// nilValue - The date to set when the terminal receives `nil`. |
| | | /// |
| | | /// Returns a RACChannelTerminal that sends the receiver's date whenever the |
| | | /// UIControlEventValueChanged control event is fired, and sets the date to the |
| | | /// values it receives. |
| | | - (RACChannelTerminal *)rac_newDateChannelWithNilValue:(NSDate *)nilValue; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIDatePicker+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UIDatePicker+RACSignalSupport.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "UIControl+RACSignalSupportPrivate.h" |
| | | |
| | | @implementation UIDatePicker (RACSignalSupport) |
| | | |
| | | - (RACChannelTerminal *)rac_newDateChannelWithNilValue:(NSDate *)nilValue { |
| | | return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.date) nilValue:nilValue]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIGestureRecognizer+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Vera on 5/5/13. |
| | | // Copyright (c) 2013 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | @interface UIGestureRecognizer (RACSignalSupport) |
| | | |
| | | /// Returns a signal that sends the receiver when its gesture occurs. |
| | | - (RACSignal *)rac_gestureSignal; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIGestureRecognizer+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Vera on 5/5/13. |
| | | // Copyright (c) 2013 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "UIGestureRecognizer+RACSignalSupport.h" |
| | | #import "RACEXTScope.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACSignal.h" |
| | | #import "RACSubscriber.h" |
| | | |
| | | @implementation UIGestureRecognizer (RACSignalSupport) |
| | | |
| | | - (RACSignal *)rac_gestureSignal { |
| | | @weakify(self); |
| | | |
| | | return [[RACSignal |
| | | createSignal:^(id<RACSubscriber> subscriber) { |
| | | @strongify(self); |
| | | |
| | | [self addTarget:subscriber action:@selector(sendNext:)]; |
| | | [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| | | [subscriber sendCompleted]; |
| | | }]]; |
| | | |
| | | return [RACDisposable disposableWithBlock:^{ |
| | | @strongify(self); |
| | | [self removeTarget:subscriber action:@selector(sendNext:)]; |
| | | }]; |
| | | }] |
| | | setNameWithFormat:@"%@ -rac_gestureSignal", self.rac_description]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIImagePickerController+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Timur Kuchkarov on 28.03.14. |
| | | // Copyright (c) 2014 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACDelegateProxy; |
| | | @class RACSignal; |
| | | |
| | | @interface UIImagePickerController (RACSignalSupport) |
| | | |
| | | /// A delegate proxy which will be set as the receiver's delegate when any of the |
| | | /// methods in this category are used. |
| | | @property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; |
| | | |
| | | /// Creates a signal for every new selected image. |
| | | /// |
| | | /// When this method is invoked, the `rac_delegateProxy` will become the |
| | | /// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy |
| | | /// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't |
| | | /// know how to handle. Setting the receiver's `delegate` afterward is considered |
| | | /// undefined behavior. |
| | | /// |
| | | /// Returns a signal which will send the dictionary with info for the selected image. |
| | | /// Caller is responsible for picker controller dismissal. The signal will complete |
| | | /// itself when the receiver is deallocated or when user cancels selection. |
| | | - (RACSignal *)rac_imageSelectedSignal; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIImagePickerController+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Timur Kuchkarov on 28.03.14. |
| | | // Copyright (c) 2014 GitHub. All rights reserved. |
| | | // |
| | | |
| | | #import "UIImagePickerController+RACSignalSupport.h" |
| | | #import "RACDelegateProxy.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @implementation UIImagePickerController (RACSignalSupport) |
| | | |
| | | static void RACUseDelegateProxy(UIImagePickerController *self) { |
| | | if (self.delegate == self.rac_delegateProxy) return; |
| | | |
| | | self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; |
| | | self.delegate = (id)self.rac_delegateProxy; |
| | | } |
| | | |
| | | - (RACDelegateProxy *)rac_delegateProxy { |
| | | RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); |
| | | if (proxy == nil) { |
| | | proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIImagePickerControllerDelegate)]; |
| | | objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | return proxy; |
| | | } |
| | | |
| | | - (RACSignal *)rac_imageSelectedSignal { |
| | | RACSignal *pickerCancelledSignal = [[self.rac_delegateProxy |
| | | signalForSelector:@selector(imagePickerControllerDidCancel:)] |
| | | merge:self.rac_willDeallocSignal]; |
| | | |
| | | RACSignal *imagePickerSignal = [[[[self.rac_delegateProxy |
| | | signalForSelector:@selector(imagePickerController:didFinishPickingMediaWithInfo:)] |
| | | reduceEach:^(UIImagePickerController *pickerController, NSDictionary *userInfo) { |
| | | return userInfo; |
| | | }] |
| | | takeUntil:pickerCancelledSignal] |
| | | setNameWithFormat:@"%@ -rac_imageSelectedSignal", self.rac_description]; |
| | | |
| | | RACUseDelegateProxy(self); |
| | | |
| | | return imagePickerSignal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIRefreshControl+RACCommandSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Dave Lee on 2013-10-17. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACCommand; |
| | | |
| | | @interface UIRefreshControl (RACCommandSupport) |
| | | |
| | | /// Manipulate the RACCommand property associated with this refresh control. |
| | | /// |
| | | /// When this refresh control is activated by the user, the command will be |
| | | /// executed. Upon completion or error of the execution signal, -endRefreshing |
| | | /// will be invoked. |
| | | @property (nonatomic, strong) RACCommand *rac_command; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIRefreshControl+RACCommandSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Dave Lee on 2013-10-17. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UIRefreshControl+RACCommandSupport.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "RACCommand.h" |
| | | #import "RACCompoundDisposable.h" |
| | | #import "RACDisposable.h" |
| | | #import "RACSignal.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "UIControl+RACSignalSupport.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | static void *UIRefreshControlRACCommandKey = &UIRefreshControlRACCommandKey; |
| | | static void *UIRefreshControlDisposableKey = &UIRefreshControlDisposableKey; |
| | | |
| | | @implementation UIRefreshControl (RACCommandSupport) |
| | | |
| | | - (RACCommand *)rac_command { |
| | | return objc_getAssociatedObject(self, UIRefreshControlRACCommandKey); |
| | | } |
| | | |
| | | - (void)setRac_command:(RACCommand *)command { |
| | | objc_setAssociatedObject(self, UIRefreshControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | |
| | | // Dispose of any active command associations. |
| | | [objc_getAssociatedObject(self, UIRefreshControlDisposableKey) dispose]; |
| | | |
| | | if (command == nil) return; |
| | | |
| | | // Like RAC(self, enabled) = command.enabled; but with access to disposable. |
| | | RACDisposable *enabledDisposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; |
| | | |
| | | RACDisposable *executionDisposable = [[[[self |
| | | rac_signalForControlEvents:UIControlEventValueChanged] |
| | | map:^(UIRefreshControl *x) { |
| | | return [[[command |
| | | execute:x] |
| | | catchTo:[RACSignal empty]] |
| | | then:^{ |
| | | return [RACSignal return:x]; |
| | | }]; |
| | | }] |
| | | concat] |
| | | subscribeNext:^(UIRefreshControl *x) { |
| | | [x endRefreshing]; |
| | | }]; |
| | | |
| | | RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]]; |
| | | objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UISegmentedControl+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACChannelTerminal; |
| | | |
| | | @interface UISegmentedControl (RACSignalSupport) |
| | | |
| | | /// Creates a new RACChannel-based binding to the receiver. |
| | | /// |
| | | /// nilValue - The segment to select when the terminal receives `nil`. |
| | | /// |
| | | /// Returns a RACChannelTerminal that sends the receiver's currently selected |
| | | /// segment's index whenever the UIControlEventValueChanged control event is |
| | | /// fired, and sets the selected segment index to the values it receives. |
| | | - (RACChannelTerminal *)rac_newSelectedSegmentIndexChannelWithNilValue:(NSNumber *)nilValue; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UISegmentedControl+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UISegmentedControl+RACSignalSupport.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "UIControl+RACSignalSupportPrivate.h" |
| | | |
| | | @implementation UISegmentedControl (RACSignalSupport) |
| | | |
| | | - (RACChannelTerminal *)rac_newSelectedSegmentIndexChannelWithNilValue:(NSNumber *)nilValue { |
| | | return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.selectedSegmentIndex) nilValue:nilValue]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UISlider+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACChannelTerminal; |
| | | |
| | | @interface UISlider (RACSignalSupport) |
| | | |
| | | /// Creates a new RACChannel-based binding to the receiver. |
| | | /// |
| | | /// nilValue - The value to set when the terminal receives `nil`. |
| | | /// |
| | | /// Returns a RACChannelTerminal that sends the receiver's value whenever the |
| | | /// UIControlEventValueChanged control event is fired, and sets the value to the |
| | | /// values it receives. |
| | | - (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UISlider+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UISlider+RACSignalSupport.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "UIControl+RACSignalSupportPrivate.h" |
| | | |
| | | @implementation UISlider (RACSignalSupport) |
| | | |
| | | - (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue { |
| | | return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.value) nilValue:nilValue]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIStepper+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACChannelTerminal; |
| | | |
| | | @interface UIStepper (RACSignalSupport) |
| | | |
| | | /// Creates a new RACChannel-based binding to the receiver. |
| | | /// |
| | | /// nilValue - The value to set when the terminal receives `nil`. |
| | | /// |
| | | /// Returns a RACChannelTerminal that sends the receiver's value whenever the |
| | | /// UIControlEventValueChanged control event is fired, and sets the value to the |
| | | /// values it receives. |
| | | - (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UIStepper+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UIStepper+RACSignalSupport.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "UIControl+RACSignalSupportPrivate.h" |
| | | |
| | | @implementation UIStepper (RACSignalSupport) |
| | | |
| | | - (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue { |
| | | return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.value) nilValue:nilValue]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UISwitch+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACChannelTerminal; |
| | | |
| | | @interface UISwitch (RACSignalSupport) |
| | | |
| | | /// Creates a new RACChannel-based binding to the receiver. |
| | | /// |
| | | /// Returns a RACChannelTerminal that sends whether the receiver is on whenever |
| | | /// the UIControlEventValueChanged control event is fired, and sets it on or off |
| | | /// when it receives @YES or @NO respectively. |
| | | - (RACChannelTerminal *)rac_newOnChannel; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UISwitch+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Uri Baghin on 20/07/2013. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UISwitch+RACSignalSupport.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "UIControl+RACSignalSupportPrivate.h" |
| | | |
| | | @implementation UISwitch (RACSignalSupport) |
| | | |
| | | - (RACChannelTerminal *)rac_newOnChannel { |
| | | return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.on) nilValue:@NO]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UITableViewCell+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-07-22. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | @interface UITableViewCell (RACSignalSupport) |
| | | |
| | | /// A signal which will send a RACUnit whenever -prepareForReuse is invoked upon |
| | | /// the receiver. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// [[[self.cancelButton |
| | | /// rac_signalForControlEvents:UIControlEventTouchUpInside] |
| | | /// takeUntil:self.rac_prepareForReuseSignal] |
| | | /// subscribeNext:^(UIButton *x) { |
| | | /// // do other things |
| | | /// }]; |
| | | @property (nonatomic, strong, readonly) RACSignal *rac_prepareForReuseSignal; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UITableViewCell+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Justin Spahr-Summers on 2013-07-22. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UITableViewCell+RACSignalSupport.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "NSObject+RACSelectorSignal.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "RACUnit.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @implementation UITableViewCell (RACSignalSupport) |
| | | |
| | | - (RACSignal *)rac_prepareForReuseSignal { |
| | | RACSignal *signal = objc_getAssociatedObject(self, _cmd); |
| | | if (signal != nil) return signal; |
| | | |
| | | signal = [[[self |
| | | rac_signalForSelector:@selector(prepareForReuse)] |
| | | mapReplace:RACUnit.defaultUnit] |
| | | setNameWithFormat:@"%@ -rac_prepareForReuseSignal", self.rac_description]; |
| | | |
| | | objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | return signal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UITableViewHeaderFooterView+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Syo Ikeda on 12/30/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACSignal; |
| | | |
| | | // This category is only applicable to iOS >= 6.0. |
| | | @interface UITableViewHeaderFooterView (RACSignalSupport) |
| | | |
| | | /// A signal which will send a RACUnit whenever -prepareForReuse is invoked upon |
| | | /// the receiver. |
| | | /// |
| | | /// Examples |
| | | /// |
| | | /// [[[self.cancelButton |
| | | /// rac_signalForControlEvents:UIControlEventTouchUpInside] |
| | | /// takeUntil:self.rac_prepareForReuseSignal] |
| | | /// subscribeNext:^(UIButton *x) { |
| | | /// // do other things |
| | | /// }]; |
| | | @property (nonatomic, strong, readonly) RACSignal *rac_prepareForReuseSignal; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UITableViewHeaderFooterView+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Syo Ikeda on 12/30/13. |
| | | // Copyright (c) 2013 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UITableViewHeaderFooterView+RACSignalSupport.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "NSObject+RACSelectorSignal.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "RACUnit.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @implementation UITableViewHeaderFooterView (RACSignalSupport) |
| | | |
| | | - (RACSignal *)rac_prepareForReuseSignal { |
| | | RACSignal *signal = objc_getAssociatedObject(self, _cmd); |
| | | if (signal != nil) return signal; |
| | | |
| | | signal = [[[self |
| | | rac_signalForSelector:@selector(prepareForReuse)] |
| | | mapReplace:RACUnit.defaultUnit] |
| | | setNameWithFormat:@"%@ -rac_prepareForReuseSignal", self.rac_description]; |
| | | |
| | | objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | return signal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UITextField+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/17/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACChannelTerminal; |
| | | @class RACSignal; |
| | | |
| | | @interface UITextField (RACSignalSupport) |
| | | |
| | | /// Creates and returns a signal for the text of the field. It always starts with |
| | | /// the current text. The signal sends next when the UIControlEventAllEditingEvents |
| | | /// control event is fired on the control. |
| | | - (RACSignal *)rac_textSignal; |
| | | |
| | | /// Creates a new RACChannel-based binding to the receiver. |
| | | /// |
| | | /// Returns a RACChannelTerminal that sends the receiver's text whenever the |
| | | /// UIControlEventAllEditingEvents control event is fired, and sets the text |
| | | /// to the values it receives. |
| | | - (RACChannelTerminal *)rac_newTextChannel; |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UITextField+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Josh Abernathy on 4/17/12. |
| | | // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| | | // |
| | | |
| | | #import "UITextField+RACSignalSupport.h" |
| | | #import "RACEXTKeyPathCoding.h" |
| | | #import "RACEXTScope.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "UIControl+RACSignalSupport.h" |
| | | #import "UIControl+RACSignalSupportPrivate.h" |
| | | |
| | | @implementation UITextField (RACSignalSupport) |
| | | |
| | | - (RACSignal *)rac_textSignal { |
| | | @weakify(self); |
| | | return [[[[[RACSignal |
| | | defer:^{ |
| | | @strongify(self); |
| | | return [RACSignal return:self]; |
| | | }] |
| | | concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]] |
| | | map:^(UITextField *x) { |
| | | return x.text; |
| | | }] |
| | | takeUntil:self.rac_willDeallocSignal] |
| | | setNameWithFormat:@"%@ -rac_textSignal", self.rac_description]; |
| | | } |
| | | |
| | | - (RACChannelTerminal *)rac_newTextChannel { |
| | | return [self rac_channelForControlEvents:UIControlEventAllEditingEvents key:@keypath(self.text) nilValue:@""]; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UITextView+RACSignalSupport.h |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Cody Krieger on 5/18/12. |
| | | // Copyright (c) 2012 Cody Krieger. All rights reserved. |
| | | // |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class RACDelegateProxy; |
| | | @class RACSignal; |
| | | |
| | | @interface UITextView (RACSignalSupport) |
| | | |
| | | /// A delegate proxy which will be set as the receiver's delegate when any of the |
| | | /// methods in this category are used. |
| | | @property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; |
| | | |
| | | /// Creates a signal for the text of the receiver. |
| | | /// |
| | | /// When this method is invoked, the `rac_delegateProxy` will become the |
| | | /// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy |
| | | /// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't |
| | | /// know how to handle. Setting the receiver's `delegate` afterward is |
| | | /// considered undefined behavior. |
| | | /// |
| | | /// Returns a signal which will send the current text upon subscription, then |
| | | /// again whenever the receiver's text is changed. The signal will complete when |
| | | /// the receiver is deallocated. |
| | | - (RACSignal *)rac_textSignal; |
| | | |
| | | @end |
| | | |
| | | @interface UITextView (RACSignalSupportUnavailable) |
| | | |
| | | - (RACSignal *)rac_signalForDelegateMethod:(SEL)method __attribute__((unavailable("Use -rac_signalForSelector:fromProtocol: instead"))); |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // UITextView+RACSignalSupport.m |
| | | // ReactiveCocoa |
| | | // |
| | | // Created by Cody Krieger on 5/18/12. |
| | | // Copyright (c) 2012 Cody Krieger. All rights reserved. |
| | | // |
| | | |
| | | #import "UITextView+RACSignalSupport.h" |
| | | #import "RACEXTScope.h" |
| | | #import "NSObject+RACDeallocating.h" |
| | | #import "NSObject+RACDescription.h" |
| | | #import "RACDelegateProxy.h" |
| | | #import "RACSignal+Operations.h" |
| | | #import "RACTuple.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @implementation UITextView (RACSignalSupport) |
| | | |
| | | static void RACUseDelegateProxy(UITextView *self) { |
| | | if (self.delegate == self.rac_delegateProxy) return; |
| | | |
| | | self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; |
| | | self.delegate = (id)self.rac_delegateProxy; |
| | | } |
| | | |
| | | - (RACDelegateProxy *)rac_delegateProxy { |
| | | RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); |
| | | if (proxy == nil) { |
| | | proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UITextViewDelegate)]; |
| | | objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | return proxy; |
| | | } |
| | | |
| | | - (RACSignal *)rac_textSignal { |
| | | @weakify(self); |
| | | RACSignal *signal = [[[[[RACSignal |
| | | defer:^{ |
| | | @strongify(self); |
| | | return [RACSignal return:RACTuplePack(self)]; |
| | | }] |
| | | concat:[self.rac_delegateProxy signalForSelector:@selector(textViewDidChange:)]] |
| | | reduceEach:^(UITextView *x) { |
| | | return x.text; |
| | | }] |
| | | takeUntil:self.rac_willDeallocSignal] |
| | | setNameWithFormat:@"%@ -rac_textSignal", self.rac_description]; |
| | | |
| | | RACUseDelegateProxy(self); |
| | | |
| | | return signal; |
| | | } |
| | | |
| | | @end |
New file |
| | |
| | | // |
| | | // EXTKeyPathCoding.h |
| | | // extobjc |
| | | // |
| | | // Created by Justin Spahr-Summers on 19.06.12. |
| | | // Copyright (C) 2012 Justin Spahr-Summers. |
| | | // Released under the MIT license. |
| | | // |
| | | |
| | | #import <Foundation/Foundation.h> |
| | | #import "RACmetamacros.h" |
| | | |
| | | /** |
| | | * \@keypath allows compile-time verification of key paths. Given a real object |
| | | * receiver and key path: |
| | | * |
| | | * @code |
| | | |
| | | NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String); |
| | | // => @"lowercaseString.UTF8String" |
| | | |
| | | NSString *versionPath = @keypath(NSObject, version); |
| | | // => @"version" |
| | | |
| | | NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString); |
| | | // => @"lowercaseString" |
| | | |
| | | * @endcode |
| | | * |
| | | * ... the macro returns an \c NSString containing all but the first path |
| | | * component or argument (e.g., @"lowercaseString.UTF8String", @"version"). |
| | | * |
| | | * In addition to simply creating a key path, this macro ensures that the key |
| | | * path is valid at compile-time (causing a syntax error if not), and supports |
| | | * refactoring, such that changing the name of the property will also update any |
| | | * uses of \@keypath. |
| | | */ |
| | | #define keypath(...) \ |
| | | metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__)) |
| | | |
| | | #define keypath1(PATH) \ |
| | | (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1)) |
| | | |
| | | #define keypath2(OBJ, PATH) \ |
| | | (((void)(NO && ((void)OBJ.PATH, NO)), # PATH)) |
| | | |
| | | /** |
| | | * \@collectionKeypath allows compile-time verification of key paths across collections NSArray/NSSet etc. Given a real object |
| | | * receiver, collection object receiver and related keypaths: |
| | | * |
| | | * @code |
| | | |
| | | NSString *employessFirstNamePath = @collectionKeypath(department.employees, Employee.new, firstName) |
| | | // => @"employees.firstName" |
| | | |
| | | NSString *employessFirstNamePath = @collectionKeypath(Department.new, employees, Employee.new, firstName) |
| | | // => @"employees.firstName" |
| | | |
| | | * @endcode |
| | | * |
| | | */ |
| | | #define collectionKeypath(...) \ |
| | | metamacro_if_eq(3, metamacro_argcount(__VA_ARGS__))(collectionKeypath3(__VA_ARGS__))(collectionKeypath4(__VA_ARGS__)) |
| | | |
| | | #define collectionKeypath3(PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String]) |
| | | |
| | | #define collectionKeypath4(OBJ, PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(OBJ, PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String]) |
| | | |
New file |
| | |
| | | // |
| | | // EXTRuntimeExtensions.h |
| | | // extobjc |
| | | // |
| | | // Created by Justin Spahr-Summers on 2011-03-05. |
| | | // Copyright (C) 2012 Justin Spahr-Summers. |
| | | // Released under the MIT license. |
| | | // |
| | | |
| | | #import <objc/runtime.h> |
| | | |
| | | /** |
| | | * Describes the memory management policy of a property. |
| | | */ |
| | | typedef enum { |
| | | /** |
| | | * The value is assigned. |
| | | */ |
| | | rac_propertyMemoryManagementPolicyAssign = 0, |
| | | |
| | | /** |
| | | * The value is retained. |
| | | */ |
| | | rac_propertyMemoryManagementPolicyRetain, |
| | | |
| | | /** |
| | | * The value is copied. |
| | | */ |
| | | rac_propertyMemoryManagementPolicyCopy |
| | | } rac_propertyMemoryManagementPolicy; |
| | | |
| | | /** |
| | | * Describes the attributes and type information of a property. |
| | | */ |
| | | typedef struct { |
| | | /** |
| | | * Whether this property was declared with the \c readonly attribute. |
| | | */ |
| | | BOOL readonly; |
| | | |
| | | /** |
| | | * Whether this property was declared with the \c nonatomic attribute. |
| | | */ |
| | | BOOL nonatomic; |
| | | |
| | | /** |
| | | * Whether the property is a weak reference. |
| | | */ |
| | | BOOL weak; |
| | | |
| | | /** |
| | | * Whether the property is eligible for garbage collection. |
| | | */ |
| | | BOOL canBeCollected; |
| | | |
| | | /** |
| | | * Whether this property is defined with \c \@dynamic. |
| | | */ |
| | | BOOL dynamic; |
| | | |
| | | /** |
| | | * The memory management policy for this property. This will always be |
| | | * #rac_propertyMemoryManagementPolicyAssign if #readonly is \c YES. |
| | | */ |
| | | rac_propertyMemoryManagementPolicy memoryManagementPolicy; |
| | | |
| | | /** |
| | | * The selector for the getter of this property. This will reflect any |
| | | * custom \c getter= attribute provided in the property declaration, or the |
| | | * inferred getter name otherwise. |
| | | */ |
| | | SEL getter; |
| | | |
| | | /** |
| | | * The selector for the setter of this property. This will reflect any |
| | | * custom \c setter= attribute provided in the property declaration, or the |
| | | * inferred setter name otherwise. |
| | | * |
| | | * @note If #readonly is \c YES, this value will represent what the setter |
| | | * \e would be, if the property were writable. |
| | | */ |
| | | SEL setter; |
| | | |
| | | /** |
| | | * The backing instance variable for this property, or \c NULL if \c |
| | | * \c @synthesize was not used, and therefore no instance variable exists. This |
| | | * would also be the case if the property is implemented dynamically. |
| | | */ |
| | | const char *ivar; |
| | | |
| | | /** |
| | | * If this property is defined as being an instance of a specific class, |
| | | * this will be the class object representing it. |
| | | * |
| | | * This will be \c nil if the property was defined as type \c id, if the |
| | | * property is not of an object type, or if the class could not be found at |
| | | * runtime. |
| | | */ |
| | | Class objectClass; |
| | | |
| | | /** |
| | | * The type encoding for the value of this property. This is the type as it |
| | | * would be returned by the \c \@encode() directive. |
| | | */ |
| | | char type[]; |
| | | } rac_propertyAttributes; |
| | | |
| | | /** |
| | | * Finds the instance method named \a aSelector on \a aClass and returns it, or |
| | | * returns \c NULL if no such instance method exists. Unlike \c |
| | | * class_getInstanceMethod(), this does not search superclasses. |
| | | * |
| | | * @note To get class methods in this manner, use a metaclass for \a aClass. |
| | | */ |
| | | Method rac_getImmediateInstanceMethod (Class aClass, SEL aSelector); |
| | | |
| | | /** |
| | | * Returns a pointer to a structure containing information about \a property. |
| | | * You must \c free() the returned pointer. Returns \c NULL if there is an error |
| | | * obtaining information from \a property. |
| | | */ |
| | | rac_propertyAttributes *rac_copyPropertyAttributes (objc_property_t property); |
New file |
| | |
| | | // |
| | | // EXTRuntimeExtensions.m |
| | | // extobjc |
| | | // |
| | | // Created by Justin Spahr-Summers on 2011-03-05. |
| | | // Copyright (C) 2012 Justin Spahr-Summers. |
| | | // Released under the MIT license. |
| | | // |
| | | |
| | | #import "RACEXTRuntimeExtensions.h" |
| | | |
| | | #import <ctype.h> |
| | | #import <Foundation/Foundation.h> |
| | | #import <libkern/OSAtomic.h> |
| | | #import <objc/message.h> |
| | | #import <pthread.h> |
| | | #import <stdio.h> |
| | | #import <stdlib.h> |
| | | #import <string.h> |
| | | |
| | | rac_propertyAttributes *rac_copyPropertyAttributes (objc_property_t property) { |
| | | const char * const attrString = property_getAttributes(property); |
| | | if (!attrString) { |
| | | fprintf(stderr, "ERROR: Could not get attribute string from property %s\n", property_getName(property)); |
| | | return NULL; |
| | | } |
| | | |
| | | if (attrString[0] != 'T') { |
| | | fprintf(stderr, "ERROR: Expected attribute string \"%s\" for property %s to start with 'T'\n", attrString, property_getName(property)); |
| | | return NULL; |
| | | } |
| | | |
| | | const char *typeString = attrString + 1; |
| | | const char *next = NSGetSizeAndAlignment(typeString, NULL, NULL); |
| | | if (!next) { |
| | | fprintf(stderr, "ERROR: Could not read past type in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); |
| | | return NULL; |
| | | } |
| | | |
| | | size_t typeLength = (size_t)(next - typeString); |
| | | if (!typeLength) { |
| | | fprintf(stderr, "ERROR: Invalid type in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); |
| | | return NULL; |
| | | } |
| | | |
| | | // allocate enough space for the structure and the type string (plus a NUL) |
| | | rac_propertyAttributes *attributes = calloc(1, sizeof(rac_propertyAttributes) + typeLength + 1); |
| | | if (!attributes) { |
| | | fprintf(stderr, "ERROR: Could not allocate rac_propertyAttributes structure for attribute string \"%s\" for property %s\n", attrString, property_getName(property)); |
| | | return NULL; |
| | | } |
| | | |
| | | // copy the type string |
| | | strncpy(attributes->type, typeString, typeLength); |
| | | attributes->type[typeLength] = '\0'; |
| | | |
| | | // if this is an object type, and immediately followed by a quoted string... |
| | | if (typeString[0] == *(@encode(id)) && typeString[1] == '"') { |
| | | // we should be able to extract a class name |
| | | const char *className = typeString + 2; |
| | | next = strchr(className, '"'); |
| | | |
| | | if (!next) { |
| | | fprintf(stderr, "ERROR: Could not read class name in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); |
| | | return NULL; |
| | | } |
| | | |
| | | if (className != next) { |
| | | size_t classNameLength = (size_t)(next - className); |
| | | char trimmedName[classNameLength + 1]; |
| | | |
| | | strncpy(trimmedName, className, classNameLength); |
| | | trimmedName[classNameLength] = '\0'; |
| | | |
| | | // attempt to look up the class in the runtime |
| | | attributes->objectClass = objc_getClass(trimmedName); |
| | | } |
| | | } |
| | | |
| | | if (*next != '\0') { |
| | | // skip past any junk before the first flag |
| | | next = strchr(next, ','); |
| | | } |
| | | |
| | | while (next && *next == ',') { |
| | | char flag = next[1]; |
| | | next += 2; |
| | | |
| | | switch (flag) { |
| | | case '\0': |
| | | break; |
| | | |
| | | case 'R': |
| | | attributes->readonly = YES; |
| | | break; |
| | | |
| | | case 'C': |
| | | attributes->memoryManagementPolicy = rac_propertyMemoryManagementPolicyCopy; |
| | | break; |
| | | |
| | | case '&': |
| | | attributes->memoryManagementPolicy = rac_propertyMemoryManagementPolicyRetain; |
| | | break; |
| | | |
| | | case 'N': |
| | | attributes->nonatomic = YES; |
| | | break; |
| | | |
| | | case 'G': |
| | | case 'S': |
| | | { |
| | | const char *nextFlag = strchr(next, ','); |
| | | SEL name = NULL; |
| | | |
| | | if (!nextFlag) { |
| | | // assume that the rest of the string is the selector |
| | | const char *selectorString = next; |
| | | next = ""; |
| | | |
| | | name = sel_registerName(selectorString); |
| | | } else { |
| | | size_t selectorLength = (size_t)(nextFlag - next); |
| | | if (!selectorLength) { |
| | | fprintf(stderr, "ERROR: Found zero length selector name in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); |
| | | goto errorOut; |
| | | } |
| | | |
| | | char selectorString[selectorLength + 1]; |
| | | |
| | | strncpy(selectorString, next, selectorLength); |
| | | selectorString[selectorLength] = '\0'; |
| | | |
| | | name = sel_registerName(selectorString); |
| | | next = nextFlag; |
| | | } |
| | | |
| | | if (flag == 'G') |
| | | attributes->getter = name; |
| | | else |
| | | attributes->setter = name; |
| | | } |
| | | |
| | | break; |
| | | |
| | | case 'D': |
| | | attributes->dynamic = YES; |
| | | attributes->ivar = NULL; |
| | | break; |
| | | |
| | | case 'V': |
| | | // assume that the rest of the string (if present) is the ivar name |
| | | if (*next == '\0') { |
| | | // if there's nothing there, let's assume this is dynamic |
| | | attributes->ivar = NULL; |
| | | } else { |
| | | attributes->ivar = next; |
| | | next = ""; |
| | | } |
| | | |
| | | break; |
| | | |
| | | case 'W': |
| | | attributes->weak = YES; |
| | | break; |
| | | |
| | | case 'P': |
| | | attributes->canBeCollected = YES; |
| | | break; |
| | | |
| | | case 't': |
| | | fprintf(stderr, "ERROR: Old-style type encoding is unsupported in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); |
| | | |
| | | // skip over this type encoding |
| | | while (*next != ',' && *next != '\0') |
| | | ++next; |
| | | |
| | | break; |
| | | |
| | | default: |
| | | fprintf(stderr, "ERROR: Unrecognized attribute string flag '%c' in attribute string \"%s\" for property %s\n", flag, attrString, property_getName(property)); |
| | | } |
| | | } |
| | | |
| | | if (next && *next != '\0') { |
| | | fprintf(stderr, "Warning: Unparsed data \"%s\" in attribute string \"%s\" for property %s\n", next, attrString, property_getName(property)); |
| | | } |
| | | |
| | | if (!attributes->getter) { |
| | | // use the property name as the getter by default |
| | | attributes->getter = sel_registerName(property_getName(property)); |
| | | } |
| | | |
| | | if (!attributes->setter) { |
| | | const char *propertyName = property_getName(property); |
| | | size_t propertyNameLength = strlen(propertyName); |
| | | |
| | | // we want to transform the name to setProperty: style |
| | | size_t setterLength = propertyNameLength + 4; |
| | | |
| | | char setterName[setterLength + 1]; |
| | | strncpy(setterName, "set", 3); |
| | | strncpy(setterName + 3, propertyName, propertyNameLength); |
| | | |
| | | // capitalize property name for the setter |
| | | setterName[3] = (char)toupper(setterName[3]); |
| | | |
| | | setterName[setterLength - 1] = ':'; |
| | | setterName[setterLength] = '\0'; |
| | | |
| | | attributes->setter = sel_registerName(setterName); |
| | | } |
| | | |
| | | return attributes; |
| | | |
| | | errorOut: |
| | | free(attributes); |
| | | return NULL; |
| | | } |
| | | |
| | | Method rac_getImmediateInstanceMethod (Class aClass, SEL aSelector) { |
| | | unsigned methodCount = 0; |
| | | Method *methods = class_copyMethodList(aClass, &methodCount); |
| | | Method foundMethod = NULL; |
| | | |
| | | for (unsigned methodIndex = 0;methodIndex < methodCount;++methodIndex) { |
| | | if (method_getName(methods[methodIndex]) == aSelector) { |
| | | foundMethod = methods[methodIndex]; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | free(methods); |
| | | return foundMethod; |
| | | } |
New file |
| | |
| | | // |
| | | // EXTScope.h |
| | | // extobjc |
| | | // |
| | | // Created by Justin Spahr-Summers on 2011-05-04. |
| | | // Copyright (C) 2012 Justin Spahr-Summers. |
| | | // Released under the MIT license. |
| | | // |
| | | |
| | | #import "RACmetamacros.h" |
| | | |
| | | /** |
| | | * \@onExit defines some code to be executed when the current scope exits. The |
| | | * code must be enclosed in braces and terminated with a semicolon, and will be |
| | | * executed regardless of how the scope is exited, including from exceptions, |
| | | * \c goto, \c return, \c break, and \c continue. |
| | | * |
| | | * Provided code will go into a block to be executed later. Keep this in mind as |
| | | * it pertains to memory management, restrictions on assignment, etc. Because |
| | | * the code is used within a block, \c return is a legal (though perhaps |
| | | * confusing) way to exit the cleanup block early. |
| | | * |
| | | * Multiple \@onExit statements in the same scope are executed in reverse |
| | | * lexical order. This helps when pairing resource acquisition with \@onExit |
| | | * statements, as it guarantees teardown in the opposite order of acquisition. |
| | | * |
| | | * @note This statement cannot be used within scopes defined without braces |
| | | * (like a one line \c if). In practice, this is not an issue, since \@onExit is |
| | | * a useless construct in such a case anyways. |
| | | */ |
| | | #define onExit \ |
| | | rac_keywordify \ |
| | | __strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^ |
| | | |
| | | /** |
| | | * Creates \c __weak shadow variables for each of the variables provided as |
| | | * arguments, which can later be made strong again with #strongify. |
| | | * |
| | | * This is typically used to weakly reference variables in a block, but then |
| | | * ensure that the variables stay alive during the actual execution of the block |
| | | * (if they were live upon entry). |
| | | * |
| | | * See #strongify for an example of usage. |
| | | */ |
| | | #define weakify(...) \ |
| | | rac_keywordify \ |
| | | metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__) |
| | | |
| | | /** |
| | | * Like #weakify, but uses \c __unsafe_unretained instead, for targets or |
| | | * classes that do not support weak references. |
| | | */ |
| | | #define unsafeify(...) \ |
| | | rac_keywordify \ |
| | | metamacro_foreach_cxt(rac_weakify_,, __unsafe_unretained, __VA_ARGS__) |
| | | |
| | | /** |
| | | * Strongly references each of the variables provided as arguments, which must |
| | | * have previously been passed to #weakify. |
| | | * |
| | | * The strong references created will shadow the original variable names, such |
| | | * that the original names can be used without issue (and a significantly |
| | | * reduced risk of retain cycles) in the current scope. |
| | | * |
| | | * @code |
| | | |
| | | id foo = [[NSObject alloc] init]; |
| | | id bar = [[NSObject alloc] init]; |
| | | |
| | | @weakify(foo, bar); |
| | | |
| | | // this block will not keep 'foo' or 'bar' alive |
| | | BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){ |
| | | // but now, upon entry, 'foo' and 'bar' will stay alive until the block has |
| | | // finished executing |
| | | @strongify(foo, bar); |
| | | |
| | | return [foo isEqual:obj] || [bar isEqual:obj]; |
| | | }; |
| | | |
| | | * @endcode |
| | | */ |
| | | #define strongify(...) \ |
| | | rac_keywordify \ |
| | | _Pragma("clang diagnostic push") \ |
| | | _Pragma("clang diagnostic ignored \"-Wshadow\"") \ |
| | | metamacro_foreach(rac_strongify_,, __VA_ARGS__) \ |
| | | _Pragma("clang diagnostic pop") |
| | | |
| | | /*** implementation details follow ***/ |
| | | typedef void (^rac_cleanupBlock_t)(); |
| | | |
| | | static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t *block) { |
| | | (*block)(); |
| | | } |
| | | |
| | | #define rac_weakify_(INDEX, CONTEXT, VAR) \ |
| | | CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR); |
| | | |
| | | #define rac_strongify_(INDEX, VAR) \ |
| | | __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_); |
| | | |
| | | // Details about the choice of backing keyword: |
| | | // |
| | | // The use of @try/@catch/@finally can cause the compiler to suppress |
| | | // return-type warnings. |
| | | // The use of @autoreleasepool {} is not optimized away by the compiler, |
| | | // resulting in superfluous creation of autorelease pools. |
| | | // |
| | | // Since neither option is perfect, and with no other alternatives, the |
| | | // compromise is to use @autorelease in DEBUG builds to maintain compiler |
| | | // analysis, and to use @try/@catch otherwise to avoid insertion of unnecessary |
| | | // autorelease pools. |
| | | #if DEBUG |
| | | #define rac_keywordify autoreleasepool {} |
| | | #else |
| | | #define rac_keywordify try {} @catch (...) {} |
| | | #endif |
New file |
| | |
| | | /** |
| | | * Macros for metaprogramming |
| | | * ExtendedC |
| | | * |
| | | * Copyright (C) 2012 Justin Spahr-Summers |
| | | * Released under the MIT license |
| | | */ |
| | | |
| | | #ifndef EXTC_METAMACROS_H |
| | | #define EXTC_METAMACROS_H |
| | | |
| | | /** |
| | | * Executes one or more expressions (which may have a void type, such as a call |
| | | * to a function that returns no value) and always returns true. |
| | | */ |
| | | #define metamacro_exprify(...) \ |
| | | ((__VA_ARGS__), true) |
| | | |
| | | /** |
| | | * Returns a string representation of VALUE after full macro expansion. |
| | | */ |
| | | #define metamacro_stringify(VALUE) \ |
| | | metamacro_stringify_(VALUE) |
| | | |
| | | /** |
| | | * Returns A and B concatenated after full macro expansion. |
| | | */ |
| | | #define metamacro_concat(A, B) \ |
| | | metamacro_concat_(A, B) |
| | | |
| | | /** |
| | | * Returns the Nth variadic argument (starting from zero). At least |
| | | * N + 1 variadic arguments must be given. N must be between zero and twenty, |
| | | * inclusive. |
| | | */ |
| | | #define metamacro_at(N, ...) \ |
| | | metamacro_concat(metamacro_at, N)(__VA_ARGS__) |
| | | |
| | | /** |
| | | * Returns the number of arguments (up to twenty) provided to the macro. At |
| | | * least one argument must be provided. |
| | | * |
| | | * Inspired by P99: http://p99.gforge.inria.fr |
| | | */ |
| | | #define metamacro_argcount(...) \ |
| | | metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) |
| | | |
| | | /** |
| | | * Identical to #metamacro_foreach_cxt, except that no CONTEXT argument is |
| | | * given. Only the index and current argument will thus be passed to MACRO. |
| | | */ |
| | | #define metamacro_foreach(MACRO, SEP, ...) \ |
| | | metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__) |
| | | |
| | | /** |
| | | * For each consecutive variadic argument (up to twenty), MACRO is passed the |
| | | * zero-based index of the current argument, CONTEXT, and then the argument |
| | | * itself. The results of adjoining invocations of MACRO are then separated by |
| | | * SEP. |
| | | * |
| | | * Inspired by P99: http://p99.gforge.inria.fr |
| | | */ |
| | | #define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \ |
| | | metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__) |
| | | |
| | | /** |
| | | * Identical to #metamacro_foreach_cxt. This can be used when the former would |
| | | * fail due to recursive macro expansion. |
| | | */ |
| | | #define metamacro_foreach_cxt_recursive(MACRO, SEP, CONTEXT, ...) \ |
| | | metamacro_concat(metamacro_foreach_cxt_recursive, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__) |
| | | |
| | | /** |
| | | * In consecutive order, appends each variadic argument (up to twenty) onto |
| | | * BASE. The resulting concatenations are then separated by SEP. |
| | | * |
| | | * This is primarily useful to manipulate a list of macro invocations into instead |
| | | * invoking a different, possibly related macro. |
| | | */ |
| | | #define metamacro_foreach_concat(BASE, SEP, ...) \ |
| | | metamacro_foreach_cxt(metamacro_foreach_concat_iter, SEP, BASE, __VA_ARGS__) |
| | | |
| | | /** |
| | | * Iterates COUNT times, each time invoking MACRO with the current index |
| | | * (starting at zero) and CONTEXT. The results of adjoining invocations of MACRO |
| | | * are then separated by SEP. |
| | | * |
| | | * COUNT must be an integer between zero and twenty, inclusive. |
| | | */ |
| | | #define metamacro_for_cxt(COUNT, MACRO, SEP, CONTEXT) \ |
| | | metamacro_concat(metamacro_for_cxt, COUNT)(MACRO, SEP, CONTEXT) |
| | | |
| | | /** |
| | | * Returns the first argument given. At least one argument must be provided. |
| | | * |
| | | * This is useful when implementing a variadic macro, where you may have only |
| | | * one variadic argument, but no way to retrieve it (for example, because \c ... |
| | | * always needs to match at least one argument). |
| | | * |
| | | * @code |
| | | |
| | | #define varmacro(...) \ |
| | | metamacro_head(__VA_ARGS__) |
| | | |
| | | * @endcode |
| | | */ |
| | | #define metamacro_head(...) \ |
| | | metamacro_head_(__VA_ARGS__, 0) |
| | | |
| | | /** |
| | | * Returns every argument except the first. At least two arguments must be |
| | | * provided. |
| | | */ |
| | | #define metamacro_tail(...) \ |
| | | metamacro_tail_(__VA_ARGS__) |
| | | |
| | | /** |
| | | * Returns the first N (up to twenty) variadic arguments as a new argument list. |
| | | * At least N variadic arguments must be provided. |
| | | */ |
| | | #define metamacro_take(N, ...) \ |
| | | metamacro_concat(metamacro_take, N)(__VA_ARGS__) |
| | | |
| | | /** |
| | | * Removes the first N (up to twenty) variadic arguments from the given argument |
| | | * list. At least N variadic arguments must be provided. |
| | | */ |
| | | #define metamacro_drop(N, ...) \ |
| | | metamacro_concat(metamacro_drop, N)(__VA_ARGS__) |
| | | |
| | | /** |
| | | * Decrements VAL, which must be a number between zero and twenty, inclusive. |
| | | * |
| | | * This is primarily useful when dealing with indexes and counts in |
| | | * metaprogramming. |
| | | */ |
| | | #define metamacro_dec(VAL) \ |
| | | metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19) |
| | | |
| | | /** |
| | | * Increments VAL, which must be a number between zero and twenty, inclusive. |
| | | * |
| | | * This is primarily useful when dealing with indexes and counts in |
| | | * metaprogramming. |
| | | */ |
| | | #define metamacro_inc(VAL) \ |
| | | metamacro_at(VAL, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21) |
| | | |
| | | /** |
| | | * If A is equal to B, the next argument list is expanded; otherwise, the |
| | | * argument list after that is expanded. A and B must be numbers between zero |
| | | * and twenty, inclusive. Additionally, B must be greater than or equal to A. |
| | | * |
| | | * @code |
| | | |
| | | // expands to true |
| | | metamacro_if_eq(0, 0)(true)(false) |
| | | |
| | | // expands to false |
| | | metamacro_if_eq(0, 1)(true)(false) |
| | | |
| | | * @endcode |
| | | * |
| | | * This is primarily useful when dealing with indexes and counts in |
| | | * metaprogramming. |
| | | */ |
| | | #define metamacro_if_eq(A, B) \ |
| | | metamacro_concat(metamacro_if_eq, A)(B) |
| | | |
| | | /** |
| | | * Identical to #metamacro_if_eq. This can be used when the former would fail |
| | | * due to recursive macro expansion. |
| | | */ |
| | | #define metamacro_if_eq_recursive(A, B) \ |
| | | metamacro_concat(metamacro_if_eq_recursive, A)(B) |
| | | |
| | | /** |
| | | * Returns 1 if N is an even number, or 0 otherwise. N must be between zero and |
| | | * twenty, inclusive. |
| | | * |
| | | * For the purposes of this test, zero is considered even. |
| | | */ |
| | | #define metamacro_is_even(N) \ |
| | | metamacro_at(N, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1) |
| | | |
| | | /** |
| | | * Returns the logical NOT of B, which must be the number zero or one. |
| | | */ |
| | | #define metamacro_not(B) \ |
| | | metamacro_at(B, 1, 0) |
| | | |
| | | // IMPLEMENTATION DETAILS FOLLOW! |
| | | // Do not write code that depends on anything below this line. |
| | | #define metamacro_stringify_(VALUE) # VALUE |
| | | #define metamacro_concat_(A, B) A ## B |
| | | #define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG) |
| | | #define metamacro_head_(FIRST, ...) FIRST |
| | | #define metamacro_tail_(FIRST, ...) __VA_ARGS__ |
| | | #define metamacro_consume_(...) |
| | | #define metamacro_expand_(...) __VA_ARGS__ |
| | | |
| | | // implemented from scratch so that metamacro_concat() doesn't end up nesting |
| | | #define metamacro_foreach_concat_iter(INDEX, BASE, ARG) metamacro_foreach_concat_iter_(BASE, ARG) |
| | | #define metamacro_foreach_concat_iter_(BASE, ARG) BASE ## ARG |
| | | |
| | | // metamacro_at expansions |
| | | #define metamacro_at0(...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__) |
| | | |
| | | // metamacro_foreach_cxt expansions |
| | | #define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT) |
| | | #define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0) |
| | | |
| | | #define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \ |
| | | metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \ |
| | | SEP \ |
| | | MACRO(1, CONTEXT, _1) |
| | | |
| | | #define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \ |
| | | metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \ |
| | | SEP \ |
| | | MACRO(2, CONTEXT, _2) |
| | | |
| | | #define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ |
| | | metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \ |
| | | SEP \ |
| | | MACRO(3, CONTEXT, _3) |
| | | |
| | | #define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ |
| | | metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ |
| | | SEP \ |
| | | MACRO(4, CONTEXT, _4) |
| | | |
| | | #define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ |
| | | metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ |
| | | SEP \ |
| | | MACRO(5, CONTEXT, _5) |
| | | |
| | | #define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ |
| | | metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ |
| | | SEP \ |
| | | MACRO(6, CONTEXT, _6) |
| | | |
| | | #define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ |
| | | metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ |
| | | SEP \ |
| | | MACRO(7, CONTEXT, _7) |
| | | |
| | | #define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ |
| | | metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ |
| | | SEP \ |
| | | MACRO(8, CONTEXT, _8) |
| | | |
| | | #define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ |
| | | metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ |
| | | SEP \ |
| | | MACRO(9, CONTEXT, _9) |
| | | |
| | | #define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ |
| | | metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ |
| | | SEP \ |
| | | MACRO(10, CONTEXT, _10) |
| | | |
| | | #define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ |
| | | metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ |
| | | SEP \ |
| | | MACRO(11, CONTEXT, _11) |
| | | |
| | | #define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ |
| | | metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ |
| | | SEP \ |
| | | MACRO(12, CONTEXT, _12) |
| | | |
| | | #define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ |
| | | metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ |
| | | SEP \ |
| | | MACRO(13, CONTEXT, _13) |
| | | |
| | | #define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ |
| | | metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ |
| | | SEP \ |
| | | MACRO(14, CONTEXT, _14) |
| | | |
| | | #define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ |
| | | metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ |
| | | SEP \ |
| | | MACRO(15, CONTEXT, _15) |
| | | |
| | | #define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ |
| | | metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ |
| | | SEP \ |
| | | MACRO(16, CONTEXT, _16) |
| | | |
| | | #define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ |
| | | metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ |
| | | SEP \ |
| | | MACRO(17, CONTEXT, _17) |
| | | |
| | | #define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ |
| | | metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ |
| | | SEP \ |
| | | MACRO(18, CONTEXT, _18) |
| | | |
| | | #define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ |
| | | metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ |
| | | SEP \ |
| | | MACRO(19, CONTEXT, _19) |
| | | |
| | | // metamacro_foreach_cxt_recursive expansions |
| | | #define metamacro_foreach_cxt_recursive0(MACRO, SEP, CONTEXT) |
| | | #define metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0) |
| | | |
| | | #define metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \ |
| | | metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) \ |
| | | SEP \ |
| | | MACRO(1, CONTEXT, _1) |
| | | |
| | | #define metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \ |
| | | metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \ |
| | | SEP \ |
| | | MACRO(2, CONTEXT, _2) |
| | | |
| | | #define metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ |
| | | metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \ |
| | | SEP \ |
| | | MACRO(3, CONTEXT, _3) |
| | | |
| | | #define metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ |
| | | metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ |
| | | SEP \ |
| | | MACRO(4, CONTEXT, _4) |
| | | |
| | | #define metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ |
| | | metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ |
| | | SEP \ |
| | | MACRO(5, CONTEXT, _5) |
| | | |
| | | #define metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ |
| | | metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ |
| | | SEP \ |
| | | MACRO(6, CONTEXT, _6) |
| | | |
| | | #define metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ |
| | | metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ |
| | | SEP \ |
| | | MACRO(7, CONTEXT, _7) |
| | | |
| | | #define metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ |
| | | metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ |
| | | SEP \ |
| | | MACRO(8, CONTEXT, _8) |
| | | |
| | | #define metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ |
| | | metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ |
| | | SEP \ |
| | | MACRO(9, CONTEXT, _9) |
| | | |
| | | #define metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ |
| | | metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ |
| | | SEP \ |
| | | MACRO(10, CONTEXT, _10) |
| | | |
| | | #define metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ |
| | | metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ |
| | | SEP \ |
| | | MACRO(11, CONTEXT, _11) |
| | | |
| | | #define metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ |
| | | metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ |
| | | SEP \ |
| | | MACRO(12, CONTEXT, _12) |
| | | |
| | | #define metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ |
| | | metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ |
| | | SEP \ |
| | | MACRO(13, CONTEXT, _13) |
| | | |
| | | #define metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ |
| | | metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ |
| | | SEP \ |
| | | MACRO(14, CONTEXT, _14) |
| | | |
| | | #define metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ |
| | | metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ |
| | | SEP \ |
| | | MACRO(15, CONTEXT, _15) |
| | | |
| | | #define metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ |
| | | metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ |
| | | SEP \ |
| | | MACRO(16, CONTEXT, _16) |
| | | |
| | | #define metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ |
| | | metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ |
| | | SEP \ |
| | | MACRO(17, CONTEXT, _17) |
| | | |
| | | #define metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ |
| | | metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ |
| | | SEP \ |
| | | MACRO(18, CONTEXT, _18) |
| | | |
| | | #define metamacro_foreach_cxt_recursive20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ |
| | | metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ |
| | | SEP \ |
| | | MACRO(19, CONTEXT, _19) |
| | | |
| | | // metamacro_for_cxt expansions |
| | | #define metamacro_for_cxt0(MACRO, SEP, CONTEXT) |
| | | #define metamacro_for_cxt1(MACRO, SEP, CONTEXT) MACRO(0, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt2(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt1(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(1, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt3(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt2(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(2, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt4(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt3(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(3, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt5(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt4(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(4, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt6(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt5(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(5, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt7(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt6(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(6, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt8(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt7(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(7, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt9(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt8(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(8, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt10(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt9(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(9, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt11(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt10(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(10, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt12(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt11(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(11, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt13(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt12(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(12, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt14(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt13(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(13, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt15(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt14(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(14, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt16(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt15(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(15, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt17(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt16(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(16, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt18(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt17(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(17, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt19(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt18(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(18, CONTEXT) |
| | | |
| | | #define metamacro_for_cxt20(MACRO, SEP, CONTEXT) \ |
| | | metamacro_for_cxt19(MACRO, SEP, CONTEXT) \ |
| | | SEP \ |
| | | MACRO(19, CONTEXT) |
| | | |
| | | // metamacro_if_eq expansions |
| | | #define metamacro_if_eq0(VALUE) \ |
| | | metamacro_concat(metamacro_if_eq0_, VALUE) |
| | | |
| | | #define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_consume_ |
| | | #define metamacro_if_eq0_1(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_2(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_3(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_4(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_5(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_6(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_7(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_8(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_9(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_10(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_11(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_12(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_13(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_14(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_15(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_16(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_17(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_18(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_19(...) metamacro_expand_ |
| | | #define metamacro_if_eq0_20(...) metamacro_expand_ |
| | | |
| | | #define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq2(VALUE) metamacro_if_eq1(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq3(VALUE) metamacro_if_eq2(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq4(VALUE) metamacro_if_eq3(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq5(VALUE) metamacro_if_eq4(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq6(VALUE) metamacro_if_eq5(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq7(VALUE) metamacro_if_eq6(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq8(VALUE) metamacro_if_eq7(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq9(VALUE) metamacro_if_eq8(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq10(VALUE) metamacro_if_eq9(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq11(VALUE) metamacro_if_eq10(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq12(VALUE) metamacro_if_eq11(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq13(VALUE) metamacro_if_eq12(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq14(VALUE) metamacro_if_eq13(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq15(VALUE) metamacro_if_eq14(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq16(VALUE) metamacro_if_eq15(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq17(VALUE) metamacro_if_eq16(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq18(VALUE) metamacro_if_eq17(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq19(VALUE) metamacro_if_eq18(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq20(VALUE) metamacro_if_eq19(metamacro_dec(VALUE)) |
| | | |
| | | // metamacro_if_eq_recursive expansions |
| | | #define metamacro_if_eq_recursive0(VALUE) \ |
| | | metamacro_concat(metamacro_if_eq_recursive0_, VALUE) |
| | | |
| | | #define metamacro_if_eq_recursive0_0(...) __VA_ARGS__ metamacro_consume_ |
| | | #define metamacro_if_eq_recursive0_1(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_2(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_3(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_4(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_5(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_6(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_7(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_8(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_9(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_10(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_11(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_12(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_13(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_14(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_15(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_16(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_17(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_18(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_19(...) metamacro_expand_ |
| | | #define metamacro_if_eq_recursive0_20(...) metamacro_expand_ |
| | | |
| | | #define metamacro_if_eq_recursive1(VALUE) metamacro_if_eq_recursive0(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive2(VALUE) metamacro_if_eq_recursive1(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive3(VALUE) metamacro_if_eq_recursive2(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive4(VALUE) metamacro_if_eq_recursive3(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive5(VALUE) metamacro_if_eq_recursive4(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive6(VALUE) metamacro_if_eq_recursive5(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive7(VALUE) metamacro_if_eq_recursive6(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive8(VALUE) metamacro_if_eq_recursive7(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive9(VALUE) metamacro_if_eq_recursive8(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive10(VALUE) metamacro_if_eq_recursive9(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive11(VALUE) metamacro_if_eq_recursive10(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive12(VALUE) metamacro_if_eq_recursive11(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive13(VALUE) metamacro_if_eq_recursive12(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive14(VALUE) metamacro_if_eq_recursive13(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive15(VALUE) metamacro_if_eq_recursive14(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive16(VALUE) metamacro_if_eq_recursive15(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive17(VALUE) metamacro_if_eq_recursive16(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive18(VALUE) metamacro_if_eq_recursive17(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive19(VALUE) metamacro_if_eq_recursive18(metamacro_dec(VALUE)) |
| | | #define metamacro_if_eq_recursive20(VALUE) metamacro_if_eq_recursive19(metamacro_dec(VALUE)) |
| | | |
| | | // metamacro_take expansions |
| | | #define metamacro_take0(...) |
| | | #define metamacro_take1(...) metamacro_head(__VA_ARGS__) |
| | | #define metamacro_take2(...) metamacro_head(__VA_ARGS__), metamacro_take1(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take3(...) metamacro_head(__VA_ARGS__), metamacro_take2(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take4(...) metamacro_head(__VA_ARGS__), metamacro_take3(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take5(...) metamacro_head(__VA_ARGS__), metamacro_take4(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take6(...) metamacro_head(__VA_ARGS__), metamacro_take5(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take7(...) metamacro_head(__VA_ARGS__), metamacro_take6(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take8(...) metamacro_head(__VA_ARGS__), metamacro_take7(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take9(...) metamacro_head(__VA_ARGS__), metamacro_take8(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take10(...) metamacro_head(__VA_ARGS__), metamacro_take9(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take11(...) metamacro_head(__VA_ARGS__), metamacro_take10(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take12(...) metamacro_head(__VA_ARGS__), metamacro_take11(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take13(...) metamacro_head(__VA_ARGS__), metamacro_take12(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take14(...) metamacro_head(__VA_ARGS__), metamacro_take13(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take15(...) metamacro_head(__VA_ARGS__), metamacro_take14(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take16(...) metamacro_head(__VA_ARGS__), metamacro_take15(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take17(...) metamacro_head(__VA_ARGS__), metamacro_take16(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take18(...) metamacro_head(__VA_ARGS__), metamacro_take17(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take19(...) metamacro_head(__VA_ARGS__), metamacro_take18(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_take20(...) metamacro_head(__VA_ARGS__), metamacro_take19(metamacro_tail(__VA_ARGS__)) |
| | | |
| | | // metamacro_drop expansions |
| | | #define metamacro_drop0(...) __VA_ARGS__ |
| | | #define metamacro_drop1(...) metamacro_tail(__VA_ARGS__) |
| | | #define metamacro_drop2(...) metamacro_drop1(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop3(...) metamacro_drop2(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop4(...) metamacro_drop3(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop5(...) metamacro_drop4(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop6(...) metamacro_drop5(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop7(...) metamacro_drop6(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop8(...) metamacro_drop7(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop9(...) metamacro_drop8(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop10(...) metamacro_drop9(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop11(...) metamacro_drop10(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop12(...) metamacro_drop11(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop13(...) metamacro_drop12(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop14(...) metamacro_drop13(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop15(...) metamacro_drop14(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop16(...) metamacro_drop15(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop17(...) metamacro_drop16(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop18(...) metamacro_drop17(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop19(...) metamacro_drop18(metamacro_tail(__VA_ARGS__)) |
| | | #define metamacro_drop20(...) metamacro_drop19(metamacro_tail(__VA_ARGS__)) |
| | | |
| | | #endif |
New file |
| | |
| | | The MIT License (MIT) |
| | | |
| | | Copyright (c) 2015 GSD_iOS |
| | | |
| | | 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: |
| | | |
| | | The above copyright notice and this permission notice shall be included in all |
| | | copies or substantial portions of the Software. |
| | | |
| | | 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. |
| | | |
New file |
| | |
| | | # SDAutoLayoutï¼ä¸è¡ä»£ç æå®èªå¨å¸å±ï¼ï¼ |
| | | |
| | | ##ä¼å¤å
¬å¸å个人å¼åè
å·²ç»ä½¿ç¨æ¬åºå¸å±ï¼ |
| | | SDAutoLayout使ç¨è
å¼åçé¨åappæªå¾ http://www.jianshu.com/p/9bc04d3effb8 |
| | | |
| | | ä¸è¡ä»£ç æå®èªå¨å¸å±ï¼è´åäºåæç®åæç¨çAutolayoutåºãThe most easy way for autolayout. |
| | | |
| | | ## ææ¯æ¯æ(QQ交æµç¾¤)ï¼ |
| | | |
| | | 497140713ï¼1ç¾¤ï¼ 519489682ï¼2ç¾¤å·²æ»¡ï¼ |
| | | |
| | | ##Podæ¯æï¼ |
| | | |
| | | æ¯æpodï¼ pod 'SDAutoLayout', '~> 2.1.3' |
| | | |
| | | 妿åç°pod search SDAutoLayout æç´¢åºæ¥ç䏿¯ææ°çæ¬ï¼éè¦å¨ç»ç«¯æ§è¡cdè½¬æ¢æä»¶è·¯å¾å½ä»¤éåå°desktopï¼ç¶åæ§è¡pod setupå½ä»¤æ´æ°æ¬å°specç¼åï¼å¯è½éè¦å åéï¼ï¼ç¶ååæç´¢å°±å¯ä»¥äº |
| | | |
| | | ## æ´æ°è®°å½ï¼ |
| | | |
| | | 2017.06.26 -- è§£å³é¨åå¼åè
ååºå åºç°âUITableViewCellContentViewâè导è´åºç¨å®¡æ ¸è¢«æé®é¢ |
| | | |
| | | 2016.08.12 -- å®ç°å¨tableviewæå
¥æ°çcellæ°æ®æ¶èªå¨ç¼å管ç |
| | | |
| | | 2016.06.30 -- å¢å å¤åç
§viewçleftSpaceToViewåtopSpaceToView约æï¼ä¾ï¼topSpaceToView(@[self.view3, self.view4], 30) |
| | | |
| | | 2016.06.24 -- ä¿®å¤ç»buttonè®¾ç½®çº¦ææ¶å¨iOS8.xç³»ç»ä¸åºç°çå´©æºé®é¢ï¼åå¸2.1.2çæ¬ |
| | | |
| | | 2016.06.23 -- å®ç°å 餿è¡cellæ¶èªå¨è°æ´heightç¼å |
| | | |
| | | 2016.05.16 -- ä¿®å¤ç¨xibçæçviewåºç°çé¨å约æå¤±æé®é¢ï¼åå¸pod2.0.0çæ¬ï¼ |
| | | |
| | | 2016.05.15 -- å¢å 设置åç§»éoffsetåè½ |
| | | |
| | | 2016.04.30 -- ä¿®å¤ä¹åbuttonä½ä¸ºç¶è§å¾æ¶å
鍿§ä»¶ä¸è½èªå¨å¸å±é®é¢ |
| | | |
| | | 2016.04.05 -- ä¿®å¤å®½åº¦èªéåºlabelå¨éç¨æ¶åå¶å°åºç°ç宽度计ç®ä¸åç¡®çé®é¢ï¼åå¸pod1.51çæ¬ï¼ |
| | | |
| | | 2016.03.23 -- å级äºç¼åæºå¶ï¼æ°çæ¬å¨tableviewæ»å¨cellæ¶åæµç
åº¦åæ§è½æå20%以ä¸ï¼åå¸pod1.50çæ¬ï¼ |
| | | |
| | | 2016.01.23 -- å¢å label对attributedStringçå
容èªéåº |
| | | |
| | | 2016.01.21 -- å®ç°tableviewå±é¨å·æ°cellé«åº¦ç¼åçèªå¨ç®¡ç |
| | | |
| | | 2016.01.20 -- demoéé
å¨ios7ä¸çå±å¹æè½¬é®é¢ |
| | | |
| | | 2016.01.18 -- æ¨åºâæ®éç®åçâtableviewçcellèªå¨é«åº¦æ¹æ³ï¼æ¨è使ç¨ï¼ï¼åæ¥çé2æ¥è®¾ç½®çæ®éçæ¹æ³å°æ è®°è¿æ |
| | | |
| | | 2016.01.13 -- å¢å å¨ä¸ç¡®å®bottom viewçæ
åµä¸çcellé«åº¦èªéåºæ¹æ³ |
| | | |
| | | 2016.01.07 -- 1.å¢å scrollview 横åå
容èªéåºåè½ï¼2.å¢å view宽é«ç¸ççåè½ |
| | | |
| | | 2016.01.03 -- å¢å ä»»ä½ç±»å对象é½å¯ä»¥å®ç°ä¸è¡ä»£ç æå®cellé«åº¦èªéåºï¼å¢å ææ¡£æ³¨é |
| | | |
| | | 2015.12.08 -- é大å级ï¼1.æ¯æscrollviewå
容èªéåºï¼2.ä»»ææ·»å æè
ä¿®æ¹çº¦æä¸å²çªï¼3.æ§è½æå40%以ä¸ï¼4.æ·»å æå¤§ãæå°å®½é«çº¦æ |
| | | |
| | | |
| | | ## è§é¢æç¨ï¼ |
| | | ââ SDAutoLayout åºç¡çè§é¢æç¨ï¼http://www.letv.com/ptv/vplay/24038772.html ââ |
| | | |
| | | ââ SDAutoLayout è¿é¶çè§é¢æç¨ï¼http://www.letv.com/ptv/vplay/24381390.html ââ |
| | | |
| | | ââ SDAutoLayout åçç®ä»è§é¢æç¨ï¼http://www.iqiyi.com/w_19rt0tec4p.html ââ |
| | | |
| | | ââ SDAutoLayout æåådemoè§é¢æç¨ï¼http://v.youku.com/v_show/id_XMTYzNzg2NzA0MA==.html ââ |
| | | |
| | | ## é¨åSDAutoLayoutçDEMOï¼ |
| | | |
| | | 宿´å¾®ä¿¡Demo https://github.com/gsdios/GSD_WeiXin |
| | | |
| | |  |
| | | |
| | | |
| | | |
| | | # ç¨æ³ç®ä» |
| | | |
| | | ## tableviewåcellé«åº¦èªéåºï¼ |
| | | |
| | | ####æ®éï¼ç®åï¼çãæ¨è使ç¨ãï¼tableview é«åº¦èªéåºè®¾ç½®åªéè¦2æ¥ |
| | | |
| | | 1. >> 设置cellé«åº¦èªéåºï¼ |
| | | // cellå¸å±è®¾ç½®å¥½ä¹åè°ç¨æ¤æ¹æ³å°±å¯ä»¥å®ç°é«åº¦èªéåºï¼æ³¨æï¼å¦æç¨é«åº¦èªéåºåä¸è¦å以cellçåºè¾¹ä¸ºåç
§å»å¸å±å
¶åviewï¼ |
| | | [cell setupAutoHeightWithBottomView:_view4 bottomMargin:10]; |
| | | |
| | | 2. >> è·åèªå¨è®¡ç®åºçcellé«åº¦ |
| | | |
| | | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath |
| | | { |
| | | id model = self.modelsArray[indexPath.row]; |
| | | // è·åcellé«åº¦ |
| | | return [self.tableView cellHeightForIndexPath:indexPath model:model keyPath:@"model" cellClass:[DemoVC9Cell class] contentViewWidth:cellContentViewWith]; |
| | | } |
| | | |
| | | |
| | | ####å级çï¼éåºäºcellæ¡æ°å°äº100çtableviewï¼ï¼tableview é«åº¦èªéåºè®¾ç½®åªéè¦2æ¥ |
| | | |
| | | 1. >> 设置cellé«åº¦èªéåºï¼ |
| | | // cellå¸å±è®¾ç½®å¥½ä¹åè°ç¨æ¤æ¹æ³å°±å¯ä»¥å®ç°é«åº¦èªéåºï¼æ³¨æï¼å¦æç¨é«åº¦èªéåºåä¸è¦å以cellçåºè¾¹ä¸ºåç
§å»å¸å±å
¶åviewï¼ |
| | | [cell setupAutoHeightWithBottomView:_view4 bottomMargin:10]; |
| | | |
| | | 2. >> è·åèªå¨è®¡ç®åºçcellé«åº¦ |
| | | |
| | | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath |
| | | { |
| | | // è·åcellé«åº¦ |
| | | return [self cellHeightForIndexPath:indexPath cellContentViewWidth:[UIScreen mainScreen].bounds.size.width]; |
| | | } |
| | | |
| | | |
| | | ## æ®éviewçèªå¨å¸å±ï¼ |
| | | |
| | | #### ç¨æ³ç¤ºä¾ |
| | | /* ç¨æ³ä¸ */ |
| | | _view.sd_layout |
| | | .leftSpaceToView(self.view, 10) |
| | | .topSpaceToView(self.view, 80) |
| | | .heightIs(130) |
| | | .widthRatioToView(self.view, 0.4); |
| | | |
| | | /* ç¨æ³äº ï¼ä¸è¡ä»£ç æå®ï¼å
¶å®ç¨æ³ä¸ä¹æ¯ä¸è¡ä»£ç ï¼ */ |
| | | _view.sd_layout.leftSpaceToView(self.view, 10).topSpaceToView(self.view,80).heightIs(130).widthRatioToView(self.view, 0.4); |
| | | |
| | | |
| | | >> UILabelæåèªéåºï¼ |
| | | // autoHeightRatio() ä¼ 0åæ ¹æ®æåèªå¨è®¡ç®é«åº¦ï¼ä¼ 大äº0çå¼åæ ¹æ®æ¤æ°å¼è®¾ç½®é«åº¦åå®½åº¦çæ¯å¼ï¼ |
| | | _label.sd_layout.autoHeightRatio(0); |
| | | |
| | | ******************************************************************************* |
| | | |
| | | 注æ:å
æéè¦èªå¨å¸å±çviewå å
¥ç¶viewç¶åå¨è¿è¡èªå¨å¸å±ï¼ä¾ï¼ |
| | | |
| | | UIView *view0 = [UIView new]; |
| | | UIView *view1 = [UIView new]; |
| | | [self.view addSubview:view0]; |
| | | [self.view addSubview:view1]; |
| | | |
| | | view0.sd_layout |
| | | .leftSpaceToView(self.view, 10) |
| | | .topSpaceToView(self.view, 80) |
| | | .heightIs(100) |
| | | .widthRatioToView(self.view, 0.4); |
| | | |
| | | view1.sd_layout |
| | | .leftSpaceToView(view0, 10) |
| | | .topEqualToView(view0) |
| | | .heightRatioToView(view0, 1) |
| | | .rightSpaceToView(self.view, 10); |
| | | ******************************************************************************* |
| | | |
| | | #### èªå¨å¸å±ç¨æ³ç®æ |
| | | |
| | |  |
| | | |
| | | |
| | | 1.1 > leftSpaceToView(self.view, 10) |
| | | |
| | | æ¹æ³åä¸å¸¦æâSpaceToViewâçæ¹æ³è¡¨ç¤ºå°æä¸ªåç
§viewçé´è·ï¼éè¦ä¼ é2ä¸ªåæ°ï¼ï¼UIViewï¼åç
§view å ï¼CGFloatï¼é´è·æ°å¼ |
| | | |
| | | 1.2 > widthRatioToView(self.view, 1) |
| | | |
| | | æ¹æ³åä¸å¸¦æâRatioToViewâçæ¹æ³è¡¨ç¤ºviewç宽度æè
é«åº¦ç屿§ç¸å¯¹äºåç
§viewç对åºå±æ§å¼çæ¯ä¾ï¼éè¦ä¼ é2ä¸ªåæ°ï¼ï¼UIViewï¼åç
§view å ï¼CGFloatï¼åæ° |
| | | |
| | | 1.3 > topEqualToView(view) |
| | | |
| | | æ¹æ³åä¸å¸¦æâEqualToViewâçæ¹æ³è¡¨ç¤ºviewçæä¸å±æ§çäºåç
§viewç对åºç屿§å¼ï¼éè¦ä¼ é1ä¸ªåæ°ï¼ï¼UIViewï¼åç
§view |
| | | |
| | | 1.4 > widthIs(100) |
| | | |
| | | æ¹æ³åä¸å¸¦æâIsâçæ¹æ³è¡¨ç¤ºviewçæä¸å±æ§å¼çäºåæ°æ°å¼ï¼éè¦ä¼ é1ä¸ªåæ°ï¼ï¼CGFloatï¼æ°å¼ |
| | | |
| | | # PS |
| | | |
| | | // 妿éè¦ç¨âæè¨âè°è¯ç¨åºè¯·æå¼æ¤å®(ä½äºUIView+SDAutoLayout.h) |
| | | |
| | | //#define SDDebugWithAssert |
| | | |
| | | |
| | |  |
| | | |
| | | |
| | |  |
| | | |
| | |  |
New file |
| | |
| | | // |
| | | // SDAutoLayout.h |
| | | // SDAutoLayoutDemo |
| | | // |
| | | // Created by gsd on 16/6/27. |
| | | // Copyright © 2016年 gsd. All rights reserved. |
| | | // |
| | | |
| | | |
| | | /* |
| | | |
| | | SDAutoLayout |
| | | çæ¬ï¼2.1.7 |
| | | åå¸ï¼2016.08.12 |
| | | |
| | | */ |
| | | |
| | | #ifndef SDAutoLayout_h |
| | | #define SDAutoLayout_h |
| | | |
| | | #import "UIView+SDAutoLayout.h" |
| | | #import "UITableView+SDAutoTableViewCellHeight.h" |
| | | |
| | | #endif |
New file |
| | |
| | | // |
| | | // UITableView+SDAutoTableViewCellHeight.h |
| | | // SDAutoLayout æµè¯ Demo |
| | | // |
| | | // Created by aier on 15/11/1. |
| | | // Copyright © 2015年 gsd. All rights reserved. |
| | | // |
| | | |
| | | /* |
| | | |
| | | ********************************************************************************* |
| | | * * |
| | | * 卿¨ä½¿ç¨æ¤èªå¨å¸å±åºçè¿ç¨ä¸å¦æåºç°bugè¯·åæ¶ä»¥ä»¥ä¸ä»»æä¸ç§æ¹å¼èç³»æä»¬ï¼æä»¬ä¼åæ¶ä¿®å¤bugå¹¶ * |
| | | * 帮æ¨è§£å³é®é¢ã * |
| | | * QQ : 2689718696(gsdios) * |
| | | * Email : gsdios@126.com * |
| | | * GitHub: https://github.com/gsdios * |
| | | * æ°æµªå¾®å:GSD_iOS * |
| | | * * |
| | | ********************************************************************************* |
| | | |
| | | */ |
| | | |
| | | |
| | | |
| | | |
| | | /* |
| | | PS:cellé«åº¦èªéåºåæ>>åºè¯¥è°ç¨cellçâ- (void)setupAutoHeightWithBottomView:(UIView *)bottomView bottomMargin:(CGFloat)bottomMarginâæ¹æ³è¿è¡cellçèªå¨é«åº¦è®¾ç½® |
| | | */ |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | #import "UIView+SDAutoLayout.h" |
| | | |
| | | @class SDCellAutoHeightManager; |
| | | |
| | | typedef void (^AutoCellHeightDataSettingBlock)(UITableViewCell *cell); |
| | | |
| | | #define kSDModelCellTag 199206 |
| | | |
| | | |
| | | |
| | | #pragma mark - UITableView æ¹æ³ï¼è¿åèªå¨è®¡ç®åºçcellé«åº¦ |
| | | |
| | | @interface UITableView (SDAutoTableViewCellHeight) |
| | | |
| | | @property (nonatomic, strong) SDCellAutoHeightManager *cellAutoHeightManager; |
| | | |
| | | |
| | | /** |
| | | * è¿å计ç®åºçcellé«åº¦ï¼æ®éç®åçæ¹æ³ï¼åæ ·åªé䏿¥è®¾ç½®å³å¯å®æï¼(ç¨æ³ï¼åcell详è§demo5ï¼å¤cell详è§demo7) |
| | | * model : cellçæ°æ®æ¨¡åå®ä¾ |
| | | * keyPath : cellçæ°æ®æ¨¡å屿§ç屿§åå符串ï¼å³kvcåçä¸çkeyï¼ |
| | | * cellClass : å½åçindexPath对åºçcellçclass |
| | | * contentViewWidth : cellçcontentViewç宽度 |
| | | */ |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath model:(id)model keyPath:(NSString *)keyPath cellClass:(Class)cellClass contentViewWidth:(CGFloat)contentViewWidth; |
| | | |
| | | /** |
| | | * è¿å计ç®åºçcellé«åº¦ï¼æ®éç®åçæ¹æ³ï¼åæ ·åªé䏿¥è®¾ç½®å³å¯å®æï¼(ç¨æ³ï¼è§DemoVC14) |
| | | * cellClass : å½åçindexPath对åºçcellçclass |
| | | * contentViewWidth : cellçcontentViewç宽度 |
| | | * cellDataSetting : 设置cellæ°æ®çblock |
| | | */ |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath cellClass:(Class)cellClass cellContentViewWidth:(CGFloat)width cellDataSetting:(AutoCellHeightDataSettingBlock)cellDataSetting; |
| | | |
| | | /** å·æ°tableViewä½ä¸æ¸
空ä¹åå·²ç»è®¡ç®å¥½çé«åº¦ç¼åï¼ç¨äºç´æ¥å°æ°æ°æ®æ¼æ¥å¨æ§æ°æ®ä¹åçtableViewå·æ° */ |
| | | - (void)reloadDataWithExistedHeightCache; |
| | | |
| | | /** å·æ°tableViewåæ¶è°æ´å·²ç»è®¡ç®å¥½çé«åº¦ç¼åï¼ç¨äºç´æ¥å°æ°æ°æ®æå¨æ§æ°æ®åé¢çtableViewçå·æ° */ |
| | | - (void)reloadDataWithInsertingDataAtTheBeginingOfSection:(NSInteger)section newDataCount:(NSInteger)count; |
| | | |
| | | /** |
| | | * å·æ°tableViewåæ¶è°æ´å·²ç»è®¡ç®å¥½çé«åº¦ç¼åï¼ç¨äºç´æ¥å°æ°æ°æ®æå¨æ§æ°æ®åé¢çtableViewçå·æ°(ç¨äºå·æ°å¤ä¸ªsection) |
| | | * sectionNumsArray : è¦å·æ°çææsectionåºå·ç»æçæ°ç», ä¾@[@(0), @(1)] |
| | | * dataCountsArray : æ¯ä¸ªsectionçæ°æ®æ¡æ°ç»æçæ°ç», ä¾@[@(20), @(10)] |
| | | */ |
| | | - (void)reloadDataWithInsertingDataAtTheBeginingOfSections:(NSArray *)sectionNumsArray newDataCounts:(NSArray *)dataCountsArray; |
| | | |
| | | /** è¿åææcellçé«åº¦æ»å */ |
| | | - (CGFloat)cellsTotalHeight; |
| | | |
| | | @property (nonatomic, copy) AutoCellHeightDataSettingBlock cellDataSetting; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | |
| | | #pragma mark - UITableViewController æ¹æ³ï¼è¿åèªå¨è®¡ç®åºçcellé«åº¦ |
| | | |
| | | @interface UITableViewController (SDTableViewControllerAutoCellHeight) |
| | | |
| | | /** (UITableViewControlleræ¹æ³)å级çï¼ä¸è¡ä»£ç ï¼ä¸æ¥è®¾ç½®ï¼æå®tableviewçcellé«åº¦èªéåº,åæ¶éç¨äºåcellåå¤cell,æ§è½æ¯æ®éçç¨å¾®å·®ä¸äº,ä¸å»ºè®®å¨æ°æ®é大çtableviewä¸ä½¿ç¨ */ |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath cellContentViewWidth:(CGFloat)width; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | #pragma mark - NSObject æ¹æ³ï¼è¿åèªå¨è®¡ç®åºçcellé«åº¦ |
| | | |
| | | @interface NSObject (SDAnyObjectAutoCellHeight) |
| | | |
| | | /** (NSObjectæ¹æ³)å级çï¼ä¸è¡ä»£ç ï¼ä¸æ¥è®¾ç½®ï¼æå®tableviewçcellé«åº¦èªéåº,åæ¶éç¨äºåcellåå¤cell,æ§è½æ¯æ®éçç¨å¾®å·®ä¸äº,ä¸å»ºè®®å¨æ°æ®é大çtableviewä¸ä½¿ç¨ */ |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath cellContentViewWidth:(CGFloat)width tableView:(UITableView *)tableView; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | // ------------------------------- 以ä¸ä¸ºåºå
é¨ä½¿ç¨æ é¡»äºè§£ -------------------- |
| | | |
| | | @interface SDCellAutoHeightManager : NSObject |
| | | |
| | | @property (nonatomic, assign) BOOL shouldKeepHeightCacheWhenReloadingData; |
| | | |
| | | @property (nonatomic, assign) CGFloat contentViewWidth; |
| | | |
| | | @property (nonatomic, assign) Class cellClass; |
| | | |
| | | @property (nonatomic, assign) CGFloat cellHeight; |
| | | |
| | | @property (nonatomic, strong) UITableViewCell *modelCell; |
| | | |
| | | @property (nonatomic, strong) NSMutableDictionary *subviewFrameCacheDict; |
| | | |
| | | @property (nonatomic, strong, readonly) NSDictionary *heightCacheDict; |
| | | |
| | | @property (nonatomic, copy) AutoCellHeightDataSettingBlock cellDataSetting; |
| | | |
| | | - (void)clearHeightCache; |
| | | |
| | | - (void)clearHeightCacheOfIndexPaths:(NSArray *)indexPaths; |
| | | |
| | | - (void)deleteThenResetHeightCache:(NSIndexPath *)indexPathToDelete; |
| | | |
| | | - (void)insertNewDataAtTheBeginingOfSection:(NSInteger)section newDataCount:(NSInteger)count; |
| | | |
| | | - (void)insertNewDataAtIndexPaths:(NSArray *)indexPaths; |
| | | |
| | | - (NSNumber *)heightCacheForIndexPath:(NSIndexPath *)indexPath; |
| | | |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath model:(id)model keyPath:(NSString *)keyPath; |
| | | |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath model:(id)model keyPath:(NSString *)keyPath cellClass:(Class)cellClass; |
| | | |
| | | |
| | | - (NSMutableArray *)subviewFrameCachesWithIndexPath:(NSIndexPath *)indexPath;; |
| | | - (void)setSubviewFrameCache:(CGRect)rect WithIndexPath:(NSIndexPath *)indexPath; |
| | | |
| | | - (instancetype)initWithCellClass:(Class)cellClass tableView:(UITableView *)tableView; |
| | | + (instancetype)managerWithCellClass:(Class)cellClass tableView:(UITableView *)tableView; |
| | | @end |
| | | |
New file |
| | |
| | | // |
| | | // UITableView+SDAutoTableViewCellHeight.m |
| | | // SDAutoLayout æµè¯ Demo |
| | | // |
| | | // Created by aier on 15/11/1. |
| | | // Copyright © 2015年 gsd. All rights reserved. |
| | | // |
| | | |
| | | /* |
| | | |
| | | ********************************************************************************* |
| | | * * |
| | | * 卿¨ä½¿ç¨æ¤èªå¨å¸å±åºçè¿ç¨ä¸å¦æåºç°bugè¯·åæ¶ä»¥ä»¥ä¸ä»»æä¸ç§æ¹å¼èç³»æä»¬ï¼æä»¬ä¼åæ¶ä¿®å¤bugå¹¶ * |
| | | * 帮æ¨è§£å³é®é¢ã * |
| | | * QQ : 2689718696(gsdios) * |
| | | * Email : gsdios@126.com * |
| | | * GitHub: https://github.com/gsdios * |
| | | * æ°æµªå¾®å:GSD_iOS * |
| | | * * |
| | | ********************************************************************************* |
| | | |
| | | */ |
| | | |
| | | #import "UITableView+SDAutoTableViewCellHeight.h" |
| | | #import <objc/runtime.h> |
| | | |
| | | @interface SDCellAutoHeightManager () |
| | | |
| | | @property (nonatomic, weak) UITableView *modelTableview; |
| | | |
| | | @end |
| | | |
| | | @implementation SDCellAutoHeightManager |
| | | { |
| | | NSMutableDictionary *_cacheDictionary; |
| | | NSMutableDictionary *_modelCellsDict; |
| | | } |
| | | |
| | | - (instancetype)init |
| | | { |
| | | if (self = [super init]) { |
| | | [self setup]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | - (instancetype)initWithCellClass:(Class)cellClass tableView:(UITableView *)tableView |
| | | { |
| | | if (self = [super init]) { |
| | | [self setup]; |
| | | self.modelTableview = tableView; |
| | | [self registerCellWithCellClass:cellClass]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | - (instancetype)initWithCellClasses:(NSArray *)cellClassArray tableView:(UITableView *)tableView |
| | | { |
| | | if (self = [super init]) { |
| | | [self setup]; |
| | | self.modelTableview = tableView; |
| | | [cellClassArray enumerateObjectsUsingBlock:^(Class obj, NSUInteger idx, BOOL *stop) { |
| | | [self registerCellWithCellClass:obj]; |
| | | }]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | - (void)setup |
| | | { |
| | | _cacheDictionary = [NSMutableDictionary new]; |
| | | _modelCellsDict = [NSMutableDictionary new]; |
| | | } |
| | | |
| | | - (void)registerCellWithCellClass:(Class)cellClass |
| | | { |
| | | [_modelTableview registerClass:cellClass forCellReuseIdentifier:NSStringFromClass(cellClass)]; |
| | | self.modelCell = [_modelTableview dequeueReusableCellWithIdentifier:NSStringFromClass(cellClass)]; |
| | | |
| | | if (!self.modelCell.contentView.subviews.count) { |
| | | NSString *path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@.nib", NSStringFromClass(cellClass)] ofType:nil]; |
| | | if (path) { |
| | | self.modelCell = nil; |
| | | [_modelTableview registerNib:[UINib nibWithNibName:NSStringFromClass(cellClass) bundle:nil] forCellReuseIdentifier:NSStringFromClass(cellClass)]; |
| | | self.modelCell = [_modelTableview dequeueReusableCellWithIdentifier:NSStringFromClass(cellClass)]; |
| | | } |
| | | } |
| | | if (self.modelCell) { |
| | | [_modelCellsDict setObject:self.modelCell forKey:NSStringFromClass(cellClass)]; |
| | | } |
| | | } |
| | | |
| | | + (instancetype)managerWithCellClass:(Class)cellClass tableView:(UITableView *)tableView |
| | | { |
| | | SDCellAutoHeightManager *manager = [[self alloc] initWithCellClass:cellClass tableView:tableView]; |
| | | return manager; |
| | | } |
| | | |
| | | - (UITableViewCell *)modelCell |
| | | { |
| | | if (_modelCell.contentView.tag != kSDModelCellTag) { |
| | | _modelCell.contentView.tag = kSDModelCellTag; |
| | | } |
| | | return _modelCell; |
| | | } |
| | | |
| | | - (NSDictionary *)heightCacheDict |
| | | { |
| | | return _cacheDictionary; |
| | | } |
| | | |
| | | - (void)clearHeightCache |
| | | { |
| | | [_cacheDictionary removeAllObjects]; |
| | | [_subviewFrameCacheDict removeAllObjects]; |
| | | } |
| | | |
| | | - (NSString *)cacheKeyForIndexPath:(NSIndexPath *)indexPath |
| | | { |
| | | return [NSString stringWithFormat:@"%ld-%ld", (long)indexPath.section, (long)indexPath.row]; |
| | | } |
| | | |
| | | - (void)clearHeightCacheOfIndexPaths:(NSArray *)indexPaths |
| | | { |
| | | [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { |
| | | NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; |
| | | [_cacheDictionary removeObjectForKey:cacheKey]; |
| | | [_subviewFrameCacheDict removeObjectForKey:cacheKey]; |
| | | }]; |
| | | } |
| | | |
| | | - (void)deleteThenResetHeightCache:(NSIndexPath *)indexPathToDelete |
| | | { |
| | | |
| | | NSString *cacheKey = [self cacheKeyForIndexPath:indexPathToDelete]; |
| | | [_cacheDictionary removeObjectForKey:cacheKey]; |
| | | [_subviewFrameCacheDict removeObjectForKey:cacheKey]; |
| | | |
| | | long sectionOfToDeleteItem = indexPathToDelete.section; |
| | | long rowOfToDeleteItem = indexPathToDelete.row; |
| | | NSMutableDictionary *tempHeightCacheDict = [NSMutableDictionary new]; |
| | | NSMutableDictionary *tempFrameCacheDict = [NSMutableDictionary new]; |
| | | for (NSString *key in _cacheDictionary.allKeys) { |
| | | NSArray *res = [key componentsSeparatedByString:@"-"]; |
| | | long section = [res.firstObject integerValue]; |
| | | long row = [res.lastObject integerValue]; |
| | | if (section == sectionOfToDeleteItem && row > rowOfToDeleteItem) { |
| | | NSNumber *heightCache = _cacheDictionary[key]; |
| | | NSArray *frameCache = _subviewFrameCacheDict[key]; |
| | | NSString *newKey = [NSString stringWithFormat:@"%ld-%ld", section, (row - 1)]; |
| | | [tempHeightCacheDict setValue:heightCache forKey:newKey]; |
| | | [tempFrameCacheDict setValue:frameCache forKey:newKey]; |
| | | [_cacheDictionary removeObjectForKey:key]; |
| | | [_subviewFrameCacheDict removeObjectForKey:key]; |
| | | } |
| | | } |
| | | [_cacheDictionary addEntriesFromDictionary:tempHeightCacheDict]; |
| | | [_subviewFrameCacheDict addEntriesFromDictionary:tempFrameCacheDict]; |
| | | |
| | | } |
| | | |
| | | - (void)insertNewDataAtTheBeginingOfSection:(NSInteger)section newDataCount:(NSInteger)count |
| | | { |
| | | NSMutableDictionary *tempHeightCacheDict = [NSMutableDictionary new]; |
| | | NSMutableDictionary *tempFrameCacheDict = [NSMutableDictionary new]; |
| | | for (NSString *key in _cacheDictionary.allKeys) { |
| | | NSArray *res = [key componentsSeparatedByString:@"-"]; |
| | | long originalSection = [res.firstObject integerValue]; |
| | | long row = [res.lastObject integerValue]; |
| | | if (originalSection == section) { |
| | | NSNumber *heightCache = _cacheDictionary[key]; |
| | | NSArray *frameCache = _subviewFrameCacheDict[key]; |
| | | NSString *newKey = [NSString stringWithFormat:@"%ld-%ld", originalSection, (row + count)]; |
| | | [tempHeightCacheDict setValue:heightCache forKey:newKey]; |
| | | [tempFrameCacheDict setValue:frameCache forKey:newKey]; |
| | | [_cacheDictionary removeObjectForKey:key]; |
| | | [_subviewFrameCacheDict removeObjectForKey:key]; |
| | | } |
| | | } |
| | | [_cacheDictionary addEntriesFromDictionary:tempHeightCacheDict]; |
| | | [_subviewFrameCacheDict addEntriesFromDictionary:tempFrameCacheDict]; |
| | | } |
| | | |
| | | - (void)insertNewDataAtIndexPaths:(NSArray *)indexPaths |
| | | { |
| | | NSMutableDictionary *sectionsdict = [NSMutableDictionary new]; |
| | | for (NSIndexPath *indexPath in indexPaths) { |
| | | NSString *sectionkey = [@(indexPath.section) stringValue]; |
| | | if (![sectionsdict objectForKey:sectionkey]) { |
| | | [sectionsdict setValue:[NSMutableArray new] forKey:sectionkey]; |
| | | } |
| | | NSMutableArray *arr = sectionsdict[sectionkey]; |
| | | [arr addObject:indexPath]; |
| | | } |
| | | for (NSString *sectionkey in sectionsdict.allKeys) { |
| | | NSMutableArray *tempHeightCaches = [NSMutableArray new]; |
| | | NSMutableArray *tempFrameCaches = [NSMutableArray new]; |
| | | NSInteger section = [sectionkey integerValue]; |
| | | NSInteger rowCount = [self.modelTableview numberOfRowsInSection:section]; |
| | | if (rowCount <= 0) { |
| | | continue; |
| | | } else { |
| | | for (int i = 0; i < rowCount; i++) { |
| | | [tempHeightCaches addObject:[NSNull null]]; |
| | | [tempFrameCaches addObject:[NSNull null]]; |
| | | } |
| | | } |
| | | |
| | | for (NSString *key in _cacheDictionary.allKeys) { |
| | | NSArray *res = [key componentsSeparatedByString:@"-"]; |
| | | long originalSection = [res.firstObject integerValue]; |
| | | long row = [res.lastObject integerValue]; |
| | | if (originalSection == section) { |
| | | NSNumber *heightCache = _cacheDictionary[key]; |
| | | NSArray *frameCache = _subviewFrameCacheDict[key]; |
| | | [tempHeightCaches setObject:heightCache atIndexedSubscript:row]; |
| | | [tempFrameCaches setObject:frameCache atIndexedSubscript:row]; |
| | | [_cacheDictionary removeObjectForKey:key]; |
| | | [_subviewFrameCacheDict removeObjectForKey:key]; |
| | | } |
| | | } |
| | | NSMutableArray *objsToInsert = [NSMutableArray new]; |
| | | NSMutableIndexSet *indexSet = [NSMutableIndexSet new]; |
| | | NSArray *indexPaths = sectionsdict[sectionkey]; |
| | | [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *obj, NSUInteger idx, BOOL *stop) { |
| | | [objsToInsert addObject:[NSNull null]]; |
| | | [indexSet addIndex:obj.row]; |
| | | }]; |
| | | [tempHeightCaches insertObjects:objsToInsert atIndexes:indexSet]; |
| | | [tempFrameCaches insertObjects:objsToInsert atIndexes:indexSet]; |
| | | [tempHeightCaches enumerateObjectsUsingBlock:^(NSNumber *heightCache, NSUInteger idx, BOOL *stop) { |
| | | if (![heightCache isKindOfClass:[NSNull class]]) { |
| | | NSString *key = [NSString stringWithFormat:@"%zd-%zd", section, idx]; |
| | | [_cacheDictionary setValue:heightCache forKey:key]; |
| | | [_subviewFrameCacheDict setValue:[tempFrameCaches objectAtIndex:idx] forKey:key]; |
| | | } |
| | | }]; |
| | | } |
| | | } |
| | | |
| | | - (NSNumber *)heightCacheForIndexPath:(NSIndexPath *)indexPath |
| | | { |
| | | /* |
| | | 妿ç¨åºå¡å¨äºè¿éå¾å¯è½æ¯ç±äºä½ ç¨äºâdequeueReusableCellWithIdentifier:forIndexPath:âæ¹æ³æ¥éç¨cellï¼æ¢æââdequeueReusableCellWithIdentifier:âï¼ä¸å¸¦IndexPathï¼æ¹æ³å³å¯è§£å³ |
| | | */ |
| | | NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; |
| | | return (NSNumber *)[_cacheDictionary objectForKey:cacheKey]; |
| | | } |
| | | |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath model:(id)model keyPath:(NSString *)keyPath |
| | | { |
| | | |
| | | NSNumber *cacheHeight = [self heightCacheForIndexPath:indexPath]; |
| | | if (cacheHeight) { |
| | | return [cacheHeight floatValue]; |
| | | } else { |
| | | if (!self.modelCell) { |
| | | return 0; |
| | | } |
| | | |
| | | if (self.modelTableview && self.modelTableview != self.modelCell.sd_tableView) { |
| | | self.modelCell.sd_tableView = self.modelTableview; |
| | | } |
| | | self.modelCell.sd_indexPath = indexPath; |
| | | |
| | | if (model && keyPath) { |
| | | [self.modelCell setValue:model forKey:keyPath]; |
| | | } else if (self.cellDataSetting) { |
| | | self.cellDataSetting(self.modelCell); |
| | | } |
| | | |
| | | |
| | | #ifdef SDDebugWithAssert |
| | | /* |
| | | 妿ç¨åºå¡å¨äºè¿é说æä½ çcellè¿æ²¡æè°ç¨âsetupAutoHeightWithBottomView:(UIView *)bottomView bottomMargin:(CGFloat)bottomMarginâæ¹æ³æè
ä½ ä¼ éçbottomView为nilï¼è¯·æ£æ¥å¹¶ä¿®æ¹ãä¾ï¼ |
| | | |
| | | //注æï¼bottomViewä¸è½ä¸ºnil |
| | | [cell setupAutoHeightWithBottomView:bottomView bottomMargin:bottomMargin]; |
| | | */ |
| | | NSAssert(self.modelCell.sd_bottomViewsArray.count, @">>>>>> ä½ çcellè¿æ²¡æè°ç¨âsetupAutoHeightWithBottomView:(UIView *)bottomView bottomMargin:(CGFloat)bottomMarginâæ¹æ³æè
ä½ ä¼ éçbottomView为nilï¼è¯·æ£æ¥å¹¶ä¿®æ¹"); |
| | | |
| | | #endif |
| | | |
| | | [self.modelCell.contentView layoutSubviews]; |
| | | NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; |
| | | [_cacheDictionary setObject:@(self.modelCell.autoHeight) forKey:cacheKey]; |
| | | |
| | | |
| | | if (self.modelCell.sd_indexPath && self.modelCell.sd_tableView) { |
| | | if (self.modelCell.contentView.shouldReadjustFrameBeforeStoreCache) { |
| | | self.modelCell.contentView.height_sd = self.modelCell.autoHeight; |
| | | [self.modelCell.contentView layoutSubviews]; |
| | | } |
| | | [self.modelCell.contentView.autoLayoutModelsArray enumerateObjectsUsingBlock:^(SDAutoLayoutModel *model, NSUInteger idx, BOOL *stop) { |
| | | [self.modelTableview.cellAutoHeightManager setSubviewFrameCache:model.needsAutoResizeView.frame WithIndexPath:self.modelCell.sd_indexPath]; |
| | | }]; |
| | | } |
| | | |
| | | |
| | | return self.modelCell.autoHeight; |
| | | } |
| | | } |
| | | |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath model:(id)model keyPath:(NSString *)keyPath cellClass:(Class)cellClass |
| | | { |
| | | if (![self.modelCell isKindOfClass:cellClass]) { |
| | | self.modelCell = nil; |
| | | self.modelCell = [_modelCellsDict objectForKey:NSStringFromClass(cellClass)]; |
| | | if (!self.modelCell) { |
| | | [self registerCellWithCellClass:cellClass]; |
| | | } |
| | | _modelCell.contentView.tag = kSDModelCellTag; |
| | | } |
| | | if (self.modelCell.contentView.width_sd != self.contentViewWidth) { |
| | | _modelCell.contentView.width_sd = self.contentViewWidth; |
| | | } |
| | | return [self cellHeightForIndexPath:indexPath model:model keyPath:keyPath]; |
| | | } |
| | | |
| | | - (void)setContentViewWidth:(CGFloat)contentViewWidth |
| | | { |
| | | if (_contentViewWidth == contentViewWidth) return; |
| | | |
| | | CGFloat lastContentViewWidth = _contentViewWidth; |
| | | _contentViewWidth = contentViewWidth; |
| | | |
| | | self.modelCell.contentView.width_sd = self.contentViewWidth; |
| | | |
| | | if (lastContentViewWidth > 0) { |
| | | [_subviewFrameCacheDict removeAllObjects]; |
| | | dispatch_async(dispatch_get_main_queue(), ^{ |
| | | [self clearHeightCache]; |
| | | [self.modelTableview reloadData]; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | |
| | | - (void)setSubviewFrameCache:(CGRect)rect WithIndexPath:(NSIndexPath *)indexPath |
| | | { |
| | | if (!self.subviewFrameCacheDict) { |
| | | self.subviewFrameCacheDict = [NSMutableDictionary new]; |
| | | } |
| | | NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; |
| | | NSMutableArray *caches = [self.subviewFrameCacheDict objectForKey:cacheKey]; |
| | | if (!caches) { |
| | | caches = [NSMutableArray new]; |
| | | [self.subviewFrameCacheDict setValue:caches forKey:cacheKey]; |
| | | } |
| | | [caches addObject:[NSValue valueWithCGRect:rect]]; |
| | | } |
| | | |
| | | - (NSMutableArray *)subviewFrameCachesWithIndexPath:(NSIndexPath *)indexPath |
| | | { |
| | | NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; |
| | | return [self.subviewFrameCacheDict valueForKey:cacheKey]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation UITableView (SDAutoTableViewCellHeight) |
| | | |
| | | + (void)load { |
| | | static dispatch_once_t onceToken; |
| | | dispatch_once(&onceToken, ^{ |
| | | |
| | | NSArray *selStringsArray = @[@"reloadData", @"reloadRowsAtIndexPaths:withRowAnimation:", @"deleteRowsAtIndexPaths:withRowAnimation:", @"insertRowsAtIndexPaths:withRowAnimation:"]; |
| | | |
| | | [selStringsArray enumerateObjectsUsingBlock:^(NSString *selString, NSUInteger idx, BOOL *stop) { |
| | | NSString *mySelString = [@"sd_" stringByAppendingString:selString]; |
| | | |
| | | Method originalMethod = class_getInstanceMethod(self, NSSelectorFromString(selString)); |
| | | Method myMethod = class_getInstanceMethod(self, NSSelectorFromString(mySelString)); |
| | | method_exchangeImplementations(originalMethod, myMethod); |
| | | }]; |
| | | }); |
| | | } |
| | | |
| | | - (void)sd_reloadData |
| | | { |
| | | if (!self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData) { |
| | | [self.cellAutoHeightManager clearHeightCache]; |
| | | } |
| | | [self sd_reloadData]; |
| | | self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData = NO; |
| | | } |
| | | |
| | | - (void)sd_reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation |
| | | { |
| | | [self.cellAutoHeightManager clearHeightCacheOfIndexPaths:indexPaths]; |
| | | [self sd_reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation]; |
| | | } |
| | | |
| | | - (void)sd_deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation |
| | | { |
| | | for (NSIndexPath *indexPath in indexPaths) { |
| | | [self.cellAutoHeightManager deleteThenResetHeightCache:indexPath]; |
| | | } |
| | | [self sd_deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation]; |
| | | } |
| | | |
| | | |
| | | - (void)sd_insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation |
| | | { |
| | | [self.cellAutoHeightManager insertNewDataAtIndexPaths:indexPaths]; |
| | | [self sd_insertRowsAtIndexPaths:indexPaths withRowAnimation:animation]; |
| | | } |
| | | |
| | | /* |
| | | * ä¸ä¸æ¥å³å°å®ç°çåè½ |
| | | |
| | | - (void)sd_moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath |
| | | { |
| | | [self sd_moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; |
| | | } |
| | | |
| | | */ |
| | | |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath model:(id)model keyPath:(NSString *)keyPath cellClass:(Class)cellClass contentViewWidth:(CGFloat)contentViewWidth |
| | | { |
| | | self.cellAutoHeightManager.modelTableview = self; |
| | | |
| | | self.cellAutoHeightManager.contentViewWidth = contentViewWidth; |
| | | |
| | | return [self.cellAutoHeightManager cellHeightForIndexPath:indexPath model:model keyPath:keyPath cellClass:cellClass]; |
| | | } |
| | | |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath cellClass:(__unsafe_unretained Class)cellClass cellContentViewWidth:(CGFloat)width cellDataSetting:(AutoCellHeightDataSettingBlock)cellDataSetting |
| | | { |
| | | |
| | | self.cellDataSetting = cellDataSetting; |
| | | |
| | | return [self cellHeightForIndexPath:indexPath model:nil keyPath:nil cellClass:cellClass contentViewWidth:width]; |
| | | } |
| | | |
| | | - (void)reloadDataWithExistedHeightCache |
| | | { |
| | | self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData = YES; |
| | | [self reloadData]; |
| | | } |
| | | |
| | | - (void)reloadDataWithInsertingDataAtTheBeginingOfSection:(NSInteger)section newDataCount:(NSInteger)count |
| | | { |
| | | self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData = YES; |
| | | [self.cellAutoHeightManager insertNewDataAtTheBeginingOfSection:section newDataCount:count]; |
| | | [self reloadData]; |
| | | } |
| | | |
| | | - (void)reloadDataWithInsertingDataAtTheBeginingOfSections:(NSArray *)sectionNumsArray newDataCounts:(NSArray *)dataCountsArray |
| | | { |
| | | self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData = YES; |
| | | [sectionNumsArray enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL *stop) { |
| | | int section = [num intValue]; |
| | | int dataCountForSection = [dataCountsArray[idx] intValue]; |
| | | [self.cellAutoHeightManager insertNewDataAtTheBeginingOfSection:section newDataCount:dataCountForSection]; |
| | | }]; |
| | | [self reloadData]; |
| | | } |
| | | |
| | | - (CGFloat)cellsTotalHeight |
| | | { |
| | | CGFloat h = 0; |
| | | if (!self.cellAutoHeightManager.heightCacheDict.count) { |
| | | [self reloadData]; |
| | | } |
| | | NSArray *values = [self.cellAutoHeightManager.heightCacheDict allValues]; |
| | | for (NSNumber *number in values) { |
| | | h += [number floatValue]; |
| | | } |
| | | return h; |
| | | } |
| | | |
| | | - (SDCellAutoHeightManager *)cellAutoHeightManager |
| | | { |
| | | |
| | | SDCellAutoHeightManager *cellAutoHeightManager = objc_getAssociatedObject(self, _cmd); |
| | | |
| | | if (!cellAutoHeightManager) { |
| | | |
| | | cellAutoHeightManager = [[SDCellAutoHeightManager alloc] init]; |
| | | |
| | | [self setCellAutoHeightManager:cellAutoHeightManager]; |
| | | } |
| | | |
| | | return cellAutoHeightManager; |
| | | } |
| | | |
| | | - (void)setCellAutoHeightManager:(SDCellAutoHeightManager *)cellAutoHeightManager |
| | | { |
| | | objc_setAssociatedObject(self, @selector(cellAutoHeightManager), cellAutoHeightManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | - (void)setCellDataSetting:(AutoCellHeightDataSettingBlock)cellDataSetting |
| | | { |
| | | self.cellAutoHeightManager.cellDataSetting = cellDataSetting; |
| | | } |
| | | |
| | | - (AutoCellHeightDataSettingBlock)cellDataSetting |
| | | { |
| | | return self.cellAutoHeightManager.cellDataSetting; |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation UITableViewController (SDTableViewControllerAutoCellHeight) |
| | | |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath cellContentViewWidth:(CGFloat)width |
| | | { |
| | | return [self cellHeightForIndexPath:indexPath cellContentViewWidth:width tableView:self.tableView]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation NSObject (SDAnyObjectAutoCellHeight) |
| | | |
| | | - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath cellContentViewWidth:(CGFloat)width tableView:(UITableView *)tableView |
| | | { |
| | | tableView.cellAutoHeightManager.modelTableview = tableView; |
| | | |
| | | if (tableView.cellAutoHeightManager.contentViewWidth != width) { |
| | | tableView.cellAutoHeightManager.contentViewWidth = width; |
| | | } |
| | | if ([tableView.cellAutoHeightManager heightCacheForIndexPath:indexPath]) { |
| | | return [[tableView.cellAutoHeightManager heightCacheForIndexPath:indexPath] floatValue]; |
| | | } |
| | | UITableViewCell *cell = [tableView.dataSource tableView:tableView cellForRowAtIndexPath:indexPath]; |
| | | tableView.cellAutoHeightManager.modelCell = cell; |
| | | if (cell.contentView.width_sd != width) { |
| | | cell.contentView.width_sd = width; |
| | | } |
| | | return [[tableView cellAutoHeightManager] cellHeightForIndexPath:indexPath model:nil keyPath:nil]; |
| | | } |
| | | |
| | | @end |
| | | |
New file |
| | |
| | | // |
| | | // UIView+SDAutoLayout.h |
| | | // |
| | | // Created by gsd on 15/10/6. |
| | | // Copyright (c) 2015å¹´ gsd. All rights reserved. |
| | | // |
| | | |
| | | /* |
| | | ************************************************************************* |
| | | |
| | | --------- INTRODUCTION --------- |
| | | |
| | | USAGE: |
| | | |
| | | MODE 1. >>>>>>>>>>>>>>> You can use it in this way: |
| | | |
| | | Demo.sd_layout |
| | | .topSpaceToView(v1, 100) |
| | | .bottomSpaceToView(v3, 100) |
| | | .leftSpaceToView(v0, 150) |
| | | .rightSpaceToView(v2, 150); |
| | | |
| | | MODE 2. >>>>>>>>>>>>>>> You can also use it in this way that is more brevity: |
| | | |
| | | Demo.sd_layout.topSpaceToView(v1, 100).bottomSpaceToView(v3, 100).leftSpaceToView(v0, 150).rightSpaceToView(v2, 150); |
| | | |
| | | |
| | | ************************************************************************* |
| | | */ |
| | | |
| | | |
| | | /* |
| | | |
| | | ********************************************************************************* |
| | | * |
| | | * 卿¨ä½¿ç¨æ¤èªå¨å¸å±åºçè¿ç¨ä¸å¦æåºç°bugè¯·åæ¶ä»¥ä»¥ä¸ä»»æä¸ç§æ¹å¼èç³»æä»¬ï¼æä»¬ä¼åæ¶ä¿®å¤bugå¹¶ |
| | | * 帮æ¨è§£å³é®é¢ã |
| | | * QQ : 2689718696(gsdios) |
| | | * Email : gsdios@126.com |
| | | * GitHub: https://github.com/gsdios |
| | | * æ°æµªå¾®å:GSD_iOS |
| | | * |
| | | * è§é¢æç¨ï¼http://www.letv.com/ptv/vplay/24038772.html |
| | | * ç¨æ³ç¤ºä¾ï¼https://github.com/gsdios/SDAutoLayout/blob/master/README.md |
| | | * |
| | | ********************************************************************************* |
| | | |
| | | |
| | | SDAutoLayout |
| | | çæ¬ï¼2.1.7 |
| | | åå¸ï¼2016.08.12 |
| | | |
| | | */ |
| | | |
| | | |
| | | // 妿éè¦ç¨âæè¨âè°è¯ç¨åºè¯·æå¼æ¤å® |
| | | |
| | | //#define SDDebugWithAssert |
| | | |
| | | #import <UIKit/UIKit.h> |
| | | |
| | | @class SDAutoLayoutModel, SDUIViewCategoryManager; |
| | | |
| | | typedef SDAutoLayoutModel *(^MarginToView)(id viewOrViewsArray, CGFloat value); |
| | | typedef SDAutoLayoutModel *(^Margin)(CGFloat value); |
| | | typedef SDAutoLayoutModel *(^MarginEqualToView)(UIView *toView); |
| | | typedef SDAutoLayoutModel *(^WidthHeight)(CGFloat value); |
| | | typedef SDAutoLayoutModel *(^WidthHeightEqualToView)(UIView *toView, CGFloat ratioValue); |
| | | typedef SDAutoLayoutModel *(^AutoHeightWidth)(CGFloat ratioValue); |
| | | typedef SDAutoLayoutModel *(^SameWidthHeight)(); |
| | | typedef SDAutoLayoutModel *(^Offset)(CGFloat value); |
| | | typedef void (^SpaceToSuperView)(UIEdgeInsets insets); |
| | | |
| | | @interface SDAutoLayoutModel : NSObject |
| | | |
| | | /* |
| | | *************************说æ************************ |
| | | |
| | | æ¹æ³åä¸å¸¦æâSpaceToViewâçéè¦ä¼ é2ä¸ªåæ°ï¼ï¼UIViewï¼åç
§view å ï¼CGFloatï¼é´è·æ°å¼ |
| | | æ¹æ³åä¸å¸¦æâRatioToViewâçéè¦ä¼ é2ä¸ªåæ°ï¼ï¼UIViewï¼åç
§view å ï¼CGFloatï¼åæ° |
| | | æ¹æ³åä¸å¸¦æâEqualToViewâçéè¦ä¼ é1ä¸ªåæ°ï¼ï¼UIViewï¼åç
§view |
| | | æ¹æ³åä¸å¸¦æâIsâçéè¦ä¼ é1ä¸ªåæ°ï¼ï¼CGFloatï¼æ°å¼ |
| | | |
| | | ***************************************************** |
| | | */ |
| | | |
| | | |
| | | /* 设置è·ç¦»å
¶å®viewçé´è· */ |
| | | |
| | | /** 左边å°å
¶åç
§viewä¹é´çé´è·ï¼åæ°ä¸ºâ(View æè
viewæ°ç», CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) MarginToView leftSpaceToView; |
| | | /** å³è¾¹å°å
¶åç
§viewä¹é´çé´è·ï¼åæ°ä¸ºâ(View, CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) MarginToView rightSpaceToView; |
| | | /** é¡¶é¨å°å
¶åç
§viewä¹é´çé´è·ï¼åæ°ä¸ºâ(View æè
viewæ°ç», CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) MarginToView topSpaceToView; |
| | | /** åºé¨å°å
¶åç
§viewä¹é´çé´è·ï¼åæ°ä¸ºâ(View, CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) MarginToView bottomSpaceToView; |
| | | |
| | | |
| | | |
| | | /* 设置xãyãwidthãheightãcenterXãcenterY å¼ */ |
| | | |
| | | /** xå¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) Margin xIs; |
| | | /** yå¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) Margin yIs; |
| | | /** centerXå¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) Margin centerXIs; |
| | | /** centerYå¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) Margin centerYIs; |
| | | /** 宽度å¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) WidthHeight widthIs; |
| | | /** é«åº¦å¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) WidthHeight heightIs; |
| | | |
| | | |
| | | |
| | | /* 设置æå¤§å®½åº¦åé«åº¦ãæå°å®½åº¦åé«åº¦ */ |
| | | |
| | | /** æå¤§å®½åº¦å¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) WidthHeight maxWidthIs; |
| | | /** æå¤§é«åº¦å¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) WidthHeight maxHeightIs; |
| | | /** æå°å®½åº¦å¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) WidthHeight minWidthIs; |
| | | /** æå°é«åº¦å¼ï¼åæ°ä¸ºâ(CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) WidthHeight minHeightIs; |
| | | |
| | | |
| | | |
| | | /* 设置åæä¸ªåç
§viewçè¾¹è·ç¸å */ |
| | | |
| | | /** å·¦é´è·ä¸åç
§viewç¸åï¼åæ°ä¸ºâ(View)â */ |
| | | @property (nonatomic, copy, readonly) MarginEqualToView leftEqualToView; |
| | | /** å³é´è·ä¸åç
§viewç¸åï¼åæ°ä¸ºâ(View)â */ |
| | | @property (nonatomic, copy, readonly) MarginEqualToView rightEqualToView; |
| | | /** é¡¶é¨é´è·ä¸åç
§viewç¸åï¼åæ°ä¸ºâ(View)â */ |
| | | @property (nonatomic, copy, readonly) MarginEqualToView topEqualToView; |
| | | /** åºé¨é´è·ä¸åç
§viewç¸åï¼åæ°ä¸ºâ(View)â */ |
| | | @property (nonatomic, copy, readonly) MarginEqualToView bottomEqualToView; |
| | | /** centerXä¸åç
§viewç¸åï¼åæ°ä¸ºâ(View)â */ |
| | | @property (nonatomic, copy, readonly) MarginEqualToView centerXEqualToView; |
| | | /** centerYä¸åç
§viewç¸åï¼åæ°ä¸ºâ(View)â */ |
| | | @property (nonatomic, copy, readonly) MarginEqualToView centerYEqualToView; |
| | | |
| | | |
| | | |
| | | /* 设置宽度æè
é«åº¦çäºåç
§viewçå¤å°å */ |
| | | |
| | | /** 宽度æ¯åç
§view宽度çå¤å°åï¼åæ°ä¸ºâ(View, CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) WidthHeightEqualToView widthRatioToView; |
| | | /** é«åº¦æ¯åç
§viewé«åº¦çå¤å°åï¼åæ°ä¸ºâ(View, CGFloat)â */ |
| | | @property (nonatomic, copy, readonly) WidthHeightEqualToView heightRatioToView; |
| | | /** 设置ä¸ä¸ªviewç宽度åå®çé«åº¦ç¸åï¼åæ°ä¸ºç©ºâ()â */ |
| | | @property (nonatomic, copy, readonly) SameWidthHeight widthEqualToHeight; |
| | | /** 设置ä¸ä¸ªviewçé«åº¦åå®ç宽度ç¸åï¼åæ°ä¸ºç©ºâ()â */ |
| | | @property (nonatomic, copy, readonly) SameWidthHeight heightEqualToWidth; |
| | | /** èªéåºé«åº¦ï¼ä¼ å
¥é«å®½æ¯å¼ï¼labelå¯ä»¥ä¼ 0å®ç°æåé«åº¦èªéåº */ |
| | | @property (nonatomic, copy, readonly) AutoHeightWidth autoHeightRatio; |
| | | |
| | | /** èªéåºå®½åº¦ï¼åæ°ä¸ºå®½é«æ¯å¼ */ |
| | | @property (nonatomic, copy, readonly) AutoHeightWidth autoWidthRatio; |
| | | |
| | | |
| | | |
| | | /* å¡«å
ç¶view(å¿«æ·æ¹æ³) */ |
| | | |
| | | /** ä¼ å
¥UIEdgeInsetsMake(top, left, bottom, right)ï¼å¯ä»¥å¿«æ·è®¾ç½®viewå°å
¶ç¶viewä¸å·¦ä¸å³çé´è· */ |
| | | @property (nonatomic, copy, readonly) SpaceToSuperView spaceToSuperView; |
| | | |
| | | /** 设置åç§»éï¼åæ°ä¸ºâ(CGFloat value)ï¼ç®ååªæå¸¦æequalToViewçæ¹æ³å¯ä»¥è®¾ç½®offsetâ */ |
| | | @property (nonatomic, copy, readonly) Offset offset; |
| | | |
| | | @property (nonatomic, weak) UIView *needsAutoResizeView; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | #pragma mark - UIView é«åº¦ã宽度èªéåºç¸å
³æ¹æ³ |
| | | |
| | | @interface UIView (SDAutoHeightWidth) |
| | | |
| | | /** 设置Cellçé«åº¦èªéåºï¼ä¹å¯ç¨äºè®¾ç½®æ®éviewå
容é«åº¦èªéåº */ |
| | | - (void)setupAutoHeightWithBottomView:(UIView *)bottomView bottomMargin:(CGFloat)bottomMargin; |
| | | |
| | | /** ç¨äºè®¾ç½®æ®éviewå
容宽度èªéåº */ |
| | | - (void)setupAutoWidthWithRightView:(UIView *)rightView rightMargin:(CGFloat)rightMargin; |
| | | |
| | | /** 设置Cellçé«åº¦èªéåºï¼ä¹å¯ç¨äºè®¾ç½®æ®éviewå
容èªéåºï¼åºç¨äºå½ä½ ä¸ç¡®å®åªä¸ªviewå¨èªå¨å¸å±ä¹å伿å¸å¨æä¸æ¹æä¸ºbottomViewçæ¶åå¯ä»¥è°ç¨æ¬¡æ¹æ³å°ææå¯è½å¨æä¸æ¹çviewé½ä¼ è¿å»ï¼ */ |
| | | - (void)setupAutoHeightWithBottomViewsArray:(NSArray *)bottomViewsArray bottomMargin:(CGFloat)bottomMargin; |
| | | |
| | | /** æ´æ°å¸å±ï¼ä¸»å¨å·æ°å¸å±ï¼å¦æä½ éè¦è®¾ç½®å®å¸å±ä»£ç å°±è·å¾viewçframe请è°ç¨æ¤æ¹æ³ï¼ */ |
| | | - (void)updateLayout; |
| | | |
| | | /** æ´æ°cellå
é¨çæ§ä»¶çå¸å±ï¼cellå
鍿§ä»¶ä¸å±çæ´æ°çº¦ææ¹æ³,妿å¯ç¨äºcell frameç¼ååä¼èªå¨æ¸
é¤ç¼ååæ´æ°çº¦æï¼ */ |
| | | - (void)updateLayoutWithCellContentView:(UIView *)cellContentView; |
| | | |
| | | /** æ¸
空é«åº¦èªéåºè®¾ç½® */ |
| | | - (void)clearAutoHeigtSettings; |
| | | |
| | | /** æ¸
空宽度èªéåºè®¾ç½® */ |
| | | - (void)clearAutoWidthSettings; |
| | | |
| | | @property (nonatomic) CGFloat autoHeight; |
| | | |
| | | @property (nonatomic, readonly) SDUIViewCategoryManager *sd_categoryManager; |
| | | |
| | | @property (nonatomic, readonly) NSMutableArray *sd_bottomViewsArray; |
| | | @property (nonatomic) CGFloat sd_bottomViewBottomMargin; |
| | | |
| | | @property (nonatomic) NSArray *sd_rightViewsArray; |
| | | @property (nonatomic) CGFloat sd_rightViewRightMargin; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | #pragma mark - UIView 设置åè§åå¾ãèªå¨å¸å±åè°blockçç¸å
³æ¹æ³ |
| | | |
| | | @interface UIView (SDLayoutExtention) |
| | | |
| | | /** èªå¨å¸å±å®æåçåè°blockï¼å¯ä»¥å¨è¿éè·åå°viewççå®frame */ |
| | | @property (nonatomic) void (^didFinishAutoLayoutBlock)(CGRect frame); |
| | | |
| | | /** æ·»å ä¸ç»åview */ |
| | | - (void)sd_addSubviews:(NSArray *)subviews; |
| | | |
| | | /* 设置åè§ */ |
| | | |
| | | /** 设置åè§åå¾å¼ */ |
| | | @property (nonatomic, strong) NSNumber *sd_cornerRadius; |
| | | /** 设置åè§åå¾å¼ä¸ºview宽度çå¤å°å */ |
| | | @property (nonatomic, strong) NSNumber *sd_cornerRadiusFromWidthRatio; |
| | | /** 设置åè§åå¾å¼ä¸ºviewé«åº¦çå¤å°å */ |
| | | @property (nonatomic, strong) NSNumber *sd_cornerRadiusFromHeightRatio; |
| | | |
| | | /** 设置ç宽åviewï¼åviewéè¦å¨å䏿°´å¹³æ¹åï¼ */ |
| | | @property (nonatomic, strong) NSArray *sd_equalWidthSubviews; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | #pragma mark - UIView ä¹å®«æ ¼æµ®å¨å¸å±ææ |
| | | |
| | | @interface UIView (SDAutoFlowItems) |
| | | |
| | | /** |
| | | * 设置类似collectionViewææçåºå®é´è·èªå¨å®½åº¦æµ®å¨åview |
| | | * viewsArray : éè¦æµ®å¨å¸å±çææè§å¾ |
| | | * perRowItemsCount : æ¯è¡æ¾ç¤ºçè§å¾ä¸ªæ° |
| | | * verticalMargin : è§å¾ä¹é´çåç´é´è· |
| | | * horizontalMargin : è§å¾ä¹é´çæ°´å¹³é´è· |
| | | * vInset : ä¸ä¸ç¼©è¿å¼ |
| | | * hInset : å·¦å³ç¼©è¿å¼ |
| | | */ |
| | | - (void)setupAutoWidthFlowItems:(NSArray *)viewsArray withPerRowItemsCount:(NSInteger)perRowItemsCount verticalMargin:(CGFloat)verticalMargin horizontalMargin:(CGFloat)horizontalMagin verticalEdgeInset:(CGFloat)vInset horizontalEdgeInset:(CGFloat)hInset; |
| | | |
| | | /** æ¸
é¤åºå®é´è·èªå¨å®½åº¦æµ®å¨åview设置 */ |
| | | - (void)clearAutoWidthFlowItemsSettings; |
| | | |
| | | /** |
| | | * 设置类似collectionViewææçåºå®å®½å¸¦èªå¨é´è·æµ®å¨åview |
| | | * viewsArray : éè¦æµ®å¨å¸å±çææè§å¾ |
| | | * perRowItemsCount : æ¯è¡æ¾ç¤ºçè§å¾ä¸ªæ° |
| | | * verticalMargin : è§å¾ä¹é´çåç´é´è· |
| | | * vInset : ä¸ä¸ç¼©è¿å¼ |
| | | * hInset : å·¦å³ç¼©è¿å¼ |
| | | */ |
| | | - (void)setupAutoMarginFlowItems:(NSArray *)viewsArray withPerRowItemsCount:(NSInteger)perRowItemsCount itemWidth:(CGFloat)itemWidth verticalMargin:(CGFloat)verticalMargin verticalEdgeInset:(CGFloat)vInset horizontalEdgeInset:(CGFloat)hInset; |
| | | |
| | | /** æ¸
é¤åºå®å®½å¸¦èªå¨é´è·æµ®å¨åview设置 */ |
| | | - (void)clearAutoMarginFlowItemsSettings; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | #pragma mark - UIView 设置约æãæ´æ°çº¦æãæ¸
空约æãä»ç¶viewç§»é¤å¹¶æ¸
空约æãå¼å¯cellçframeç¼åçç¸å
³æ¹æ³ |
| | | |
| | | @interface UIView (SDAutoLayout) |
| | | |
| | | /** å¼å§èªå¨å¸å± */ |
| | | - (SDAutoLayoutModel *)sd_layout; |
| | | |
| | | /** æ¸
空ä¹åçèªå¨å¸å±è®¾ç½®ï¼éæ°å¼å§èªå¨å¸å±(éæ°çæå¸å±çº¦æå¹¶ä½¿å
¶å¨ç¶viewçå¸å±åºåæ°ç»ä¸ä½ç½®ä¿æä¸å) */ |
| | | - (SDAutoLayoutModel *)sd_resetLayout; |
| | | |
| | | /** æ¸
空ä¹åçèªå¨å¸å±è®¾ç½®ï¼éæ°å¼å§èªå¨å¸å±(éæ°çæå¸å±çº¦æå¹¶æ·»å å°ç¶viewå¸å±åºåæ°ç»ä¸çæåä¸ä¸ªä½ç½®) */ |
| | | - (SDAutoLayoutModel *)sd_resetNewLayout; |
| | | |
| | | /** æ¯å¦å
³éèªå¨å¸å± */ |
| | | @property (nonatomic, getter = sd_isClosingAutoLayout) BOOL sd_closeAutoLayout; |
| | | |
| | | /** ä»ç¶viewç§»é¤å¹¶æ¸
空约æ */ |
| | | - (void)removeFromSuperviewAndClearAutoLayoutSettings; |
| | | |
| | | /** æ¸
空ä¹åçèªå¨å¸å±è®¾ç½® */ |
| | | - (void)sd_clearAutoLayoutSettings; |
| | | |
| | | /** å°èªèº«frameæ¸
é¶ï¼ä¸è¬å¨cellå
鍿§ä»¶éç¨åè°ç¨ï¼ */ |
| | | - (void)sd_clearViewFrameCache; |
| | | |
| | | /** å°èªå·±çéè¦èªå¨å¸å±çsubviewsçframe(æè
frameç¼å)æ¸
é¶ */ |
| | | - (void)sd_clearSubviewsAutoLayoutFrameCaches; |
| | | |
| | | /** 设置åºå®å®½åº¦ä¿è¯å®½åº¦ä¸å¨èªå¨å¸å±è¿ç¨ååä¸è°æ´ */ |
| | | @property (nonatomic, strong) NSNumber *fixedWidth; |
| | | |
| | | /** 设置åºå®é«åº¦ä¿è¯é«åº¦ä¸å¨èªå¨å¸å±è¿ç¨ä¸ååè°æ´ */ |
| | | @property (nonatomic, strong) NSNumber *fixedHeight; |
| | | |
| | | /** å¯ç¨cell frameç¼åï¼å¯ä»¥æé«cellæ»å¨çæµç
度, ç®å为cellä¸ç¨æ¹æ³ï¼åæä¼æ©å±å°å
¶ä»viewï¼ */ |
| | | - (void)useCellFrameCacheWithIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableview; |
| | | |
| | | /** æå±tableviewï¼ç®å为cellä¸ç¨å±æ§ï¼åæä¼æ©å±å°å
¶ä»viewï¼ */ |
| | | @property (nonatomic) UITableView *sd_tableView; |
| | | |
| | | /** cellçindexPathï¼ç®å为cellä¸ç¨å±æ§ï¼åæä¼æ©å±å°cellçå
¶ä»åviewï¼ */ |
| | | @property (nonatomic) NSIndexPath *sd_indexPath; |
| | | |
| | | - (NSMutableArray *)autoLayoutModelsArray; |
| | | - (void)addAutoLayoutModel:(SDAutoLayoutModel *)model; |
| | | @property (nonatomic) SDAutoLayoutModel *ownLayoutModel; |
| | | @property (nonatomic, strong) NSNumber *sd_maxWidth; |
| | | @property (nonatomic, strong) NSNumber *autoHeightRatioValue; |
| | | @property (nonatomic, strong) NSNumber *autoWidthRatioValue; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | #pragma mark - UIScrollView å
容ç«åèªéåºãå
容横åèªéåºæ¹æ³ |
| | | |
| | | @interface UIScrollView (SDAutoContentSize) |
| | | |
| | | /** 设置scrollviewå
容ç«åèªéåº */ |
| | | - (void)setupAutoContentSizeWithBottomView:(UIView *)bottomView bottomMargin:(CGFloat)bottomMargin; |
| | | |
| | | /** 设置scrollviewå
容横åèªéåº */ |
| | | - (void)setupAutoContentSizeWithRightView:(UIView *)rightView rightMargin:(CGFloat)rightMargin; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | #pragma mark - UILabel å¼å¯å¯ææ¬å¸å±ã设置åè¡ææ¬label宽度èªéåºã 设置labelæå¤å¯ä»¥æ¾ç¤ºçè¡æ° |
| | | |
| | | @interface UILabel (SDLabelAutoResize) |
| | | |
| | | /** æ¯å¦æ¯attributedString */ |
| | | @property (nonatomic) BOOL isAttributedContent; |
| | | |
| | | /** 设置åè¡ææ¬label宽度èªéåº */ |
| | | - (void)setSingleLineAutoResizeWithMaxWidth:(CGFloat)maxWidth; |
| | | |
| | | /** 设置labelæå¤å¯ä»¥æ¾ç¤ºå¤å°è¡ï¼å¦æä¼ 0åæ¾ç¤ºææè¡æå */ |
| | | - (void)setMaxNumberOfLinesToShow:(NSInteger)lineCount; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | #pragma mark - UIButton 设置buttonæ ¹æ®åè¡æåèªéåº |
| | | |
| | | @interface UIButton (SDExtention) |
| | | |
| | | /* |
| | | * 设置buttonæ ¹æ®åè¡æåèªéåº |
| | | * hPaddingï¼å·¦å³è¾¹è· |
| | | */ |
| | | - (void)setupAutoSizeWithHorizontalPadding:(CGFloat)hPadding buttonHeight:(CGFloat)buttonHeight; |
| | | |
| | | @end |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | #pragma mark - å
¶ä»æ¹æ³ï¼å¦ææéè¦å¯ä»¥èªå·±å©ç¨ä»¥ä¸æ¥å£æå±æ´å¤åè½ï¼ |
| | | |
| | | @interface SDAutoLayoutModelItem : NSObject |
| | | |
| | | @property (nonatomic, strong) NSNumber *value; |
| | | @property (nonatomic, weak) UIView *refView; |
| | | @property (nonatomic, assign) CGFloat offset; |
| | | @property (nonatomic, strong) NSArray *refViewsArray; |
| | | |
| | | @end |
| | | |
| | | |
| | | @interface UIView (SDChangeFrame) |
| | | |
| | | @property (nonatomic) BOOL shouldReadjustFrameBeforeStoreCache; |
| | | |
| | | @property (nonatomic) CGFloat left_sd; |
| | | @property (nonatomic) CGFloat top_sd; |
| | | @property (nonatomic) CGFloat right_sd; |
| | | @property (nonatomic) CGFloat bottom_sd; |
| | | @property (nonatomic) CGFloat centerX_sd; |
| | | @property (nonatomic) CGFloat centerY_sd; |
| | | |
| | | @property (nonatomic) CGFloat width_sd; |
| | | @property (nonatomic) CGFloat height_sd; |
| | | |
| | | |
| | | @property (nonatomic) CGPoint origin_sd; |
| | | @property (nonatomic) CGSize size_sd; |
| | | |
| | | |
| | | // å
¼å®¹æ§çæ¬ |
| | | @property (nonatomic) CGFloat left; |
| | | @property (nonatomic) CGFloat top; |
| | | @property (nonatomic) CGFloat right; |
| | | @property (nonatomic) CGFloat bottom; |
| | | @property (nonatomic) CGFloat centerX; |
| | | @property (nonatomic) CGFloat centerY; |
| | | @property (nonatomic) CGFloat width; |
| | | @property (nonatomic) CGFloat height; |
| | | @property (nonatomic) CGPoint origin; |
| | | @property (nonatomic) CGSize size; |
| | | |
| | | @end |
| | | |
| | | |
| | | @interface SDUIViewCategoryManager : NSObject |
| | | |
| | | @property (nonatomic, strong) NSArray *rightViewsArray; |
| | | @property (nonatomic, assign) CGFloat rightViewRightMargin; |
| | | |
| | | @property (nonatomic, weak) UITableView *sd_tableView; |
| | | @property (nonatomic, strong) NSIndexPath *sd_indexPath; |
| | | |
| | | @property (nonatomic, assign) BOOL hasSetFrameWithCache; |
| | | |
| | | @property (nonatomic) BOOL shouldReadjustFrameBeforeStoreCache; |
| | | |
| | | @property (nonatomic, assign, getter = sd_isClosingAutoLayout) BOOL sd_closeAutoLayout; |
| | | |
| | | |
| | | /** 设置类似collectionViewææçåºå®é´è·èªå¨å®½åº¦æµ®å¨åview */ |
| | | |
| | | @property (nonatomic, strong) NSArray *flowItems; |
| | | @property (nonatomic, assign) CGFloat verticalMargin; |
| | | @property (nonatomic, assign) CGFloat horizontalMargin; |
| | | @property (nonatomic, assign) NSInteger perRowItemsCount; |
| | | @property (nonatomic, assign) CGFloat lastWidth; |
| | | |
| | | |
| | | /** 设置类似collectionViewææçåºå®å®½å¸¦èªå¨é´è·æµ®å¨åview */ |
| | | |
| | | @property (nonatomic, assign) CGFloat flowItemWidth; |
| | | @property (nonatomic, assign) BOOL shouldShowAsAutoMarginViews; |
| | | |
| | | |
| | | @property (nonatomic) CGFloat horizontalEdgeInset; |
| | | @property (nonatomic) CGFloat verticalEdgeInset; |
| | | |
| | | @end |
| | | |
New file |
| | |
| | | // |
| | | // UIView+SDAutoLayout.m |
| | | // |
| | | // Created by gsd on 15/10/6. |
| | | // Copyright (c) 2015å¹´ gsd. All rights reserved. |
| | | // |
| | | |
| | | /* |
| | | |
| | | ********************************************************************************* |
| | | * * |
| | | * 卿¨ä½¿ç¨æ¤èªå¨å¸å±åºçè¿ç¨ä¸å¦æåºç°bugè¯·åæ¶ä»¥ä»¥ä¸ä»»æä¸ç§æ¹å¼èç³»æä»¬ï¼æä»¬ä¼åæ¶ä¿®å¤bugå¹¶ * |
| | | * 帮æ¨è§£å³é®é¢ã * |
| | | * QQ : 2689718696(gsdios) * |
| | | * Email : gsdios@126.com * |
| | | * GitHub: https://github.com/gsdios * |
| | | * æ°æµªå¾®å:GSD_iOS * |
| | | * * |
| | | ********************************************************************************* |
| | | |
| | | */ |
| | | |
| | | #import "UIView+SDAutoLayout.h" |
| | | #import "UITableView+SDAutoTableViewCellHeight.h" |
| | | |
| | | #import <objc/runtime.h> |
| | | |
| | | Class cellContVClass() |
| | | { |
| | | // 为äºåºä»SBå®¡æ ¸çSBæ¡æ¬¾ The use of non-public APIs is not permitted on the App Store because it can lead to a poor user experience should these APIs change. |
| | | static UITableViewCell *tempCell; |
| | | |
| | | if (!tempCell) { |
| | | tempCell = [UITableViewCell new]; |
| | | } |
| | | return [tempCell.contentView class]; |
| | | } |
| | | |
| | | @interface SDAutoLayoutModel () |
| | | |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *width; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *height; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *left; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *top; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *right; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *bottom; |
| | | @property (nonatomic, strong) NSNumber *centerX; |
| | | @property (nonatomic, strong) NSNumber *centerY; |
| | | |
| | | @property (nonatomic, strong) NSNumber *maxWidth; |
| | | @property (nonatomic, strong) NSNumber *maxHeight; |
| | | @property (nonatomic, strong) NSNumber *minWidth; |
| | | @property (nonatomic, strong) NSNumber *minHeight; |
| | | |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *ratio_width; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *ratio_height; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *ratio_left; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *ratio_top; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *ratio_right; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *ratio_bottom; |
| | | |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *equalLeft; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *equalRight; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *equalTop; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *equalBottom; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *equalCenterX; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *equalCenterY; |
| | | |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *widthEqualHeight; |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *heightEqualWidth; |
| | | |
| | | @property (nonatomic, strong) SDAutoLayoutModelItem *lastModelItem; |
| | | |
| | | @end |
| | | |
| | | @implementation SDAutoLayoutModel |
| | | |
| | | @synthesize leftSpaceToView = _leftSpaceToView; |
| | | @synthesize rightSpaceToView = _rightSpaceToView; |
| | | @synthesize topSpaceToView = _topSpaceToView; |
| | | @synthesize bottomSpaceToView = _bottomSpaceToView; |
| | | @synthesize widthIs = _widthIs; |
| | | @synthesize heightIs = _heightIs; |
| | | @synthesize widthRatioToView = _widthRatioToView; |
| | | @synthesize heightRatioToView = _heightRatioToView; |
| | | @synthesize leftEqualToView = _leftEqualToView; |
| | | @synthesize rightEqualToView = _rightEqualToView; |
| | | @synthesize topEqualToView = _topEqualToView; |
| | | @synthesize bottomEqualToView = _bottomEqualToView; |
| | | @synthesize centerXEqualToView = _centerXEqualToView; |
| | | @synthesize centerYEqualToView = _centerYEqualToView; |
| | | @synthesize xIs = _xIs; |
| | | @synthesize yIs = _yIs; |
| | | @synthesize centerXIs = _centerXIs; |
| | | @synthesize centerYIs = _centerYIs; |
| | | @synthesize autoHeightRatio = _autoHeightRatio; |
| | | @synthesize autoWidthRatio = _autoWidthRatio; |
| | | @synthesize spaceToSuperView = _spaceToSuperView; |
| | | @synthesize maxWidthIs = _maxWidthIs; |
| | | @synthesize maxHeightIs = _maxHeightIs; |
| | | @synthesize minWidthIs = _minWidthIs; |
| | | @synthesize minHeightIs = _minHeightIs; |
| | | @synthesize widthEqualToHeight = _widthEqualToHeight; |
| | | @synthesize heightEqualToWidth = _heightEqualToWidth; |
| | | @synthesize offset = _offset; |
| | | |
| | | |
| | | - (MarginToView)leftSpaceToView |
| | | { |
| | | if (!_leftSpaceToView) { |
| | | _leftSpaceToView = [self marginToViewblockWithKey:@"left"]; |
| | | } |
| | | return _leftSpaceToView; |
| | | } |
| | | |
| | | - (MarginToView)rightSpaceToView |
| | | { |
| | | if (!_rightSpaceToView) { |
| | | _rightSpaceToView = [self marginToViewblockWithKey:@"right"]; |
| | | } |
| | | return _rightSpaceToView; |
| | | } |
| | | |
| | | - (MarginToView)topSpaceToView |
| | | { |
| | | if (!_topSpaceToView) { |
| | | _topSpaceToView = [self marginToViewblockWithKey:@"top"]; |
| | | } |
| | | return _topSpaceToView; |
| | | } |
| | | |
| | | - (MarginToView)bottomSpaceToView |
| | | { |
| | | if (!_bottomSpaceToView) { |
| | | _bottomSpaceToView = [self marginToViewblockWithKey:@"bottom"]; |
| | | } |
| | | return _bottomSpaceToView; |
| | | } |
| | | |
| | | - (MarginToView)marginToViewblockWithKey:(NSString *)key |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | return ^(id viewOrViewsArray, CGFloat value) { |
| | | SDAutoLayoutModelItem *item = [SDAutoLayoutModelItem new]; |
| | | item.value = @(value); |
| | | if ([viewOrViewsArray isKindOfClass:[UIView class]]) { |
| | | item.refView = viewOrViewsArray; |
| | | } else if ([viewOrViewsArray isKindOfClass:[NSArray class]]) { |
| | | item.refViewsArray = [viewOrViewsArray copy]; |
| | | } |
| | | [weakSelf setValue:item forKey:key]; |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | |
| | | - (WidthHeight)widthIs |
| | | { |
| | | if (!_widthIs) { |
| | | __weak typeof(self) weakSelf = self; |
| | | _widthIs = ^(CGFloat value) { |
| | | weakSelf.needsAutoResizeView.fixedWidth = @(value); |
| | | SDAutoLayoutModelItem *widthItem = [SDAutoLayoutModelItem new]; |
| | | widthItem.value = @(value); |
| | | weakSelf.width = widthItem; |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | return _widthIs; |
| | | } |
| | | |
| | | - (WidthHeight)heightIs |
| | | { |
| | | if (!_heightIs) { |
| | | __weak typeof(self) weakSelf = self; |
| | | _heightIs = ^(CGFloat value) { |
| | | weakSelf.needsAutoResizeView.fixedHeight = @(value); |
| | | SDAutoLayoutModelItem *heightItem = [SDAutoLayoutModelItem new]; |
| | | heightItem.value = @(value); |
| | | weakSelf.height = heightItem; |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | return _heightIs; |
| | | } |
| | | |
| | | - (WidthHeightEqualToView)widthRatioToView |
| | | { |
| | | if (!_widthRatioToView) { |
| | | __weak typeof(self) weakSelf = self; |
| | | _widthRatioToView = ^(UIView *view, CGFloat value) { |
| | | weakSelf.ratio_width = [SDAutoLayoutModelItem new]; |
| | | weakSelf.ratio_width.value = @(value); |
| | | weakSelf.ratio_width.refView = view; |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | return _widthRatioToView; |
| | | } |
| | | |
| | | - (WidthHeightEqualToView)heightRatioToView |
| | | { |
| | | if (!_heightRatioToView) { |
| | | __weak typeof(self) weakSelf = self; |
| | | _heightRatioToView = ^(UIView *view, CGFloat value) { |
| | | weakSelf.ratio_height = [SDAutoLayoutModelItem new]; |
| | | weakSelf.ratio_height.refView = view; |
| | | weakSelf.ratio_height.value = @(value); |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | return _heightRatioToView; |
| | | } |
| | | |
| | | - (WidthHeight)maxWidthIs |
| | | { |
| | | if (!_maxWidthIs) { |
| | | _maxWidthIs = [self limitingWidthHeightWithKey:@"maxWidth"]; |
| | | } |
| | | return _maxWidthIs; |
| | | } |
| | | |
| | | - (WidthHeight)maxHeightIs |
| | | { |
| | | if (!_maxHeightIs) { |
| | | _maxHeightIs = [self limitingWidthHeightWithKey:@"maxHeight"]; |
| | | } |
| | | return _maxHeightIs; |
| | | } |
| | | |
| | | - (WidthHeight)minWidthIs |
| | | { |
| | | if (!_minWidthIs) { |
| | | _minWidthIs = [self limitingWidthHeightWithKey:@"minWidth"]; |
| | | } |
| | | return _minWidthIs; |
| | | } |
| | | |
| | | - (WidthHeight)minHeightIs |
| | | { |
| | | if (!_minHeightIs) { |
| | | _minHeightIs = [self limitingWidthHeightWithKey:@"minHeight"]; |
| | | } |
| | | return _minHeightIs; |
| | | } |
| | | |
| | | |
| | | - (WidthHeight)limitingWidthHeightWithKey:(NSString *)key |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | |
| | | return ^(CGFloat value) { |
| | | [weakSelf setValue:@(value) forKey:key]; |
| | | |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | |
| | | |
| | | - (MarginEqualToView)marginEqualToViewBlockWithKey:(NSString *)key |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | |
| | | return ^(UIView *view) { |
| | | SDAutoLayoutModelItem *item = [SDAutoLayoutModelItem new]; |
| | | item.refView = view; |
| | | [weakSelf setValue:item forKey:key]; |
| | | weakSelf.lastModelItem = item; |
| | | if ([view isKindOfClass:cellContVClass()] && ([key isEqualToString:@"equalCenterY"] || [key isEqualToString:@"equalBottom"])) { |
| | | view.shouldReadjustFrameBeforeStoreCache = YES; |
| | | } |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | |
| | | - (MarginEqualToView)leftEqualToView |
| | | { |
| | | if (!_leftEqualToView) { |
| | | _leftEqualToView = [self marginEqualToViewBlockWithKey:@"equalLeft"]; |
| | | } |
| | | return _leftEqualToView; |
| | | } |
| | | |
| | | - (MarginEqualToView)rightEqualToView |
| | | { |
| | | if (!_rightEqualToView) { |
| | | _rightEqualToView = [self marginEqualToViewBlockWithKey:@"equalRight"]; |
| | | } |
| | | return _rightEqualToView; |
| | | } |
| | | |
| | | - (MarginEqualToView)topEqualToView |
| | | { |
| | | if (!_topEqualToView) { |
| | | _topEqualToView = [self marginEqualToViewBlockWithKey:@"equalTop"]; |
| | | } |
| | | return _topEqualToView; |
| | | } |
| | | |
| | | - (MarginEqualToView)bottomEqualToView |
| | | { |
| | | if (!_bottomEqualToView) { |
| | | _bottomEqualToView = [self marginEqualToViewBlockWithKey:@"equalBottom"]; |
| | | } |
| | | return _bottomEqualToView; |
| | | } |
| | | |
| | | - (MarginEqualToView)centerXEqualToView |
| | | { |
| | | if (!_centerXEqualToView) { |
| | | _centerXEqualToView = [self marginEqualToViewBlockWithKey:@"equalCenterX"]; |
| | | } |
| | | return _centerXEqualToView; |
| | | } |
| | | |
| | | - (MarginEqualToView)centerYEqualToView |
| | | { |
| | | if (!_centerYEqualToView) { |
| | | _centerYEqualToView = [self marginEqualToViewBlockWithKey:@"equalCenterY"]; |
| | | } |
| | | return _centerYEqualToView; |
| | | } |
| | | |
| | | |
| | | - (Margin)marginBlockWithKey:(NSString *)key |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | |
| | | return ^(CGFloat value) { |
| | | |
| | | if ([key isEqualToString:@"x"]) { |
| | | weakSelf.needsAutoResizeView.left_sd = value; |
| | | } else if ([key isEqualToString:@"y"]) { |
| | | weakSelf.needsAutoResizeView.top_sd = value; |
| | | } else if ([key isEqualToString:@"centerX"]) { |
| | | weakSelf.centerX = @(value); |
| | | } else if ([key isEqualToString:@"centerY"]) { |
| | | weakSelf.centerY = @(value); |
| | | } |
| | | |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | |
| | | - (Margin)xIs |
| | | { |
| | | if (!_xIs) { |
| | | _xIs = [self marginBlockWithKey:@"x"]; |
| | | } |
| | | return _xIs; |
| | | } |
| | | |
| | | - (Margin)yIs |
| | | { |
| | | if (!_yIs) { |
| | | _yIs = [self marginBlockWithKey:@"y"]; |
| | | } |
| | | return _yIs; |
| | | } |
| | | |
| | | - (Margin)centerXIs |
| | | { |
| | | if (!_centerXIs) { |
| | | _centerXIs = [self marginBlockWithKey:@"centerX"]; |
| | | } |
| | | return _centerXIs; |
| | | } |
| | | |
| | | - (Margin)centerYIs |
| | | { |
| | | if (!_centerYIs) { |
| | | _centerYIs = [self marginBlockWithKey:@"centerY"]; |
| | | } |
| | | return _centerYIs; |
| | | } |
| | | |
| | | - (AutoHeightWidth)autoHeightRatio |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | |
| | | if (!_autoHeightRatio) { |
| | | _autoHeightRatio = ^(CGFloat ratioaValue) { |
| | | weakSelf.needsAutoResizeView.autoHeightRatioValue = @(ratioaValue); |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | return _autoHeightRatio; |
| | | } |
| | | |
| | | - (AutoHeightWidth)autoWidthRatio |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | |
| | | if (!_autoWidthRatio) { |
| | | _autoWidthRatio = ^(CGFloat ratioaValue) { |
| | | weakSelf.needsAutoResizeView.autoWidthRatioValue = @(ratioaValue); |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | return _autoWidthRatio; |
| | | } |
| | | |
| | | - (SpaceToSuperView)spaceToSuperView |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | |
| | | if (!_spaceToSuperView) { |
| | | _spaceToSuperView = ^(UIEdgeInsets insets) { |
| | | UIView *superView = weakSelf.needsAutoResizeView.superview; |
| | | if (superView) { |
| | | weakSelf.needsAutoResizeView.sd_layout |
| | | .leftSpaceToView(superView, insets.left) |
| | | .topSpaceToView(superView, insets.top) |
| | | .rightSpaceToView(superView, insets.right) |
| | | .bottomSpaceToView(superView, insets.bottom); |
| | | } |
| | | }; |
| | | } |
| | | return _spaceToSuperView; |
| | | } |
| | | |
| | | - (SameWidthHeight)widthEqualToHeight |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | |
| | | if (!_widthEqualToHeight) { |
| | | _widthEqualToHeight = ^() { |
| | | weakSelf.widthEqualHeight = [SDAutoLayoutModelItem new]; |
| | | weakSelf.lastModelItem = weakSelf.widthEqualHeight; |
| | | // 主å¨è§¦å䏿¬¡èµå¼æä½ |
| | | weakSelf.needsAutoResizeView.height_sd = weakSelf.needsAutoResizeView.height_sd; |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | return _widthEqualToHeight; |
| | | } |
| | | |
| | | - (SameWidthHeight)heightEqualToWidth |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | |
| | | if (!_heightEqualToWidth) { |
| | | _heightEqualToWidth = ^() { |
| | | weakSelf.heightEqualWidth = [SDAutoLayoutModelItem new]; |
| | | weakSelf.lastModelItem = weakSelf.heightEqualWidth; |
| | | // 主å¨è§¦å䏿¬¡èµå¼æä½ |
| | | weakSelf.needsAutoResizeView.width_sd = weakSelf.needsAutoResizeView.width_sd; |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | return _heightEqualToWidth; |
| | | } |
| | | |
| | | - (SDAutoLayoutModel *(^)(CGFloat))offset |
| | | { |
| | | __weak typeof(self) weakSelf = self; |
| | | if (!_offset) { |
| | | _offset = ^(CGFloat offset) { |
| | | weakSelf.lastModelItem.offset = offset; |
| | | return weakSelf; |
| | | }; |
| | | } |
| | | return _offset; |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation UIView (SDAutoHeightWidth) |
| | | |
| | | - (SDUIViewCategoryManager *)sd_categoryManager |
| | | { |
| | | SDUIViewCategoryManager *manager = objc_getAssociatedObject(self, _cmd); |
| | | if (!manager) { |
| | | objc_setAssociatedObject(self, _cmd, [SDUIViewCategoryManager new], OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setupAutoHeightWithBottomView:(UIView *)bottomView bottomMargin:(CGFloat)bottomMargin |
| | | { |
| | | if (!bottomView) return; |
| | | |
| | | [self setupAutoHeightWithBottomViewsArray:@[bottomView] bottomMargin:bottomMargin]; |
| | | } |
| | | |
| | | - (void)setupAutoWidthWithRightView:(UIView *)rightView rightMargin:(CGFloat)rightMargin |
| | | { |
| | | if (!rightView) return; |
| | | |
| | | self.sd_rightViewsArray = @[rightView]; |
| | | self.sd_rightViewRightMargin = rightMargin; |
| | | } |
| | | |
| | | - (void)setupAutoHeightWithBottomViewsArray:(NSArray *)bottomViewsArray bottomMargin:(CGFloat)bottomMargin |
| | | { |
| | | if (!bottomViewsArray) return; |
| | | |
| | | // æ¸
空ä¹åçview |
| | | [self.sd_bottomViewsArray removeAllObjects]; |
| | | [self.sd_bottomViewsArray addObjectsFromArray:bottomViewsArray]; |
| | | self.sd_bottomViewBottomMargin = bottomMargin; |
| | | } |
| | | |
| | | - (void)clearAutoHeigtSettings |
| | | { |
| | | [self.sd_bottomViewsArray removeAllObjects]; |
| | | } |
| | | |
| | | - (void)clearAutoWidthSettings |
| | | { |
| | | self.sd_rightViewsArray = nil; |
| | | } |
| | | |
| | | - (void)updateLayout |
| | | { |
| | | [self.superview layoutSubviews]; |
| | | } |
| | | |
| | | - (void)updateLayoutWithCellContentView:(UIView *)cellContentView |
| | | { |
| | | if (cellContentView.sd_indexPath) { |
| | | [cellContentView sd_clearSubviewsAutoLayoutFrameCaches]; |
| | | } |
| | | [self updateLayout]; |
| | | } |
| | | |
| | | - (CGFloat)autoHeight |
| | | { |
| | | return [objc_getAssociatedObject(self, _cmd) floatValue]; |
| | | } |
| | | |
| | | - (void)setAutoHeight:(CGFloat)autoHeight |
| | | { |
| | | objc_setAssociatedObject(self, @selector(autoHeight), @(autoHeight), OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | - (NSMutableArray *)sd_bottomViewsArray |
| | | { |
| | | NSMutableArray *array = objc_getAssociatedObject(self, _cmd); |
| | | if (!array) { |
| | | objc_setAssociatedObject(self, _cmd, [NSMutableArray new], OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (NSArray *)sd_rightViewsArray |
| | | { |
| | | return [[self sd_categoryManager] rightViewsArray]; |
| | | } |
| | | |
| | | - (void)setSd_rightViewsArray:(NSArray *)sd_rightViewsArray |
| | | { |
| | | [[self sd_categoryManager] setRightViewsArray:sd_rightViewsArray]; |
| | | } |
| | | |
| | | - (CGFloat)sd_bottomViewBottomMargin |
| | | { |
| | | return [objc_getAssociatedObject(self, _cmd) floatValue]; |
| | | } |
| | | |
| | | - (void)setSd_bottomViewBottomMargin:(CGFloat)sd_bottomViewBottomMargin |
| | | { |
| | | objc_setAssociatedObject(self, @selector(sd_bottomViewBottomMargin), @(sd_bottomViewBottomMargin), OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | - (void)setSd_rightViewRightMargin:(CGFloat)sd_rightViewRightMargin |
| | | { |
| | | [[self sd_categoryManager] setRightViewRightMargin:sd_rightViewRightMargin]; |
| | | } |
| | | |
| | | - (CGFloat)sd_rightViewRightMargin |
| | | { |
| | | return [[self sd_categoryManager] rightViewRightMargin]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation UIView (SDLayoutExtention) |
| | | |
| | | - (void (^)(CGRect))didFinishAutoLayoutBlock |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setDidFinishAutoLayoutBlock:(void (^)(CGRect))didFinishAutoLayoutBlock |
| | | { |
| | | objc_setAssociatedObject(self, @selector(didFinishAutoLayoutBlock), didFinishAutoLayoutBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); |
| | | } |
| | | |
| | | - (NSNumber *)sd_cornerRadius |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setSd_cornerRadius:(NSNumber *)sd_cornerRadius |
| | | { |
| | | objc_setAssociatedObject(self, @selector(sd_cornerRadius), sd_cornerRadius, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | |
| | | - (NSNumber *)sd_cornerRadiusFromWidthRatio |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setSd_cornerRadiusFromWidthRatio:(NSNumber *)sd_cornerRadiusFromWidthRatio |
| | | { |
| | | objc_setAssociatedObject(self, @selector(sd_cornerRadiusFromWidthRatio), sd_cornerRadiusFromWidthRatio, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | |
| | | - (NSNumber *)sd_cornerRadiusFromHeightRatio |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setSd_cornerRadiusFromHeightRatio:(NSNumber *)sd_cornerRadiusFromHeightRatio |
| | | { |
| | | objc_setAssociatedObject(self, @selector(sd_cornerRadiusFromHeightRatio), sd_cornerRadiusFromHeightRatio, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | - (NSArray *)sd_equalWidthSubviews |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setSd_equalWidthSubviews:(NSArray *)sd_equalWidthSubviews |
| | | { |
| | | objc_setAssociatedObject(self, @selector(sd_equalWidthSubviews), sd_equalWidthSubviews, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | - (void)sd_addSubviews:(NSArray *)subviews |
| | | { |
| | | [subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) { |
| | | if ([view isKindOfClass:[UIView class]]) { |
| | | [self addSubview:view]; |
| | | } |
| | | }]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation UIView (SDAutoFlowItems) |
| | | |
| | | - (void)setupAutoWidthFlowItems:(NSArray *)viewsArray withPerRowItemsCount:(NSInteger)perRowItemsCount verticalMargin:(CGFloat)verticalMargin horizontalMargin:(CGFloat)horizontalMagin verticalEdgeInset:(CGFloat)vInset horizontalEdgeInset:(CGFloat)hInset |
| | | { |
| | | self.sd_categoryManager.flowItems = viewsArray; |
| | | self.sd_categoryManager.perRowItemsCount = perRowItemsCount; |
| | | self.sd_categoryManager.verticalMargin = verticalMargin; |
| | | self.sd_categoryManager.horizontalMargin = horizontalMagin; |
| | | self.verticalEdgeInset = vInset; |
| | | self.horizontalEdgeInset = hInset; |
| | | |
| | | self.sd_categoryManager.lastWidth = 0; |
| | | |
| | | if (viewsArray.count) { |
| | | [self setupAutoHeightWithBottomView:viewsArray.lastObject bottomMargin:vInset]; |
| | | } else { |
| | | [self clearAutoHeigtSettings]; |
| | | } |
| | | } |
| | | |
| | | - (void)clearAutoWidthFlowItemsSettings |
| | | { |
| | | [self setupAutoWidthFlowItems:nil withPerRowItemsCount:0 verticalMargin:0 horizontalMargin:0 verticalEdgeInset:0 horizontalEdgeInset:0]; |
| | | } |
| | | |
| | | - (void)setupAutoMarginFlowItems:(NSArray *)viewsArray withPerRowItemsCount:(NSInteger)perRowItemsCount itemWidth:(CGFloat)itemWidth verticalMargin:(CGFloat)verticalMargin verticalEdgeInset:(CGFloat)vInset horizontalEdgeInset:(CGFloat)hInset |
| | | { |
| | | self.sd_categoryManager.shouldShowAsAutoMarginViews = YES; |
| | | self.sd_categoryManager.flowItemWidth = itemWidth; |
| | | [self setupAutoWidthFlowItems:viewsArray withPerRowItemsCount:perRowItemsCount verticalMargin:verticalMargin horizontalMargin:0 verticalEdgeInset:vInset horizontalEdgeInset:hInset]; |
| | | } |
| | | |
| | | - (void)clearAutoMarginFlowItemsSettings |
| | | { |
| | | [self setupAutoMarginFlowItems:nil withPerRowItemsCount:0 itemWidth:0 verticalMargin:0 verticalEdgeInset:0 horizontalEdgeInset:0]; |
| | | } |
| | | |
| | | - (void)setHorizontalEdgeInset:(CGFloat)horizontalEdgeInset |
| | | { |
| | | self.sd_categoryManager.horizontalEdgeInset = horizontalEdgeInset; |
| | | } |
| | | |
| | | - (CGFloat)horizontalEdgeInset |
| | | { |
| | | return self.sd_categoryManager.horizontalEdgeInset; |
| | | } |
| | | |
| | | - (void)setVerticalEdgeInset:(CGFloat)verticalEdgeInset |
| | | { |
| | | self.sd_categoryManager.verticalEdgeInset = verticalEdgeInset; |
| | | } |
| | | |
| | | - (CGFloat)verticalEdgeInset |
| | | { |
| | | return self.sd_categoryManager.verticalEdgeInset; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation UIScrollView (SDAutoContentSize) |
| | | |
| | | - (void)setupAutoContentSizeWithBottomView:(UIView *)bottomView bottomMargin:(CGFloat)bottomMargin |
| | | { |
| | | [self setupAutoHeightWithBottomView:bottomView bottomMargin:bottomMargin]; |
| | | } |
| | | |
| | | - (void)setupAutoContentSizeWithRightView:(UIView *)rightView rightMargin:(CGFloat)rightMargin |
| | | { |
| | | if (!rightView) return; |
| | | |
| | | self.sd_rightViewsArray = @[rightView]; |
| | | self.sd_rightViewRightMargin = rightMargin; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation UILabel (SDLabelAutoResize) |
| | | |
| | | + (void)load |
| | | { |
| | | static dispatch_once_t onceToken; |
| | | dispatch_once(&onceToken, ^{ |
| | | |
| | | NSArray *selStringsArray = @[@"setText:"]; |
| | | |
| | | [selStringsArray enumerateObjectsUsingBlock:^(NSString *selString, NSUInteger idx, BOOL *stop) { |
| | | NSString *mySelString = [@"sd_" stringByAppendingString:selString]; |
| | | |
| | | Method originalMethod = class_getInstanceMethod(self, NSSelectorFromString(selString)); |
| | | Method myMethod = class_getInstanceMethod(self, NSSelectorFromString(mySelString)); |
| | | method_exchangeImplementations(originalMethod, myMethod); |
| | | }]; |
| | | }); |
| | | } |
| | | |
| | | - (void)sd_setText:(NSString *)text |
| | | { |
| | | // 妿ç¨åºå´©æºå¨è¿è¡ä»£ç è¯´ææ¯ä½ çlabel卿§è¡âsetTextâæ¹æ³æ¶åºäºé®é¢è䏿¯å¨æ¤èªå¨å¸å±åºå
é¨åºç°äºé®é¢ï¼è¯·æ£æ¥ä½ çâsetTextâæ¹æ³ |
| | | [self sd_setText:text]; |
| | | |
| | | |
| | | if (self.sd_maxWidth) { |
| | | [self sizeToFit]; |
| | | } else if (self.autoHeightRatioValue) { |
| | | self.size_sd = CGSizeZero; |
| | | } |
| | | } |
| | | |
| | | - (BOOL)isAttributedContent |
| | | { |
| | | return [objc_getAssociatedObject(self, _cmd) boolValue]; |
| | | } |
| | | |
| | | - (void)setIsAttributedContent:(BOOL)isAttributedContent |
| | | { |
| | | objc_setAssociatedObject(self, @selector(isAttributedContent), @(isAttributedContent), OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | - (void)setSingleLineAutoResizeWithMaxWidth:(CGFloat)maxWidth |
| | | { |
| | | self.sd_maxWidth = @(maxWidth); |
| | | } |
| | | |
| | | - (void)setMaxNumberOfLinesToShow:(NSInteger)lineCount |
| | | { |
| | | NSAssert(self.ownLayoutModel, @"请å¨å¸å±å®æä¹åå忤æ¥è®¾ç½®ï¼"); |
| | | if (lineCount > 0) { |
| | | self.sd_layout.maxHeightIs(self.font.lineHeight * lineCount + 0.1); |
| | | } else { |
| | | self.sd_layout.maxHeightIs(MAXFLOAT); |
| | | } |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation UIButton (SDExtention) |
| | | |
| | | - (void)setupAutoSizeWithHorizontalPadding:(CGFloat)hPadding buttonHeight:(CGFloat)buttonHeight |
| | | { |
| | | self.fixedHeight = @(buttonHeight); |
| | | |
| | | self.titleLabel.sd_layout |
| | | .leftSpaceToView(self, hPadding) |
| | | .topEqualToView(self) |
| | | .heightIs(buttonHeight); |
| | | |
| | | [self.titleLabel setSingleLineAutoResizeWithMaxWidth:MAXFLOAT]; |
| | | [self setupAutoWidthWithRightView:self.titleLabel rightMargin:hPadding]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation SDAutoLayoutModelItem |
| | | |
| | | - (instancetype)init |
| | | { |
| | | if (self = [super init]) { |
| | | _offset = 0; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation UIView (SDAutoLayout) |
| | | |
| | | + (void)load |
| | | { |
| | | static dispatch_once_t onceToken; |
| | | dispatch_once(&onceToken, ^{ |
| | | |
| | | NSArray *selStringsArray = @[@"layoutSubviews"]; |
| | | |
| | | [selStringsArray enumerateObjectsUsingBlock:^(NSString *selString, NSUInteger idx, BOOL *stop) { |
| | | NSString *mySelString = [@"sd_" stringByAppendingString:selString]; |
| | | |
| | | Method originalMethod = class_getInstanceMethod(self, NSSelectorFromString(selString)); |
| | | Method myMethod = class_getInstanceMethod(self, NSSelectorFromString(mySelString)); |
| | | method_exchangeImplementations(originalMethod, myMethod); |
| | | }]; |
| | | }); |
| | | } |
| | | |
| | | #pragma mark - properties |
| | | |
| | | - (NSMutableArray *)autoLayoutModelsArray |
| | | { |
| | | if (!objc_getAssociatedObject(self, _cmd)) { |
| | | objc_setAssociatedObject(self, _cmd, [NSMutableArray array], OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (NSNumber *)fixedWidth |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setFixedWidth:(NSNumber *)fixedWidth |
| | | { |
| | | if (fixedWidth) { |
| | | self.width_sd = [fixedWidth floatValue]; |
| | | } |
| | | objc_setAssociatedObject(self, @selector(fixedWidth), fixedWidth, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | - (NSNumber *)fixedHeight |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setFixedHeight:(NSNumber *)fixedHeight |
| | | { |
| | | if (fixedHeight) { |
| | | self.height_sd = [fixedHeight floatValue]; |
| | | } |
| | | objc_setAssociatedObject(self, @selector(fixedHeight), fixedHeight, OBJC_ASSOCIATION_RETAIN); |
| | | } |
| | | |
| | | - (NSNumber *)autoHeightRatioValue |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setAutoHeightRatioValue:(NSNumber *)autoHeightRatioValue |
| | | { |
| | | objc_setAssociatedObject(self, @selector(autoHeightRatioValue), autoHeightRatioValue, OBJC_ASSOCIATION_RETAIN); |
| | | } |
| | | |
| | | - (NSNumber *)autoWidthRatioValue |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setAutoWidthRatioValue:(NSNumber *)autoWidthRatioValue |
| | | { |
| | | objc_setAssociatedObject(self, @selector(autoWidthRatioValue), autoWidthRatioValue, OBJC_ASSOCIATION_RETAIN); |
| | | } |
| | | |
| | | - (NSNumber *)sd_maxWidth |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setSd_maxWidth:(NSNumber *)sd_maxWidth |
| | | { |
| | | objc_setAssociatedObject(self, @selector(sd_maxWidth), sd_maxWidth, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | |
| | | - (void)useCellFrameCacheWithIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableview |
| | | { |
| | | self.sd_indexPath = indexPath; |
| | | self.sd_tableView = tableview; |
| | | } |
| | | |
| | | - (UITableView *)sd_tableView |
| | | { |
| | | return self.sd_categoryManager.sd_tableView; |
| | | } |
| | | |
| | | - (void)setSd_tableView:(UITableView *)sd_tableView |
| | | { |
| | | if ([self isKindOfClass:[UITableViewCell class]]) { |
| | | [(UITableViewCell *)self contentView].sd_tableView = sd_tableView; |
| | | } |
| | | self.sd_categoryManager.sd_tableView = sd_tableView; |
| | | } |
| | | |
| | | - (NSIndexPath *)sd_indexPath |
| | | { |
| | | return self.sd_categoryManager.sd_indexPath; |
| | | } |
| | | |
| | | - (void)setSd_indexPath:(NSIndexPath *)sd_indexPath |
| | | { |
| | | if ([self isKindOfClass:[UITableViewCell class]]) { |
| | | [(UITableViewCell *)self contentView].sd_indexPath = sd_indexPath; |
| | | } |
| | | self.sd_categoryManager.sd_indexPath = sd_indexPath; |
| | | } |
| | | |
| | | - (SDAutoLayoutModel *)ownLayoutModel |
| | | { |
| | | return objc_getAssociatedObject(self, _cmd); |
| | | } |
| | | |
| | | - (void)setOwnLayoutModel:(SDAutoLayoutModel *)ownLayoutModel |
| | | { |
| | | objc_setAssociatedObject(self, @selector(ownLayoutModel), ownLayoutModel, OBJC_ASSOCIATION_RETAIN); |
| | | } |
| | | |
| | | - (SDAutoLayoutModel *)sd_layout |
| | | { |
| | | |
| | | #ifdef SDDebugWithAssert |
| | | /* |
| | | å¡å¨è¿é说æä½ çè¦èªå¨å¸å±çview卿²¡ææ·»å å°ç¶viewçæ
åµä¸å°±å¼å§è®¾ç½®å¸å±,ä½ éè¦è¿æ ·ï¼ |
| | | 1. UIView *view = [UIView new]; |
| | | 2. [superView addSubview:view]; |
| | | 3. view.sd_layout |
| | | .leftEqualToView()... |
| | | */ |
| | | NSAssert(self.superview, @">>>>>>>>>å¨å å
¥ç¶viewä¹åæå¯ä»¥åèªå¨å¸å±è®¾ç½®"); |
| | | |
| | | #endif |
| | | |
| | | SDAutoLayoutModel *model = [self ownLayoutModel]; |
| | | if (!model) { |
| | | model = [SDAutoLayoutModel new]; |
| | | model.needsAutoResizeView = self; |
| | | [self setOwnLayoutModel:model]; |
| | | [self.superview.autoLayoutModelsArray addObject:model]; |
| | | } |
| | | |
| | | return model; |
| | | } |
| | | |
| | | - (SDAutoLayoutModel *)sd_resetLayout |
| | | { |
| | | /* |
| | | * æ¹æ¡å¾
å® |
| | | [self sd_clearAutoLayoutSettings]; |
| | | return [self sd_layout]; |
| | | */ |
| | | |
| | | SDAutoLayoutModel *model = [self ownLayoutModel]; |
| | | SDAutoLayoutModel *newModel = [SDAutoLayoutModel new]; |
| | | newModel.needsAutoResizeView = self; |
| | | [self sd_clearViewFrameCache]; |
| | | NSInteger index = 0; |
| | | if (model) { |
| | | index = [self.superview.autoLayoutModelsArray indexOfObject:model]; |
| | | [self.superview.autoLayoutModelsArray replaceObjectAtIndex:index withObject:newModel]; |
| | | } else { |
| | | [self.superview.autoLayoutModelsArray addObject:newModel]; |
| | | } |
| | | [self setOwnLayoutModel:newModel]; |
| | | [self sd_clearExtraAutoLayoutItems]; |
| | | return newModel; |
| | | } |
| | | |
| | | - (SDAutoLayoutModel *)sd_resetNewLayout |
| | | { |
| | | [self sd_clearAutoLayoutSettings]; |
| | | [self sd_clearExtraAutoLayoutItems]; |
| | | return [self sd_layout]; |
| | | } |
| | | |
| | | - (BOOL)sd_isClosingAutoLayout |
| | | { |
| | | return self.sd_categoryManager.sd_isClosingAutoLayout; |
| | | } |
| | | |
| | | - (void)setSd_closeAutoLayout:(BOOL)sd_closeAutoLayout |
| | | { |
| | | self.sd_categoryManager.sd_closeAutoLayout = sd_closeAutoLayout; |
| | | } |
| | | |
| | | - (void)removeFromSuperviewAndClearAutoLayoutSettings |
| | | { |
| | | [self sd_clearAutoLayoutSettings]; |
| | | [self removeFromSuperview]; |
| | | } |
| | | |
| | | - (void)sd_clearAutoLayoutSettings |
| | | { |
| | | SDAutoLayoutModel *model = [self ownLayoutModel]; |
| | | if (model) { |
| | | [self.superview.autoLayoutModelsArray removeObject:model]; |
| | | [self setOwnLayoutModel:nil]; |
| | | } |
| | | [self sd_clearExtraAutoLayoutItems]; |
| | | } |
| | | |
| | | - (void)sd_clearExtraAutoLayoutItems |
| | | { |
| | | if (self.autoHeightRatioValue) { |
| | | self.autoHeightRatioValue = nil; |
| | | } |
| | | self.fixedHeight = nil; |
| | | self.fixedWidth = nil; |
| | | } |
| | | |
| | | - (void)sd_clearViewFrameCache |
| | | { |
| | | self.frame = CGRectZero; |
| | | } |
| | | |
| | | - (void)sd_clearSubviewsAutoLayoutFrameCaches |
| | | { |
| | | if (self.sd_tableView && self.sd_indexPath) { |
| | | [self.sd_tableView.cellAutoHeightManager clearHeightCacheOfIndexPaths:@[self.sd_indexPath]]; |
| | | return; |
| | | } |
| | | |
| | | if (self.autoLayoutModelsArray.count == 0) return; |
| | | |
| | | [self.autoLayoutModelsArray enumerateObjectsUsingBlock:^(SDAutoLayoutModel *model, NSUInteger idx, BOOL *stop) { |
| | | model.needsAutoResizeView.frame = CGRectZero; |
| | | }]; |
| | | } |
| | | |
| | | - (void)sd_layoutSubviews |
| | | { |
| | | // 妿ç¨åºå´©æºå¨è¿è¡ä»£ç è¯´ææ¯ä½ çview卿§è¡âlayoutSubviesâæ¹æ³æ¶åºäºé®é¢è䏿¯å¨æ¤èªå¨å¸å±åºå
é¨åºç°äºé®é¢ï¼è¯·æ£æ¥ä½ çâlayoutSubviesâæ¹æ³ |
| | | [self sd_layoutSubviews]; |
| | | |
| | | [self sd_layoutSubviewsHandle]; |
| | | } |
| | | |
| | | - (void)sd_layoutSubviewsHandle{ |
| | | |
| | | if (self.sd_equalWidthSubviews.count) { |
| | | __block CGFloat totalMargin = 0; |
| | | [self.sd_equalWidthSubviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) { |
| | | SDAutoLayoutModel *model = view.sd_layout; |
| | | CGFloat left = model.left ? [model.left.value floatValue] : model.needsAutoResizeView.left_sd; |
| | | totalMargin += (left + [model.right.value floatValue]); |
| | | }]; |
| | | CGFloat averageWidth = (self.width_sd - totalMargin) / self.sd_equalWidthSubviews.count; |
| | | [self.sd_equalWidthSubviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) { |
| | | view.width_sd = averageWidth; |
| | | view.fixedWidth = @(averageWidth); |
| | | }]; |
| | | } |
| | | |
| | | if (self.sd_categoryManager.flowItems.count && (self.sd_categoryManager.lastWidth != self.width_sd)) { |
| | | |
| | | self.sd_categoryManager.lastWidth = self.width_sd; |
| | | |
| | | NSInteger perRowItemsCount = self.sd_categoryManager.perRowItemsCount; |
| | | CGFloat horizontalMargin = 0; |
| | | CGFloat w = 0; |
| | | if (self.sd_categoryManager.shouldShowAsAutoMarginViews) { |
| | | w = self.sd_categoryManager.flowItemWidth; |
| | | long itemsCount = self.sd_categoryManager.perRowItemsCount; |
| | | if (itemsCount > 1) { |
| | | horizontalMargin = (self.width_sd - (self.horizontalEdgeInset * 2) - itemsCount * w) / (itemsCount - 1); |
| | | } |
| | | } else { |
| | | horizontalMargin = self.sd_categoryManager.horizontalMargin; |
| | | w = (self.width_sd - (self.horizontalEdgeInset * 2) - (perRowItemsCount - 1) * horizontalMargin) / perRowItemsCount; |
| | | } |
| | | CGFloat verticalMargin = self.sd_categoryManager.verticalMargin; |
| | | |
| | | __block UIView *referencedView = self; |
| | | [self.sd_categoryManager.flowItems enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) { |
| | | if (idx < perRowItemsCount) { |
| | | if (idx == 0) { |
| | | /* ä¿ç |
| | | BOOL shouldShowAsAutoMarginViews = self.sd_categoryManager.shouldShowAsAutoMarginViews; |
| | | */ |
| | | view.sd_layout |
| | | .leftSpaceToView(referencedView, self.horizontalEdgeInset) |
| | | .topSpaceToView(referencedView, self.verticalEdgeInset) |
| | | .widthIs(w); |
| | | } else { |
| | | view.sd_layout |
| | | .leftSpaceToView(referencedView, horizontalMargin) |
| | | .topEqualToView(referencedView) |
| | | .widthIs(w); |
| | | } |
| | | referencedView = view; |
| | | } else { |
| | | referencedView = self.sd_categoryManager.flowItems[idx - perRowItemsCount]; |
| | | view.sd_layout |
| | | .leftEqualToView(referencedView) |
| | | .widthIs(w) |
| | | .topSpaceToView(referencedView, verticalMargin); |
| | | } |
| | | }]; |
| | | } |
| | | |
| | | if (self.autoLayoutModelsArray.count) { |
| | | |
| | | NSMutableArray *caches = nil; |
| | | |
| | | if ([self isKindOfClass:cellContVClass()] && self.sd_tableView) { |
| | | caches = [self.sd_tableView.cellAutoHeightManager subviewFrameCachesWithIndexPath:self.sd_indexPath]; |
| | | } |
| | | |
| | | [self.autoLayoutModelsArray enumerateObjectsUsingBlock:^(SDAutoLayoutModel *model, NSUInteger idx, BOOL *stop) { |
| | | if (idx < caches.count) { |
| | | CGRect originalFrame = model.needsAutoResizeView.frame; |
| | | CGRect newFrame = [[caches objectAtIndex:idx] CGRectValue]; |
| | | if (CGRectEqualToRect(originalFrame, newFrame)) { |
| | | [model.needsAutoResizeView setNeedsLayout]; |
| | | } else { |
| | | model.needsAutoResizeView.frame = newFrame; |
| | | } |
| | | [self setupCornerRadiusWithView:model.needsAutoResizeView model:model]; |
| | | model.needsAutoResizeView.sd_categoryManager.hasSetFrameWithCache = YES; |
| | | } else { |
| | | if (model.needsAutoResizeView.sd_categoryManager.hasSetFrameWithCache) { |
| | | model.needsAutoResizeView.sd_categoryManager.hasSetFrameWithCache = NO; |
| | | } |
| | | [self sd_resizeWithModel:model]; |
| | | } |
| | | }]; |
| | | } |
| | | |
| | | if (self.tag == kSDModelCellTag && [self isKindOfClass:cellContVClass()]) { |
| | | UITableViewCell *cell = (UITableViewCell *)(self.superview); |
| | | |
| | | while (cell && ![cell isKindOfClass:[UITableViewCell class]]) { |
| | | cell = (UITableViewCell *)cell.superview; |
| | | } |
| | | |
| | | if ([cell isKindOfClass:[UITableViewCell class]]) { |
| | | CGFloat height = 0; |
| | | for (UIView *view in cell.sd_bottomViewsArray) { |
| | | height = MAX(height, view.bottom_sd); |
| | | } |
| | | cell.autoHeight = height + cell.sd_bottomViewBottomMargin; |
| | | } |
| | | } else if (![self isKindOfClass:[UITableViewCell class]] && (self.sd_bottomViewsArray.count || self.sd_rightViewsArray.count)) { |
| | | if (self.sd_categoryManager.hasSetFrameWithCache) { |
| | | self.sd_categoryManager.hasSetFrameWithCache = NO; |
| | | return; |
| | | } |
| | | CGFloat contentHeight = 0; |
| | | CGFloat contentWidth = 0; |
| | | if (self.sd_bottomViewsArray) { |
| | | CGFloat height = 0; |
| | | for (UIView *view in self.sd_bottomViewsArray) { |
| | | height = MAX(height, view.bottom_sd); |
| | | } |
| | | contentHeight = height + self.sd_bottomViewBottomMargin; |
| | | } |
| | | if (self.sd_rightViewsArray) { |
| | | CGFloat width = 0; |
| | | for (UIView *view in self.sd_rightViewsArray) { |
| | | width = MAX(width, view.right_sd); |
| | | } |
| | | contentWidth = width + self.sd_rightViewRightMargin; |
| | | } |
| | | if ([self isKindOfClass:[UIScrollView class]]) { |
| | | UIScrollView *scrollView = (UIScrollView *)self; |
| | | CGSize contentSize = scrollView.contentSize; |
| | | if (contentHeight > 0) { |
| | | contentSize.height = contentHeight; |
| | | } |
| | | if (contentWidth > 0) { |
| | | contentSize.width = contentWidth; |
| | | } |
| | | if (contentSize.width <= 0) { |
| | | contentSize.width = scrollView.width_sd; |
| | | } |
| | | if (!CGSizeEqualToSize(contentSize, scrollView.contentSize)) { |
| | | scrollView.contentSize = contentSize; |
| | | } |
| | | } else { |
| | | // 妿è¿éåºç°å¾ªç¯è°ç¨æ
åµè¯·ædemoåéå°gsdios@126.comï¼è°¢è°¢é
åã |
| | | if (self.sd_bottomViewsArray.count && (floorf(contentHeight) != floorf(self.height_sd))) { |
| | | self.height_sd = contentHeight; |
| | | self.fixedHeight = @(self.height_sd); |
| | | } |
| | | |
| | | if (self.sd_rightViewsArray.count && (floorf(contentWidth) != floorf(self.width_sd))) { |
| | | self.width_sd = contentWidth; |
| | | self.fixedWidth = @(self.width_sd); |
| | | } |
| | | } |
| | | |
| | | SDAutoLayoutModel *model = self.ownLayoutModel; |
| | | |
| | | if (![self isKindOfClass:[UIScrollView class]] && self.sd_rightViewsArray.count && (model.right || model.equalRight || model.centerX || model.equalCenterX)) { |
| | | self.fixedWidth = @(self.width); |
| | | if (model.right || model.equalRight) { |
| | | [self layoutRightWithView:self model:model]; |
| | | } else { |
| | | [self layoutLeftWithView:self model:model]; |
| | | } |
| | | self.fixedWidth = nil; |
| | | } |
| | | |
| | | if (![self isKindOfClass:[UIScrollView class]] && self.sd_bottomViewsArray.count && (model.bottom || model.equalBottom || model.centerY || model.equalCenterY)) { |
| | | self.fixedHeight = @(self.height); |
| | | if (model.bottom || model.equalBottom) { |
| | | [self layoutBottomWithView:self model:model]; |
| | | } else { |
| | | [self layoutTopWithView:self model:model]; |
| | | } |
| | | self.fixedHeight = nil; |
| | | } |
| | | |
| | | if (self.didFinishAutoLayoutBlock) { |
| | | self.didFinishAutoLayoutBlock(self.frame); |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)sd_resizeWithModel:(SDAutoLayoutModel *)model |
| | | { |
| | | UIView *view = model.needsAutoResizeView; |
| | | |
| | | if (!view || view.sd_isClosingAutoLayout) return; |
| | | |
| | | if (view.sd_maxWidth && (model.rightSpaceToView || model.rightEqualToView)) { // é å³å¸å±åæè®¾ç½® |
| | | [self layoutAutoWidthWidthView:view model:model]; |
| | | view.fixedWidth = @(view.width_sd); |
| | | } |
| | | |
| | | [self layoutWidthWithView:view model:model]; |
| | | |
| | | [self layoutHeightWithView:view model:model]; |
| | | |
| | | [self layoutLeftWithView:view model:model]; |
| | | |
| | | [self layoutRightWithView:view model:model]; |
| | | |
| | | if (view.autoHeightRatioValue && view.width_sd > 0 && (model.bottomEqualToView || model.bottomSpaceToView)) { // åºé¨å¸å±åæè®¾ç½® |
| | | [self layoutAutoHeightWidthView:view model:model]; |
| | | view.fixedHeight = @(view.height_sd); |
| | | } |
| | | |
| | | if (view.autoWidthRatioValue) { |
| | | view.fixedWidth = @(view.height_sd * [view.autoWidthRatioValue floatValue]); |
| | | } |
| | | |
| | | |
| | | [self layoutTopWithView:view model:model]; |
| | | |
| | | [self layoutBottomWithView:view model:model]; |
| | | |
| | | if (view.sd_maxWidth) { |
| | | [self layoutAutoWidthWidthView:view model:model]; |
| | | } |
| | | |
| | | if (model.maxWidth && [model.maxWidth floatValue] < view.width_sd) { |
| | | view.width_sd = [model.maxWidth floatValue]; |
| | | } |
| | | |
| | | if (model.minWidth && [model.minWidth floatValue] > view.width_sd) { |
| | | view.width_sd = [model.minWidth floatValue]; |
| | | } |
| | | |
| | | if (view.autoHeightRatioValue && view.width_sd > 0) { |
| | | [self layoutAutoHeightWidthView:view model:model]; |
| | | } |
| | | |
| | | if (model.maxHeight && [model.maxHeight floatValue] < view.height_sd) { |
| | | view.height_sd = [model.maxHeight floatValue]; |
| | | } |
| | | |
| | | if (model.minHeight && [model.minHeight floatValue] > view.height_sd) { |
| | | view.height_sd = [model.minHeight floatValue]; |
| | | } |
| | | |
| | | if (model.widthEqualHeight) { |
| | | view.width_sd = view.height_sd; |
| | | } |
| | | |
| | | if (model.heightEqualWidth) { |
| | | view.height_sd = view.width_sd; |
| | | } |
| | | |
| | | if (view.didFinishAutoLayoutBlock) { |
| | | view.didFinishAutoLayoutBlock(view.frame); |
| | | } |
| | | |
| | | if (view.sd_bottomViewsArray.count || view.sd_rightViewsArray.count) { |
| | | [view layoutSubviews]; |
| | | } |
| | | |
| | | [self setupCornerRadiusWithView:view model:model]; |
| | | } |
| | | |
| | | - (void)layoutAutoHeightWidthView:(UIView *)view model:(SDAutoLayoutModel *)model |
| | | { |
| | | if ([view.autoHeightRatioValue floatValue] > 0) { |
| | | view.height_sd = view.width_sd * [view.autoHeightRatioValue floatValue]; |
| | | } else { |
| | | if ([view isKindOfClass:[UILabel class]]) { |
| | | UILabel *label = (UILabel *)view; |
| | | label.numberOfLines = 0; |
| | | if (label.text.length) { |
| | | if (!label.isAttributedContent) { |
| | | CGRect rect = [label.text boundingRectWithSize:CGSizeMake(label.width_sd, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : label.font} context:nil]; |
| | | label.height_sd = rect.size.height + 0.1; |
| | | } else { |
| | | [label sizeToFit]; |
| | | } |
| | | } else { |
| | | label.height_sd = 0; |
| | | } |
| | | } else { |
| | | view.height_sd = 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)layoutAutoWidthWidthView:(UIView *)view model:(SDAutoLayoutModel *)model |
| | | { |
| | | if ([view isKindOfClass:[UILabel class]]) { |
| | | UILabel *label = (UILabel *)view; |
| | | CGFloat width = [view.sd_maxWidth floatValue] > 0 ? [view.sd_maxWidth floatValue] : MAXFLOAT; |
| | | label.numberOfLines = 1; |
| | | if (label.text.length) { |
| | | if (!label.isAttributedContent) { |
| | | CGRect rect = [label.text boundingRectWithSize:CGSizeMake(MAXFLOAT, label.height_sd) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : label.font} context:nil]; |
| | | if (rect.size.width > width) { |
| | | rect.size.width = width; |
| | | } |
| | | label.width_sd = rect.size.width + 0.1; |
| | | } else{ |
| | | [label sizeToFit]; |
| | | if (label.width_sd > width) { |
| | | label.width_sd = width; |
| | | } |
| | | } |
| | | } else { |
| | | label.size_sd = CGSizeZero; |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)layoutWidthWithView:(UIView *)view model:(SDAutoLayoutModel *)model |
| | | { |
| | | if (model.width) { |
| | | view.width_sd = [model.width.value floatValue]; |
| | | view.fixedWidth = @(view.width_sd); |
| | | } else if (model.ratio_width) { |
| | | view.width_sd = model.ratio_width.refView.width_sd * [model.ratio_width.value floatValue]; |
| | | view.fixedWidth = @(view.width_sd); |
| | | } |
| | | } |
| | | |
| | | - (void)layoutHeightWithView:(UIView *)view model:(SDAutoLayoutModel *)model |
| | | { |
| | | if (model.height) { |
| | | view.height_sd = [model.height.value floatValue]; |
| | | view.fixedHeight = @(view.height_sd); |
| | | } else if (model.ratio_height) { |
| | | view.height_sd = [model.ratio_height.value floatValue] * model.ratio_height.refView.height_sd; |
| | | view.fixedHeight = @(view.height_sd); |
| | | } |
| | | } |
| | | |
| | | - (void)layoutLeftWithView:(UIView *)view model:(SDAutoLayoutModel *)model |
| | | { |
| | | if (model.left) { |
| | | if (view.superview == model.left.refView) { |
| | | if (!view.fixedWidth) { // view.autoLeft && view.autoRight |
| | | view.width_sd = view.right_sd - [model.left.value floatValue]; |
| | | } |
| | | view.left_sd = [model.left.value floatValue]; |
| | | } else { |
| | | if (model.left.refViewsArray.count) { |
| | | CGFloat lastRefRight = 0; |
| | | for (UIView *ref in model.left.refViewsArray) { |
| | | if ([ref isKindOfClass:[UIView class]] && ref.right_sd > lastRefRight) { |
| | | model.left.refView = ref; |
| | | lastRefRight = ref.right_sd; |
| | | } |
| | | } |
| | | } |
| | | if (!view.fixedWidth) { // view.autoLeft && view.autoRight |
| | | view.width_sd = view.right_sd - model.left.refView.right_sd - [model.left.value floatValue]; |
| | | } |
| | | view.left_sd = model.left.refView.right_sd + [model.left.value floatValue]; |
| | | } |
| | | |
| | | } else if (model.equalLeft) { |
| | | if (!view.fixedWidth) { |
| | | if (model.needsAutoResizeView == view.superview) { |
| | | view.width_sd = view.right_sd - (0 + model.equalLeft.offset); |
| | | } else { |
| | | view.width_sd = view.right_sd - (model.equalLeft.refView.left_sd + model.equalLeft.offset); |
| | | } |
| | | } |
| | | if (view.superview == model.equalLeft.refView) { |
| | | view.left_sd = 0 + model.equalLeft.offset; |
| | | } else { |
| | | view.left_sd = model.equalLeft.refView.left_sd + model.equalLeft.offset; |
| | | } |
| | | } else if (model.equalCenterX) { |
| | | if (view.superview == model.equalCenterX.refView) { |
| | | view.centerX_sd = model.equalCenterX.refView.width_sd * 0.5 + model.equalCenterX.offset; |
| | | } else { |
| | | view.centerX_sd = model.equalCenterX.refView.centerX_sd + model.equalCenterX.offset; |
| | | } |
| | | } else if (model.centerX) { |
| | | view.centerX_sd = [model.centerX floatValue]; |
| | | } |
| | | } |
| | | |
| | | - (void)layoutRightWithView:(UIView *)view model:(SDAutoLayoutModel *)model |
| | | { |
| | | if (model.right) { |
| | | if (view.superview == model.right.refView) { |
| | | if (!view.fixedWidth) { // view.autoLeft && view.autoRight |
| | | view.width_sd = model.right.refView.width_sd - view.left_sd - [model.right.value floatValue]; |
| | | } |
| | | view.right_sd = model.right.refView.width_sd - [model.right.value floatValue]; |
| | | } else { |
| | | if (!view.fixedWidth) { // view.autoLeft && view.autoRight |
| | | view.width_sd = model.right.refView.left_sd - view.left_sd - [model.right.value floatValue]; |
| | | } |
| | | view.right_sd = model.right.refView.left_sd - [model.right.value floatValue]; |
| | | } |
| | | } else if (model.equalRight) { |
| | | if (!view.fixedWidth) { |
| | | if (model.equalRight.refView == view.superview) { |
| | | view.width_sd = model.equalRight.refView.width_sd - view.left_sd + model.equalRight.offset; |
| | | } else { |
| | | view.width_sd = model.equalRight.refView.right_sd - view.left_sd + model.equalRight.offset; |
| | | } |
| | | } |
| | | |
| | | view.right_sd = model.equalRight.refView.right_sd + model.equalRight.offset; |
| | | if (view.superview == model.equalRight.refView) { |
| | | view.right_sd = model.equalRight.refView.width_sd + model.equalRight.offset; |
| | | } |
| | | |
| | | } |
| | | } |
| | | |
| | | - (void)layoutTopWithView:(UIView *)view model:(SDAutoLayoutModel *)model |
| | | { |
| | | if (model.top) { |
| | | if (view.superview == model.top.refView) { |
| | | if (!view.fixedHeight) { // view.autoTop && view.autoBottom && view.bottom |
| | | view.height_sd = view.bottom_sd - [model.top.value floatValue]; |
| | | } |
| | | view.top_sd = [model.top.value floatValue]; |
| | | } else { |
| | | if (model.top.refViewsArray.count) { |
| | | CGFloat lastRefBottom = 0; |
| | | for (UIView *ref in model.top.refViewsArray) { |
| | | if ([ref isKindOfClass:[UIView class]] && ref.bottom_sd > lastRefBottom) { |
| | | model.top.refView = ref; |
| | | lastRefBottom = ref.bottom_sd; |
| | | } |
| | | } |
| | | } |
| | | if (!view.fixedHeight) { // view.autoTop && view.autoBottom && view.bottom |
| | | view.height_sd = view.bottom_sd - model.top.refView.bottom_sd - [model.top.value floatValue]; |
| | | } |
| | | view.top_sd = model.top.refView.bottom_sd + [model.top.value floatValue]; |
| | | } |
| | | } else if (model.equalTop) { |
| | | if (view.superview == model.equalTop.refView) { |
| | | if (!view.fixedHeight) { |
| | | view.height_sd = view.bottom_sd - model.equalTop.offset; |
| | | } |
| | | view.top_sd = 0 + model.equalTop.offset; |
| | | } else { |
| | | if (!view.fixedHeight) { |
| | | view.height_sd = view.bottom_sd - (model.equalTop.refView.top_sd + model.equalTop.offset); |
| | | } |
| | | view.top_sd = model.equalTop.refView.top_sd + model.equalTop.offset; |
| | | } |
| | | } else if (model.equalCenterY) { |
| | | if (view.superview == model.equalCenterY.refView) { |
| | | view.centerY_sd = model.equalCenterY.refView.height_sd * 0.5 + model.equalCenterY.offset; |
| | | } else { |
| | | view.centerY_sd = model.equalCenterY.refView.centerY_sd + model.equalCenterY.offset; |
| | | } |
| | | } else if (model.centerY) { |
| | | view.centerY_sd = [model.centerY floatValue]; |
| | | } |
| | | } |
| | | |
| | | - (void)layoutBottomWithView:(UIView *)view model:(SDAutoLayoutModel *)model |
| | | { |
| | | if (model.bottom) { |
| | | if (view.superview == model.bottom.refView) { |
| | | if (!view.fixedHeight) { |
| | | view.height_sd = view.superview.height_sd - view.top_sd - [model.bottom.value floatValue]; |
| | | } |
| | | view.bottom_sd = model.bottom.refView.height_sd - [model.bottom.value floatValue]; |
| | | } else { |
| | | if (!view.fixedHeight) { |
| | | view.height_sd = model.bottom.refView.top_sd - view.top_sd - [model.bottom.value floatValue]; |
| | | } |
| | | view.bottom_sd = model.bottom.refView.top_sd - [model.bottom.value floatValue]; |
| | | } |
| | | |
| | | } else if (model.equalBottom) { |
| | | if (view.superview == model.equalBottom.refView) { |
| | | if (!view.fixedHeight) { |
| | | view.height_sd = view.superview.height_sd - view.top_sd + model.equalBottom.offset; |
| | | } |
| | | view.bottom_sd = model.equalBottom.refView.height_sd + model.equalBottom.offset; |
| | | } else { |
| | | if (!view.fixedHeight) { |
| | | view.height_sd = model.equalBottom.refView.bottom_sd - view.top_sd + model.equalBottom.offset; |
| | | } |
| | | view.bottom_sd = model.equalBottom.refView.bottom_sd + model.equalBottom.offset; |
| | | } |
| | | } |
| | | if (model.widthEqualHeight && !view.fixedHeight) { |
| | | [self layoutRightWithView:view model:model]; |
| | | } |
| | | } |
| | | |
| | | |
| | | - (void)setupCornerRadiusWithView:(UIView *)view model:(SDAutoLayoutModel *)model |
| | | { |
| | | CGFloat cornerRadius = view.layer.cornerRadius; |
| | | CGFloat newCornerRadius = 0; |
| | | |
| | | if (view.sd_cornerRadius && (cornerRadius != [view.sd_cornerRadius floatValue])) { |
| | | newCornerRadius = [view.sd_cornerRadius floatValue]; |
| | | } else if (view.sd_cornerRadiusFromWidthRatio && (cornerRadius != [view.sd_cornerRadiusFromWidthRatio floatValue] * view.width_sd)) { |
| | | newCornerRadius = view.width_sd * [view.sd_cornerRadiusFromWidthRatio floatValue]; |
| | | } else if (view.sd_cornerRadiusFromHeightRatio && (cornerRadius != view.height_sd * [view.sd_cornerRadiusFromHeightRatio floatValue])) { |
| | | newCornerRadius = view.height_sd * [view.sd_cornerRadiusFromHeightRatio floatValue]; |
| | | } |
| | | |
| | | if (newCornerRadius > 0) { |
| | | view.layer.cornerRadius = newCornerRadius; |
| | | view.clipsToBounds = YES; |
| | | } |
| | | } |
| | | |
| | | - (void)addAutoLayoutModel:(SDAutoLayoutModel *)model |
| | | { |
| | | [self.autoLayoutModelsArray addObject:model]; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation UIButton (SDAutoLayoutButton) |
| | | |
| | | + (void)load |
| | | { |
| | | static dispatch_once_t onceToken; |
| | | dispatch_once(&onceToken, ^{ |
| | | NSString *selString = @"layoutSubviews"; |
| | | NSString *mySelString = [@"sd_button_" stringByAppendingString:selString]; |
| | | |
| | | Method originalMethod = class_getInstanceMethod(self, NSSelectorFromString(selString)); |
| | | Method myMethod = class_getInstanceMethod(self, NSSelectorFromString(mySelString)); |
| | | method_exchangeImplementations(originalMethod, myMethod); |
| | | }); |
| | | } |
| | | |
| | | - (void)sd_button_layoutSubviews |
| | | { |
| | | // 妿ç¨åºå´©æºå¨è¿è¡ä»£ç è¯´ææ¯ä½ çview卿§è¡âlayoutSubviesâæ¹æ³æ¶åºäºé®é¢è䏿¯å¨æ¤èªå¨å¸å±åºå
é¨åºç°äºé®é¢ï¼è¯·æ£æ¥ä½ çâlayoutSubviesâæ¹æ³ |
| | | [self sd_button_layoutSubviews]; |
| | | |
| | | [self sd_layoutSubviewsHandle]; |
| | | |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation UIView (SDChangeFrame) |
| | | |
| | | - (BOOL)shouldReadjustFrameBeforeStoreCache |
| | | { |
| | | return self.sd_categoryManager.shouldReadjustFrameBeforeStoreCache; |
| | | } |
| | | |
| | | - (void)setShouldReadjustFrameBeforeStoreCache:(BOOL)shouldReadjustFrameBeforeStoreCache |
| | | { |
| | | self.sd_categoryManager.shouldReadjustFrameBeforeStoreCache = shouldReadjustFrameBeforeStoreCache; |
| | | } |
| | | |
| | | - (CGFloat)left_sd { |
| | | return self.frame.origin.x; |
| | | } |
| | | |
| | | - (void)setLeft_sd:(CGFloat)x_sd { |
| | | CGRect frame = self.frame; |
| | | frame.origin.x = x_sd; |
| | | self.frame = frame; |
| | | } |
| | | |
| | | - (CGFloat)top_sd { |
| | | return self.frame.origin.y; |
| | | } |
| | | |
| | | - (void)setTop_sd:(CGFloat)y_sd { |
| | | CGRect frame = self.frame; |
| | | frame.origin.y = y_sd; |
| | | self.frame = frame; |
| | | } |
| | | |
| | | - (CGFloat)right_sd { |
| | | return self.frame.origin.x + self.frame.size.width; |
| | | } |
| | | |
| | | - (void)setRight_sd:(CGFloat)right_sd { |
| | | CGRect frame = self.frame; |
| | | frame.origin.x = right_sd - frame.size.width; |
| | | self.frame = frame; |
| | | } |
| | | |
| | | - (CGFloat)bottom_sd { |
| | | return self.frame.origin.y + self.frame.size.height; |
| | | } |
| | | |
| | | - (void)setBottom_sd:(CGFloat)bottom_sd { |
| | | CGRect frame = self.frame; |
| | | frame.origin.y = bottom_sd - frame.size.height; |
| | | self.frame = frame; |
| | | } |
| | | |
| | | - (CGFloat)centerX_sd |
| | | { |
| | | return self.left_sd + self.width_sd * 0.5; |
| | | } |
| | | |
| | | - (void)setCenterX_sd:(CGFloat)centerX_sd |
| | | { |
| | | self.left_sd = centerX_sd - self.width_sd * 0.5; |
| | | } |
| | | |
| | | - (CGFloat)centerY_sd |
| | | { |
| | | return self.top_sd + self.height_sd * 0.5; |
| | | } |
| | | |
| | | - (void)setCenterY_sd:(CGFloat)centerY_sd |
| | | { |
| | | self.top_sd = centerY_sd - self.height_sd * 0.5; |
| | | } |
| | | |
| | | - (CGFloat)width_sd { |
| | | return self.frame.size.width; |
| | | } |
| | | |
| | | - (void)setWidth_sd:(CGFloat)width_sd { |
| | | if (self.ownLayoutModel.widthEqualHeight) { |
| | | if (width_sd != self.height_sd) return; |
| | | } |
| | | [self setWidth:width_sd]; |
| | | if (self.ownLayoutModel.heightEqualWidth) { |
| | | self.height_sd = width_sd; |
| | | } |
| | | } |
| | | |
| | | - (CGFloat)height_sd { |
| | | return self.frame.size.height; |
| | | } |
| | | |
| | | - (void)setHeight_sd:(CGFloat)height_sd { |
| | | if (self.ownLayoutModel.heightEqualWidth) { |
| | | if (height_sd != self.width_sd) return; |
| | | } |
| | | [self setHeight:height_sd]; |
| | | if (self.ownLayoutModel.widthEqualHeight) { |
| | | self.width_sd = height_sd; |
| | | } |
| | | } |
| | | |
| | | - (CGPoint)origin_sd { |
| | | return self.frame.origin; |
| | | } |
| | | |
| | | - (void)setOrigin_sd:(CGPoint)origin_sd { |
| | | CGRect frame = self.frame; |
| | | frame.origin = origin_sd; |
| | | self.frame = frame; |
| | | } |
| | | |
| | | - (CGSize)size_sd { |
| | | return self.frame.size; |
| | | } |
| | | |
| | | - (void)setSize_sd:(CGSize)size_sd { |
| | | [self setSize:size_sd]; |
| | | } |
| | | |
| | | - (void)setWidth:(CGFloat)width |
| | | { |
| | | CGRect frame = self.frame; |
| | | frame.size.width = width; |
| | | self.frame = frame; |
| | | } |
| | | |
| | | - (void)setHeight:(CGFloat)height { |
| | | CGRect frame = self.frame; |
| | | frame.size.height = height; |
| | | self.frame = frame; |
| | | } |
| | | |
| | | - (void)setSize:(CGSize)size { |
| | | CGRect frame = self.frame; |
| | | frame.size = size; |
| | | self.frame = frame; |
| | | } |
| | | |
| | | // å
¼å®¹æ§çæ¬ |
| | | |
| | | - (CGFloat)left |
| | | { |
| | | return self.left_sd; |
| | | } |
| | | |
| | | - (void)setLeft:(CGFloat)left |
| | | { |
| | | self.left_sd = left; |
| | | } |
| | | |
| | | - (CGFloat)right |
| | | { |
| | | return self.right_sd; |
| | | } |
| | | |
| | | - (void)setRight:(CGFloat)right |
| | | { |
| | | self.right_sd = right; |
| | | } |
| | | |
| | | - (CGFloat)width |
| | | { |
| | | return self.width_sd; |
| | | } |
| | | |
| | | - (CGFloat)height |
| | | { |
| | | return self.height_sd; |
| | | } |
| | | |
| | | - (CGFloat)top |
| | | { |
| | | return self.top_sd; |
| | | } |
| | | |
| | | - (void)setTop:(CGFloat)top |
| | | { |
| | | self.top_sd = top; |
| | | } |
| | | |
| | | - (CGFloat)bottom |
| | | { |
| | | return self.bottom_sd; |
| | | } |
| | | |
| | | - (void)setBottom:(CGFloat)bottom |
| | | { |
| | | self.bottom_sd = bottom; |
| | | } |
| | | |
| | | - (CGFloat)centerX |
| | | { |
| | | return self.centerX_sd; |
| | | } |
| | | |
| | | - (void)setCenterX:(CGFloat)centerX |
| | | { |
| | | self.centerX_sd = centerX; |
| | | } |
| | | |
| | | - (CGFloat)centerY |
| | | { |
| | | return self.centerY_sd; |
| | | } |
| | | |
| | | - (void)setCenterY:(CGFloat)centerY |
| | | { |
| | | self.centerY_sd = centerY; |
| | | } |
| | | |
| | | - (CGPoint)origin |
| | | { |
| | | return self.origin_sd; |
| | | } |
| | | |
| | | - (void)setOrigin:(CGPoint)origin |
| | | { |
| | | self.origin_sd = origin; |
| | | } |
| | | |
| | | - (CGSize)size |
| | | { |
| | | return self.size_sd; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation SDUIViewCategoryManager |
| | | |
| | | @end |
| | | |
Pods/Target Support Files/Pods-MIduo/Pods-MIduo-acknowledgements.markdown
Pods/Target Support Files/Pods-MIduo/Pods-MIduo-acknowledgements.plist
Pods/Target Support Files/Pods-MIduo/Pods-MIduo-frameworks.sh
Pods/Target Support Files/Pods-MIduo/Pods-MIduo.debug.xcconfig
Pods/Target Support Files/Pods-MIduo/Pods-MIduo.release.xcconfig
Pods/Target Support Files/ReactiveCocoa/Info.plist
Pods/Target Support Files/ReactiveCocoa/ReactiveCocoa-dummy.m
Pods/Target Support Files/ReactiveCocoa/ReactiveCocoa-prefix.pch
Pods/Target Support Files/ReactiveCocoa/ReactiveCocoa-umbrella.h
Pods/Target Support Files/ReactiveCocoa/ReactiveCocoa.modulemap
Pods/Target Support Files/ReactiveCocoa/ReactiveCocoa.xcconfig
Pods/Target Support Files/SDAutoLayout/Info.plist
Pods/Target Support Files/SDAutoLayout/SDAutoLayout-dummy.m
Pods/Target Support Files/SDAutoLayout/SDAutoLayout-prefix.pch
Pods/Target Support Files/SDAutoLayout/SDAutoLayout-umbrella.h
Pods/Target Support Files/SDAutoLayout/SDAutoLayout.modulemap
Pods/Target Support Files/SDAutoLayout/SDAutoLayout.xcconfig |