Reputation: 42582
Say I have a private method -(void) doSomething
defined in m file of School
class (it is not declared in interface header):
@implementation School
-(void) doSomething {
...
}
@end
I have a unit test case for School
class:
@interface SchoolTest : XCTestCase
@end
@implementation SchoolTest
- (void)testExample {
id mySchoolMock = [OCMockObject partialMockForObject:[School new]];
// I know I can't access '-(void)doSomething' since it is a private method
}
@end
I know I normally can't access this private method. But if I re-declare the -(void) doSomething
in my test class as below:
@interface SchoolTest : XCTestCase
// re-declare it in my test class
-(void) doSomething
@end
@implementation SchoolTest
- (void)testExample {
id mySchoolMock = [OCMockObject partialMockForObject:[School new]];
// Now I can access '-(void)doSomething'!!! Why now I can access the private method in `mySchool` Instance in my test class ?
[mySchoolMock doSomething];
}
@end
Why in above way I can access private method of School
class with mySchool
instance? What is the objective-c theory behind this?
(I am doing this because I have read the answer from this question, but I don't understand why we can do it? what is the theory behind?)
Upvotes: 1
Views: 407
Reputation: 726479
Objective-C lacks protection mechanism for methods and fields that would be active at run time. All "protection" is done at compile time, when you hide the method in .m
file to make it "private".
Once the method is compiled, its information is stored in a table that is used to dispatch method invocations at runtime. Anyone who has the selector for your method can invoke it, as if it were public. This enables a lot of dynamic behavior in Objective-C.
Unfortunately, there is no protection of selectors at runtime. When you re-declare the private method for testing, you are essentially telling Objective-C that you know that the method is there, and insist on letting you make a call.
Two things could happen when you do this: if the method is actually there, the call is going to succeed. If the method is not there, however, you would end up with a crash on calling an invalid selector.
the method is re-delclared in my test class not in School class
This part is slightly tricky. id
is a special type, which allows you to call any method with very little compile-time checking. Basically, the compiler verifies that there is any type that it knows that has a method with the matching signature, and then it lets you make the call. This is similar to creating a @SELECTOR
for your method, and then calling it dynamically on an id
-typed object. Essentially, the compiler verifiers that you didn't make an obvious typo, and lets the call to proceed.
Upvotes: 1
Reputation: 8006
It is worth noting that you didn't "redeclare" the method - you added a doSomething
method to SchoolTest
class. This is much different from what was mentioned in the linked answer - there a category was used to show "private" methods to the test class. So something like this would be better :
@interface School (Tests)
- (void)doSomething;
@end
What you did worked, because mySchoolMock
is of id
type, and doSomething
was a visible symbol, so Xcode and compilers didn't warn you about anything, and it worked because mechanisms explained by @dasblinkenlight did their job at runtime.
Upvotes: 0