1

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?)

Community
  • 1
  • 1
Leem.fin
  • 40,781
  • 83
  • 202
  • 354
  • There's really no such thing as a private method in ObjC. There are only methods the compiler pretends it can't see from certain perspectives. I am unsure why your code doesn't at least trigger a warning that `doSomething` is an unknown selector. Is your code not using ARC? – Avi May 24 '16 at 11:01
  • @Avi, yes, ARC is used. – Leem.fin May 24 '16 at 11:02

2 Answers2

1

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • thanks for the explanation, but 1. Could you please be more specific, especially the last paragraph in your answer, `if the method is actually there, the call is going to succeed. If the method is not there ...`. So what does `there` mean in my example? 2. What I don't understand is the method is re-delclared in my test class not in School class, but I am able to call this private method on my School instance after re-declared it in test class (very weird to me). Do you mean once a method is declared no matter where, it is visible by any class at runtime? – Leem.fin May 24 '16 at 11:12
  • @Leem.fin 1. In your case the method is there, because the implementation has code to handle the no-argument call to `doSomething`. If your re-declaration was 'doSomethingElse', the compiler would let you compile, but the call would crash at run-time. 2. I answered this part in an edit. – Sergey Kalinichenko May 24 '16 at 11:17
0

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.

Losiowaty
  • 7,911
  • 2
  • 32
  • 47