Runtime之@dynamic關(guān)鍵字

講述@dynamic之前,需要了解幾個(gè)名詞。最后再重點(diǎn)介紹@dynamic的用法

  • @property
    • 原子性
    • 存取控制
    • 內(nèi)存管理
  • @synthesize

@property

使用 @property 聲明的屬性,可以方便快捷的為實(shí)例變量創(chuàng)建存取器(getter 和 setter)。默認(rèn)以下劃線 _ 開頭。比如

@interface Person : NSObject
@property(nonatomic, readwrite, strong) NSString *name;
-(void)sayHello;
@end
    
@implementation Person
@synthesize name = _name;//默認(rèn)情況,可以不寫。除非想通過其他的單詞(非_name)來使用
-(void)sayHello{
    NSLog(@"%@ says hello",_name);
}
@end

此時(shí),我們可以通過點(diǎn)訪問符 . 來給對象的name屬性進(jìn)行存取。

    //設(shè)置
    Person* person1 = [[Person alloc] init];
    person1.name = @"zhangsan";
    [person1 sayHello];
    //獲取
    NSLog(@"person1's name is %@",[person1.name sayBye]);
原子性

? 在上述例子中,我們看到了聲明語句括號內(nèi)的nonatomic關(guān)鍵字。具體原子性包含兩個(gè)關(guān)鍵字:

  • atomic
    • 默認(rèn)。原子的,意味著只有一個(gè)線程訪問實(shí)例變量(的getter和setter)。
    • 線程安全,至少在當(dāng)前的存取器上是安全的
    • 影響效率,使用較少
  • nonatomic
    • 非原子的,可以同時(shí)被多個(gè)線程訪問
    • 效率高
    • 多線程下不安全,使用較多
存取控制

? 在上述例子中,我們看到了聲明語句括號內(nèi)的readwrite關(guān)鍵字。表示屬性的存取特征。存取控制具體包含以下幾個(gè):

  • readwrite
    • 默認(rèn)屬性,系統(tǒng)會自動給屬性生成getter和setter存取器
  • readonly
    • 只讀屬性,只會生成getter;不能通過點(diǎn)運(yùn)算符(setter)給屬性賦值
內(nèi)存管理

? 在上述例子中,我們看到了聲明語句括號內(nèi)的strong關(guān)鍵字。表示屬性內(nèi)存管理的關(guān)鍵字,有以下幾個(gè):

  • assign
    • 默認(rèn),適用于值類型。如int、NSInteger、CGFloat、float等。
  • retain
    • 在setter方法中,會對傳入的對象的引用計(jì)數(shù)器加1(理解ARC)。
  • strong
    • strong是iOS引入ARC之后的關(guān)鍵字,是retain的一個(gè)可選的替代。
  • weak
    • 與retain相比,對引入對象的引用計(jì)數(shù)器,不進(jìn)行加1操作。經(jīng)常用于delegate等。
  • copy
    • 與strong類似,區(qū)別是copy是創(chuàng)建了一個(gè)新對象。通常NSString類屬性會使用copy。

@synthesize

? @synthesize默認(rèn)會給屬性添加一個(gè)別名,比如上個(gè)例子中的_name 。默認(rèn)情況下,對于@property的變量都會生成相關(guān)別名和存取器

@dynamic

? 如果某屬性已實(shí)現(xiàn)了自己的getter和setter(當(dāng)然對于 readonly 的屬性只有 getter ),可以通過@dynamic關(guān)鍵字來阻止自動生成getter和setter的覆蓋。@dynamic關(guān)鍵字有兩個(gè)作用:

  • 讓編譯器不要創(chuàng)建實(shí)現(xiàn)屬性所用的實(shí)例變量;
  • 讓編譯器不要創(chuàng)建該屬性的get和setter方法。

編譯時(shí)沒問題,在運(yùn)行時(shí)才執(zhí)行相應(yīng)的方法,這就是所謂的動態(tài)綁定。

假如一個(gè)屬性被聲明為@dynamic var,然而沒有提供對應(yīng)的getter和setter方法,編譯的時(shí)候不會報(bào)錯,但是當(dāng)程序運(yùn)行到 instance.var = xxx 時(shí),由于缺少setter方法會導(dǎo)致程序崩潰


@dynamic的使用

這里介紹三種

  • 通過私有變量來實(shí)現(xiàn)@dynamic
  • Category擴(kuò)展
  • 動態(tài)解析和函數(shù)重簽名

通過私有變量來實(shí)現(xiàn)@dynamic

通過私有變量來實(shí)現(xiàn)@dynamic,可以達(dá)到隱藏某些信息的目的。
例如DemoClass1中有個(gè)dynamicVar屬性,我們將其設(shè)置為@dynamic,在程序運(yùn)行期間,我們想通過他來存取某個(gè)私有(對象的)屬性,簡單起見,我們將對應(yīng)的私有屬性命名為privateVar。
.h 文件:

/***
 * 用來展示通過私有變量來實(shí)現(xiàn)@dynamic
 * 用于隱藏某些信息
 ***/
@interface DemoClass1 : NSObject
@property (nonatomic , strong) NSString* dynamicVar;
@end

.m 文件:

@interface DemoClass1()
@property (nonatomic, strong) NSString* privateVar;
@end

@implementation DemoClass1
@dynamic dynamicVar;
@synthesize privateVar = _privateVar;

- (void)setDynamicVar:(NSString *)dynamicVar{
    self.privateVar = dynamicVar;
}
-(NSString*)dynamicVar{
    return self.privateVar;
}

-(void)setPrivateVar:(NSString *)privateVar{
    _privateVar = privateVar;
}
-(NSString*)privateVar{
    if (!_privateVar) {
        _privateVar = @"This is private var";
    }
    return _privateVar;
}

方法的調(diào)用,我們通過調(diào)用dynamicVar的getter和setter,然后看下對應(yīng)值

    DemoClass1* demo1 = [[DemoClass1 alloc] init];
    NSLog(@"Before - DemoClass1's dynamicVar is :%@", demo1.dynamicVar);
    demo1.dynamicVar = @"Set dynamicVar";
    NSLog(@"After - DemoClass1's dynamicVar is :%@", demo1.dynamicVar);

輸出信息如下:

2019-01-25 09:19:13.253753+0800 property_dynamic[18448:7311996] Before - DemoClass1's dynamicVar is :This is private var
2019-01-25 09:19:13.253893+0800 property_dynamic[18448:7311996] After - DemoClass1's dynamicVar is :Set dynamicVar

我們可以看到,當(dāng)我們訪問dynamicVar的getter方法時(shí),最終展示的私有變量privateVar。而訪問setter,最終設(shè)置的也是privateVar。
通過@dynamic關(guān)鍵字最終實(shí)現(xiàn)了將私有信息暴漏的目的。

Category擴(kuò)展

另一個(gè)較常用的用法就是Category,例如,我們想對UILabel進(jìn)行行間距和列間距的設(shè)置,我們想通過兩個(gè)屬性來設(shè)置
.h 文件:

@interface UILabel (Demo2)
//行間距
@property (nonatomic, assign) CGFloat ltx_lineSpace;
//字間距
@property (nonatomic, assign) CGFloat ltx_wordSpace;
@end

.m 文件:

#import "UILabel+Demo2.h"
#import <objc/runtime.h>

NSString const *ltx_label_lineSpace = @"ltx_label_lineSpace";
NSString const *ltx_label_wordSpace = @"ltx_label_wordSpace";
@implementation UILabel (Demo2)
@dynamic ltx_lineSpace;
@dynamic ltx_wordSpace;

-(void)ltx_updateLabelLineSpace{
    NSString* text = self.text;
    NSInteger lineSpace = self.ltx_lineSpace;
    NSInteger wordSpace = self.ltx_wordSpace;
    if ([text length] > 0) {
        NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];
        if (lineSpace > 0) {
            NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
            [paragraphStyle setLineSpacing:lineSpace];
            [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [text length])];
        }
        if (wordSpace > 0) {
            [attributedString addAttribute:NSKernAttributeName value:[NSNumber numberWithFloat:wordSpace] range:NSMakeRange(0, [text length])];
        }
        self.attributedText = attributedString;
    }
}


#pragma mark - Getter && Setter
-(void)setLtx_lineSpace:(CGFloat)ltx_lineSpace{
    NSNumber* number = [NSNumber numberWithFloat:ltx_lineSpace];
    objc_setAssociatedObject(self, &ltx_label_lineSpace, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self ltx_updateLabelLineSpace];
}
-(CGFloat)ltx_lineSpace{
    NSNumber* number =  objc_getAssociatedObject(self, &ltx_label_lineSpace);
    return [number floatValue];
}

-(void)setLtx_wordSpace:(CGFloat)ltx_wordSpace{
    NSNumber* number = [NSNumber numberWithFloat:ltx_wordSpace];
    objc_setAssociatedObject(self, &ltx_label_wordSpace, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self ltx_updateLabelLineSpace];
}
-(CGFloat)ltx_wordSpace{
    NSNumber* number =  objc_getAssociatedObject(self, &ltx_label_wordSpace);
    return [number floatValue];
}

方法的調(diào)用,我們通過調(diào)用ltx_lineSpaceltx_wordSpace 來對labe的列間距和字間距進(jìn)行設(shè)置

    //設(shè)置UILabel的行間距和字間距
    self.label.ltx_lineSpace = 8;
    self.label.ltx_wordSpace = 4;

效果如下

動態(tài)修改UILabel的行間距和字間距.png

更多擴(kuò)展,請移步參照:https://github.com/liangtongdev/LTxCategories

動態(tài)解析和函數(shù)重簽名

即通過對@dynamic動態(tài)屬性的getter和setter進(jìn)行以下操作。

  • 動態(tài)解析
    • 重寫NSObject的方法 + (BOOL)resolveInstanceMethod:(SEL)sel
    • 利用class_addMethod方法,將其他方法的實(shí)現(xiàn)添加給屬性的getter和setter方法。
  • 函數(shù)重簽名
    • 消息轉(zhuǎn)發(fā)
      • 重寫NSObject的方法-(id)forwardingTargetForSelector:(SEL)aSelector
      • 在方法內(nèi)部,對動態(tài)屬性的getter和setter方法進(jìn)行轉(zhuǎn)發(fā),由其他對象來實(shí)現(xiàn)具體細(xì)節(jié)。
    • 方法重簽名
      • 重寫NSObject的方法 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
      • 在方法內(nèi),返回動態(tài)屬性的getter和setter方法的新的方法簽名
      • 重寫NSObject的方法- (void)forwardInvocation:(NSInvocation *)anInvocation
      • 在方法內(nèi)部,對重新簽名的方法的target進(jìn)行賦值,并喚醒

具體細(xì)節(jié),請移步參照文章:Runtime之objc_msgSend執(zhí)行流程


Demo

https://github.com/liangtongdev/Demo-property_dynamic

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

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

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