Skip to content

Commit a22241f

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 c34a820 commit a22241f

File tree

11 files changed

+381
-106
lines changed

11 files changed

+381
-106
lines changed

Source/OCMock/NSInvocation+OCMAdditions.h

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

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

23-
- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude;
24-
23+
- (void)applyConstraintOptionsFromStubInvocation:(NSInvocation *)stubInvocation excludingObject:(id)objectToExclude;
2524
- (id)getArgumentAtIndexAsObject:(NSInteger)argIndex;
2625

2726
- (NSString *)invocationDescription;

Source/OCMock/NSInvocation+OCMAdditions.m

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

5656

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

59-
- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
70+
- (void)applyConstraintOptionsFromStubInvocation:(NSInvocation *)stubInvocation excludingObject:(id)objectToExclude
6071
{
6172
if(objc_getAssociatedObject(self, OCMRetainedObjectArgumentsKey) != nil)
6273
{
@@ -112,7 +123,21 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
112123
}
113124
else
114125
{
115-
[retainedArguments addObject:argument];
126+
// Conform to the constraintOptions in the stub (if any).
127+
OCMConstraintOptions constraintOptions = [stubInvocation getArgumentContraintOptionsForArgumentAtIndex:index];
128+
if((constraintOptions & OCMConstraintCopyInvocationArg))
129+
{
130+
// Copy not only retains the copy in our array
131+
// but updates the arg in the invocation that we store.
132+
id argCopy = [argument copy];
133+
[retainedArguments addObject:argCopy];
134+
[self setArgument:&argCopy atIndex:index];
135+
[argCopy release];
136+
}
137+
else if(!(constraintOptions & OCMConstraintDoNotRetainInvocationArg))
138+
{
139+
[retainedArguments addObject:argument];
140+
}
116141
}
117142
}
118143
}

Source/OCMock/OCMArg.h

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,36 @@
1616

1717
#import <Foundation/Foundation.h>
1818

19-
@interface OCMArg : NSObject
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),
2041

42+
OCMArgNeverRetainArg = OCMArgDoNotRetainStubArg | OCMArgDoNotRetainInvocationArg,
43+
};
44+
45+
@interface OCMArg : NSObject
2146
// constraining arguments
2247

48+
// constrain using OCMArgDefaultOptions
2349
+ (id)any;
2450
+ (SEL)anySelector;
2551
+ (void *)anyPointer;
@@ -32,6 +58,15 @@
3258
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject;
3359
+ (id)checkWithBlock:(BOOL (^)(id obj))block;
3460

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

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

Source/OCMock/OCMArg.m

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ @implementation OCMArg
2525

2626
+ (id)any
2727
{
28-
return [[[OCMAnyConstraint alloc] init] autorelease];
28+
return [self anyWithOptions:OCMArgDefaultOptions];
2929
}
3030

3131
+ (void *)anyPointer
@@ -45,39 +45,80 @@ + (SEL)anySelector
4545

4646
+ (id)isNil
4747
{
48-
return [[OCMIsNilConstraint alloc] init];
48+
49+
return [self isNilWithOptions:OCMArgDefaultOptions];
4950
}
5051

5152
+ (id)isNotNil
5253
{
53-
return [[OCMIsNotNilConstraint alloc] init];
54+
return [self isNotNilWithOptions:OCMArgDefaultOptions];
5455
}
5556

5657
+ (id)isEqual:(id)value
5758
{
58-
return [[[OCMIsEqualConstraint alloc] initWithTestValue:value] autorelease];
59+
return [self isEqual:value options:OCMArgDefaultOptions];
5960
}
6061

6162
+ (id)isNotEqual:(id)value
6263
{
63-
return [[[OCMIsNotEqualConstraint alloc] initWithTestValue:value] autorelease];
64+
return [self isNotEqual:value options:OCMArgDefaultOptions];
6465
}
6566

6667
+ (id)isKindOfClass:(Class)cls
6768
{
68-
return [[[OCMBlockConstraint alloc] initWithConstraintBlock:^BOOL(id obj) {
69-
return [obj isKindOfClass:cls];
70-
}] autorelease];
69+
return [self isKindOfClass:cls options:OCMArgDefaultOptions];
7170
}
7271

7372
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject
7473
{
75-
return [OCMConstraint constraintWithSelector:selector onObject:anObject];
74+
return [self checkWithSelector:selector onObject:anObject options:OCMArgDefaultOptions];
7675
}
7776

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

83124
+ (id *)setTo:(id)value
@@ -140,4 +181,13 @@ + (id)resolveSpecialValues:(NSValue *)value
140181
return value;
141182
}
142183

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+
143193
@end

Source/OCMock/OCMConstraint.h

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,22 @@
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+
};
27+
1928
@interface OCMConstraint : NSObject
2029

30+
@property (readonly) OCMConstraintOptions constraintOptions;
31+
32+
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_DESIGNATED_INITIALIZER;
33+
- (instancetype)init NS_UNAVAILABLE;
34+
2135
- (BOOL)evaluate:(id)value;
2236

2337
// if you are looking for any, isNil, etc, they have moved to OCMArg
@@ -27,6 +41,8 @@
2741
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject;
2842
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue;
2943

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;
3046

3147
@end
3248

@@ -44,8 +60,8 @@
4460
id testValue;
4561
}
4662

47-
- (instancetype)initWithTestValue:(id)testValue NS_DESIGNATED_INITIALIZER;
48-
- (instancetype)init NS_UNAVAILABLE;
63+
- (instancetype)initWithTestValue:(id)testValue options:(OCMConstraintOptions)options NS_DESIGNATED_INITIALIZER;
64+
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_UNAVAILABLE;
4965

5066
@end
5167

@@ -60,8 +76,8 @@
6076
NSInvocation *invocation;
6177
}
6278

63-
- (instancetype)initWithInvocation:(NSInvocation *)invocation NS_DESIGNATED_INITIALIZER;
64-
- (instancetype)init NS_UNAVAILABLE;
79+
- (instancetype)initWithInvocation:(NSInvocation *)invocation options:(OCMConstraintOptions)options NS_DESIGNATED_INITIALIZER;
80+
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_UNAVAILABLE;
6581

6682
@end
6783

@@ -70,8 +86,8 @@
7086
BOOL (^block)(id);
7187
}
7288

73-
- (instancetype)initWithConstraintBlock:(BOOL (^)(id))block NS_DESIGNATED_INITIALIZER;
74-
- (instancetype)init NS_UNAVAILABLE;
89+
- (instancetype)initWithOptions:(OCMConstraintOptions)options block:(BOOL (^)(id))block NS_DESIGNATED_INITIALIZER;
90+
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_UNAVAILABLE;
7591

7692
@end
7793

0 commit comments

Comments
 (0)