readonly 关键字用于属性的存取控制 ,如果不使用readonly ,编译器默认属性关键字为 readwrite。
1、我们在制作sdk的过程中,经常遇见readonly修饰的属性,那么这修饰的原因是什么呢?
就如字面意思一样,我们不想让外部调用的时修改此属性,而只允许获取属性值。
2、引申出来的问题,类的内部怎么修改属性值呢,这里我们就需要在.m文件中重写属性。
在.h中
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, copy,readonly) NSString *testName;
@end
在.m中
//对外是只读,对内可读可写
@interface Person ()
@property (nonatomic, copy, readwrite) NSString *testName;
@end
@implementation Person
- (instancetype)init{
self = [super init];
if (self) {
[self text];
}
return self;
}
- (void)text{
self.testName = @"zhangkai";
NSLog(@"~~~~~~%@",self.testName);
}
我们可以看到,在Person类的.m文件中可以读写testName属性了。
3、让我们再来思考一个问题,引用Person的Controller中既然不能使用self.testName = @"123"方法修改readonly的testName属性,那么有其他方法可以修改吗?
Person *person = [Person new];
// person.testName = @"张三丰";
// [person text];
NSLog(@"在外部调用只读属性:%@",person.testName);
[person setValue:@"张三丰" forKey:@"testName"];
NSLog(@"使用KVC修改只读属性:%@",person.testName);
打印结果是:
2019-06-13 14:38:08.101953+0800 TestOne[3468:218570] ~~~~~~zhangkai
2019-06-13 14:38:08.102164+0800 TestOne[3468:218570] 在外部调用只读属性:zhangkai
2019-06-13 14:38:08.102335+0800 TestOne[3468:218570] 使用KVC修改只读属性:张三丰
是不是惊奇的发现KVO居然修改了readonly的textName属性,But Why?下面我们就详细分析下:
当使用 setValue:forKey: 来设置对象的属性时,会以下面的优先顺序来寻找对应的 key:
1、消息接收对象会查找是否存在满足set<key>:格式的存取方法。
2、如果不存在满足条件的存取方法,且消息接收对象的类方法 + (BOOL)accessInstanceVariablesDirectly返回 YES,那么该对象会以 _<key>, _is<Key>, <key>, is<Key> 的顺序查找是否存在对应的key。
3、如果存在对应的存取方法或者找到对应的实例变量,那么就会改变该 key 所对应的值 value。必要的话,value 所对应的值会从对象中解析出来。
4、如果没有找到对应的存取方法或者实例变量,那么该消息对象的 setValue:forUndefinedKey: 将会被调用。
这样,在走到第2步的时候,就完成了对属性的修改。
4、我们在引申一下问题,作为SDK的提供方,我们肯定不想让用户通过KVC的方法修改SDK的只读属性值,这样会导致SDK内部一些莫名的问题,我们怎么才能禁止KVC的修改呢?
重写其类方法 + (BOOL)accessInstanceVariablesDirectly 返回 NO (该方法默认返回 YES,即在不存在满足条件的存取方法时,允许直接访问属性对应的实例变量);在搜索实例变量时,会首先检查带下划线的实例变量,然后检查不带下划线的实例变量。
在person类中添加方法,然后再通过KVC方法修改:
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
注意:如果我们在.m内部重写了属性的readwrite,那么通过KVC还是可以修改person类内部的属性值,除非我们并未重写属性的readwrite,那么此时上面的设置是有效的,只不过我们Person内部也无法修改属性了
屏蔽重写之后,KVC并没有成功修改只读属性值,打印如下:
2019-06-13 15:17:42.623201+0800 TestOne[3630:243570] 在外部调用只读属性:zhangkai
2019-06-13 15:17:42.623423+0800 TestOne[3630:243570] 使用KVC修改只读属性:zhangkai
5、发散下思维,如果用户手动删除掉我们SDK头文件中的readonly呢?这样的话会不会就能修改属性了呢?
这里就分两种情况了:
1、只是在.h文件中声明的只读属性,而.m中没有重写读写属性,那么删除掉readonly修饰是可以修改属性的。
2、如果在.m中重写了读写属性,那么删除掉.h中修饰属性的readonly时,会报错
,错误提示非法重新声明,主属性必须是readonly。
Illegal redeclaration of property in class extension 'Person' (attribute must be 'readwrite', while its primary must be 'readonly')
6、测试发现的有趣现象
Person在.中属性设置只读,.m中不再重写。如果使用self.testName调用就会报只读的错,但是如果使用_testName调用就没问题,而且使用_testName可以重新赋值。为什么换成下滑下调用就可以重新赋值呢?
不要忘记,我们在声明一个 textName 的属性时,编译器会为我们自动合成一个 _textName 的实例变量,这样我们就可以通过_textName修改值了 。
Person暴露的test方法调用,改变只读属性的值:
- (void)test{
_testName = @"柳眼眉腮";
NSLog(@"~~~~~~~~~~~_testName:%@",_testName);
}
2019-06-13 15:41:57.409051+0800 TestOne[3750:258795] 在外部调用只读属性:zhangkai
2019-06-13 15:41:57.409357+0800 TestOne[3750:258795] 使用KVC修改只读属性:zhangkai
2019-06-13 15:41:57.409510+0800 TestOne[3750:258795] ~~~~~~~~~~~_testName:柳眼眉腮
网友评论