前言
实例变量本质上就是成员变量,只是实例是针对类而言,属性是通过关键字@property编译器自动生成setter与getter方法和一个以下划线开头的成员变量,通过setter和getter方法可以访问成员变量,我们会发现针对成员变量的声明
和访问
出现了一系列的关键字,例如:
- 针对成员变量访问范围的@public、@private、@protected、@package
- 针对通过setter与getter方法访问成员变量的@property、@sythesize、@dynamic
下面主要介绍一下这一系列关键字对成员变量的声明和访问有哪些影响。
@public、@private、@protected、@package
@public 在任何地方都能直接访问对象的成员变量
@private 只能在当前类的对象方法中直接访问,如果子类要访问需要调用父类的get/set方法
@protected 可以在当前类及其子类对象方法中直接访问(系统默认下是用它来修饰的)
@package:在同一个包下就可以直接访问,比如说在同一个框架
例如:在父类.h文件中声明如下成员变量
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject{
@public
NSString *_name;
@private
NSInteger _age;
@protected
NSString *_sex;
@package
NSInteger _height;
}
@end
NS_ASSUME_NONNULL_END
在父类.m文件中声明如下成员变量
#import "Person.h"
@implementation Person{
@public
NSString *_job;
@private
NSString *_income;
@protected
NSString *_father;
@package
NSString *_mother;
}
@end
-
创建一个Student类继承父类Person,在子类方法中调用父类的成员变量
从上图可以看出我们在.m里面声明的成员变量子类是无法访问的(即使给他@public),也会被认为是@private,所以我们的对外的成员变量都会放到.h去声明,然而由于_age变量是@private,所以子类还是无法访问的。
-
在外部调用Person类的成员变量
可以看到由于我们没有在Person或它的子类里面,所以只能访问.h中@public和@package修饰的变量,也就是_name和_height,由于_sex是@ protected在外部是不能被访问的。
@property
@property的作用是定义属性,可以通过点语法来访问,当我们在@interface内部声明了一个属性,编译器会自动帮我们实现以下三件事
- 生成一个以下划线开头的成员变量_propertyName(默认用@private修饰)
- 属性setter、getter方法的声明
- 属性setter、getter方法的实现
我们可以看到属性是通过setter和getter方法来访问成员变量的,只不过通过@property关键字编译器自动帮我们生成了成员变量、getter和setter方法,调用点语法实质上是调用setter和getter方法访问成员变量。@property用readonly修饰时属性是只读的,也即是只会生成getter方法。
注意:
在category中使用@property编译器不会生成成员变量,只会生成setter和getter方法的声明,也不会生成setter和getter方法的实现,因为category中只能添加方法,不能添加成员变量,没有成员变量就无法与外面的值产生关联,setter和getter方法就无从访问。
属性的原理就是利用getter和setter方法去访问对象中某个变量关联的值,通过属性的原理利用runtime是可以为category添加属性的。
创建一个NSObject分类,为分类添加一个name属性
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (objc)
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
添加getter和setter方法的实现
#import "NSObject+Objc.h"
#import <objc/message.h>
@implementation NSObject (objc)
- (void)setName:(NSString *)name
{
// 添加属性,给某个对象产生关联
// object:给哪个对象添加属性
// key:属性名,根据key去获取关联的对象 ,void * == id
// value:关联的值
// policy:策越
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, @"name");
}
@end
@sythesize
如果想要修改@property自动生成的_propertyName成员变量名,我们就可以通过@synthesize propertyName = newName;
当同时重写getter和setter两个方法的时候,实现了完全的自定义实现,无法对应到默认的变量_propertyName,_propertyName就无效了,需要手动定义一个变量或者使用@synthesize指定一个变量来绑定到属性上。
方式一:手动定义一个变量
//方式一
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end
@implementation ViewController{
//手动添加一个变量与属性绑定
NSString *_name;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(NSString *)name {
return _name;
}
- (void)setName:(NSString *)name {
if (_name != name) {
_name = name;
}
}
@end
方式二:使用@synthesize指定一个变量来绑定到属性
//方式二
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end
@implementation ViewController
//使用@synthesize指定一个变量来绑定到属性
@synthesize name = newName;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(NSString *)name {
return newName;
}
- (void)setName:(NSString *)name {
if (newName != name) {
newName = name;
}
}
@end
同样的,如果你的属性是只读属性,但是你重写了getter方法,编译器不会为你自动生成成员变量,也需要手动定义一个变量或者使用@synthesize指定一个变量来绑定到属性上。
注意:
在@protocol中使用@property只会生成getter和setter方法的声明,这样就跟在协议中添加普通的方法声明一样,如果希望遵守该协议的类能像使用属性那样使用它,那就需要遵守该协议的类不仅实现getter和setter方法,还要手动定义一个变量或者使用@synthesize指定一个变量来绑定到属性上。
例如:
#import <Foundation/Foundation.h>
@protocol personDelegate <NSObject>
@property (nonatomic, copy) NSString *name;
@end
@interface Person : NSObject
@end
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Student : NSObject <personDelegate>
{
NSString *_name;
}
@end
#import "Student.h"
@implementation Student
- (void)setName:(NSString *)name
{
_name = name;
}
- (NSString *)name
{
return _name;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Student *stu = [[Student alloc]init];
stu.name = @"abc";
NSLog(@"%@", stu.name);
}
@dynamic
- @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。
- 假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法。编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃。
- 或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。
-
编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
写在最后
由于技术有限,文中若有错误之处请留言指正。
参考链接
http://08643.cn/p/0695ecbe9e06
https://www.cnblogs.com/Jenaral/p/5970393.html
https://my.oschina.net/Jerod/blog/1935419