iOS 成员变量、实例变量、属性解析
1、成员变量、实例变量
@interface ViewController (){
NSString *_type;
NSString *_gender;
NSInteger _age;
}
@property(nonatomic,copy) NSString *name;
@end
{ }
中声明的变量是成员变量,@property
申明的是属性,那么实例变量又是什么?变量类型为对象的成员变量就是实例变量,_type
、_gender
就是实例变量,实例变量是成员变量的特殊情况
成员变量 = 基本数据类型变量 + 实例变量
成员变量 : 用于类内部,因为成员变量不会生成setter
、getter
方法,所以外界无法访问成员变量。
成员变量访问方式如下:
_type = @"ttt";
NSLog(@"_type ==== %@",self->_type);
给_type
赋值,通过_type
、self->_type
两种方式都是OK的
2、属性
@property
申明的是属性
2.1、Class中的property
编写如下代码:
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"123";
_name = @"123";
}
- (void)setName:(NSString *)name{
_name = name;
NSLog(@"setName 被调用%s",__func__);
}
给name
赋值,可以通过self.name
、_name
两种方式进行访问
通过self.name
访问属性时,setName
会被调用,而通过_name
访问属性,setName
不会被调用
为什么通过self.name
访问属性时,setName
会被调用?
在oc中点表达式其实就是调用对象的setter
和getter
方法的一种快捷方式
如果点表达式出现在等号左边,该属性名称的setter
方法将被调用,如果点表达式出现在等号右边,该属性名称的getter
方法将被调用。
为什么可以通过_name
进行访问呢?
iOS5
之前声明一个能被外面访问的属性,必须声明属性跟与之对应的成员变量,setter
、getter
方法
@interface ViewController (){
NSString *_name;
}
@property(nonatomic,copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)setName:(NSString *)name{
_name = name;
NSLog(@"setName 被调用%s",__func__);
}
- (NSString *)name{
NSLog(@"getName 被调用%s",__func__);
return _name;
}
但是iOS5
之后,苹果是建议以下的方式来使用:
@interface ViewController ()
@property(nonatomic,copy) NSString *name;
@end
什么都不写,默认生成成员变量_name
、setter
、getter
方法
由此可见:property的本质
= ivar
(成员变量) + getter
+ setter
;
因为编译器会自动编写访问属性所需的方法,此过程叫做“自动合成”
( auto synthesis
)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法” (synthesized method
)的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的成员变量,并且在属性名前面加下划线,以此作为成员变量的名字。
此外可以通过 @synthesize
语法来指定成员变量的名字,比如你想给name
属性的成员变量改一下名字,不再使用_name
进行访问,那么可以用@synthesize name = _newName;
之后使用_name
进行访问就会报错,因为name
属性的成员变量名称是_newName
咯。
@synthesize name;
就是将name
属性的成员变量命名为name
但是,增加一个属性_newName
,此时就会出现警告,属性_newName
不能自动合成
,因为已经存在一个名字相同的成员变量咯
此时通过self._newName = @"123"
进行赋值,就会出现崩溃,[ViewController set_newName:]: unrecognized selector sent to instance 0x7faeb241b2e0
因为属性_newName
没有自动合成
,点语法又是访问setter
方法,setter
方法根本不存在,
总结下 @synthesize
合成成员变量的规则,有以下3点:
- 如果指定了的名称,会生成一个指定名称的成员变量;
- 如果没有指定成员变量的名称,会自动生成一个属性同名的成员变量;
- 如果这个成员变量已经存在,就不会自动合成
2.2、Protocol、Category中的property
一般情况下,不能向存在的类添加属性,因为property的本质
= ivar
(成员变量) + getter
+ setter
;
runtime
库中,class_addIvar
函数用于给类添加成员变量,但是文档中特别说明:
@note This function may only be called after objc_allocateClassPair and before objc_registerClassPair.
* Adding an instance variable to an existing class is not supported.
意思是这个函数只能在类创建(objc_allocateClassPair
)类注册(objc_registerClassPair
)之间使用,往已经的类中添加实例变量是不支持的,经过编译的类在程序启动后就已经加载runtime
,没有机会调用addIvar
,所以没有机会再添加实例变量,也就是不能添加属性了。
在protocol
和 category
中如何使用 property
?
在protocol
中使用 property
只会生成setter
和 getter
方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
category
使用 property
也是只会生成 setter
和 getter
方法的声明,如果我们真的需要给 category
增加属性的实现,需要借助于runrime
的两个函数:objc_setAssociatedObject
和 objc_getAssociatedObject
如给Person
类创建一个分类,增加gender
属性
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person (gender)
@property (nonatomic , copy) NSString *gender;
@end
NS_ASSUME_NONNULL_END
#import "Person+gender.h"
#import <objc/message.h>
@implementation Person (gender)
- (void)setGender:(NSString *)gender{
objc_setAssociatedObject(self, @"gender", gender, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)gender{
return objc_getAssociatedObject(self, @"gender");
}
@end
3、@synthesize和@dynamic分别有什么作用?
-
@property
有两个对应的词,一个是@synthesize
,一个是@dynamic
。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
-
@synthesize
: 如果你没有手动实现setter
方法和getter
方法,那么编译器会自动为你加上这两个方法。 -
@dynamic
: 告诉编译器属性的setter
与getter
方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为@dynamic var
,然后你没有提供@setter
方法和@getter
方法,编译的时候没问题,但是当程序运行到instance.var = someVar
,由于缺setter
方法会导致程序崩溃;或者当运行到someVar = var
时,由于缺getter
方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
参考地址:
iOS中属性与成员变量的区别
招聘一个靠谱的iOS