Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-c: why private ivars are not hidden from the outside access when using KVC

After trying to access ivars using KVC, I have noticed that there was no protection on private and protected ivars. It doesn't matter what I put a in front of the ivar (private or protected keyword) - an ivar is always a public ivar when using KVC method "setValue". Here is my code where all of the seven ivars and properties are changeble outside the class instance:

//************ interface file ***************//
@interface MyClass : NSObject {
@public    
  NSNumber *public_num;
@protected 
  NSNumber *protected_num;
@private 
  NSNumber *private_num;
  NSNumber *private_property;
}
@property (retain) NSNumber *public_property;
@property (retain) NSNumber *private_property;
@end

//********* implementation file *********//
@interface MyClass(){
@private
  NSNumber *very_private_num;
}
@property (retain) NSNumber *very_private_property;
@end

@implementation MyClass
@synthesize public_property, private_property, very_private_property;
@end

//****** main **********//
MyClass *myClass = [[MyClass alloc] init];

[myClass setValue:[NSNumber numberWithInt:1] forKey:@"public_num"];
[myClass setValue:[NSNumber numberWithInt:2] forKey:@"protected_num"];
[myClass setValue:[NSNumber numberWithInt:3] forKey:@"private_num"];
[myClass setValue:[NSNumber numberWithInt:4] forKey:@"public_property"];
[myClass setValue:[NSNumber numberWithInt:5] forKey:@"private_property"];
[myClass setValue:[NSNumber numberWithInt:6] forKey:@"very_private_num"];
[myClass setValue:[NSNumber numberWithInt:7] forKey:@"very_private_property"];

NSNumber *l_public_num = [myClass valueForKey:@"public_num"];
NSNumber *l_protected_num = [myClass valueForKey:@"protected_num"];
NSNumber *l_private_num = [myClass valueForKey:@"private_num"];
NSNumber *l_public_property = [myClass valueForKey:@"public_property"];
NSNumber *l_private_property = [myClass valueForKey:@"private_property"];
NSNumber *l_very_private_num = [myClass valueForKey:@"very_private_num"];
NSNumber *l_very_private_property = [myClass valueForKey:@"very_private_property"];

NSLog(@"public_num = %@, protected_num = %@, private_num = %@, public_property = %@, private_property = %@, very_private_num = %@, very_private_property = %@", l_public_num, l_protected_num, l_private_num, l_public_property, l_private_property, l_very_private_num, l_very_private_property);

The result of the output> public_num = 1, protected_num = 2, private_num = 3, public_property = 4, private_property = 5, very_private_num = 6, very_private_property = 7.

Even if the ivar declared at private interface, it is still changeable outside the class. So how do I have to enforce encapsulation and "to protect my ivars from evil other programmers" :)

like image 463
Centurion Avatar asked May 25 '11 09:05

Centurion


4 Answers

NSObject conforms to the NSKeyValueCoding informal protocol. This defines setValue:forKey: and valueForKey:. setValue:forKey: and valueForKey: search for a way to access the value of the key according to specific search rules which includes directly accessing the instance variable. This direct accessing is controlled by accessInstanceVariablesDirectly method which is a part of the NSKeyValueCoding informal protocol, which by default returns YES, allowing those methods to directly access the instance variables and as a result not really making them private as such. They are still private from direct access.

To resolve this, you will have have to override the methods mentioned above and defined in the NSKeyValueCoding informal protocol to prevent their access.

As mentioned by Abizern, properties for private variables are still accessible since Objective-C has no concept of private methods.

like image 127
Daniel Burke Avatar answered Nov 14 '22 08:11

Daniel Burke


Don't declare a @property for an iVar if you really want it to remain private.

It isn't the iVar that is no longer private. The Objective-C runtime doesn't have a concept of private methods. Since using @property and @synthesize generates KVC compliant accessor methods, you can always call the methods, regardless of whether the backing iVar is private or not.

But it isn't as bad as you think. Using the methods you have doesn't directly change the iVar - it goes through the setters. If you need extra protection you can write your own setter that implements whatever protection you need.

If you just declare an iVar as @private and don't make it KVC compliant - it will remain private. Of course; you then can't use KVC or KVO on that iVar, but if you wanted to be able to use them you shouldn't be declaring it as a private iVar.

like image 29
Abizern Avatar answered Nov 14 '22 09:11

Abizern


Today, I have noticed interesting thing. Stephen Kochan, in his "Programming in Objective c 2.0" book, states one interesting fact about obj-c and c relation: "When you define a new class and its instance variables, those instance variables are actually stored inside a structure". Therefore, the direct access to such ivar could be made using -> operator. So, finaly, I found where such keywords as @private and @protected really matters. If I try directly to change public ivar value in the main program using then everything is ok - the value will be changed. But if I try changing private ivar - then the compiler will warn me about private_num being a private ivar

myClass->public_num = [NSNumber numberWithInt:11];
myClass->private_num = [NSNumber numberWithInt:11]; // compiler will complain and reject the compilation

But since default KVC mechanism still allows access to private or public ivars outside the class, the real encapsulation and protection must be explicitly enforced by overriding setValue:forKey: and valueForKey: methods that are declared in the NSKeyValueCoding informal protocol and by default implemented in NSObject

like image 23
Centurion Avatar answered Nov 14 '22 08:11

Centurion


I'll add my two cents to this old question.

I think the @private, @protected is there also to prevent access to a variable using the '->' operator.

Imagine you have a iVar called myPrivateVar declared like below:

@interface MyClass:NSObject{
  @public
    NSArray *myPrivateVar;
}

So even if you implement the below class method to return NO and don't have accessors declared for the iVar:

+accessInstanceVariablesDirectly{
    return NO;
}

the variable is still accessible if you use myClassObj->myPrivateVar;

On the other hand if you just make the @public to @private and don't implement accessInstanceVariableDirectly, the variable is still accessible by using KVC:

[myClassObj valueForKey:@"myPrivateVar"];

(and not accessible via myClassObj->myPrivateVar)

So to make your iVar completely private it should be declared as @private and also accessInstanceVariablesDirectly should be implemented to return NO.

like image 1
Rakesh Avatar answered Nov 14 '22 07:11

Rakesh