前言
實(shí)例變量本質(zhì)上就是成員變量,只是實(shí)例是針對類而言,屬性是通過關(guān)鍵字@property編譯器自動生成setter與getter方法和一個以下劃線開頭的成員變量,通過setter和getter方法可以訪問成員變量,我們會發(fā)現(xiàn)針對成員變量的聲明和訪問出現(xiàn)了一系列的關(guān)鍵字,例如:
- 針對成員變量訪問范圍的@public、@private、@protected、@package
- 針對通過setter與getter方法訪問成員變量的@property、@sythesize、@dynamic
下面主要介紹一下這一系列關(guān)鍵字對成員變量的聲明和訪問有哪些影響。
@public、@private、@protected、@package
@public 在任何地方都能直接訪問對象的成員變量
@private 只能在當(dāng)前類的對象方法中直接訪問,如果子類要訪問需要調(diào)用父類的get/set方法
@protected 可以在當(dāng)前類及其子類對象方法中直接訪問(系統(tǒng)默認(rèn)下是用它來修飾的)
@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
-
創(chuàng)建一個Student類繼承父類Person,在子類方法中調(diào)用父類的成員變量
WX20190731-152423@2x.png從上圖可以看出我們在.m里面聲明的成員變量子類是無法訪問的(即使給他@public),也會被認(rèn)為是@private,所以我們的對外的成員變量都會放到.h去聲明,然而由于_age變量是@private,所以子類還是無法訪問的。
-
在外部調(diào)用Person類的成員變量
從外部調(diào)用成員變量.png可以看到由于我們沒有在Person或它的子類里面,所以只能訪問.h中@public和@package修飾的變量,也就是_name和_height,由于_sex是@ protected在外部是不能被訪問的。
@property
@property的作用是定義屬性,可以通過點(diǎn)語法來訪問,當(dāng)我們在@interface內(nèi)部聲明了一個屬性,編譯器會自動幫我們實(shí)現(xiàn)以下三件事
- 生成一個以下劃線開頭的成員變量_propertyName(默認(rèn)用@private修飾)
- 屬性setter、getter方法的聲明
- 屬性setter、getter方法的實(shí)現(xiàn)
我們可以看到屬性是通過setter和getter方法來訪問成員變量的,只不過通過@property關(guān)鍵字編譯器自動幫我們生成了成員變量、getter和setter方法,調(diào)用點(diǎn)語法實(shí)質(zhì)上是調(diào)用setter和getter方法訪問成員變量。@property用readonly修飾時屬性是只讀的,也即是只會生成getter方法。
注意:在category中使用@property編譯器不會生成成員變量,只會生成setter和getter方法的聲明,也不會生成setter和getter方法的實(shí)現(xiàn),因?yàn)閏ategory中只能添加方法,不能添加成員變量,沒有成員變量就無法與外面的值產(chǎn)生關(guān)聯(lián),setter和getter方法就無從訪問。
屬性的原理就是利用getter和setter方法去訪問對象中某個變量關(guān)聯(lián)的值,通過屬性的原理利用runtime是可以為category添加屬性的。
創(chuàng)建一個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方法的實(shí)現(xiàn)
#import "NSObject+Objc.h"
#import <objc/message.h>
@implementation NSObject (objc)
- (void)setName:(NSString *)name
{
// 添加屬性,給某個對象產(chǎn)生關(guān)聯(lián)
// object:給哪個對象添加屬性
// key:屬性名,根據(jù)key去獲取關(guān)聯(lián)的對象 ,void * == id
// value:關(guān)聯(lián)的值
// policy:策越
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, @"name");
}
@end
@sythesize
如果想要修改@property自動生成的_propertyName成員變量名,我們就可以通過@synthesize propertyName = newName;

當(dāng)同時重寫getter和setter兩個方法的時候,實(shí)現(xiàn)了完全的自定義實(shí)現(xiàn),無法對應(yīng)到默認(rèn)的變量_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方法的聲明,這樣就跟在協(xié)議中添加普通的方法聲明一樣,如果希望遵守該協(xié)議的類能像使用屬性那樣使用它,那就需要遵守該協(xié)議的類不僅實(shí)現(xiàn)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 方法由用戶自己實(shí)現(xiàn),不自動生成。(當(dāng)然對于 readonly 的屬性只需提供 getter 即可)。
- 假如一個屬性被聲明為 @dynamic var,然后你沒有提供 @setter方法和 @getter 方法。編譯的時候沒問題,但是當(dāng)程序運(yùn)行到 instance.var = someVar,由于缺 setter 方法會導(dǎo)致程序崩潰。
- 或者當(dāng)運(yùn)行到 someVar = var 時,由于缺 getter 方法同樣會導(dǎo)致崩潰。
-
編譯時沒問題,運(yùn)行時才執(zhí)行相應(yīng)的方法,這就是所謂的動態(tài)綁定。
沒有實(shí)現(xiàn)setter和getter方法.png
寫在最后
由于技術(shù)有限,文中若有錯誤之處請留言指正。
參考鏈接
http://m.itdecent.cn/p/0695ecbe9e06
https://www.cnblogs.com/Jenaral/p/5970393.html
https://my.oschina.net/Jerod/blog/1935419


