一、iOS成员变量、实例变量、成员属性说明:
1、成员变量、实例变量:
1)、成员变量是在{}中声明的变量,如下代码所示:
2)、如果成员变量的类型是一个类则称这个变量为实例变量
3)、成员变量包括实例变量,所以可以通称为成员变量(这里只是便于概念理解分开解释)
实例变量 = 成员变量 = ivar
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Persion : NSObject{
NSString *name; //实例变量
int age; //成员变量
}
@end
NS_ASSUME_NONNULL_END
2、成员属性(也可称属性变量):
通常我们使用@property声明的变量都叫做成员属性,也可称属性变量。
3、成员变量和成员属性的关系:
1、属性对成员变量扩充了存取方法 (例如在get和set方法中做其他逻辑);
2、属性默认会生成带下划线的成员变量 ;
3、但只声明了变量,是不会有属性的,可以通过以下代码证明:
在Person.h 头文件中
@interface Person : NSObject {
@private
//name为私有成员变量
NSString *name;
}
// age 为成员属性
@property (nonatomic ,copy) NSString *age;
在viewController.m 中,通过RunTime机制获得对象的所有成员变量和成员属性。
Person *p = [Person new];
unsigned int count = 0; //count记录变量的数量
// 获取类的所有成员变量
Ivar *members = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = members[i];
// 取得变量名并转成字符串类型
const char *memberName = ivar_getName(ivar);
NSLog(@"变量名 = %s",memberName);
}
// 获取类的所有成员属性
objc_property_t *properties =class_copyPropertyList([Person class], &count);
for (int i = 0; i<count; i++)
{
objc_property_t property = properties[i];
const char* char_f =property_getName(property);
NSString *propertyName = [NSString stringWithUTF8String:char_f];
NSLog(@"属性名 = %@",propertyName);
}
打印结果为
变量名 = name
变量名 = _age
属性名 = age
二、@property、@synthesize、@dynamic说明:
1、@property
@property是用来定义成员属性的,通常情况会自动合成成员变量和set/get方法。
简单来说:我们写@property声明属性,其实是做了三件事(@property = _ivar + getter + setter):
.h: 声明了getter和setter方法;
.m: 声明了成员变量(默认:下划线+属性名);
.m: 实现了getter和setter方法。
以上三件事是由编译器自动加上了@synthesize关键字的功能,只是常规情况下默认省略了。
- 如果这个成员变量(同名_ivar)已经存在,@property就不再生成新成员变量;
- 默认合成的成员变量创建后默认是@private类型,只能在本类中访问,子类也无法访问父类默认生成的成员变量_ivar;
- 在.h声明的成员变量会被子类访问,是@protected类型。(补充:.h中声明的成员变量都是protected,想要被非子类访问需要用@public修饰)
接下来先看看以下问题:
1、那@synthesize,@dynamic到底是干什么的?
2、什么情况下不会自动合成成员变量和set/get方法呢?
2、@synthesize
@synthesize 是配合@property使用的。字面意思是合成,这个关键字在默认情况下可以省略,编译器自动会实现这个关键字的功能,也可以手动加上实现。
- 如果属性没有手动实现setter和getter方法,编译器为你自动生成setter与getter方法;
- 可以指定与属性对应的实例变量(例如:@syntheszie ivar = _ivar123,就会为成员属性ivar生成一个_ivar123的成员变量);
- 如果子类中有和父类重名的属性,就会报错:
Auto property synthesis will not synthesize property 'name';
it will be implemented by its superclass, use @dynamic to acknowledge intention
这是因为当编译器检测到父类相同属性的时候子类不会自动生成@sythesize ivar = _ivar,此时子类只有属性没有生成对应成员变量_ivar,也不会有对应的set和get方法。
在子类调用self.ivar时实际上是调用父类的属性。一旦这个子类的属性是开发者自定义的,开发者用这个属性调用方法时用了自定义的方法,这个方法父类属性没有的时候,就会造成崩溃;
这个时候可以在子类添加@syntheszie ivar = _ivar ,子类会生成自己私有的_ivar成员变量,这个时候子类的self.ivar也就会访问自己的属性。
另外,以下这些场景定义的属性不会合成成员变量:
1)同时重写了 setter 和 getter 时
2)重写了只读属性的 getter 时,如下第三部分readonly 和 writeonly情况下重写
3)使用了 @dynamic 时
4)在 @protocol 中定义的所有属性
5)在 category 中定义的所有属性
6)重载的属性(如果子类中有和父类重名的属性,就会警告,需要用@synthesize)
如果条件满足且需要成员变量可以使用@synthesize关键字来合成
3、@dynamic
@dynamic告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。
假如一个属性被声明为 @dynamic var,而且你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
三、重写getter和setter方法注意事项
只重写getter(懒加载):默认会自动生成下划线开头的变量,在getter中要使用下划线(return _ivar)来返回值,不能使用self.否则造成死循环
只重写setter:默认会自动生成下划线开头的变量,在setter中要使用下划线( _ivar= ivar)来接收值,不能使用self.否则造成死循环
两个都重写:同时手动重写了一个属性的get和set方法的话,Xcode不会再自动生成带有下划线的私有成员变量了这时如果不加,@synthesize就会报错,解决方法就是添加@syntheszie ivar = _ivar
readonly 和 writeonly情况下重写:这时属性只会生成getter或者setter方法,如果我们重写了该方法,就需要我们重新添加@synthesize
四、Objective-C 中的点语法
-
点表达式(.)
看起来与C语言中的结构体访问以及java语言汇总的对象访问有点类似,如果点表达式出现在等号=
左边,调用该属性名称的setter
方法。如果点表达式出现在=
右边,调用该属性名称的getter
方法。 - OC中
点表达式(.)
其实就是调用对象的setter
和getter
方法的一种快捷方式,self.myString = @"张三";
实际就是[self setmyString:@"张三"];
属性访问方式 :
这是我们最容易掌握的一种使用方式,所以甚至有的开发者在开发中只会定义属性
person .name = @"xiaoming";
指针访问方式 :
作为一个有洁癖的程序员,更多时候还是定义成员变量而不是属性,因为至少减少了一次方法调用,减少了内存占用
person->_name = @"xiaowang";
KVC访问方式 :
如果一个类的成员变量是私有的,然后我想访问它,可以使用KVC的方式
[person setValue:@"xiaohua" forKey:@"name"];
五、self.ivar和_ivar的区别
其中self.ivar是调用的xx属性的get/set方法,而_ivar则只是使用成员变量_ivar,并不会调用get/set方法。两者的更深层次的区别在于,通过存取方法访问比直接访问多做了一些其他的事情(例如内存管理,复制值等),例如如果属性在@property中属性的修饰符有retain,那么当使用self.xx的时候相应的属性的引用计数器由于生成了setter方法而进行加1操作,此时的retaincount为2
六、属性、成员变量、self.ivar、_ivar使用经验总结
需要与外部类交互的都写成属性
所有属性在使用时最好使用self.来调用,其他内部使用的对象尽量用成员变量定义(减少内存占用,调用更快)
需要懒加载的对象定义为属性(或私有属性)
重写getter(懒加载)和setter方法时在内部使用_ivar来操作,避免造成死锁。
七、其他
1、经常看到block里面有报警:
Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior
block中使用了self的成员变量_ivar,因此block会隐式的retain住self。Xcode认为这可能会给开发者造成困惑,或者因此而因袭循环引用,所以警告我们要显示的在block中使用self,以达到block显示retain住self的目的。