iOS成員變量、實(shí)例變量、屬性

前言

實(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)以下三件事

  1. 生成一個以下劃線開頭的成員變量_propertyName(默認(rèn)用@private修飾)
  2. 屬性setter、getter方法的聲明
  3. 屬性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;


新變量名.png

當(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容