mirror of
https://github.com/zhigang1992/ReactiveCocoa.git
synced 2026-06-16 13:20:00 +08:00
create RACTuple to encapsulate the idea of a collection that could contain nils
This commit is contained in:
@@ -30,10 +30,6 @@
|
||||
881B3747152253810079220B /* NSObject+RACFastEnumeration.m in Sources */ = {isa = PBXBuildFile; fileRef = 881B3745152253810079220B /* NSObject+RACFastEnumeration.m */; };
|
||||
881B37CC152260BF0079220B /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = 881B37CA152260BF0079220B /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
881B37CD152260BF0079220B /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = 881B37CB152260BF0079220B /* RACUnit.m */; };
|
||||
882036E31509677A002428D3 /* EXTNil.h in Headers */ = {isa = PBXBuildFile; fileRef = 882036DD1509677A002428D3 /* EXTNil.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
882036E41509677A002428D3 /* EXTNil.m in Sources */ = {isa = PBXBuildFile; fileRef = 882036DE1509677A002428D3 /* EXTNil.m */; };
|
||||
882036E51509677A002428D3 /* EXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 882036DF1509677A002428D3 /* EXTRuntimeExtensions.h */; };
|
||||
882036E61509677A002428D3 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 882036E01509677A002428D3 /* EXTRuntimeExtensions.m */; };
|
||||
8820370A15096A4F002428D3 /* RACMaybe.h in Headers */ = {isa = PBXBuildFile; fileRef = 8820370815096A4F002428D3 /* RACMaybe.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
8820370B15096A4F002428D3 /* RACMaybe.m in Sources */ = {isa = PBXBuildFile; fileRef = 8820370915096A4F002428D3 /* RACMaybe.m */; };
|
||||
8820937C1501C8A600796685 /* RACSubscribableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8820937B1501C8A600796685 /* RACSubscribableSpec.m */; };
|
||||
@@ -47,8 +43,6 @@
|
||||
8857BB7E152A2747009804CC /* NSObject+RACExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8857BB7C152A2747009804CC /* NSObject+RACExtensions.h */; };
|
||||
8857BB7F152A2747009804CC /* NSObject+RACExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8857BB7D152A2747009804CC /* NSObject+RACExtensions.m */; };
|
||||
8857BB82152A27A9009804CC /* NSObject+RACKVOWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 8857BB81152A27A9009804CC /* NSObject+RACKVOWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
886044D8152A9693008ABD0D /* NSArray+EXTNilSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 886044D6152A9693008ABD0D /* NSArray+EXTNilSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
886044D9152A9693008ABD0D /* NSArray+EXTNilSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 886044D7152A9693008ABD0D /* NSArray+EXTNilSupport.m */; };
|
||||
8866784E1518D93E00DE77EC /* RACSubscribable+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = 8866784D1518D93D00DE77EC /* RACSubscribable+Operations.m */; };
|
||||
886678711518DCD800DE77EC /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = 886678701518DCD800DE77EC /* NSObject+RACPropertySubscribing.m */; };
|
||||
8867D5F9152BDAC300321BD5 /* RACSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = 8867D5F7152BDAC300321BD5 /* RACSwizzling.h */; };
|
||||
@@ -64,6 +58,8 @@
|
||||
88977C861512EA7C00A09EC5 /* RACAsyncSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D4AB481510F8F10011494F /* RACAsyncSubject.m */; };
|
||||
88977C871512EA7E00A09EC5 /* RACMaybe.m in Sources */ = {isa = PBXBuildFile; fileRef = 8820370915096A4F002428D3 /* RACMaybe.m */; };
|
||||
88ABAB631509A67C0073CDA2 /* RACAsyncFunctionOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 88ABAB621509A5E40073CDA2 /* RACAsyncFunctionOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
88B76F8E153726B00053EAE2 /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B76F8C153726B00053EAE2 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
88B76F8F153726B00053EAE2 /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = 88B76F8D153726B00053EAE2 /* RACTuple.m */; };
|
||||
88CBB39A1517A29F00DDC7D7 /* NSObject+RACSubscribable.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CBB3981517A29E00DDC7D7 /* NSObject+RACSubscribable.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
88CBB39B1517A29F00DDC7D7 /* NSObject+RACSubscribable.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CBB3991517A29E00DDC7D7 /* NSObject+RACSubscribable.m */; };
|
||||
88CDF7DE15000FCF00163A9F /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88CDF7DD15000FCF00163A9F /* SenTestingKit.framework */; };
|
||||
@@ -75,7 +71,7 @@
|
||||
88D4AB4A1510F8F10011494F /* RACAsyncSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D4AB481510F8F10011494F /* RACAsyncSubject.m */; };
|
||||
88DA309715071CBA00C19D0F /* RACValueTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 88DA309515071CBA00C19D0F /* RACValueTransformer.h */; settings = {ATTRIBUTES = (); }; };
|
||||
88DA309815071CBA00C19D0F /* RACValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DA309615071CBA00C19D0F /* RACValueTransformer.m */; };
|
||||
88F5870215361BCD0084BD32 /* RACConnectableSubscribable.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F5870015361BCD0084BD32 /* RACConnectableSubscribable.h */; };
|
||||
88F5870215361BCD0084BD32 /* RACConnectableSubscribable.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F5870015361BCD0084BD32 /* RACConnectableSubscribable.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
88F5870315361BCD0084BD32 /* RACConnectableSubscribable.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F5870115361BCD0084BD32 /* RACConnectableSubscribable.m */; };
|
||||
88F5870615361C170084BD32 /* RACConnectableSubscribable+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F5870515361C170084BD32 /* RACConnectableSubscribable+Private.h */; };
|
||||
88F70068152D2D7B00B32771 /* NSObject+RACBindings.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F70066152D2D7B00B32771 /* NSObject+RACBindings.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -185,10 +181,6 @@
|
||||
881B3745152253810079220B /* NSObject+RACFastEnumeration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACFastEnumeration.m"; sourceTree = "<group>"; };
|
||||
881B37CA152260BF0079220B /* RACUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACUnit.h; sourceTree = "<group>"; };
|
||||
881B37CB152260BF0079220B /* RACUnit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACUnit.m; sourceTree = "<group>"; };
|
||||
882036DD1509677A002428D3 /* EXTNil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTNil.h; sourceTree = "<group>"; };
|
||||
882036DE1509677A002428D3 /* EXTNil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXTNil.m; sourceTree = "<group>"; };
|
||||
882036DF1509677A002428D3 /* EXTRuntimeExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTRuntimeExtensions.h; sourceTree = "<group>"; };
|
||||
882036E01509677A002428D3 /* EXTRuntimeExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXTRuntimeExtensions.m; sourceTree = "<group>"; };
|
||||
8820370815096A4F002428D3 /* RACMaybe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACMaybe.h; sourceTree = "<group>"; };
|
||||
8820370915096A4F002428D3 /* RACMaybe.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACMaybe.m; sourceTree = "<group>"; };
|
||||
8820937B1501C8A600796685 /* RACSubscribableSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscribableSpec.m; sourceTree = "<group>"; };
|
||||
@@ -205,8 +197,6 @@
|
||||
8857BB7C152A2747009804CC /* NSObject+RACExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACExtensions.h"; sourceTree = "<group>"; };
|
||||
8857BB7D152A2747009804CC /* NSObject+RACExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACExtensions.m"; sourceTree = "<group>"; };
|
||||
8857BB81152A27A9009804CC /* NSObject+RACKVOWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACKVOWrapper.h"; sourceTree = "<group>"; };
|
||||
886044D6152A9693008ABD0D /* NSArray+EXTNilSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+EXTNilSupport.h"; sourceTree = "<group>"; };
|
||||
886044D7152A9693008ABD0D /* NSArray+EXTNilSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+EXTNilSupport.m"; sourceTree = "<group>"; };
|
||||
8866784D1518D93D00DE77EC /* RACSubscribable+Operations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RACSubscribable+Operations.m"; sourceTree = "<group>"; };
|
||||
886678701518DCD800DE77EC /* NSObject+RACPropertySubscribing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACPropertySubscribing.m"; sourceTree = "<group>"; };
|
||||
8867D5F7152BDAC300321BD5 /* RACSwizzling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSwizzling.h; sourceTree = "<group>"; };
|
||||
@@ -217,6 +207,8 @@
|
||||
88977C58151296D600A09EC5 /* RACSubscribable+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RACSubscribable+Private.h"; sourceTree = "<group>"; };
|
||||
88977C7E1512E69200A09EC5 /* RACSubscribable+Operations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACSubscribable+Operations.h"; sourceTree = "<group>"; };
|
||||
88ABAB621509A5E40073CDA2 /* RACAsyncFunctionOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RACAsyncFunctionOperation.h; sourceTree = "<group>"; };
|
||||
88B76F8C153726B00053EAE2 /* RACTuple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTuple.h; sourceTree = "<group>"; };
|
||||
88B76F8D153726B00053EAE2 /* RACTuple.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTuple.m; sourceTree = "<group>"; };
|
||||
88CBB3981517A29E00DDC7D7 /* NSObject+RACSubscribable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACSubscribable.h"; sourceTree = "<group>"; };
|
||||
88CBB3991517A29E00DDC7D7 /* NSObject+RACSubscribable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACSubscribable.m"; sourceTree = "<group>"; };
|
||||
88CDF7BF15000FCE00163A9F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
@@ -298,25 +290,11 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
882036DA1509677A002428D3 /* libextobjc */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
882036DD1509677A002428D3 /* EXTNil.h */,
|
||||
882036DE1509677A002428D3 /* EXTNil.m */,
|
||||
882036DF1509677A002428D3 /* EXTRuntimeExtensions.h */,
|
||||
882036E01509677A002428D3 /* EXTRuntimeExtensions.m */,
|
||||
);
|
||||
name = libextobjc;
|
||||
path = ../../external/libextobjc;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
886044CC152A95B5008ABD0D /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8857BB7C152A2747009804CC /* NSObject+RACExtensions.h */,
|
||||
8857BB7D152A2747009804CC /* NSObject+RACExtensions.m */,
|
||||
886044D6152A9693008ABD0D /* NSArray+EXTNilSupport.h */,
|
||||
886044D7152A9693008ABD0D /* NSArray+EXTNilSupport.m */,
|
||||
8867D5F7152BDAC300321BD5 /* RACSwizzling.h */,
|
||||
8867D5F8152BDAC300321BD5 /* RACSwizzling.m */,
|
||||
886DCD84152EA272004A666F /* NSObject+RACOperations.h */,
|
||||
@@ -395,7 +373,6 @@
|
||||
88CDF7C615000FCE00163A9F /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
882036DA1509677A002428D3 /* libextobjc */,
|
||||
88CDF7C715000FCE00163A9F /* ReactiveCocoa-Info.plist */,
|
||||
88CDF7C815000FCE00163A9F /* InfoPlist.strings */,
|
||||
88CDF7CD15000FCE00163A9F /* ReactiveCocoa-Prefix.pch */,
|
||||
@@ -463,6 +440,8 @@
|
||||
881B3745152253810079220B /* NSObject+RACFastEnumeration.m */,
|
||||
881B37CA152260BF0079220B /* RACUnit.h */,
|
||||
881B37CB152260BF0079220B /* RACUnit.m */,
|
||||
88B76F8C153726B00053EAE2 /* RACTuple.h */,
|
||||
88B76F8D153726B00053EAE2 /* RACTuple.m */,
|
||||
);
|
||||
name = Core;
|
||||
sourceTree = "<group>";
|
||||
@@ -496,7 +475,6 @@
|
||||
88D4AB3E1510F6C30011494F /* RACReplaySubject.h in Headers */,
|
||||
88D4AB491510F8F10011494F /* RACAsyncSubject.h in Headers */,
|
||||
883A84DA1513964B006DB4C7 /* RACBehaviorSubject.h in Headers */,
|
||||
882036E31509677A002428D3 /* EXTNil.h in Headers */,
|
||||
883A84DF1513B5EC006DB4C7 /* RACDisposable.h in Headers */,
|
||||
88CBB39A1517A29F00DDC7D7 /* NSObject+RACSubscribable.h in Headers */,
|
||||
88977C801512E69200A09EC5 /* RACSubscribable+Operations.h in Headers */,
|
||||
@@ -504,14 +482,13 @@
|
||||
884476E4152367D100958F44 /* RACScopedDisposable.h in Headers */,
|
||||
881B3746152253810079220B /* NSObject+RACFastEnumeration.h in Headers */,
|
||||
88F70068152D2D7B00B32771 /* NSObject+RACBindings.h in Headers */,
|
||||
886044D8152A9693008ABD0D /* NSArray+EXTNilSupport.h in Headers */,
|
||||
8857BB82152A27A9009804CC /* NSObject+RACKVOWrapper.h in Headers */,
|
||||
886DCD86152EA272004A666F /* NSObject+RACOperations.h in Headers */,
|
||||
882036E51509677A002428D3 /* EXTRuntimeExtensions.h in Headers */,
|
||||
88F5870215361BCD0084BD32 /* RACConnectableSubscribable.h in Headers */,
|
||||
88B76F8E153726B00053EAE2 /* RACTuple.h in Headers */,
|
||||
88DA309715071CBA00C19D0F /* RACValueTransformer.h in Headers */,
|
||||
8857BB7E152A2747009804CC /* NSObject+RACExtensions.h in Headers */,
|
||||
8867D5F9152BDAC300321BD5 /* RACSwizzling.h in Headers */,
|
||||
88F5870215361BCD0084BD32 /* RACConnectableSubscribable.h in Headers */,
|
||||
88F5870615361C170084BD32 /* RACConnectableSubscribable+Private.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -700,8 +677,6 @@
|
||||
88037FCD1505648C001A5B19 /* NSButton+RACCommandSupport.m in Sources */,
|
||||
88037FCF1505648C001A5B19 /* RACAsyncCommand.m in Sources */,
|
||||
88DA309815071CBA00C19D0F /* RACValueTransformer.m in Sources */,
|
||||
882036E41509677A002428D3 /* EXTNil.m in Sources */,
|
||||
882036E61509677A002428D3 /* EXTRuntimeExtensions.m in Sources */,
|
||||
8820370B15096A4F002428D3 /* RACMaybe.m in Sources */,
|
||||
880B9177150B09190008488E /* RACSubject.m in Sources */,
|
||||
88D4AB3F1510F6C30011494F /* RACReplaySubject.m in Sources */,
|
||||
@@ -716,11 +691,11 @@
|
||||
881B37CD152260BF0079220B /* RACUnit.m in Sources */,
|
||||
884476E5152367D100958F44 /* RACScopedDisposable.m in Sources */,
|
||||
8857BB7F152A2747009804CC /* NSObject+RACExtensions.m in Sources */,
|
||||
886044D9152A9693008ABD0D /* NSArray+EXTNilSupport.m in Sources */,
|
||||
8867D5FA152BDAC300321BD5 /* RACSwizzling.m in Sources */,
|
||||
88F70069152D2D7B00B32771 /* NSObject+RACBindings.m in Sources */,
|
||||
886DCD87152EA272004A666F /* NSObject+RACOperations.m in Sources */,
|
||||
88F5870315361BCD0084BD32 /* RACConnectableSubscribable.m in Sources */,
|
||||
88B76F8F153726B00053EAE2 /* RACTuple.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// NSArray+EXTNilSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 4/2/12.
|
||||
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface NSArray (EXTNilSupport)
|
||||
|
||||
- (id)rac_objectOrNilAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// NSArray+EXTNilSupport.m
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 4/2/12.
|
||||
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSArray+EXTNilSupport.h"
|
||||
#import "EXTNil.h"
|
||||
|
||||
|
||||
@implementation NSArray (EXTNilSupport)
|
||||
|
||||
- (id)rac_objectOrNilAtIndex:(NSUInteger)index {
|
||||
id object = [self objectAtIndex:index];
|
||||
return [object isKindOfClass:[EXTNil class]] ? nil : object;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -9,10 +9,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSubscribable;
|
||||
@class RACTuple;
|
||||
|
||||
|
||||
@interface NSObject (RACOperations)
|
||||
|
||||
- (RACSubscribable *)rac_whenAny:(NSArray *)keyPaths reduce:(id (^)(NSArray *xs))reduceBlock;
|
||||
- (RACSubscribable *)rac_whenAny:(NSArray *)keyPaths reduce:(id (^)(RACTuple *xs))reduceBlock;
|
||||
|
||||
@end
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
#import "NSObject+RACSubscribable.h"
|
||||
#import "RACSubscribable.h"
|
||||
#import "RACSubscribable+Operations.h"
|
||||
#import "EXTNil.h"
|
||||
#import "RACSubscriber.h"
|
||||
#import "NSObject+RACFastEnumeration.h"
|
||||
#import "RACTuple.h"
|
||||
|
||||
|
||||
@implementation NSObject (RACOperations)
|
||||
|
||||
- (RACSubscribable *)rac_whenAny:(NSArray *)keyPaths reduce:(id (^)(NSArray *xs))reduceBlock {
|
||||
- (RACSubscribable *)rac_whenAny:(NSArray *)keyPaths reduce:(id (^)(RACTuple *xs))reduceBlock {
|
||||
NSParameterAssert(keyPaths != nil);
|
||||
NSParameterAssert(reduceBlock != NULL);
|
||||
|
||||
@@ -26,13 +26,13 @@
|
||||
return [RACSubscribable createSubscribable:^RACDisposable *(id<RACSubscriber> observer) {
|
||||
NSObject *strongSelf = weakSelf;
|
||||
|
||||
NSArray * (^currentValues)(void) = ^{
|
||||
RACTuple * (^currentValues)(void) = ^{
|
||||
NSMutableArray *values = [NSMutableArray arrayWithCapacity:keyPaths.count];
|
||||
for(NSString *keyPath in keyPaths) {
|
||||
[values addObject:[strongSelf valueForKeyPath:keyPath] ? : [EXTNil null]];
|
||||
[values addObject:[strongSelf valueForKeyPath:keyPath] ? : [NSNull null]];
|
||||
}
|
||||
|
||||
return values;
|
||||
return [RACTuple tupleWithObjectsFromArray:values];
|
||||
};
|
||||
|
||||
[observer sendNext:reduceBlock(currentValues())];
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import "RACMaybe.h"
|
||||
#import "EXTNil.h"
|
||||
#import "RACTuple.h"
|
||||
|
||||
@interface RACMaybe ()
|
||||
@property (nonatomic, strong) id object;
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
+ (id)maybeWithObject:(id)object {
|
||||
RACMaybe *maybe = [[self alloc] init];
|
||||
maybe.object = object ? : [EXTNil null];
|
||||
maybe.object = object ? : [RACTupleNil tupleNil];
|
||||
return maybe;
|
||||
}
|
||||
|
||||
@@ -43,4 +43,8 @@
|
||||
return self.error != nil;
|
||||
}
|
||||
|
||||
- (id)object {
|
||||
return object == [RACTupleNil tupleNil] ? nil : object;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import "RACReplaySubject.h"
|
||||
#import "EXTNil.h"
|
||||
#import "RACSubscriber.h"
|
||||
|
||||
@interface RACReplaySubject ()
|
||||
@@ -33,7 +32,7 @@
|
||||
- (RACDisposable *)subscribe:(id<RACSubscriber>)observer {
|
||||
RACDisposable * disposable = [super subscribe:observer];
|
||||
for(id value in self.valuesReceived) {
|
||||
[observer sendNext:value];
|
||||
[observer sendNext:[value isKindOfClass:[NSNull class]] ? nil : value];
|
||||
}
|
||||
|
||||
return disposable;
|
||||
@@ -45,7 +44,7 @@
|
||||
- (void)sendNext:(id)value {
|
||||
[super sendNext:value];
|
||||
|
||||
[self.valuesReceived addObject:value ? : [EXTNil null]];
|
||||
[self.valuesReceived addObject:value ? : [NSNull null]];
|
||||
|
||||
while(self.valuesReceived.count > self.capacity) {
|
||||
[self.valuesReceived removeObjectAtIndex:0];
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "RACSubscribable.h"
|
||||
|
||||
@class RACTuple;
|
||||
@class RACConnectableSubscribable;
|
||||
|
||||
|
||||
@@ -37,17 +38,17 @@
|
||||
// Divide the `next`s of the subscribable into windows. When `openSubscribable` sends a next, a window is opened and the `closeBlock` is asked for a close subscribable. The window is closed when the close subscribable sends a `next`.
|
||||
- (instancetype)windowWithStart:(id<RACSubscribable>)openSubscribable close:(id<RACSubscribable> (^)(id<RACSubscribable> start))closeBlock;
|
||||
|
||||
// Divide the `next`s into buffers with `bufferCount` items each.
|
||||
// Divide the `next`s into buffers with `bufferCount` items each. The `next` will be a RACTuple of values.
|
||||
- (instancetype)buffer:(NSUInteger)bufferCount;
|
||||
|
||||
// Divide the `next`s into buffers delivery every `interval` seconds.
|
||||
// Divide the `next`s into buffers delivery every `interval` seconds. The `next` will be a RACTuple of values.
|
||||
- (instancetype)bufferWithTime:(NSTimeInterval)interval;
|
||||
|
||||
// Take `count` `next`s and then completes.
|
||||
- (instancetype)take:(NSUInteger)count;
|
||||
|
||||
// Combine the latest values from each of the subscribables once all the subscribables have sent a `next`.
|
||||
+ (instancetype)combineLatest:(NSArray *)observables reduce:(id (^)(NSArray *xs))reduceBlock;
|
||||
+ (instancetype)combineLatest:(NSArray *)observables reduce:(id (^)(RACTuple *xs))reduceBlock;
|
||||
|
||||
// Sends a `+[RACUnit defaultUnit]` when all the subscribables have sent a `next`.
|
||||
+ (instancetype)whenAll:(NSArray *)observables;
|
||||
@@ -109,7 +110,7 @@
|
||||
// The source must be a subscribable of subscribables. Subscribe and send `next`s for the latest subscribable. This is mostly useful when combined with `-selectMany:`.
|
||||
- (instancetype)switch;
|
||||
|
||||
// Add every `next` to an array. Note that this is a blocking call.
|
||||
// Add every `next` to an array. Nils are represented by NSNulls. Note that this is a blocking call.
|
||||
- (NSArray *)toArray;
|
||||
|
||||
// Creates and returns a connectable subscribable. This allows you to share 1 single subscription to the underlying subscribable.
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
#import "NSObject+RACExtensions.h"
|
||||
#import "RACBehaviorSubject.h"
|
||||
#import "RACDisposable.h"
|
||||
#import "EXTNil.h"
|
||||
#import "RACUnit.h"
|
||||
#import "RACMaybe.h"
|
||||
#import "RACConnectableSubscribable+Private.h"
|
||||
#import "RACTuple.h"
|
||||
|
||||
|
||||
@implementation RACSubscribable (Operations)
|
||||
@@ -275,13 +275,13 @@
|
||||
__block RACDisposable *innerDisposable = nil;
|
||||
RACDisposable *outerDisposable = [[self windowWithStart:windowOpenSubject close:^(id<RACSubscribable> start) {
|
||||
return [[[RACSubscribable interval:interval] take:1] doNext:^(id x) {
|
||||
[observer sendNext:values];
|
||||
[observer sendNext:[RACTuple tupleWithObjectsFromArray:values]];
|
||||
[values removeAllObjects];
|
||||
[windowOpenSubject sendNext:[RACUnit defaultUnit]];
|
||||
}];
|
||||
}] subscribeNext:^(id x) {
|
||||
innerDisposable = [x subscribeNext:^(id x) {
|
||||
[values addObject:x ? : [EXTNil null]];
|
||||
[values addObject:x ? : [RACTupleNil tupleNil]];
|
||||
}];
|
||||
} error:^(NSError *error) {
|
||||
[observer sendError:error];
|
||||
@@ -314,7 +314,7 @@
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)combineLatest:(NSArray *)observables reduce:(id (^)(NSArray *xs))reduceBlock {
|
||||
+ (instancetype)combineLatest:(NSArray *)observables reduce:(id (^)(RACTuple *xs))reduceBlock {
|
||||
NSParameterAssert(reduceBlock != NULL);
|
||||
|
||||
return [RACSubscribable createSubscribable:^(id<RACSubscriber> observer) {
|
||||
@@ -323,7 +323,7 @@
|
||||
NSMutableDictionary *lastValues = [NSMutableDictionary dictionaryWithCapacity:observables.count];
|
||||
for(id<RACSubscribable> observable in observables) {
|
||||
RACDisposable *disposable = [observable subscribe:[RACSubscriber subscriberWithNext:^(id x) {
|
||||
[lastValues setObject:x ? : [EXTNil null] forKey:[NSString stringWithFormat:@"%p", observable]];
|
||||
[lastValues setObject:x ? : [NSNull null] forKey:[NSString stringWithFormat:@"%p", observable]];
|
||||
|
||||
if(lastValues.count == observables.count) {
|
||||
NSMutableArray *orderedValues = [NSMutableArray arrayWithCapacity:observables.count];
|
||||
@@ -331,7 +331,7 @@
|
||||
[orderedValues addObject:[lastValues objectForKey:[NSString stringWithFormat:@"%p", o]]];
|
||||
}
|
||||
|
||||
[observer sendNext:reduceBlock(orderedValues)];
|
||||
[observer sendNext:reduceBlock([RACTuple tupleWithObjectsFromArray:orderedValues])];
|
||||
}
|
||||
} error:^(NSError *error) {
|
||||
[observer sendError:error];
|
||||
@@ -356,7 +356,7 @@
|
||||
}
|
||||
|
||||
+ (instancetype)whenAll:(NSArray *)observables {
|
||||
return [self combineLatest:observables reduce:^(NSArray *xs) { return [RACUnit defaultUnit]; }];
|
||||
return [self combineLatest:observables reduce:^(RACTuple *xs) { return [RACUnit defaultUnit]; }];
|
||||
}
|
||||
|
||||
+ (instancetype)merge:(NSArray *)observables {
|
||||
@@ -679,7 +679,7 @@
|
||||
NSMutableArray *values = [NSMutableArray array];
|
||||
__block BOOL stop = NO;
|
||||
[self subscribeNext:^(id x) {
|
||||
[values addObject:x ? : [EXTNil null]];
|
||||
[values addObject:x ? : [NSNull null]];
|
||||
} error:^(NSError *error) {
|
||||
stop = YES;
|
||||
} completed:^{
|
||||
|
||||
30
ReactiveCocoaFramework/ReactiveCocoa/RACTuple.h
Normal file
30
ReactiveCocoaFramework/ReactiveCocoa/RACTuple.h
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// RACTuple.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 4/12/12.
|
||||
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface RACTupleNil : NSObject
|
||||
+ (RACTupleNil *)tupleNil;
|
||||
@end
|
||||
|
||||
|
||||
// A tuple is an ordered collection of objects. It may contain nils, represented by RACTupleNil.
|
||||
@interface RACTuple : NSObject <NSCopying, NSFastEnumeration>
|
||||
|
||||
@property (nonatomic, readonly) NSUInteger count;
|
||||
|
||||
+ (id)tupleWithObjectsFromArray:(NSArray *)array;
|
||||
+ (id)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
|
||||
|
||||
// Returns the object at `index` or nil if the object is a RACTupleNil.
|
||||
- (id)objectAtIndex:(NSUInteger)index;
|
||||
|
||||
- (NSArray *)allObjects;
|
||||
|
||||
@end
|
||||
99
ReactiveCocoaFramework/ReactiveCocoa/RACTuple.m
Normal file
99
ReactiveCocoaFramework/ReactiveCocoa/RACTuple.m
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// RACTuple.m
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 4/12/12.
|
||||
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RACTuple.h"
|
||||
|
||||
|
||||
@implementation RACTupleNil
|
||||
|
||||
+ (RACTupleNil *)tupleNil {
|
||||
static dispatch_once_t onceToken;
|
||||
static RACTupleNil *tupleNil = nil;
|
||||
dispatch_once(&onceToken, ^{
|
||||
tupleNil = [[self alloc] init];
|
||||
});
|
||||
|
||||
return tupleNil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface RACTuple ()
|
||||
@property (nonatomic, strong) NSArray *backingArray;
|
||||
@end
|
||||
|
||||
|
||||
@implementation RACTuple
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if(self == nil) return nil;
|
||||
|
||||
self.backingArray = [NSArray array];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#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
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
// we're immutable, bitches!
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark API
|
||||
|
||||
@synthesize backingArray;
|
||||
|
||||
+ (id)tupleWithObjectsFromArray:(NSArray *)array {
|
||||
RACTuple *tuple = [[self alloc] init];
|
||||
tuple.backingArray = [array copy];
|
||||
return tuple;
|
||||
}
|
||||
|
||||
+ (id)tupleWithObjects:(id)object, ... {
|
||||
RACTuple *tuple = [[self alloc] init];
|
||||
|
||||
NSMutableArray *objects = [NSMutableArray array];
|
||||
|
||||
va_list args;
|
||||
va_start(args, object);
|
||||
for(id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) {
|
||||
[objects addObject:currentObject];
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
tuple.backingArray = [objects copy];
|
||||
|
||||
return tuple;
|
||||
}
|
||||
|
||||
- (id)objectAtIndex:(NSUInteger)index {
|
||||
id object = [self.backingArray objectAtIndex:index];
|
||||
return object == [RACTupleNil tupleNil] ? nil : object;
|
||||
}
|
||||
|
||||
- (NSArray *)allObjects {
|
||||
return self.backingArray;
|
||||
}
|
||||
|
||||
- (NSUInteger)count {
|
||||
return self.backingArray.count;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,7 +14,6 @@
|
||||
#import <ReactiveCocoa/NSButton+RACCommandSupport.h>
|
||||
#import <ReactiveCocoa/RACAsyncCommand.h>
|
||||
#import <ReactiveCocoa/RACMaybe.h>
|
||||
#import <ReactiveCocoa/EXTNil.h>
|
||||
#import <ReactiveCocoa/RACAsyncFunctionOperation.h>
|
||||
#import <ReactiveCocoa/RACSubject.h>
|
||||
#import <ReactiveCocoa/RACReplaySubject.h>
|
||||
@@ -28,3 +27,4 @@
|
||||
#import <ReactiveCocoa/NSArray+EXTNilSupport.h>
|
||||
#import <ReactiveCocoa/NSObject+RACBindings.h>
|
||||
#import <ReactiveCocoa/NSObject+RACOperations.h>
|
||||
#import <ReactiveCocoa/RACTuple.h>
|
||||
|
||||
37
external/libextobjc/EXTNil.h
vendored
37
external/libextobjc/EXTNil.h
vendored
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// EXTNil.h
|
||||
// extobjc
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2011-04-25.
|
||||
// Released into the public domain.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Like \c NSNull, this class provides a singleton object that can be used to
|
||||
* represent a \c NULL or \c nil value. Unlike \c NSNull, this object behaves
|
||||
* more similarly to a \c nil object, responding to messages with "zero" values.
|
||||
* This eliminates the need for \c NSNull class or equality checks with
|
||||
* collections that need to contain null values.
|
||||
*
|
||||
* This class will pretend to be \c NSNull when queried for its class or
|
||||
* compared for equality, to keep compatibility with code that expects or uses
|
||||
* \c NSNull.
|
||||
*
|
||||
* @note Because this class does still behave like an object in some ways, it
|
||||
* will respond to certain \c NSObject protocol methods where an actually \c nil
|
||||
* object would not.
|
||||
*/
|
||||
@interface EXTNil : NSProxy {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton \c EXTNil instance. This naming matches that of \c
|
||||
* NSNull -- \c nil as a method name is unusable because it is a language
|
||||
* keyword.
|
||||
*/
|
||||
+ (id)null;
|
||||
|
||||
@end
|
||||
89
external/libextobjc/EXTNil.m
vendored
89
external/libextobjc/EXTNil.m
vendored
@@ -1,89 +0,0 @@
|
||||
//
|
||||
// EXTNil.m
|
||||
// extobjc
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2011-04-25.
|
||||
// Released into the public domain.
|
||||
//
|
||||
|
||||
#import "EXTNil.h"
|
||||
#import "EXTRuntimeExtensions.h"
|
||||
|
||||
static id singleton = nil;
|
||||
|
||||
@implementation EXTNil
|
||||
+ (void)initialize {
|
||||
if (self == [EXTNil class]) {
|
||||
if (!singleton)
|
||||
singleton = [self alloc];
|
||||
}
|
||||
}
|
||||
|
||||
+ (EXTNil *)null {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Forwarding machinery
|
||||
|
||||
- (void)forwardInvocation:(NSInvocation *)anInvocation {
|
||||
NSUInteger returnLength = [[anInvocation methodSignature] methodReturnLength];
|
||||
if (!returnLength) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// set return value to all zero bits
|
||||
char buffer[returnLength];
|
||||
memset(buffer, 0, returnLength);
|
||||
|
||||
[anInvocation setReturnValue:buffer];
|
||||
}
|
||||
|
||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
|
||||
return ext_globalMethodSignatureForSelector(selector);
|
||||
}
|
||||
|
||||
- (BOOL)respondsToSelector:(SEL)selector {
|
||||
// behave like nil
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark NSObject protocol
|
||||
|
||||
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)obj {
|
||||
return !obj || obj == self || [obj isEqual:[NSNull null]];
|
||||
}
|
||||
|
||||
- (BOOL)isKindOfClass:(Class)class {
|
||||
return [class isEqual:[EXTNil class]] || [class isEqual:[NSNull class]];
|
||||
}
|
||||
|
||||
- (BOOL)isMemberOfClass:(Class)class {
|
||||
return [class isEqual:[EXTNil class]] || [class isEqual:[NSNull class]];
|
||||
}
|
||||
|
||||
- (BOOL)isProxy {
|
||||
// not really a proxy -- we just inherit from NSProxy because it makes
|
||||
// method signature lookup simpler
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
372
external/libextobjc/EXTRuntimeExtensions.h
vendored
372
external/libextobjc/EXTRuntimeExtensions.h
vendored
@@ -1,372 +0,0 @@
|
||||
//
|
||||
// EXTRuntimeExtensions.h
|
||||
// extobjc
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2011-03-05.
|
||||
// Released into the public domain.
|
||||
//
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
/**
|
||||
* A callback indicating that the given method failed to be added to the given
|
||||
* class. The reason for the failure depends on the attempted task.
|
||||
*/
|
||||
typedef void (*ext_failedMethodCallback)(Class, Method);
|
||||
|
||||
/**
|
||||
* Used with #ext_injectMethods to determine injection behavior.
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* Indicates that any existing methods on the destination class should be
|
||||
* overwritten.
|
||||
*/
|
||||
ext_methodInjectionReplace = 0x00,
|
||||
|
||||
/**
|
||||
* Avoid overwriting methods on the immediate destination class.
|
||||
*/
|
||||
ext_methodInjectionFailOnExisting = 0x01,
|
||||
|
||||
/**
|
||||
* Avoid overriding methods implemented in any superclass of the destination
|
||||
* class.
|
||||
*/
|
||||
ext_methodInjectionFailOnSuperclassExisting = 0x02,
|
||||
|
||||
/**
|
||||
* Avoid overwriting methods implemented in the immediate destination class
|
||||
* or any superclass. This is equivalent to
|
||||
* <tt>ext_methodInjectionFailOnExisting | ext_methodInjectionFailOnSuperclassExisting</tt>.
|
||||
*/
|
||||
ext_methodInjectionFailOnAnyExisting = 0x03,
|
||||
|
||||
/**
|
||||
* Ignore the \c +load class method. This does not affect instance method
|
||||
* injection.
|
||||
*/
|
||||
ext_methodInjectionIgnoreLoad = 1U << 2,
|
||||
|
||||
/**
|
||||
* Ignore the \c +initialize class method. This does not affect instance method
|
||||
* injection.
|
||||
*/
|
||||
ext_methodInjectionIgnoreInitialize = 1U << 3
|
||||
} ext_methodInjectionBehavior;
|
||||
|
||||
/**
|
||||
* A mask for the overwriting behavior flags of #ext_methodInjectionBehavior.
|
||||
*/
|
||||
static const ext_methodInjectionBehavior ext_methodInjectionOverwriteBehaviorMask = 0x3;
|
||||
|
||||
/**
|
||||
* Describes the memory management policy of a property.
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* The value is assigned.
|
||||
*/
|
||||
ext_propertyMemoryManagementPolicyAssign = 0,
|
||||
|
||||
/**
|
||||
* The value is retained.
|
||||
*/
|
||||
ext_propertyMemoryManagementPolicyRetain,
|
||||
|
||||
/**
|
||||
* The value is copied.
|
||||
*/
|
||||
ext_propertyMemoryManagementPolicyCopy
|
||||
} ext_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
|
||||
* #ext_propertyMemoryManagementPolicyAssign if #readonly is \c YES.
|
||||
*/
|
||||
ext_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[];
|
||||
} ext_propertyAttributes;
|
||||
|
||||
/**
|
||||
* Iterates through the first \a count entries in \a methods and attempts to add
|
||||
* each one to \a aClass. If a method by the same name already exists on \a
|
||||
* aClass, it is \e not overridden. If \a checkSuperclasses is \c YES, and
|
||||
* a method by the same name already exists on any superclass of \a aClass, it
|
||||
* is not overridden.
|
||||
*
|
||||
* Returns the number of methods added successfully. For each method that fails
|
||||
* to be added, \a failedToAddCallback (if provided) is invoked.
|
||||
*/
|
||||
unsigned ext_addMethods (Class aClass, Method *methods, unsigned count, BOOL checkSuperclasses, ext_failedMethodCallback failedToAddCallback);
|
||||
|
||||
/**
|
||||
* Iterates through all instance and class methods of \a srcClass and attempts
|
||||
* to add each one to \a dstClass. If a method by the same name already exists
|
||||
* on \a aClass, it is \e not overridden. If \a checkSuperclasses is \c YES, and
|
||||
* a method by the same name already exists on any superclass of \a aClass, it
|
||||
* is not overridden.
|
||||
*
|
||||
* Returns whether all methods were added successfully. For each method that fails
|
||||
* to be added, \a failedToAddCallback (if provided) is invoked.
|
||||
*
|
||||
* @note This ignores any \c +load method on \a srcClass. \a srcClass and \a
|
||||
* dstClass must not be metaclasses.
|
||||
*/
|
||||
BOOL ext_addMethodsFromClass (Class srcClass, Class dstClass, BOOL checkSuperclasses, ext_failedMethodCallback failedToAddCallback);
|
||||
|
||||
/**
|
||||
* Returns the superclass of \a receiver which immediately descends from \a
|
||||
* superclass. If \a superclass is not in the hierarchy of \a receiver, or is
|
||||
* equal to \a receiver, \c nil is returned.
|
||||
*/
|
||||
Class ext_classBeforeSuperclass (Class receiver, Class superclass);
|
||||
|
||||
/**
|
||||
* Returns whether \a receiver is \a aClass, or inherits directly from it.
|
||||
*/
|
||||
BOOL ext_classIsKindOfClass (Class receiver, Class aClass);
|
||||
|
||||
/**
|
||||
* Returns the full list of classes registered with the runtime, terminated with
|
||||
* \c NULL. If \a count is not \c NULL, it is filled in with the total number of
|
||||
* classes returned. You must \c free() the returned array.
|
||||
*/
|
||||
Class *ext_copyClassList (unsigned *count);
|
||||
|
||||
/**
|
||||
* Looks through the complete list of classes registered with the runtime and
|
||||
* finds all classes which conform to \a protocol. Returns \c *count classes
|
||||
* terminated by a \c NULL. You must \c free() the returned array. If there are no
|
||||
* classes conforming to \a protocol, \c NULL is returned.
|
||||
*
|
||||
* @note \a count may be \c NULL.
|
||||
*/
|
||||
Class *ext_copyClassListConformingToProtocol (Protocol *protocol, unsigned *count);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
ext_propertyAttributes *ext_copyPropertyAttributes (objc_property_t property);
|
||||
|
||||
/**
|
||||
* Looks through the complete list of classes registered with the runtime and
|
||||
* finds all classes which are descendant from \a aClass. Returns \c
|
||||
* *subclassCount classes terminated by a \c NULL. You must \c free() the
|
||||
* returned array. If there are no subclasses of \a aClass, \c NULL is
|
||||
* returned.
|
||||
*
|
||||
* @note \a subclassCount may be \c NULL. \a aClass may be a metaclass to get
|
||||
* all subclass metaclass objects.
|
||||
*/
|
||||
Class *ext_copySubclassList (Class aClass, unsigned *subclassCount);
|
||||
|
||||
/**
|
||||
* 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 ext_getImmediateInstanceMethod (Class aClass, SEL aSelector);
|
||||
|
||||
/**
|
||||
* Returns the value of \c Ivar \a IVAR from instance \a OBJ. The instance
|
||||
* variable must be of type \a TYPE, and is returned as such.
|
||||
*
|
||||
* @warning Depending on the platform, this may or may not work with aggregate
|
||||
* or floating-point types.
|
||||
*/
|
||||
#define ext_getIvar(OBJ, IVAR, TYPE) \
|
||||
((TYPE (*)(id, Ivar)object_getIvar)((OBJ), (IVAR)))
|
||||
|
||||
/**
|
||||
* Returns the value of the instance variable identified by the string \a NAME
|
||||
* from instance \a OBJ. The instance variable must be of type \a TYPE, and is
|
||||
* returned as such.
|
||||
*
|
||||
* @note \a OBJ is evaluated twice.
|
||||
*
|
||||
* @warning Depending on the platform, this may or may not work with aggregate
|
||||
* or floating-point types.
|
||||
*/
|
||||
#define ext_getIvarByName(OBJ, NAME, TYPE) \
|
||||
ext_getIvar((OBJ), class_getInstanceVariable(object_getClass((OBJ)), (NAME)), TYPE)
|
||||
|
||||
/**
|
||||
* Returns the accessor methods for \a property, as implemented in \a aClass or
|
||||
* any of its superclasses. The getter, if implemented, is returned in \a
|
||||
* getter, and the setter, if implemented, is returned in \a setter. If either
|
||||
* \a getter or \a setter are \c NULL, that accessor is not returned. If either
|
||||
* accessor is not implemented, the argument is left unmodified.
|
||||
*
|
||||
* Returns \c YES if a valid accessor was found, or \c NO if \a aClass and its
|
||||
* superclasses do not implement \a property or if an error occurs.
|
||||
*/
|
||||
BOOL ext_getPropertyAccessorsForClass (objc_property_t property, Class aClass, Method *getter, Method *setter);
|
||||
|
||||
/**
|
||||
* For all classes registered with the runtime, invokes \c
|
||||
* methodSignatureForSelector: and \c instanceMethodSignatureForSelector: to
|
||||
* determine a method signature for \a aSelector. If one or more valid
|
||||
* signatures is found, the first one is returned. If no valid signatures were
|
||||
* found, \c nil is returned.
|
||||
*/
|
||||
NSMethodSignature *ext_globalMethodSignatureForSelector (SEL aSelector);
|
||||
|
||||
/**
|
||||
* Highly-configurable method injection. Adds the first \a count entries from \a
|
||||
* methods into \a aClass according to \a behavior.
|
||||
*
|
||||
* Returns the number of methods added successfully. For each method that fails
|
||||
* to be added, \a failedToAddCallback (if provided) is invoked.
|
||||
*
|
||||
* @note \c +load and \c +initialize methods are included in the number of
|
||||
* successful methods when ignored for injection.
|
||||
*/
|
||||
unsigned ext_injectMethods (Class aClass, Method *methods, unsigned count, ext_methodInjectionBehavior behavior, ext_failedMethodCallback failedToAddCallback);
|
||||
|
||||
/**
|
||||
* Invokes #ext_injectMethods with the instance methods and class methods from
|
||||
* \a srcClass. #ext_methodInjectionIgnoreLoad is added to #behavior for class
|
||||
* method injection.
|
||||
*
|
||||
* Returns whether all methods were added successfully. For each method that fails
|
||||
* to be added, \a failedToAddCallback (if provided) is invoked.
|
||||
*
|
||||
* @note \c +load and \c +initialize methods are considered to be added
|
||||
* successfully when ignored for injection.
|
||||
*/
|
||||
BOOL ext_injectMethodsFromClass (Class srcClass, Class dstClass, ext_methodInjectionBehavior behavior, ext_failedMethodCallback failedToAddCallback);
|
||||
|
||||
/**
|
||||
* Loads a "special protocol" into an internal list. A special protocol is any
|
||||
* protocol for which implementing classes need injection behavior (i.e., any
|
||||
* class conforming to the protocol needs to be reflected upon). Returns \c NO
|
||||
* if loading failed.
|
||||
*
|
||||
* Using this facility proceeds as follows:
|
||||
*
|
||||
* @li Each protocol is loaded with #ext_loadSpecialProtocol and a custom block
|
||||
* that describes its injection behavior on each conforming class.
|
||||
* @li Each protocol is marked as being ready for injection with
|
||||
* #ext_specialProtocolReadyForInjection.
|
||||
* @li The entire Objective-C class list is retrieved, and each special
|
||||
* protocol's \a injectionBehavior block is run for all conforming classes.
|
||||
*
|
||||
* It is an error to call this function without later calling
|
||||
* #ext_specialProtocolReadyForInjection as well.
|
||||
*
|
||||
* @note A special protocol X which conforms to another special protocol Y is
|
||||
* always injected \e after Y.
|
||||
*/
|
||||
BOOL ext_loadSpecialProtocol (Protocol *protocol, void (^injectionBehavior)(Class destinationClass));
|
||||
|
||||
/**
|
||||
* Marks a special protocol as being ready for injection. Injection is actually
|
||||
* performed only after all special protocols have been marked in this way.
|
||||
*
|
||||
* @sa ext_loadSpecialProtocol
|
||||
*/
|
||||
void ext_specialProtocolReadyForInjection (Protocol *protocol);
|
||||
|
||||
/**
|
||||
* "Removes" any instance method matching \a methodName from \a aClass. This
|
||||
* removal can mean one of two things:
|
||||
*
|
||||
* @li If any superclass of \a aClass implements a method by the same name, the
|
||||
* implementation of the closest such superclass is used.
|
||||
* @li If no superclasses of \a aClass implement a method by the same name, the
|
||||
* method is replaced with an implementation internal to the runtime, used for
|
||||
* message forwarding.
|
||||
*
|
||||
* @warning Adding a method by the same name into a superclass of \a aClass \e
|
||||
* after using this function may obscure it from the subclass.
|
||||
*/
|
||||
void ext_removeMethod (Class aClass, SEL methodName);
|
||||
|
||||
/**
|
||||
* Iterates through the first \a count entries in \a methods and adds each one
|
||||
* to \a aClass, replacing any existing implementation.
|
||||
*/
|
||||
void ext_replaceMethods (Class aClass, Method *methods, unsigned count);
|
||||
|
||||
/**
|
||||
* Iterates through all instance and class methods of \a srcClass and adds each
|
||||
* one to \a dstClass, replacing any existing implementation.
|
||||
*
|
||||
* @note This ignores any \c +load method on \a srcClass. \a srcClass and \a
|
||||
* dstClass must not be metaclasses.
|
||||
*/
|
||||
void ext_replaceMethodsFromClass (Class srcClass, Class dstClass);
|
||||
|
||||
932
external/libextobjc/EXTRuntimeExtensions.m
vendored
932
external/libextobjc/EXTRuntimeExtensions.m
vendored
@@ -1,932 +0,0 @@
|
||||
//
|
||||
// EXTRuntimeExtensions.m
|
||||
// extobjc
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2011-03-05.
|
||||
// Released into the public domain.
|
||||
//
|
||||
|
||||
#import "EXTRuntimeExtensions.h"
|
||||
#import <ctype.h>
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <objc/message.h>
|
||||
#import <pthread.h>
|
||||
#import <stdio.h>
|
||||
#import <stdlib.h>
|
||||
#import <string.h>
|
||||
|
||||
typedef NSMethodSignature *(*methodSignatureForSelectorIMP)(id, SEL, SEL);
|
||||
typedef void (^ext_specialProtocolInjectionBlock)(Class);
|
||||
|
||||
// contains the information needed to reference a full special protocol
|
||||
typedef struct {
|
||||
// the actual protocol declaration (@protocol block)
|
||||
__unsafe_unretained Protocol *protocol;
|
||||
|
||||
// the injection block associated with this protocol
|
||||
//
|
||||
// this block is RETAINED and must eventually be released by transferring it
|
||||
// back to ARC
|
||||
void *injectionBlock;
|
||||
|
||||
// whether this protocol is ready to be injected to its conforming classes
|
||||
//
|
||||
// this does NOT refer to a special protocol having been injected already
|
||||
BOOL ready;
|
||||
} EXTSpecialProtocol;
|
||||
|
||||
// the full list of special protocols (an array of EXTSpecialProtocol structs)
|
||||
static EXTSpecialProtocol * restrict specialProtocols = NULL;
|
||||
|
||||
// the number of special protocols stored in the array
|
||||
static size_t specialProtocolCount = 0;
|
||||
|
||||
// the total capacity of the array
|
||||
// we use a doubling algorithm to amortize the cost of insertion, so this is
|
||||
// generally going to be a power-of-two
|
||||
static size_t specialProtocolCapacity = 0;
|
||||
|
||||
// the number of EXTSpecialProtocols which have been marked as ready for
|
||||
// injection (though not necessary injected)
|
||||
//
|
||||
// in other words, the total count which have 'ready' set to YES
|
||||
static size_t specialProtocolsReady = 0;
|
||||
|
||||
// a mutex is used to guard against multiple threads changing the above static
|
||||
// variables
|
||||
static pthread_mutex_t specialProtocolsLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/**
|
||||
* This function actually performs the hard work of special protocol injection.
|
||||
* It obtains a full list of all classes registered with the Objective-C
|
||||
* runtime, finds those conforming to special protocols, and then runs the
|
||||
* injection blocks as appropriate.
|
||||
*/
|
||||
static void ext_injectSpecialProtocols (void) {
|
||||
/*
|
||||
* don't lock specialProtocolsLock in this function, as it is called only
|
||||
* from public functions which already perform the synchronization
|
||||
*/
|
||||
|
||||
/*
|
||||
* This will sort special protocols in the order they should be loaded. If
|
||||
* a special protocol conforms to another special protocol, the former
|
||||
* will be prioritized above the latter.
|
||||
*/
|
||||
qsort_b(specialProtocols, specialProtocolCount, sizeof(EXTSpecialProtocol), ^(const void *a, const void *b){
|
||||
// if the pointers are equal, it must be the same protocol
|
||||
if (a == b)
|
||||
return 0;
|
||||
|
||||
const EXTSpecialProtocol *protoA = a;
|
||||
const EXTSpecialProtocol *protoB = b;
|
||||
|
||||
// A higher return value here means a higher priority
|
||||
int (^protocolInjectionPriority)(const EXTSpecialProtocol *) = ^(const EXTSpecialProtocol *specialProtocol){
|
||||
int runningTotal = 0;
|
||||
|
||||
for (size_t i = 0;i < specialProtocolCount;++i) {
|
||||
// the pointer passed into this block is guaranteed to point
|
||||
// into the 'specialProtocols' array, so we can compare the
|
||||
// pointers directly for identity
|
||||
if (specialProtocol == specialProtocols + i)
|
||||
continue;
|
||||
|
||||
if (protocol_conformsToProtocol(specialProtocol->protocol, specialProtocols[i].protocol))
|
||||
runningTotal++;
|
||||
}
|
||||
|
||||
return runningTotal;
|
||||
};
|
||||
|
||||
/*
|
||||
* This will return:
|
||||
* 0 if the protocols are equal in priority (such that load order does not matter)
|
||||
* < 0 if A is more important than B
|
||||
* > 0 if B is more important than A
|
||||
*/
|
||||
return protocolInjectionPriority(protoB) - protocolInjectionPriority(protoA);
|
||||
});
|
||||
|
||||
unsigned classCount = 0;
|
||||
Class *allClasses = ext_copyClassList(&classCount);
|
||||
|
||||
if (!classCount || !allClasses) {
|
||||
fprintf(stderr, "ERROR: No classes registered with the runtime\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* set up an autorelease pool in case any Cocoa classes get used during
|
||||
* the injection process or +initialize
|
||||
*/
|
||||
@autoreleasepool {
|
||||
// loop through the special protocols, and apply each one to all the
|
||||
// classes in turn
|
||||
//
|
||||
// ORDER IS IMPORTANT HERE: protocols have to be injected to all classes in
|
||||
// the order in which they appear in specialProtocols. Consider classes
|
||||
// X and Y that implement protocols A and B, respectively. B needs to get
|
||||
// its implementation into Y before A gets into X.
|
||||
for (size_t i = 0;i < specialProtocolCount;++i) {
|
||||
Protocol *protocol = specialProtocols[i].protocol;
|
||||
|
||||
// transfer ownership of the injection block to ARC and remove it
|
||||
// from the structure
|
||||
ext_specialProtocolInjectionBlock injectionBlock = (__bridge_transfer id)specialProtocols[i].injectionBlock;
|
||||
specialProtocols[i].injectionBlock = NULL;
|
||||
|
||||
// loop through all classes
|
||||
for (unsigned classIndex = 0;classIndex < classCount;++classIndex) {
|
||||
Class class = allClasses[classIndex];
|
||||
|
||||
// if this class doesn't conform to the protocol, continue to the
|
||||
// next class immediately
|
||||
if (!class_conformsToProtocol(class, protocol))
|
||||
continue;
|
||||
|
||||
injectionBlock(class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// free the allocated class list
|
||||
free(allClasses);
|
||||
|
||||
// now that everything's injected, the special protocol list can also be
|
||||
// destroyed
|
||||
free(specialProtocols); specialProtocols = NULL;
|
||||
specialProtocolCount = 0;
|
||||
specialProtocolCapacity = 0;
|
||||
specialProtocolsReady = 0;
|
||||
}
|
||||
|
||||
unsigned ext_injectMethods (
|
||||
Class aClass,
|
||||
Method *methods,
|
||||
unsigned count,
|
||||
ext_methodInjectionBehavior behavior,
|
||||
ext_failedMethodCallback failedToAddCallback
|
||||
) {
|
||||
unsigned successes = 0;
|
||||
|
||||
/*
|
||||
* set up an autorelease pool in case any Cocoa classes invoke +initialize
|
||||
* during this process
|
||||
*/
|
||||
@autoreleasepool {
|
||||
BOOL isMeta = class_isMetaClass(aClass);
|
||||
|
||||
if (!isMeta) {
|
||||
// clear any +load and +initialize ignore flags
|
||||
behavior &= ~(ext_methodInjectionIgnoreLoad | ext_methodInjectionIgnoreInitialize);
|
||||
}
|
||||
|
||||
for (unsigned methodIndex = 0;methodIndex < count;++methodIndex) {
|
||||
Method method = methods[methodIndex];
|
||||
SEL methodName = method_getName(method);
|
||||
|
||||
if (behavior & ext_methodInjectionIgnoreLoad) {
|
||||
if (methodName == @selector(load)) {
|
||||
++successes;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (behavior & ext_methodInjectionIgnoreInitialize) {
|
||||
if (methodName == @selector(initialize)) {
|
||||
++successes;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL success = YES;
|
||||
IMP impl = method_getImplementation(method);
|
||||
const char *type = method_getTypeEncoding(method);
|
||||
|
||||
switch (behavior & ext_methodInjectionOverwriteBehaviorMask) {
|
||||
case ext_methodInjectionFailOnExisting:
|
||||
success = class_addMethod(aClass, methodName, impl, type);
|
||||
break;
|
||||
|
||||
case ext_methodInjectionFailOnAnyExisting:
|
||||
if (class_getInstanceMethod(aClass, methodName)) {
|
||||
success = NO;
|
||||
break;
|
||||
}
|
||||
|
||||
// else fall through
|
||||
|
||||
case ext_methodInjectionReplace:
|
||||
class_replaceMethod(aClass, methodName, impl, type);
|
||||
break;
|
||||
|
||||
case ext_methodInjectionFailOnSuperclassExisting:
|
||||
{
|
||||
Class superclass = class_getSuperclass(aClass);
|
||||
if (superclass && class_getInstanceMethod(superclass, methodName))
|
||||
success = NO;
|
||||
else
|
||||
class_replaceMethod(aClass, methodName, impl, type);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "ERROR: Unrecognized method injection behavior: %i\n", (int)(behavior & ext_methodInjectionOverwriteBehaviorMask));
|
||||
}
|
||||
|
||||
if (success)
|
||||
++successes;
|
||||
else
|
||||
failedToAddCallback(aClass, method);
|
||||
}
|
||||
}
|
||||
|
||||
return successes;
|
||||
}
|
||||
|
||||
BOOL ext_injectMethodsFromClass (
|
||||
Class srcClass,
|
||||
Class dstClass,
|
||||
ext_methodInjectionBehavior behavior,
|
||||
ext_failedMethodCallback failedToAddCallback)
|
||||
{
|
||||
unsigned count, addedCount;
|
||||
BOOL success = YES;
|
||||
|
||||
count = 0;
|
||||
Method *instanceMethods = class_copyMethodList(srcClass, &count);
|
||||
|
||||
addedCount = ext_injectMethods(
|
||||
dstClass,
|
||||
instanceMethods,
|
||||
count,
|
||||
behavior,
|
||||
failedToAddCallback
|
||||
);
|
||||
|
||||
free(instanceMethods);
|
||||
if (addedCount < count)
|
||||
success = NO;
|
||||
|
||||
count = 0;
|
||||
Method *classMethods = class_copyMethodList(object_getClass(srcClass), &count);
|
||||
|
||||
// ignore +load
|
||||
behavior |= ext_methodInjectionIgnoreLoad;
|
||||
addedCount = ext_injectMethods(
|
||||
object_getClass(dstClass),
|
||||
classMethods,
|
||||
count,
|
||||
behavior,
|
||||
failedToAddCallback
|
||||
);
|
||||
|
||||
free(classMethods);
|
||||
if (addedCount < count)
|
||||
success = NO;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
Class ext_classBeforeSuperclass (Class receiver, Class superclass) {
|
||||
Class previousClass = nil;
|
||||
|
||||
while (![receiver isEqual:superclass]) {
|
||||
previousClass = receiver;
|
||||
receiver = class_getSuperclass(receiver);
|
||||
}
|
||||
|
||||
return previousClass;
|
||||
}
|
||||
|
||||
Class *ext_copyClassList (unsigned *count) {
|
||||
// get the number of classes registered with the runtime
|
||||
int classCount = objc_getClassList(NULL, 0);
|
||||
if (!classCount) {
|
||||
if (count)
|
||||
*count = 0;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// allocate space for them plus NULL
|
||||
Class *allClasses = (Class *)malloc(sizeof(Class) * (classCount + 1));
|
||||
if (!allClasses) {
|
||||
fprintf(stderr, "ERROR: Could allocate memory for all classes\n");
|
||||
if (count)
|
||||
*count = 0;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// and then actually pull the list of the class objects
|
||||
classCount = objc_getClassList(allClasses, classCount);
|
||||
allClasses[classCount] = NULL;
|
||||
|
||||
@autoreleasepool {
|
||||
// weed out classes that do weird things when reflected upon
|
||||
for (int i = 0;i < classCount;) {
|
||||
Class class = allClasses[i];
|
||||
BOOL keep = YES;
|
||||
|
||||
if (keep)
|
||||
keep &= class_respondsToSelector(class, @selector(methodSignatureForSelector:));
|
||||
|
||||
if (keep) {
|
||||
if (class_respondsToSelector(class, @selector(isProxy)))
|
||||
keep &= ![class isProxy];
|
||||
}
|
||||
|
||||
if (!keep) {
|
||||
if (--classCount > i) {
|
||||
memmove(allClasses + i, allClasses + i + 1, (classCount - i) * sizeof(*allClasses));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
if (count)
|
||||
*count = (unsigned)classCount;
|
||||
|
||||
return allClasses;
|
||||
}
|
||||
|
||||
unsigned ext_addMethods (Class aClass, Method *methods, unsigned count, BOOL checkSuperclasses, ext_failedMethodCallback failedToAddCallback) {
|
||||
ext_methodInjectionBehavior behavior = ext_methodInjectionFailOnExisting;
|
||||
if (checkSuperclasses)
|
||||
behavior |= ext_methodInjectionFailOnSuperclassExisting;
|
||||
|
||||
return ext_injectMethods(
|
||||
aClass,
|
||||
methods,
|
||||
count,
|
||||
behavior,
|
||||
failedToAddCallback
|
||||
);
|
||||
}
|
||||
|
||||
BOOL ext_addMethodsFromClass (Class srcClass, Class dstClass, BOOL checkSuperclasses, ext_failedMethodCallback failedToAddCallback) {
|
||||
ext_methodInjectionBehavior behavior = ext_methodInjectionFailOnExisting;
|
||||
if (checkSuperclasses)
|
||||
behavior |= ext_methodInjectionFailOnSuperclassExisting;
|
||||
|
||||
return ext_injectMethodsFromClass(srcClass, dstClass, behavior, failedToAddCallback);
|
||||
}
|
||||
|
||||
BOOL ext_classIsKindOfClass (Class receiver, Class aClass) {
|
||||
while (receiver) {
|
||||
if (receiver == aClass)
|
||||
return YES;
|
||||
|
||||
receiver = class_getSuperclass(receiver);
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
Class *ext_copyClassListConformingToProtocol (Protocol *protocol, unsigned *count) {
|
||||
Class *allClasses;
|
||||
|
||||
/*
|
||||
* set up an autorelease pool in case any Cocoa classes invoke +initialize
|
||||
* during this process
|
||||
*/
|
||||
@autoreleasepool {
|
||||
unsigned classCount = 0;
|
||||
allClasses = ext_copyClassList(&classCount);
|
||||
|
||||
// we're going to reuse allClasses for the return value, so returnIndex will
|
||||
// keep track of the indices we replace with new values
|
||||
unsigned returnIndex = 0;
|
||||
|
||||
for (unsigned classIndex = 0;classIndex < classCount;++classIndex) {
|
||||
Class cls = allClasses[classIndex];
|
||||
if (class_conformsToProtocol(cls, protocol))
|
||||
allClasses[returnIndex++] = cls;
|
||||
}
|
||||
|
||||
allClasses[returnIndex] = NULL;
|
||||
if (count)
|
||||
*count = returnIndex;
|
||||
}
|
||||
|
||||
return allClasses;
|
||||
}
|
||||
|
||||
ext_propertyAttributes *ext_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 = 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)
|
||||
ext_propertyAttributes *attributes = calloc(1, sizeof(ext_propertyAttributes) + typeLength + 1);
|
||||
if (!attributes) {
|
||||
fprintf(stderr, "ERROR: Could not allocate ext_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 == *(@encode(id)) && *next == '"') {
|
||||
// we should be able to extract a class name
|
||||
const char *className = next + 1;
|
||||
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 = next - className;
|
||||
char trimmedName[classNameLength];
|
||||
|
||||
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 = ext_propertyMemoryManagementPolicyCopy;
|
||||
break;
|
||||
|
||||
case '&':
|
||||
attributes->memoryManagementPolicy = ext_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 = 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;
|
||||
}
|
||||
|
||||
Class *ext_copySubclassList (Class targetClass, unsigned *subclassCount) {
|
||||
unsigned classCount = 0;
|
||||
Class *allClasses = ext_copyClassList(&classCount);
|
||||
if (!allClasses || !classCount) {
|
||||
fprintf(stderr, "ERROR: No classes registered with the runtime, cannot find %s!\n", class_getName(targetClass));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// we're going to reuse allClasses for the return value, so returnIndex will
|
||||
// keep track of the indices we replace with new values
|
||||
unsigned returnIndex = 0;
|
||||
|
||||
BOOL isMeta = class_isMetaClass(targetClass);
|
||||
|
||||
for (unsigned classIndex = 0;classIndex < classCount;++classIndex) {
|
||||
Class cls = allClasses[classIndex];
|
||||
Class superclass = class_getSuperclass(cls);
|
||||
|
||||
while (superclass != NULL) {
|
||||
if (isMeta) {
|
||||
if (object_getClass(superclass) == targetClass)
|
||||
break;
|
||||
} else if (superclass == targetClass)
|
||||
break;
|
||||
|
||||
superclass = class_getSuperclass(superclass);
|
||||
}
|
||||
|
||||
if (!superclass)
|
||||
continue;
|
||||
|
||||
// at this point, 'cls' is definitively a subclass of targetClass
|
||||
if (isMeta)
|
||||
cls = object_getClass(cls);
|
||||
|
||||
allClasses[returnIndex++] = cls;
|
||||
}
|
||||
|
||||
allClasses[returnIndex] = NULL;
|
||||
if (subclassCount)
|
||||
*subclassCount = returnIndex;
|
||||
|
||||
return allClasses;
|
||||
}
|
||||
|
||||
Method ext_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;
|
||||
}
|
||||
|
||||
BOOL ext_getPropertyAccessorsForClass (objc_property_t property, Class aClass, Method *getter, Method *setter) {
|
||||
ext_propertyAttributes *attributes = ext_copyPropertyAttributes(property);
|
||||
if (!attributes)
|
||||
return NO;
|
||||
|
||||
SEL getterName = attributes->getter;
|
||||
SEL setterName = attributes->setter;
|
||||
|
||||
free(attributes);
|
||||
attributes = NO;
|
||||
|
||||
/*
|
||||
* set up an autorelease pool in case this sends aClass its first message
|
||||
*/
|
||||
@autoreleasepool {
|
||||
Method foundGetter = class_getInstanceMethod(aClass, getterName);
|
||||
if (!foundGetter) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (getter)
|
||||
*getter = foundGetter;
|
||||
|
||||
if (setter) {
|
||||
Method foundSetter = class_getInstanceMethod(aClass, setterName);
|
||||
if (foundSetter)
|
||||
*setter = foundSetter;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSMethodSignature *ext_globalMethodSignatureForSelector (SEL aSelector) {
|
||||
// set up a simplistic cache to avoid repeatedly scouring every class in the
|
||||
// runtime
|
||||
static const size_t selectorCacheLength = 1 << 8;
|
||||
static const uintptr_t selectorCacheMask = (selectorCacheLength - 1);
|
||||
static void * volatile selectorCache[selectorCacheLength];
|
||||
|
||||
const char *cachedType = selectorCache[(uintptr_t)aSelector & selectorCacheMask];
|
||||
if (cachedType) {
|
||||
return [NSMethodSignature signatureWithObjCTypes:cachedType];
|
||||
}
|
||||
|
||||
unsigned classCount = 0;
|
||||
Class *classes = ext_copyClassList(&classCount);
|
||||
if (!classes)
|
||||
return nil;
|
||||
|
||||
NSMethodSignature *signature = nil;
|
||||
|
||||
/*
|
||||
* set up an autorelease pool in case any Cocoa classes invoke +initialize
|
||||
* during this process
|
||||
*/
|
||||
@autoreleasepool {
|
||||
for (unsigned i = 0;i < classCount;++i) {
|
||||
Class cls = classes[i];
|
||||
Method method;
|
||||
|
||||
method = class_getInstanceMethod(cls, aSelector);
|
||||
if (!method)
|
||||
method = class_getClassMethod(cls, aSelector);
|
||||
|
||||
if (method) {
|
||||
const char *type = method_getTypeEncoding(method);
|
||||
uintptr_t cacheLocation = ((uintptr_t)aSelector & selectorCacheMask);
|
||||
|
||||
// this doesn't need to be a barrier, and we don't care whether
|
||||
// it succeeds, since our only goal is to make things faster in
|
||||
// the future
|
||||
OSAtomicCompareAndSwapPtr(selectorCache[cacheLocation], (void *)type, selectorCache + cacheLocation);
|
||||
|
||||
signature = [NSMethodSignature signatureWithObjCTypes:type];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(classes);
|
||||
return signature;
|
||||
}
|
||||
|
||||
BOOL ext_loadSpecialProtocol (Protocol *protocol, void (^injectionBehavior)(Class destinationClass)) {
|
||||
@autoreleasepool {
|
||||
NSCParameterAssert(protocol != nil);
|
||||
NSCParameterAssert(injectionBehavior != nil);
|
||||
|
||||
// lock the mutex to prevent accesses from other threads while we perform
|
||||
// this work
|
||||
if (pthread_mutex_lock(&specialProtocolsLock) != 0) {
|
||||
fprintf(stderr, "ERROR: Could not synchronize on special protocol data\n");
|
||||
return NO;
|
||||
}
|
||||
|
||||
// if we've hit the hard maximum for number of special protocols, we can't
|
||||
// continue
|
||||
if (specialProtocolCount == SIZE_MAX) {
|
||||
pthread_mutex_unlock(&specialProtocolsLock);
|
||||
return NO;
|
||||
}
|
||||
|
||||
// if the array has no more space, we will need to allocate additional
|
||||
// entries
|
||||
if (specialProtocolCount >= specialProtocolCapacity) {
|
||||
size_t newCapacity;
|
||||
if (specialProtocolCapacity == 0)
|
||||
// if there are no entries, make space for just one
|
||||
newCapacity = 1;
|
||||
else {
|
||||
// otherwise, double the current capacity
|
||||
newCapacity = specialProtocolCapacity << 1;
|
||||
|
||||
// if the new capacity is less than the current capacity, that's
|
||||
// unsigned integer overflow
|
||||
if (newCapacity < specialProtocolCapacity) {
|
||||
// set it to the maximum possible instead
|
||||
newCapacity = SIZE_MAX;
|
||||
|
||||
// if the new capacity is still not greater than the current
|
||||
// (for instance, if it was already SIZE_MAX), we can't continue
|
||||
if (newCapacity <= specialProtocolCapacity) {
|
||||
pthread_mutex_unlock(&specialProtocolsLock);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we have a new capacity, so resize the list of all special protocols
|
||||
// to add the new entries
|
||||
void * restrict ptr = realloc(specialProtocols, sizeof(*specialProtocols) * newCapacity);
|
||||
if (!ptr) {
|
||||
// the allocation failed, abort
|
||||
pthread_mutex_unlock(&specialProtocolsLock);
|
||||
return NO;
|
||||
}
|
||||
|
||||
specialProtocols = ptr;
|
||||
specialProtocolCapacity = newCapacity;
|
||||
}
|
||||
|
||||
// at this point, there absolutely must be at least one empty entry in the
|
||||
// array
|
||||
assert(specialProtocolCount < specialProtocolCapacity);
|
||||
|
||||
// disable warning about "leaking" this block, which is released in
|
||||
// ext_injectSpecialProtocols()
|
||||
#ifndef __clang_analyzer__
|
||||
ext_specialProtocolInjectionBlock copiedBlock = [injectionBehavior copy];
|
||||
|
||||
// construct a new EXTSpecialProtocol structure and add it to the first
|
||||
// empty space in the array
|
||||
specialProtocols[specialProtocolCount] = (EXTSpecialProtocol){
|
||||
.protocol = protocol,
|
||||
.injectionBlock = (__bridge_retained void *)copiedBlock,
|
||||
.ready = NO
|
||||
};
|
||||
#endif
|
||||
|
||||
++specialProtocolCount;
|
||||
pthread_mutex_unlock(&specialProtocolsLock);
|
||||
}
|
||||
|
||||
// success!
|
||||
return YES;
|
||||
}
|
||||
|
||||
void ext_specialProtocolReadyForInjection (Protocol *protocol) {
|
||||
@autoreleasepool {
|
||||
NSCParameterAssert(protocol != nil);
|
||||
|
||||
// lock the mutex to prevent accesses from other threads while we perform
|
||||
// this work
|
||||
if (pthread_mutex_lock(&specialProtocolsLock) != 0) {
|
||||
fprintf(stderr, "ERROR: Could not synchronize on special protocol data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// loop through all the special protocols in our list, trying to find the
|
||||
// one associated with 'protocol'
|
||||
for (size_t i = 0;i < specialProtocolCount;++i) {
|
||||
if (specialProtocols[i].protocol == protocol) {
|
||||
// found the matching special protocol, check to see if it's
|
||||
// already ready
|
||||
if (!specialProtocols[i].ready) {
|
||||
// if it's not, mark it as being ready now
|
||||
specialProtocols[i].ready = YES;
|
||||
|
||||
// since this special protocol was in our array, and it was not
|
||||
// loaded, the total number of protocols loaded must be less
|
||||
// than the total count at this point in time
|
||||
assert(specialProtocolsReady < specialProtocolCount);
|
||||
|
||||
// ... and then increment the total number of special protocols
|
||||
// loaded – if it now matches the total count of special
|
||||
// protocols, begin the injection process
|
||||
if (++specialProtocolsReady == specialProtocolCount)
|
||||
ext_injectSpecialProtocols();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&specialProtocolsLock);
|
||||
}
|
||||
}
|
||||
|
||||
void ext_removeMethod (Class aClass, SEL methodName) {
|
||||
Method existingMethod = ext_getImmediateInstanceMethod(aClass, methodName);
|
||||
if (!existingMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* set up an autorelease pool in case any Cocoa classes invoke +initialize
|
||||
* during this process
|
||||
*/
|
||||
@autoreleasepool {
|
||||
Method superclassMethod = NULL;
|
||||
Class superclass = class_getSuperclass(aClass);
|
||||
if (superclass)
|
||||
superclassMethod = class_getInstanceMethod(superclass, methodName);
|
||||
|
||||
if (superclassMethod) {
|
||||
method_setImplementation(existingMethod, method_getImplementation(superclassMethod));
|
||||
} else {
|
||||
// since we now know that the method doesn't exist on any
|
||||
// superclass, get an IMP internal to the runtime for message forwarding
|
||||
IMP forward = class_getMethodImplementation(superclass, methodName);
|
||||
|
||||
method_setImplementation(existingMethod, forward);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ext_replaceMethods (Class aClass, Method *methods, unsigned count) {
|
||||
ext_injectMethods(
|
||||
aClass,
|
||||
methods,
|
||||
count,
|
||||
ext_methodInjectionReplace,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
void ext_replaceMethodsFromClass (Class srcClass, Class dstClass) {
|
||||
ext_injectMethodsFromClass(srcClass, dstClass, ext_methodInjectionReplace, NULL);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user