前言
相信大家對這兩個詞都不陌生,但是大家會很容易將這兩個詞混淆,所以在探究之前,先來說下什么是成員變量,什么是屬性。
成員變量就是我們在開發中,類似下面這樣定義的變量,例如:
@interface Person : NSObject
{
@public
NSString *_name;
CGFloat _age;
}
@end
則_name,_age便是成員變量。
屬性就是在開發中,我們用 @property 關鍵字聲明的變量,如:
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) CGFloat *age;
該方法會自動生成_name和_age成員變量,name,age便是我們聲明的屬性
Student.m
#import "Student.h"
@interface Student()
{
NSString *_address;
}
@property (nonatomic,copy) NSString *name;
@end
@implementation Student
@end
將Student.m文件用clang -rewrite-objc Student.m
重新編譯下得到Student.cpp,從該文件中,我們可以得到如下信息:
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_address;
NSString *_name;
};
static NSString * _I_Student_name(Student * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Student$_name)); }
static void _I_Student_setName_(Student * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Student, _name), (id)name, 0, 1); }
編譯器將屬性自動轉換成了成員變量,并且自動生成了getter和setter方法。因此兩者最直觀的區別是屬性會有相應的getter方法和setter方法,而成員變量沒有,另外,外部訪問屬性可以用"."來訪問,訪問成員變量需要用"->"來訪問
成員變量(Ivar)
定義
runtime.h文件中對Ivar的定義為:
typedef struct objc_ivar *Ivar;
其為指向結構體objc_ivar的指針。objc_ivar中包含了類的單個成員變量的信息,其定義為:
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
- ivar_name
成員變量名稱,可以用const char * ivar_getName(Ivar ivar)來獲得 - ivar_type
成員變量類型,可以用const char * ivar_getTypeEncoding(Ivar ivar) 來獲得,這里得到的類型,并不是變量真正的成員變量類型,而是經過類型編碼的c字符串。 - ivar_offset
基地址偏移量。其實在訪問變量的時候,是先找到類所在的地址,然后根據地址偏移量,去找到我們要訪問的變量的。我們可以用ptrdiff_t ivar_getOffset(Ivar ivar)來得到某個變量的偏移量。通過這個偏移量,我們也可以訪問到類的私有變量。
那么變量在類中是怎么存儲的呢?繼續來看的類的定義:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本號
long info OBJC2_UNAVAILABLE; // 類信息
long instance_size OBJC2_UNAVAILABLE; // 類的實例大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 成員變量列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法列表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協議列表
#endif
} OBJC2_UNAVAILABLE;
來看結構體objc_ivar_list定義:
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
在類的定義中,用objc_ivar_list類型的結構體指針變量來記錄類的所有成員變量的相關信息。objc_ivar_list中存放著一個objc_ivar結構體數組,objc_ivar結構體中存放著類的單個成員變量的所有信息。
對變量的操作函數
- BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
向類中添加成員變量,該方法只能在動態創建類的時候使用,不能向已存在的類中添加成員變量。 - Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
獲得成員變量列表,outCount如果有返回值,則返回的是類中成員變量的個數,如果NULL,則沒有返回成員變量的個數 - const char * ivar_getName( Ivar ivar)
返回成員變量的name - const char * ivar_getTypeEncoding( Ivar ivar)
返回成員變量的類型編碼 - ptrdiff_t ivar_getOffset( Ivar ivar)
返回成員變量的基地址偏移量 - id object_getIvar(id object, Ivar ivar)
可以用這種便捷方式來獲得成員變量的值 - void object_setIvar(id object, Ivar ivar, id value)
設置成員變量的值
代碼示例
Person.h
@interface Person : NSObject
{
@public
NSString *_name;
CGFloat _age;
@private
int _temp;
}
@property (nonatomic,assign) CGFloat height;
@end
Person.m
@implementation Person
- (NSString *)description{
return [NSString stringWithFormat:@"私有變量_temp的值為%d",_temp];
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 添加成員變量
Class cls = objc_allocateClassPair([NSObject class],"myClass", 0);
BOOL res = class_addIvar(cls, "sex", sizeof(NSString *), log2(sizeof(NSString *)), "@");
if(res){
NSLog(@"添加成功");
}else{
NSLog(@"添加失敗");
}
Person *p = [[Person alloc] init];
unsigned int outCount = 0;
NSLog(@"=============獲取成員變量列表============");
Ivar *ivars = class_copyIvarList([p class], &outCount);
NSLog(@"成員變量個數: %d",outCount);
for (int i = 0; i<outCount; i++) {
Ivar ivar = ivars[i];
NSLog(@"變量名稱: %s,類型: %s,偏移量: %td",ivar_getName(ivar),ivar_getTypeEncoding(ivar),ivar_getOffset(ivar));
}
free(ivars);
NSLog(@"=============訪問私有變量============");
NSLog(@"實例變量p地址:%p",p);
Ivar tempIvar = class_getInstanceVariable([p class], "_temp");
NSLog(@"私有變量_temp的偏移量:%td",ivar_getOffset(tempIvar));
int *temp = (int *)((__bridge void *)(p) + ivar_getOffset(tempIvar));
NSLog(@"私有變量_temp的地址:%p",temp);
*temp = 10;
NSLog(@"%@",p);
}
return 0;
}
輸出結果為:
添加成功
=============獲取成員變量列表============
成員變量個數: 4
變量名稱: _name,類型: @"NSString",偏移量: 8
變量名稱: _age,類型: d,偏移量: 16
變量名稱: _temp,類型: i,偏移量: 24
變量名稱: _height,類型: d,偏移量: 32
=============訪問私有變量============
實例變量p地址:0x100200000
私有變量_temp的偏移量:24
私有變量_temp的地址:0x100200018
私有變量_temp的值為10
屬性(Property)
在類的定義中,我們沒有發現存儲屬性的變量,那么屬性是怎么存儲的呢?從上面重新編譯Student.m生成的Student.cpp中,我們可以看到編譯器將屬性轉換成了成員變量,但是仍然找不到屬性是用什么存儲的。怎么辦呢?我們可以從添加屬性的方法入手,添加屬性的方法:
BOOL class_addProperty(Class cls, const char *name,const objc_property_attribute_t *attrs, unsigned int n)
其方法實現如下:
static bool _class_addProperty(Class cls, const char *name,
const objc_property_attribute_t *attrs, unsigned int count,
bool replace){
if (!cls) return NO;
if (!name) return NO;
property_t *prop = class_getProperty(cls, name);
if (prop && !replace) {
// already exists, refuse to replace
return NO;
}
else if (prop) {
// replace existing
rwlock_writer_t lock(runtimeLock);
try_free(prop->attributes);
prop->attributes = copyPropertyAttributeString(attrs, count);
return YES;
}
else {
rwlock_writer_t lock(runtimeLock);
assert(cls->isRealized());
property_list_t *proplist = (property_list_t *)
malloc(sizeof(*proplist));
proplist->count = 1;
proplist->entsizeAndFlags = sizeof(proplist->first);
proplist->first.name = strdup(name);
proplist->first.attributes = copyPropertyAttributeString(attrs, count);
cls->data()->properties.attachLists(&proplist, 1);
return YES;
}
}
從中我們可以看到:其最終是用property_list_t來存儲單個屬性信息的。
對屬性操作的函數
- objc_property_t class_getProperty(Class cls, const char *name)
獲得類的某個屬性的信息 - objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount)
獲得類的屬性列表,不包含父類的屬性,outCount中返回類的屬性個數。 - const char *property_getName(objc_property_t property)
獲得屬性名稱 - const char *property_getAttributes(objc_property_t property)
獲得屬性的屬性信息,即屬性的描述信息。 - char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
獲得屬性的某個描述信息的值 - objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
屬性的描述信息列表
代碼演練
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
NSLog(@"=========動態添加屬性==========");
objc_property_attribute_t type= {"T","@\"NSString\""}; // type
objc_property_attribute_t refType = {"C",""}; // copy
objc_property_attribute_t backValue = {"V","_sex"}; // 返回值
objc_property_attribute_t attrs[] = {type, refType, backValue};
BOOL flag = class_addProperty([p class], "sex",attrs, 3);
if(flag){
NSLog(@"屬性添加成功");
}else{
NSLog(@"屬性添加失敗");
}
NSLog(@"=========獲得屬性列表==========");
unsigned int outCount = 0;
objc_property_t *props = class_copyPropertyList([p class], &outCount);
for(int i=0; i<outCount; i++){
objc_property_t p = props[i];
NSLog(@"屬性: %s,描述信息:%s",property_getName(p),property_getAttributes(p));
}
free(props);
NSLog(@"=============獲取成員變量列表============");
unsigned int outIvarCount = 0;
Ivar *ivars = class_copyIvarList([p class], &outIvarCount);
NSLog(@"成員變量個數: %d",outIvarCount);
for (int i = 0; i<outIvarCount; i++) {
Ivar ivar = ivars[i];
NSLog(@"變量名稱: %s",ivar_getName(ivar));
}
free(ivars);
}
return 0;
}
輸出結果:
=========動態添加屬性==========
屬性添加成功
=========獲得屬性列表==========
屬性: sex,描述信息:T@"NSString",C,V_sex
屬性: height,描述信息:Td,N,V_height
=============獲取成員變量列表============
成員變量個數: 4
變量名稱: _name
變量名稱: _age
變量名稱: _temp
變量名稱: _height
由代碼輸出結果,我們可以看到類的屬性的一些信息,同時我們也可以看到,我們動態添加的屬性,是不會自動生成對應的成員變量的。因此我們在給動態添加的屬性賦值的時候,是不能直接用_屬性名稱
去賦值的。那怎么辦呢?其實,我們用@property聲明的屬性,系統會自動生成getter和setter方法,我們也可以仿造系統的做法,同樣的給我們新添加的屬性,增加getter和setter方法。給類增加這兩個方法,由多種實現方式,但是在不改變原有類的代碼的基礎上,我們需要用到對象關聯
對象關聯(Associative References)
對象關聯是動態添加屬性的常用方法,相關操作函數如下:
- void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
給對象設置一個關聯的值, objc_AssociationPolicy:關聯策略,其實就是值的引用類型,是retain,copy,weak或assign - id objc_getAssociatedObject(id object, void *key)
得到對象關聯的值 - void objc_removeAssociatedObjects(id object)
移除所有對象的關聯值
這里演示下,將上面的屬性sex添加完善一下。
代碼演練:
static const void *sexTag = &sexTag;
NSString *sex(id self, SEL _cmd) {
return objc_getAssociatedObject(self, sexTag);
}
void setSex(id self, SEL _cmd, NSString *sex) {
objc_setAssociatedObject(self, sexTag, sex, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
NSLog(@"=========動態添加屬性==========");
objc_property_attribute_t type= {"T","@\"NSString\""};
objc_property_attribute_t refType = {"C",""};
objc_property_attribute_t backValue = {"V","_sex"};
objc_property_attribute_t attrs[] = {type, refType, backValue};
BOOL flag = class_addProperty([p class], "sex",attrs, 3);
if(flag){
NSLog(@"屬性添加成功");
class_addMethod([p class], @selector(sex), (IMP)sex, "@@:");
class_addMethod([p class], @selector(setSex:), (IMP)setSex, "v@:@");
}else{
NSLog(@"屬性添加失敗");
}
NSLog(@"=============屬性賦值及獲取============");
[p performSelector:@selector(setSex:) withObject:@"男"];
NSLog(@"屬性sex的值為:%@",[p performSelector:@selector(sex)]);
輸出結果為:
=========動態添加屬性==========
屬性添加成功
=============屬性賦值及獲取============
屬性sex的值為:男