前言
介绍下类中的 @property @synthesize @dynamic
@property @synthesize
一定要分清属性和变量的区别,不能混淆
@property:帮我们自动生成属性的setter和getter方法的声明
@synthesize:帮我们自动生成setter和getter方法的实现以及下划线成员变量(私有)
例如:
@property(nonatomic, strong) NSString *age; 生成age属性
相当于在.h文件中 声明了下面2个方法
//- (void)setAge:(NSString *)age;
//- (NSString *)age;
@synthesize age = _age @synthesize 声明的属性=变量
相当于在.m中实现了下面2个方法
//- (void)setAge:(NSString *)age {
// _age = age;
//}
//- (NSString *)age {
// return _age;
//}
同时还生成了个一个 @private类型的成员变量_age
例子1
@interface Person : NSObject
@property(nonatomic, strong) NSString *age;
- (void)test;
@end
@implementation Person
@synthesize age = _age;
- (void)test {
// 使用 synthesize 帮我们生成的 _age 成员变量
self->_age = @"666";
}
@end
调用
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
[p test];
// 访问Person实例的getter方法 synthesize默认帮我们实现
NSLog(@"%s%@",__func__, p.age);
// 访问Person实例的setter方法 synthesize 默认帮我们实现
p.age = @"9";
NSLog(@"%s%@",__func__, p.age);
}
输出
-[ViewController viewDidLoad]666
-[ViewController viewDidLoad]9
分析
使用 synthesize 帮我们生成的 _age 成员变量(私有)
访问Person实例的getter方法 synthesize默认帮我们实现
访问Person实例的setter方法 synthesize默认帮我们实现
Clang Person 类
// @implementation Person
// @synthesize age = _age;
static NSString * _Nonnull _I_Person_age(Person * self, SEL _cmd) {
return (*(NSString * _Nonnull __strong *)((char *)self + OBJC_IVAR_$_Person$_age));
}
static void _I_Person_setAge_(Person * self, SEL _cmd, NSString * _Nonnull age) {
(*(NSString * _Nonnull __strong *)((char *)self + OBJC_IVAR_$_Person$_age)) = age;
}
static void _I_Person_test(Person * self, SEL _cmd) {
(*(NSString * _Nonnull __strong *)((char *)self + OBJC_IVAR_$_Person$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_zm_558cwfjs099fbm2r8kxg8wt00000gt_T_Person_9c430c_mi_0;
}
// @end
可以看到 默认实现的 setter getter 方法
property = set + get方法声明
synthesize = 实例变量 + set + get方法实现
property autosynthesis 自动合成
以前我们需要手动对每个@property添加@synthesize,而在 iOS 6 之后 LLVM 编译器引入了 “property autosynthesis”,即属性自动合成。换句话说,就是编译器会自动为每个@property添加@synthesize,我们不用写了。
具体流程
1、自动合成 getter, setter: 编译器会自动检查,如果没有手动实现,会自动添加 getter,setter,如果实现了,则不做处理
2、自动指定变量名字:@synthesize age = _age;
3、变量_age 自动添加 @private 关键字 私有
4、getter方法名 age setter方法名 setAge
那些情况下自动合成会失效
1、同时重写了 setter 和 getter
2、使用了 @dynamic
3、在 @protocol 中定义的所有属性
4、在 category 中定义的所有属性
例子2
@implementation Person
- (void)test {
self->_age = @"666";
}
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
[p test];
NSLog(@"%s%@",__func__, p.age);
p.age = @"9";
NSLog(@"%s%@",__func__, p.age);
}
@end
输出
-[ViewController viewDidLoad]666
-[ViewController viewDidLoad]9
删除synthesize 依然可以使用 _age
那@synthesize现在还有什么作用呢? 我们接着往下看
重写set get方法
有时我们需要重写set get方法 想一下下面的代码为什么会报错
单独重写任意一个方法却不会报错
输出
7
分析
如果我们同时重写了setter和getter方法,则编译器就不会为这个@property添加@synthesize,这时候就不存在下划线成员变量 所以解决报错有2个方法
1、 手动添加@synthesize
2、 自己声明实例变量_age
这两个方法可以解决Person内部编译报错问题,在外部访问Person实例时候有区别 我们通过几段代码对比下
例子3
添加@synthesize 内部调用
@implementation Person
@synthesize age = _age;
- (void)setAge:(NSString *)age {
if (_age != age) {
_age = age;
}
}
- (NSString *)age {
return _age;
}
- (void)test {
self.age = @"5";
self->_age = @"7";
NSLog(@"%@",_age);
}
@end
输出
7
解决了我们Person内部编译报错问题
例子4
添加@synthesize 外界访问
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
[p test];
p.age = @"9";
// p->_age = @"11";
NSLog(@"%s%@",__func__, p.age);
}
输出报错
'Person' does not have a member named '_age'
外部不能访问 @synthesize 帮我们生成的_age变量 ,你可以理解成 @synthesize 帮生成的_age 是私有的
例子5
自己声明实例变量_age 内部调用
// 自己声明实例变量
@interface Person : NSObject {
NSString *_age;
}
@property(nonatomic, strong) NSString *age;
- (void)test;
@end
@implementation Person
- (void)setAge:(NSString *)age {
if (_age != age) {
_age = age;
}
}
- (NSString *)age {
return _age;
}
- (void)test {
self.age = @"5";
self->_age = @"7";
NSLog(@"%@",_age);
}
输出
7
同样也解决了Person类编译报错问题
例子6
自己声明实例变量_age 外界调用
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
[p test];
p.age = @"9";
p->_age = @"11";
NSLog(@"%s%@",__func__, p.age);
}
输出报错
Instance variable '_age' is protected
分析
默认 _age 是protected权限
这里说下 oc语言 成员变量的访问权限
@private 成员只能在当前类内部可以访问
@protected 成员可以在当前类和当前类的子类中访问
@public 成员可以被在任何地方访问
例子7
加 @public修改 _age 权限
@interface Person : NSObject {
@public NSString *_age;
}
@property(nonatomic, strong) NSString *age;
- (void)test;
@end
@implementation Person
- (void)setAge:(NSString *)age {
if (_age != age) {
_age = age;
}
}
- (NSString *)age {
return _age;
}
- (void)test {
self.age = @"5";
self->_age = @"7";
NSLog(@"%@",_age);
}
调用
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
[p test];
p.age = @"9";
p->_age = @"11";
NSLog(@"%s%@",__func__, p.age);
}
输出
7
-[ViewController viewDidLoad]11
编译运行成功 外部可以访问
分析
- property的本质是生成了set+get方法声明, 并不会生成对应的_age变量
_age变量+set+get方法实现是由@synthesize合成的
只不过现在的xcode不需要写synthesize, 会作为默认补充, 所以,看上去property = (set+get的方法声明) + (_name + set + get方法实现) - @synthesize生成的_age变量 外部不能访问
- 如果想在外部通过-> 方式访问一个成员变量 ,需要加 @public
例子8
如果只写了set方法,却没有在里面做任何操作呢
@interface Person : NSObject
@property(nonatomic, strong) NSString *age;
@end
@implementation Person
// set方法什么都不操作
- (void)setAge:(NSString *)age {
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
// 调用 set方法 但是set方法里面什么都没做 所以9 不会赋给_age
p.age = @"9";
NSLog(@"%s%@",__func__, p.age);
}
输出
-[ViewController viewDidLoad](null)
分析
编译运行ok
调用set方法 但是set方法里面什么都没做 所以9 不会赋给_age 所以输出为null
例子9
如果只写了get方法,在里面return @“6666”
@interface Person : NSObject
@property(nonatomic, strong) NSString *age;
@end
@implementation Person
- (NSString *)age {
return @"666";
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
p.age = @"9";
// 调用 get方法 get方法return为 666
NSLog(@"%s%@",__func__, p.age);
}
输出
-[ViewController viewDidLoad]666
分析
打印_age也就是重写的get方法里面返回的值
想一下到底 9 有没有赋给 _age
我们上面说过@public 加声明_age的方式可以外部访问
我们验证一下
例子10
@interface Person : NSObject {
@public NSString *_age;
}
@property(nonatomic, strong) NSString *age;
@end
@implementation Person
- (NSString *)age {
return @"666";
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
p.age = @"9";
NSLog(@"%s%@",__func__, p.age);
NSLog(@"%s%@",__func__, p->_age);
}
输出
-[ViewController viewDidLoad]666
-[ViewController viewDidLoad]9
分析
将9赋给_age 赋值成功
p.age输出打印访问_age的值时候 因为重写了get方法 返回了 @“666” 所以 9 没有被正确打印出来,是因为我们访问方式的问题。
@property @synthesize 会帮我们自动生成一个 下划线的 age
我们有自己声明了一个_age 这两个_age 是一个吗
我们验证下Person 对象的大小
例子11
@synthesize 自动生成一个 下划线的 age 的内存大小
@interface Person : NSObject
@property(nonatomic, strong) NSString *age;
@end
@implementation Person
- (NSString *)age {
return @"666";
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
p.age = @"9";
NSLog(@"%s%@",__func__, p.age);
NSLog(@"malloc_size = %d", malloc_size((__bridge const void *)p));
NSLog(@"InstanceSize= %d", class_getInstanceSize([Person class]));
}
输出
-[ViewController viewDidLoad]666
malloc_size = 16
InstanceSize= 16
分析
malloc_size 堆空间【实际】分配给对象的内存大小 【16】的倍数 16对齐
InstanceSize 运行时获取 创建的对象【至少】需要的内存大小 内存对齐一般是以【8】对齐
isa指针 8字节 _age 8字节 所以 InstanceSize 16字节
例子12
声明 @public NSString *_age;
@interface Person : NSObject {
@public NSString *_age;
}
@property(nonatomic, strong) NSString *age;
@end
@implementation Person
- (NSString *)age {
return @"666";
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
p.age = @"9";
NSLog(@"%s%@",__func__, p.age);
NSLog(@"%s%@",__func__, p->_age);
NSLog(@"malloc_size = %d", malloc_size((__bridge const void *)p));
NSLog(@"InstanceSize= %d", class_getInstanceSize([Person class]));
}
输出
-[ViewController viewDidLoad]666
-[ViewController viewDidLoad]9
malloc_size = 16
InstanceSize= 16
分析
malloc_size InstanceSize 还是16字节
如果是2个_age 对象
malloc_size 应该是 32
InstanceSize 应该是 24
所以 @public NSString *_age; @property(nonatomic, strong) NSString *age; 是一个成员变量
@dynamic
告诉编译器不用自动进行@synthesize,等到运行时再添加方法实现,但是它不会影响@property生成的setter和getter方法的声明。@dynamic是 OC 为动态运行时语言的体现
@interface Person : NSObject {
NSString * _age;
}
@property(nonatomic, strong) NSString *age;
@end
@implementation Person
@dynamic age;
- (NSString *)age {
return _age;
}
- (void)setAge:(NSString *)age {
_age = age ;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
p.age = @"9";
NSLog(@"%s%@",__func__, p.age);
}
输出
-[ViewController viewDidLoad]9
官方文档:: dynamic