講述@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, <x_label_lineSpace, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self ltx_updateLabelLineSpace];
}
-(CGFloat)ltx_lineSpace{
NSNumber* number = objc_getAssociatedObject(self, <x_label_lineSpace);
return [number floatValue];
}
-(void)setLtx_wordSpace:(CGFloat)ltx_wordSpace{
NSNumber* number = [NSNumber numberWithFloat:ltx_wordSpace];
objc_setAssociatedObject(self, <x_label_wordSpace, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self ltx_updateLabelLineSpace];
}
-(CGFloat)ltx_wordSpace{
NSNumber* number = objc_getAssociatedObject(self, <x_label_wordSpace);
return [number floatValue];
}
方法的調(diào)用,我們通過調(diào)用ltx_lineSpace 和 ltx_wordSpace 來對labe的列間距和字間距進(jìn)行設(shè)置
//設(shè)置UILabel的行間距和字間距
self.label.ltx_lineSpace = 8;
self.label.ltx_wordSpace = 4;
效果如下

更多擴(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)行賦值,并喚醒
- 消息轉(zhuǎn)發(fā)
具體細(xì)節(jié),請移步參照文章:Runtime之objc_msgSend執(zhí)行流程