Skip to content

Commit 7156c9e

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 a41d9df commit 7156c9e

16 files changed

+309
-343
lines changed

Source/OCMock.xcodeproj/project.pbxproj

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,6 @@
281281
817EB1661BD7674D0047E85A /* OCMFunctionsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 03F370CA1BAA1DE800CAD3E8 /* OCMFunctionsPrivate.h */; };
282282
8BF73E53246CA75E00B9A52C /* OCMNoEscapeBlockTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */; settings = {COMPILER_FLAGS = "-Xclang -fexperimental-optimized-noescape"; }; };
283283
8BF73E54246CA75E00B9A52C /* OCMNoEscapeBlockTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */; settings = {COMPILER_FLAGS = "-Xclang -fexperimental-optimized-noescape"; }; };
284-
8BF740142476E4B400B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
285-
8BF740152476E4B400B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
286-
8BF740162476E59A00B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
287-
8BF740172476E59A00B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
288-
8BF740182476E59B00B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
289-
8BF740192476E59C00B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
290-
8BF7401A24771FD600B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
291-
8BF7401B24771FD700B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
292-
8BF7401C24771FD700B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
293-
8BF7401D24771FD800B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
294284
8DE97C5522B43EE60098C63F /* OCMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B3159E146333BF0052CD09 /* OCMockObject.m */; };
295285
8DE97C5622B43EE60098C63F /* OCClassMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B3158C146333BF0052CD09 /* OCClassMockObject.m */; };
296286
8DE97C5722B43EE60098C63F /* OCPartialMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B315AA146333BF0052CD09 /* OCPartialMockObject.m */; };
@@ -582,8 +572,6 @@
582572
3CFBDD761BB3DB200050D9C5 /* TestClassWithCustomReferenceCounting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestClassWithCustomReferenceCounting.m; sourceTree = "<group>"; };
583573
817EB1621BD765130047E85A /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; };
584574
8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMNoEscapeBlockTests.m; sourceTree = "<group>"; };
585-
8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMUnretainedArgument.h; sourceTree = "<group>"; };
586-
8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMUnretainedArgument.m; sourceTree = "<group>"; };
587575
8DE97CA022B43EE60098C63F /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; };
588576
A02926811CA0725A00594AAF /* TestObjects.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TestObjects.xcdatamodel; sourceTree = "<group>"; };
589577
D31108AD1828DB8700737925 /* OCMockLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OCMockLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -883,8 +871,6 @@
883871
03B315A2146333BF0052CD09 /* OCMPassByRefSetter.m */,
884872
2FA2891034E7B73AA3511D17 /* OCMBlockArgCaller.h */,
885873
2FA283D58AA7569D8A5B0C57 /* OCMBlockArgCaller.m */,
886-
8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */,
887-
8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */,
888874
);
889875
name = "Argument Constraints and Actions";
890876
sourceTree = "<group>";
@@ -975,7 +961,6 @@
975961
03B315F5146333C00052CD09 /* OCMPassByRefSetter.h in Headers */,
976962
03B315FA146333C00052CD09 /* OCMRealObjectForwarder.h in Headers */,
977963
03B315FF146333C00052CD09 /* OCMObjectReturnValueProvider.h in Headers */,
978-
8BF740142476E4B400B9A52C /* OCMUnretainedArgument.h in Headers */,
979964
03B31604146333C00052CD09 /* OCObserverMockObject.h in Headers */,
980965
03B31609146333C00052CD09 /* OCPartialMockObject.h in Headers */,
981966
0368656D1D357317005E6BEE /* OCMQuantifier.h in Headers */,
@@ -1025,7 +1010,6 @@
10251010
817EB1661BD7674D0047E85A /* OCMFunctionsPrivate.h in Headers */,
10261011
03B31605146333C00052CD09 /* OCObserverMockObject.h in Headers */,
10271012
03B3160A146333C00052CD09 /* OCPartialMockObject.h in Headers */,
1028-
8BF7401A24771FD600B9A52C /* OCMUnretainedArgument.h in Headers */,
10291013
03B31614146333C00052CD09 /* OCProtocolMockObject.h in Headers */,
10301014
2FA28E1EB6B8536785258DF5 /* OCMInvocationMatcher.h in Headers */,
10311015
0322DA6A19118B4600CACAF1 /* OCMVerifier.h in Headers */,
@@ -1077,7 +1061,6 @@
10771061
817EB1591BD765130047E85A /* NSObject+OCMAdditions.h in Headers */,
10781062
817EB15A1BD765130047E85A /* NSValue+OCMAdditions.h in Headers */,
10791063
817EB15B1BD765130047E85A /* OCMFunctions.h in Headers */,
1080-
8BF7401C24771FD700B9A52C /* OCMUnretainedArgument.h in Headers */,
10811064
817EB15C1BD765130047E85A /* OCMBlockArgCaller.h in Headers */,
10821065
817EB15D1BD765130047E85A /* OCMArgAction.h in Headers */,
10831066
2FA28806443827E286F12F6F /* OCMNonRetainingObjectReturnValueProvider.h in Headers */,
@@ -1110,7 +1093,6 @@
11101093
8DE97C8C22B43EE60098C63F /* OCMBoxedReturnValueProvider.h in Headers */,
11111094
8DE97C8D22B43EE60098C63F /* OCMExceptionReturnValueProvider.h in Headers */,
11121095
8DE97C8E22B43EE60098C63F /* OCMIndirectReturnValueProvider.h in Headers */,
1113-
8BF7401D24771FD800B9A52C /* OCMUnretainedArgument.h in Headers */,
11141096
8DE97C8F22B43EE60098C63F /* OCMNotificationPoster.h in Headers */,
11151097
8DE97C9022B43EE60098C63F /* OCMObjectReturnValueProvider.h in Headers */,
11161098
8DE97C9122B43EE60098C63F /* OCMFunctionsPrivate.h in Headers */,
@@ -1168,7 +1150,6 @@
11681150
F0B951481B00810C00942C38 /* NSObject+OCMAdditions.h in Headers */,
11691151
F0B951491B00810C00942C38 /* NSValue+OCMAdditions.h in Headers */,
11701152
F0B9514A1B00810C00942C38 /* OCMFunctions.h in Headers */,
1171-
8BF7401B24771FD700B9A52C /* OCMUnretainedArgument.h in Headers */,
11721153
2FA28B7BDB3319A499E90525 /* OCMBlockArgCaller.h in Headers */,
11731154
2FA280E60213BA09F007C173 /* OCMArgAction.h in Headers */,
11741155
2FA28AFBD67EAB9DD1F23BF5 /* OCMNonRetainingObjectReturnValueProvider.h in Headers */,
@@ -1432,7 +1413,6 @@
14321413
03B315CA146333BF0052CD09 /* OCMBlockCaller.m in Sources */,
14331414
036865681D3572ED005E6BEE /* OCMQuantifier.m in Sources */,
14341415
03B315CF146333BF0052CD09 /* OCMBoxedReturnValueProvider.m in Sources */,
1435-
8BF740152476E4B400B9A52C /* OCMUnretainedArgument.m in Sources */,
14361416
03B315D4146333BF0052CD09 /* OCMConstraint.m in Sources */,
14371417
03B315D9146333BF0052CD09 /* OCMExceptionReturnValueProvider.m in Sources */,
14381418
03B315DE146333BF0052CD09 /* OCMIndirectReturnValueProvider.m in Sources */,
@@ -1475,7 +1455,6 @@
14751455
03B315CC146333BF0052CD09 /* OCMBlockCaller.m in Sources */,
14761456
036865691D3572ED005E6BEE /* OCMQuantifier.m in Sources */,
14771457
03B315D1146333BF0052CD09 /* OCMBoxedReturnValueProvider.m in Sources */,
1478-
8BF740162476E59A00B9A52C /* OCMUnretainedArgument.m in Sources */,
14791458
03DCED6D183406BC0059089E /* NSObject+OCMAdditions.m in Sources */,
14801459
03B315D6146333BF0052CD09 /* OCMConstraint.m in Sources */,
14811460
03B315DB146333BF0052CD09 /* OCMExceptionReturnValueProvider.m in Sources */,
@@ -1547,7 +1526,6 @@
15471526
817EB11F1BD765130047E85A /* OCMInvocationMatcher.m in Sources */,
15481527
0368656B1D3572ED005E6BEE /* OCMQuantifier.m in Sources */,
15491528
817EB1201BD765130047E85A /* OCMInvocationStub.m in Sources */,
1550-
8BF740182476E59B00B9A52C /* OCMUnretainedArgument.m in Sources */,
15511529
817EB1211BD765130047E85A /* OCMInvocationExpectation.m in Sources */,
15521530
817EB1221BD765130047E85A /* OCMRealObjectForwarder.m in Sources */,
15531531
817EB1231BD765130047E85A /* OCMBlockCaller.m in Sources */,
@@ -1590,7 +1568,6 @@
15901568
8DE97C5C22B43EE60098C63F /* OCMVerifier.m in Sources */,
15911569
8DE97C5D22B43EE60098C63F /* OCMInvocationMatcher.m in Sources */,
15921570
8DE97C5E22B43EE60098C63F /* OCMInvocationStub.m in Sources */,
1593-
8BF740192476E59C00B9A52C /* OCMUnretainedArgument.m in Sources */,
15941571
8DE97C5F22B43EE60098C63F /* OCMInvocationExpectation.m in Sources */,
15951572
8DE97C6022B43EE60098C63F /* OCMRealObjectForwarder.m in Sources */,
15961573
8DE97C6122B43EE60098C63F /* OCMBlockCaller.m in Sources */,
@@ -1662,7 +1639,6 @@
16621639
F0B951141B0080EC00942C38 /* OCMInvocationMatcher.m in Sources */,
16631640
0368656A1D3572ED005E6BEE /* OCMQuantifier.m in Sources */,
16641641
F0B951151B0080EC00942C38 /* OCMInvocationStub.m in Sources */,
1665-
8BF740172476E59A00B9A52C /* OCMUnretainedArgument.m in Sources */,
16661642
F0B951161B0080EC00942C38 /* OCMInvocationExpectation.m in Sources */,
16671643
F0B951171B0080EC00942C38 /* OCMRealObjectForwarder.m in Sources */,
16681644
F0B951181B0080EC00942C38 /* OCMBlockCaller.m in Sources */,

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 excludingObjectsAtIndexes:(NSIndexSet *)indexes;
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: 28 additions & 12 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 isProxy] && [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 excludingObjectsAtIndexes:(NSIndexSet *)indexes;
69+
- (void)applyConstraintOptionsFromStubInvocation:(NSInvocation *)stubInvocation excludingObject:(id)objectToExclude
5970
{
6071
if(objc_getAssociatedObject(self, OCMRetainedObjectArgumentsKey) != nil)
6172
{
@@ -80,16 +91,7 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude excludingObject
8091
for(NSUInteger index = 2; index < numberOfArguments; index++)
8192
{
8293
const char *argumentType = [[self methodSignature] getArgumentTypeAtIndex:index];
83-
BOOL isObjectType = OCMIsObjectType(argumentType);
84-
if ([indexes containsIndex:index])
85-
{
86-
if (!isObjectType)
87-
{
88-
[NSException raise:NSInternalInconsistencyException format:@"Argument at %d is not an object", (int)index];
89-
}
90-
continue;
91-
}
92-
if (isObjectType)
94+
if (OCMIsObjectType(argumentType))
9395
{
9496
id argument;
9597
[self getArgument:&argument atIndex:index];
@@ -118,7 +120,21 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude excludingObject
118120
}
119121
else
120122
{
121-
[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+
}
122138
}
123139
}
124140
}

Source/OCMock/OCMArg.h

Lines changed: 35 additions & 23 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,21 +59,14 @@
3259
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject;
3360
+ (id)checkWithBlock:(BOOL (^)(id obj))block;
3461

35-
// Unretained object arguments are not retained by invocations on the mock, but are retained by the
36-
// stub itself. A use case for this is when you are stubbing an argument to a method that does not
37-
// retain its argument using an `OCMArg` variant that you do not want to keep a reference to.
38-
// See `OCMOCK_ANY_UNRETAINED`.
39-
+ (id)unretainedObject:(id)anObject;
40-
41-
// Unsafe unretained object arguments are not retained by invocations on the mock or by the stub.
42-
// A potential use case for this is when you are stubbing methods that do not retain their
43-
// arguments and you want to verify dealloc conditions. An example of this would be verifying
44-
// KVO registration/deregistration that occurs in the init/dealloc of an object. If the object were
45-
// retained by the mocking system in any way you would never see the deregistration.
46-
// Note that you *must* keep a reference to anObject outside this call or you will crash.
47-
// Something like `[OCMArg unsafeUnretainedObject:[[Foo alloc] init]]` under ARC is a guaranteed
48-
// dangling pointer problem.
49-
+ (id)unsafeUnretainedObject:(id)anObject;
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;
5070

5171
// manipulating arguments
5272

@@ -61,18 +81,10 @@
6181

6282
+ (id)resolveSpecialValues:(NSValue *)value;
6383

64-
// Return YES if `object` is either an unretained or an unsafe unretained object.
65-
+ (BOOL)isUnretained:(id)object;
66-
6784
@end
6885

6986
#define OCMOCK_ANY [OCMArg any]
7087

71-
// See comments on [OCMArg unretainedObject] and [OCMArg unsafeUnretainedObject].
72-
#define OCMOCK_UNSAFE_UNRETAINED(x) [OCMArg unsafeUnretainedObject:(x)]
73-
#define OCMOCK_UNRETAINED(x) [OCMArg unretainedObject:(x)]
74-
#define OCMOCK_ANY_UNRETAINED OCMOCK_UNRETAINED(OCMOCK_ANY)
75-
7688
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
7789
#define OCMOCK_VALUE(variable) \
7890
({ __typeof__(variable) __v = (variable); [NSValue value:&__v withObjCType:@encode(__typeof__(__v))]; })

0 commit comments

Comments
 (0)