Цель-c: почему частные ивары не скрыты от внешнего доступа при использовании KVC

После попытки получить доступ к ивару с помощью KVC, я заметил, что на частных и защищенных иварах не было защиты. Неважно, что я ставлю перед ivar (приватное или защищенное ключевое слово) - ivar всегда является общедоступным ivar при использовании метода KVC "setValue". Вот мой код, где все семь иваров и их свойства могут изменяться вне экземпляра класса:

//************ 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);

Результат вывода> public_num = 1, protected_num = 2, private_num = 3, public_property = 4, private_property = 5, very_private_num = 6, very_private_property = 7.

Даже если ivar объявлен в закрытом интерфейсе, он все еще может изменяться вне класса. Итак, как мне обеспечить принудительную инкапсуляцию и "защитить свои ивары от злых других программистов":)

5 ответов

Решение

NSObject соответствует неформальному протоколу NSKeyValueCoding. Это определяет setValue:forKey: а также valueForKey:, setValue:forKey: а также valueForKey: искать способ доступа к значению ключа в соответствии с конкретными правилами поиска, который включает в себя прямой доступ к переменной экземпляра. Этот прямой доступ контролируется accessInstanceVariablesDirectly метод, который является частью неофициального протокола NSKeyValueCoding, который по умолчанию возвращает YESпозволяя этим методам напрямую обращаться к переменным экземпляра и в результате не делать их частными как таковые. Они по-прежнему приватны от прямого доступа.

Чтобы решить эту проблему, вам придется переопределить методы, упомянутые выше и определенные в NSKeyValueCoding неофициальный протокол для предотвращения их доступа.

Как упомянул Abizern, свойства для частных переменных все еще доступны, так как Objective-C не имеет понятия частных методов.

Не объявляйте @property для iVar, если вы действительно хотите, чтобы он оставался приватным.

Это не iVar, который больше не является частным. Среда выполнения Objective C не имеет концепции частных методов. С использованием @property а также @synthesize генерирует методы доступа, совместимые с KVC, вы всегда можете вызывать методы независимо от того, является ли резервная копия iVar частной или нет.

Но это не так плохо, как вы думаете. Использование методов, которые у вас есть, напрямую не меняет iVar - он проходит через установщики. Если вам нужна дополнительная защита, вы можете написать свой собственный установщик, который реализует любую необходимую защиту.

Если вы просто объявите iVar как @private и не делайте это KVC-совместимым - это останется частным. Конечно; тогда вы не можете использовать KVC или KVO на этом iVar, но если вы хотите иметь возможность использовать их, вам не следует объявлять его как частный iVar.

Сегодня я заметил интересную вещь. Стивен Кочан в своей книге "Программирование в Objective c 2.0" констатирует один интересный факт об отношениях obj-c и c: "Когда вы определяете новый класс и его переменные экземпляра, эти переменные экземпляра фактически сохраняются внутри структуры". Поэтому прямой доступ к такому ивару может быть осуществлен с помощью оператора ->. Итак, наконец, я нашел, где такие ключевые слова, как @private и @protected действительно имеют значение. Если я попытаюсь напрямую изменить общедоступное значение ivar в основной программе, то все в порядке - значение будет изменено. Но если я попытаюсь изменить приватный ivar - компилятор предупредит меня о том, что private_num является приватным ivar

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

Но поскольку механизм KVC по умолчанию по-прежнему разрешает доступ к частным или общедоступным иварам вне класса, настоящая инкапсуляция и защита должны быть явно применены путем переопределения setValue:forKey: и valueForKey: методы, которые объявлены в неофициальном протоколе NSKeyValueCoding и по умолчанию реализованы в NSObject.

Я добавлю свои два цента к этому старому вопросу.

я думаю @private, @protected Также есть возможность запретить доступ к переменной с помощью оператора "->".

Представьте, что у вас есть iVar под названием myPrivateVar объявлено как ниже:

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

Так что даже если вы реализуете метод класса ниже, чтобы вернуть NO и нет заявителей, объявленных для iVar:

+accessInstanceVariablesDirectly{
    return NO;
}

переменная по-прежнему доступна, если вы используете myClassObj->myPrivateVar;

С другой стороны, если вы просто сделаете @public в @private и не реализовывать accessInstanceVariableDirectly переменная по-прежнему доступна с помощью KVC:

[myClassObj valueForKey:@"myPrivateVar"];

(и не доступны через myClassObj->myPrivateVar)

Таким образом, чтобы сделать ваш iVar полностью приватным, он должен быть объявлен как @private а также accessInstanceVariablesDirectly должен быть реализован, чтобы вернуться NO,

Запретить через переопределение записей kvc:

@implementation MONObject

- (id)valueForKey:(NSString *)key
{
/* enforce it */
    return nil;
}

- (void)setValue:(id)value forKey:(NSString *)key
{
/* enforce it */
}

/* and so on */

@end
Другие вопросы по тегам