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_prope开发者_如何学Pythonrty = [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" :)
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.
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.
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
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
.
forbid via an override of the kvc entries:
@implementation MONObject
- (id)valueForKey:(NSString *)key
{
/* enforce it */
return nil;
}
- (void)setValue:(id)value forKey:(NSString *)key
{
/* enforce it */
}
/* and so on */
@end
精彩评论