问题
前两天面试,被问到关键词readonly修饰的属性访问操作,如何实现只读属性的修改?
比如:在某些SDK中,没有提供相应接口,但实际项目中却要求要有对某些属性的修改权限,这时可能就要考虑如何修改只读属性了。
再比如:UITableView的只读属性style(UITableViewStylePlain/UITableViewStyleGrouped)
// LGYStudent.h
// 对外提供只读属性firstName
@interface LGYStudent : NSObject
@property (nonatomic, assign, readonly) NSInteger studentId;
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
- (instancetype)initWithStudentId:(NSInteger)studentId firstName:(NSString *)firstName lastName:(NSString *)lastName;
@end
解决方案
- 方法一: KVC setValue:forKey:
LGYStudent *student = [[LGYStudent alloc] initWithStudentId:1 firstName:@"yang" lastName:@"Liu"];
NSLog(@"student firstName: %@", student.firstName); // student firstName: yang
[student setValue:@"KVC" forKey:NSStringFromSelector(@selector(firstName))];
NSLog(@"student firstName after changed-KVC: %@", student.firstName); // student firstName after changed-KVC: KVC
参考:http://www.jianshu.com/p/1ffa6414008e
Apple文档相关链接:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/Compliant.html
- 方法二: setValue:forKeyPath:
[student setValue:@"KeyPath" forKeyPath:@"firstName"];
NSLog(@"student firstName after changed-KeyPath: %@", student.firstName); // student firstName after changed-KVC: KeyPath
- 方法三: category + runtime 重写get/set方法
在分类中通过runtime重新设置属性的set、get方法时,需要在对象初始化对该属性赋值之后,单独再对该属性作一下赋值操作。
因为分类里的重写方法要先于并取代 本类里的原方法执行。
而且在本类的初始化构造器方法里对属性赋值多是以实例变量的下划线形式(_associatedObject)赋值,不通过set方法。
// LGYStudent+RunTime.h
#import "LGYStudent.h"
@interface LGYStudent (RunTime)
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
@end
// LGYStudent+RunTime.m
#import "LGYStudent+RunTime.h"
#import <objc/runtime.h>
@implementation LGYStudent (RunTime)
static char kFirstName;
- (NSString *)firstName {
return objc_getAssociatedObject(self, &kFirstName);
}
-(void)setFirstName:(NSString *)firstName {
objc_setAssociatedObject(self, &kFirstName, firstName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
修改该对象的只读属性时
student.firstName = @"RunTime";
NSLog(@"student firstName after changed-RunTime: %@", _student.firstName); // student firstName after changed-RunTime: RunTime
总结
- 当我们声明一个 readonly 的属性,外部可能会通过 KVC 修改该属性值。
- 为了避免 KVC 修改属性值,须将定义属性所在类的类方法 + (BOOL)accessInstanceVariablesDirectly 重写,使其返回 NO.
- 请注意:如果使用上面第三种方法runtime重写set/get方法,则 + (BOOL)accessInstanceVariablesDirectly方法是不执行的。
- 因为如上所述KVC原理的执行顺序,此时已经找到set方法了,就不会再执行这个类方法了。返回YES/NO都不再起作用了。
如果是自己封装SDK的话,在.h 文件设计中,对外属性p的设计是readonly时,外界便不能修改该属性。
那么想要在该类内部要修改该p属性,有两种方式
- 可以通过指针访问
_studentId = studentId;
self->_firstName = [firstName copy];
- 也可以在类的拓展里重新声明属性为可读属性
@interface LGYStudent ()
@property (nonatomic, copy) NSString *firstName;
@end
参考:http://blog.csdn.net/skymingst/article/details/42026695
或者用协议protocol的方式,外部通过protocol获取属性,这样也可以达到隐藏成员变量的效果。比如UITableView的numberOfSections,indexPathForSelectedRow等只读属性,就是通过协议方法设置的。
附上示例代码链接 https://pan.baidu.com/s/1hsmkOfE