本文討論的是,對於類中聲明為 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 : 消息接收對象會查找是否存在滿足 set<Key>: 格式的存取方法。 如果不存在滿足條件的存取方法,且消息接收對象的類方法 + (BOOL)accessInstanceVariablesDirectly 返回 YES,那么該對象會以 _<key> , _is<Key> , <key> , is<Key> 的順序查找是否存在對應的key。 如果存在對應的存取方法或者找到對應的實例變量,那么就會改變該 key 所對應的值 value。必要的話,value 所對應的值會從對象中解析出來,如 Representing Non-Object Values 所描述的那樣。 如果沒有找到對應的存取方法或者實例變量,那么該消息對象的 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.