类中的 @property @synthesize @dynamic

3 篇文章 0 订阅

前言

介绍下类中的 @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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值