講述@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)訪問符 . 來給對(duì)象的name屬性進(jìn)行存取。
//設(shè)置
Person* person1 = [[Person alloc] init];
person1.name = @"zhangsan";
[person1 sayHello];
//獲取
NSLog(@"person1's name is %@",[person1.name sayBye]);
原子性
? 在上述例子中乒裆,我們看到了聲明語句括號(hào)內(nèi)的nonatomic關(guān)鍵字套利。具體原子性包含兩個(gè)關(guān)鍵字:
- atomic
- 默認(rèn)。原子的鹤耍,意味著只有一個(gè)線程訪問實(shí)例變量(的getter和setter)肉迫。
- 線程安全,至少在當(dāng)前的存取器上是安全的
- 影響效率稿黄,使用較少
- nonatomic
- 非原子的喊衫,可以同時(shí)被多個(gè)線程訪問
- 效率高
- 多線程下不安全,使用較多
存取控制
? 在上述例子中抛猖,我們看到了聲明語句括號(hào)內(nèi)的readwrite關(guān)鍵字格侯。表示屬性的存取特征。存取控制具體包含以下幾個(gè):
- readwrite
- 默認(rèn)屬性财著,系統(tǒng)會(huì)自動(dòng)給屬性生成getter和setter存取器
- readonly
- 只讀屬性联四,只會(huì)生成getter;不能通過點(diǎn)運(yùn)算符(setter)給屬性賦值
內(nèi)存管理
? 在上述例子中撑教,我們看到了聲明語句括號(hào)內(nèi)的strong關(guān)鍵字朝墩。表示屬性內(nèi)存管理的關(guān)鍵字,有以下幾個(gè):
- assign
- 默認(rèn)伟姐,適用于值類型收苏。如int、NSInteger愤兵、CGFloat鹿霸、float等。
- retain
- 在setter方法中秆乳,會(huì)對(duì)傳入的對(duì)象的引用計(jì)數(shù)器加1(理解ARC)懦鼠。
- strong
- strong是iOS引入ARC之后的關(guān)鍵字,是retain的一個(gè)可選的替代屹堰。
- weak
- 與retain相比肛冶,對(duì)引入對(duì)象的引用計(jì)數(shù)器,不進(jìn)行加1操作扯键。經(jīng)常用于delegate等睦袖。
- copy
- 與strong類似,區(qū)別是copy是創(chuàng)建了一個(gè)新對(duì)象荣刑。通常NSString類屬性會(huì)使用copy馅笙。
@synthesize
? @synthesize默認(rèn)會(huì)給屬性添加一個(gè)別名伦乔,比如上個(gè)例子中的_name 。默認(rèn)情況下延蟹,對(duì)于@property的變量都會(huì)生成相關(guān)別名和存取器
@dynamic
? 如果某屬性已實(shí)現(xiàn)了自己的getter和setter(當(dāng)然對(duì)于 readonly 的屬性只有 getter )评矩,可以通過@dynamic關(guān)鍵字來阻止自動(dòng)生成getter和setter的覆蓋。@dynamic關(guān)鍵字有兩個(gè)作用:
- 讓編譯器不要?jiǎng)?chuàng)建實(shí)現(xiàn)屬性所用的實(shí)例變量阱飘;
- 讓編譯器不要?jiǎng)?chuàng)建該屬性的get和setter方法斥杜。
編譯時(shí)沒問題,在運(yùn)行時(shí)才執(zhí)行相應(yīng)的方法沥匈,這就是所謂的動(dòng)態(tài)綁定蔗喂。
假如一個(gè)屬性被聲明為@dynamic var,然而沒有提供對(duì)應(yīng)的getter和setter方法高帖,編譯的時(shí)候不會(huì)報(bào)錯(cuò)缰儿,但是當(dāng)程序運(yùn)行到 instance.var = xxx 時(shí),由于缺少setter方法會(huì)導(dǎo)致程序崩潰
@dynamic的使用
這里介紹三種
- 通過私有變量來實(shí)現(xiàn)@dynamic
- Category擴(kuò)展
- 動(dòng)態(tài)解析和函數(shù)重簽名
通過私有變量來實(shí)現(xiàn)@dynamic
通過私有變量來實(shí)現(xiàn)@dynamic散址,可以達(dá)到隱藏某些信息的目的乖阵。
例如DemoClass1中有個(gè)dynamicVar屬性,我們將其設(shè)置為@dynamic预麸,在程序運(yùn)行期間瞪浸,我們想通過他來存取某個(gè)私有(對(duì)象的)屬性,簡(jiǎn)單起見吏祸,我們將對(duì)應(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贡翘,然后看下對(duì)應(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)我們?cè)L問dynamicVar的getter方法時(shí),最終展示的私有變量privateVar鸣驱。而訪問setter泛鸟,最終設(shè)置的也是privateVar。
通過@dynamic關(guān)鍵字最終實(shí)現(xiàn)了將私有信息暴漏的目的踊东。
Category擴(kuò)展
另一個(gè)較常用的用法就是Category谈况,例如,我們想對(duì)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 來對(duì)labe的列間距和字間距進(jìn)行設(shè)置
//設(shè)置UILabel的行間距和字間距
self.label.ltx_lineSpace = 8;
self.label.ltx_wordSpace = 4;
效果如下
更多擴(kuò)展赡茸,請(qǐng)移步參照:https://github.com/liangtongdev/LTxCategories
動(dòng)態(tài)解析和函數(shù)重簽名
即通過對(duì)@dynamic動(dòng)態(tài)屬性的getter和setter進(jìn)行以下操作缎脾。
- 動(dòng)態(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)部遗菠,對(duì)動(dòng)態(tài)屬性的getter和setter方法進(jìn)行轉(zhuǎn)發(fā)联喘,由其他對(duì)象來實(shí)現(xiàn)具體細(xì)節(jié)。
- 方法重簽名
- 重寫NSObject的方法 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- 在方法內(nèi)辙纬,返回動(dòng)態(tài)屬性的getter和setter方法的新的方法簽名
- 重寫NSObject的方法- (void)forwardInvocation:(NSInvocation *)anInvocation
- 在方法內(nèi)部豁遭,對(duì)重新簽名的方法的target進(jìn)行賦值,并喚醒
- 消息轉(zhuǎn)發(fā)
具體細(xì)節(jié)贺拣,請(qǐng)移步參照文章:Runtime之objc_msgSend執(zhí)行流程