Skip to content

Commit a8b4878

Browse files
committed
Add support for controlling retain/copy semantics for arguments to stubs.
Allows marking an argument in a stub as having various semantics: - is not retained by invocations Object arguments are retained by default in OCMock. In some cases to avoid retain loops you need to mark an argument as unretained. - is not retained by stub Stub arguments are retained by default in OCMock. In some specialized cases you do not want the stub arguments retained - is copied by invocation Some arguments have copy semantics and we need the invocation to copy the argument instead of retain it.
1 parent d91eb1d commit a8b4878

11 files changed

+377
-63
lines changed

Source/OCMock/NSInvocation+OCMAdditions.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
+ (NSInvocation *)invocationForBlock:(id)block withArguments:(NSArray *)arguments;
2222

23-
- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude;
23+
- (void)applyConstraintOptionsFromStubInvocation:(NSInvocation *)stubInvocation excludingObject:(id)objectToExclude;
2424

2525
- (id)getArgumentAtIndexAsObject:(NSInteger)argIndex;
2626

Source/OCMock/NSInvocation+OCMAdditions.m

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,20 @@ + (NSInvocation *)invocationForBlock:(id)block withArguments:(NSArray *)argument
5353
}
5454

5555

56+
- (OCMConstraintOptions)getArgumentContraintOptionsForArgumentAtIndex:(NSUInteger)index
57+
{
58+
id argument;
59+
[self getArgument:&argument atIndex:index];
60+
if([argument isKindOfClass:[OCMConstraint class]])
61+
{
62+
return [(OCMConstraint *)argument constraintOptions];
63+
}
64+
return OCMConstraintDefaultOptions;
65+
}
66+
5667
static NSString *const OCMRetainedObjectArgumentsKey = @"OCMRetainedObjectArgumentsKey";
5768

58-
- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
69+
- (void)applyConstraintOptionsFromStubInvocation:(NSInvocation *)stubInvocation excludingObject:(id)objectToExclude
5970
{
6071
if(objc_getAssociatedObject(self, OCMRetainedObjectArgumentsKey) != nil)
6172
{
@@ -109,7 +120,21 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
109120
}
110121
else
111122
{
112-
[retainedArguments addObject:argument];
123+
// Conform to the constraintOptions in the stub (if any).
124+
OCMConstraintOptions constraintOptions = [stubInvocation getArgumentContraintOptionsForArgumentAtIndex:index];
125+
if((constraintOptions & OCMConstraintCopyInvocationArg))
126+
{
127+
// Copy not only retains the copy in our array
128+
// but updates the arg in the invocation that we store.
129+
id argCopy = [argument copy];
130+
[retainedArguments addObject:argCopy];
131+
[self setArgument:&argCopy atIndex:index];
132+
[argCopy release];
133+
}
134+
else if(!(constraintOptions & OCMConstraintDoNotRetainInvocationArg))
135+
{
136+
[retainedArguments addObject:argument];
137+
}
113138
}
114139
}
115140
}

Source/OCMock/OCMArg.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,37 @@
1616

1717
#import <Foundation/Foundation.h>
1818

19+
// Options for controlling how OCMArgs function.
20+
typedef NS_OPTIONS(NSUInteger, OCMArgOptions) {
21+
// The OCMArg will retain/release the value passed to it, and invocations on a stub that has
22+
// arguments that the OCMArg is constraining will retain the values passed to them for the
23+
// arguments being constrained by the OCMArg.
24+
OCMArgDefaultOptions = 0UL,
25+
26+
// The OCMArg will not retain/release the value passed to it. Is only applicable for
27+
// `isEqual:options:` and `isNotEqual:options`. The caller is responsible for making sure that the
28+
// arg is valid for the required lifetime. Note that unless `OCMArgDoNotRetainInvocationArg` is
29+
// also specified, invocations of the stub that the OCMArg arg is constraining will retain values
30+
// passed to them for the arguments being constrained by the OCMArg. `OCMArgNeverRetainArg` is
31+
// usually what you want to use.
32+
OCMArgDoNotRetainStubArg = (1UL << 0),
33+
34+
// Invocations on a stub that has arguments that the OCMArg is constraining will retain/release
35+
// the values passed to them for the arguments being constrained by the OCMArg.
36+
OCMArgDoNotRetainInvocationArg = (1UL << 1),
37+
38+
// Invocations on a stub that has arguments that the OCMArg is constraining will copy/release
39+
// the values passed to them for the arguments being constrained by the OCMArg.
40+
OCMArgCopyInvocationArg = (1UL << 2),
41+
42+
OCMArgNeverRetainArg = OCMArgDoNotRetainStubArg | OCMArgDoNotRetainInvocationArg,
43+
};
44+
1945
@interface OCMArg : NSObject
2046

2147
// constraining arguments
2248

49+
// constrain using OCMArgDefaultOptions
2350
+ (id)any;
2451
+ (SEL)anySelector;
2552
+ (void *)anyPointer;
@@ -32,6 +59,15 @@
3259
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject;
3360
+ (id)checkWithBlock:(BOOL (^)(id obj))block;
3461

62+
+ (id)anyWithOptions:(OCMArgOptions)options;
63+
+ (id)isNilWithOptions:(OCMArgOptions)options;
64+
+ (id)isNotNilWithOptions:(OCMArgOptions)options;
65+
+ (id)isEqual:(id)value options:(OCMArgOptions)options;
66+
+ (id)isNotEqual:(id)value options:(OCMArgOptions)options;
67+
+ (id)isKindOfClass:(Class)cls options:(OCMArgOptions)options;
68+
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject options:(OCMArgOptions)options;
69+
+ (id)checkWithOptions:(OCMArgOptions)options withBlock:(BOOL (^)(id obj))block;
70+
3571
// manipulating arguments
3672

3773
+ (id *)setTo:(id)value;

Source/OCMock/OCMArg.m

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ @implementation OCMArg
2424

2525
+ (id)any
2626
{
27-
return [[[OCMAnyConstraint alloc] init] autorelease];
27+
return [self anyWithOptions:OCMArgDefaultOptions];
2828
}
2929

3030
+ (void *)anyPointer
@@ -44,39 +44,79 @@ + (SEL)anySelector
4444

4545
+ (id)isNil
4646
{
47-
return [[[OCMIsEqualConstraint alloc] initWithTestValue:nil] autorelease];
47+
return [self isNilWithOptions:OCMArgDefaultOptions];
4848
}
4949

5050
+ (id)isNotNil
5151
{
52-
return [[[OCMIsNotEqualConstraint alloc] initWithTestValue:nil] autorelease];
52+
return [self isNotNilWithOptions:OCMArgDefaultOptions];
5353
}
5454

5555
+ (id)isEqual:(id)value
5656
{
57-
return [[[OCMIsEqualConstraint alloc] initWithTestValue:value] autorelease];
57+
return [self isEqual:value options:OCMArgDefaultOptions];
5858
}
5959

6060
+ (id)isNotEqual:(id)value
6161
{
62-
return [[[OCMIsNotEqualConstraint alloc] initWithTestValue:value] autorelease];
62+
return [self isNotEqual:value options:OCMArgDefaultOptions];
6363
}
6464

6565
+ (id)isKindOfClass:(Class)cls
6666
{
67-
return [[[OCMBlockConstraint alloc] initWithConstraintBlock:^BOOL(id obj) {
68-
return [obj isKindOfClass:cls];
69-
}] autorelease];
67+
return [self isKindOfClass:cls options:OCMArgDefaultOptions];
7068
}
7169

7270
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject
7371
{
74-
return [OCMConstraint constraintWithSelector:selector onObject:anObject];
72+
return [self checkWithSelector:selector onObject:anObject options:OCMArgDefaultOptions];
7573
}
7674

7775
+ (id)checkWithBlock:(BOOL (^)(id))block
7876
{
79-
return [[[OCMBlockConstraint alloc] initWithConstraintBlock:block] autorelease];
77+
return [self checkWithOptions:OCMArgDefaultOptions withBlock:block];
78+
}
79+
80+
+ (id)anyWithOptions:(OCMArgOptions)options
81+
{
82+
return [[[OCMAnyConstraint alloc] initWithOptions:[self constraintOptionsFromArgOptions:options]] autorelease];
83+
}
84+
85+
+ (id)isNilWithOptions:(OCMArgOptions)options
86+
{
87+
return [[[OCMIsEqualConstraint alloc] initWithTestValue:nil options:[self constraintOptionsFromArgOptions:options]] autorelease];
88+
}
89+
90+
+ (id)isNotNilWithOptions:(OCMArgOptions)options
91+
{
92+
return [[[OCMIsNotEqualConstraint alloc] initWithTestValue:nil options:[self constraintOptionsFromArgOptions:options]] autorelease];
93+
}
94+
95+
+ (id)isEqual:(id)value options:(OCMArgOptions)options
96+
{
97+
return [[[OCMIsEqualConstraint alloc] initWithTestValue:value options:[self constraintOptionsFromArgOptions:options]] autorelease];
98+
}
99+
100+
+ (id)isNotEqual:(id)value options:(OCMArgOptions)options
101+
{
102+
return [[[OCMIsNotEqualConstraint alloc] initWithTestValue:value options:[self constraintOptionsFromArgOptions:options]] autorelease];
103+
}
104+
105+
+ (id)isKindOfClass:(Class)cls options:(OCMArgOptions)options
106+
{
107+
return [[[OCMBlockConstraint alloc] initWithOptions:[self constraintOptionsFromArgOptions:options] block:^BOOL(id obj) {
108+
return [obj isKindOfClass:cls];
109+
}] autorelease];
110+
}
111+
112+
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject options:(OCMArgOptions)options
113+
{
114+
return [OCMConstraint constraintWithSelector:selector onObject:anObject options:[self constraintOptionsFromArgOptions:options]];
115+
}
116+
117+
+ (id)checkWithOptions:(OCMArgOptions)options withBlock:(BOOL (^)(id obj))block
118+
{
119+
return [[[OCMBlockConstraint alloc] initWithOptions:[self constraintOptionsFromArgOptions:options] block:block] autorelease];
80120
}
81121

82122
+ (id *)setTo:(id)value
@@ -141,4 +181,13 @@ + (id)resolveSpecialValues:(NSValue *)value
141181
return value;
142182
}
143183

184+
+ (OCMConstraintOptions)constraintOptionsFromArgOptions:(OCMArgOptions)argOptions
185+
{
186+
OCMConstraintOptions constraintOptions = 0;
187+
if(argOptions & OCMArgDoNotRetainStubArg) constraintOptions |= OCMConstraintDoNotRetainStubArg;
188+
if(argOptions & OCMArgDoNotRetainInvocationArg) constraintOptions |= OCMConstraintDoNotRetainInvocationArg;
189+
if(argOptions & OCMArgCopyInvocationArg) constraintOptions |= OCMConstraintCopyInvocationArg;
190+
return constraintOptions;
191+
}
192+
144193
@end

Source/OCMock/OCMConstraint.h

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,21 @@
1616

1717
#import <Foundation/Foundation.h>
1818

19+
// See OCMArgOptions for documentation on options.
20+
typedef NS_OPTIONS(NSUInteger, OCMConstraintOptions) {
21+
OCMConstraintDefaultOptions = 0UL,
22+
OCMConstraintDoNotRetainStubArg = (1UL << 0),
23+
OCMConstraintDoNotRetainInvocationArg = (1UL << 1),
24+
OCMConstraintCopyInvocationArg = (1UL << 2),
25+
OCMConstraintNeverRetainArg = OCMConstraintDoNotRetainStubArg | OCMConstraintDoNotRetainInvocationArg,
26+
};
1927

20-
@interface OCMConstraint : NSObject
28+
@interface OCMConstraint : NSObject
29+
30+
@property (readonly) OCMConstraintOptions constraintOptions;
31+
32+
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_DESIGNATED_INITIALIZER;
33+
- (instancetype)init NS_UNAVAILABLE;
2134

2235
- (BOOL)evaluate:(id)value;
2336

@@ -28,6 +41,8 @@
2841
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject;
2942
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue;
3043

44+
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject options:(OCMConstraintOptions)options;
45+
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue options:(OCMConstraintOptions)options;
3146

3247
@end
3348

@@ -39,8 +54,8 @@
3954
id testValue;
4055
}
4156

42-
- (instancetype)initWithTestValue:(id)testValue NS_DESIGNATED_INITIALIZER;
43-
- (instancetype)init NS_UNAVAILABLE;
57+
- (instancetype)initWithTestValue:(id)testValue options:(OCMConstraintOptions)options NS_DESIGNATED_INITIALIZER;
58+
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_UNAVAILABLE;
4459

4560
@end
4661

@@ -55,8 +70,8 @@
5570
NSInvocation *invocation;
5671
}
5772

58-
- (instancetype)initWithInvocation:(NSInvocation *)invocation NS_DESIGNATED_INITIALIZER;
59-
- (instancetype)init NS_UNAVAILABLE;
73+
- (instancetype)initWithInvocation:(NSInvocation *)invocation options:(OCMConstraintOptions)options NS_DESIGNATED_INITIALIZER;
74+
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_UNAVAILABLE;
6075

6176
@end
6277

@@ -65,8 +80,8 @@
6580
BOOL (^block)(id);
6681
}
6782

68-
- (instancetype)initWithConstraintBlock:(BOOL (^)(id))block NS_DESIGNATED_INITIALIZER;
69-
- (instancetype)init NS_UNAVAILABLE;
83+
- (instancetype)initWithOptions:(OCMConstraintOptions)options block:(BOOL (^)(id))block NS_DESIGNATED_INITIALIZER;
84+
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_UNAVAILABLE;
7085

7186
@end
7287

0 commit comments

Comments
 (0)