美文网首页工作文档iOS杂问题
iOS修改声明为readonly的属性值

iOS修改声明为readonly的属性值

作者: CaryaLiu | 来源:发表于2015-12-12 16:52 被阅读6553次

    欢迎到我的 个人博客 http://liumh.com 浏览此文

    本文讨论的是,对于类中声明为 readonly 的属性值,我们就不可以修改其值了么?如何可以,那么如何修改呢?

    为了便于说明,定义一个 ACLStudent 的类:

    
    ACLStudent.h
    
    @interface ACLStudent : NSObject
    
    @property (nonatomic, assign, readonly) NSInteger studentId;
    @property (nonatomic, copy, readonly) NSString *firstName;
    @property (nonatomic, copy, readonly) NSString *lastName;
    
    - (instancetype)initWithStudentId:(NSInteger)studentId firstName:(NSString *)firstName lastName:(NSString *)lastName;
    
    @end
    
    --------------------------
    
    ACLStudent.m
    
    @implementation ACLStudent
    
    - (instancetype)initWithStudentId:(NSInteger)studentId firstName:(NSString *)firstName lastName:(NSString *)lastName {
        self = [super init];
        if (self) {
            _studentId = studentId;
            _firstName = [firstName copy];
            _lastName = [lastName copy];
        }
        
        return self;
    }
    
    @end
    

    接下来定义一个 ACLStudent 类的对象:

    ACLStudent *student = [[ACLStudent alloc] initWithStudentId:1 firstName:@"Carya" lastName:@"Liu"];
    NSLog(@"student firstName: %@", student.firstName);
    

    现在我们考虑的就是如何修改 student 对象的 firstName 属性值为@"Qiu"

    如果直接调用 firstName 的 setter 方法,student.firstName = @"Qiu", 那么就直接报错,提示不能够给声明为 readonly 的属性赋值。那么使用 KVC 呢?

    [student setValue:@"Qiu" forKey:NSStringFromSelector(@selector(firstName))];
    NSLog(@"student firstName after changed: %@", student.firstName);
    

    运行,发现属性值被成功修改。哈哈,那么现在来看看 KVC 为什么能够修改该属性值呢?看看文档 Accessor Search Implementation Details

    当使用 setValue:forKey: 来设置对象的属性时,会以下面的优先顺序来寻找对应的 key

    1. 消息接收对象会查找是否存在满足 set<Key>: 格式的存取方法。
    2. 如果不存在满足条件的存取方法,且消息接收对象的类方法 + (BOOL)accessInstanceVariablesDirectly 返回 YES,那么该对象会以 _<key>, _is<Key>, <key>, is<Key> 的顺序查找是否存在对应的key。
    3. 如果存在对应的存取方法或者找到对应的实例变量,那么就会改变该 key 所对应的值 value。必要的话,value 所对应的值会从对象中解析出来,如 Representing Non-Object Values 所描述的那样。
    4. 如果没有找到对应的存取方法或者实例变量,那么该消息对象的 setValue:forUndefinedKey: 将会调用。

    对于上述第2点说明一下,如果我们不想让 setValue:forKey: 方法改变对象的属性值,那么重写其类方法 + (BOOL)accessInstanceVariablesDirectly 返回 NO (该方法默认返回 YES,即在不存在满足条件的存取方法时,允许直接访问属性对应的实例变量);在搜索实例变量时,会首先检查带下划线的实例变量,然后检查不带下划线的实例变量。

    对于上述第3点举例说明,如果修改 student 对象的属性 NSInteger studentId, 注意其是 NSInteger 类型,我们在调用 setValue:forKey: 方法时可以像这样

    [student setValue:@(20) forKey:NSStringFromSelector(@selector(studentId))];

    传入一个 NSNumber 对象也可以,Objective-C 会处理好一切。

    对于上面的示例,使用 setValue:forKey: 实际修改的是 student 实例中 _firstName 实例变量的值。不要忘记,我们在声明一个 firstName 的属性时,编译器会为我们自动合成一个 _firstName 的实例变量。

    总结:

    • 当我们声明一个 readonly 的属性,外部可能会通过 KVC 修改该属性值。
    • 为了避免 KVC 修改属性值,须将定义属性所在类的类方法 + (BOOL)accessInstanceVariablesDirectly 重写,使其返回 NO.

    相关文章

      网友评论

      • 丶无极:楼主,WKUserScript这个类,如何动态去改变source,使用KVC去替换,一直提示找不到这个属性崩溃,按照楼主你写的内容,建一个继承WKUserScript的子类,重写+accessInstanceVariablesDirectly这个方法返回YES依然会崩溃。
      • FengxinLi:请问一下楼主 (BOOL)accessInstanceVariablesDirectly 返回NO的话,再改变值会崩溃?那就不能改变了?
        达若漠沙:@FengxinLi 在对象类中 重写 setValue forUndefinedKey 方法,就不会崩溃。且属性原值保持不变
        FengxinLi:@CaryaLiu 谢谢你又多了解了一点。以前都没想过可以这样。只知它是只读属性
        CaryaLiu:@Fengxinliju 对,返回 NO 的话,再改变值就会崩溃

      本文标题:iOS修改声明为readonly的属性值

      本文链接:https://www.haomeiwen.com/subject/pcnyhttx.html