runtime這玩意第一次聽說時都不知道是什么,經過了解后才知道它就是oc動態語言的機制,沒有它那oc就不能稱為動態語言。在之前可能大家對runtime了解都不深,隨着編程技能的日益加深和需要,大家開始更加關心底層的實現,並用自己更需要的方式實現。這時runtime開始慢慢火起來了,作為一個iOS程序員,如果出去說自己不知道runtime無疑是一件很丟分的事情。由於runtime的底層實現筆者也沒搞得太明白,在這里就跟大家提提幾個關於runtime的方法。
1.runtime之動態添加屬性
在最開始學習oc之時,我相信大家都聽過,在類目中是無法添加屬性的。而我在這里將要實現給UIButton增加一個block屬性,方便實現其點擊方法。首先創建一個UIButton的類目(UIButton+Block),接着我們來實現
1 UIButton+Block.h文件 2 //定義一個block 3 typedef void(^ActionBlock)(UIButton *sender); 4 5 @interface UIButton (Block) 6 //定義方法當button通過某點擊狀態使用一個block 7 - (void)handleClickEvent:(UIControlEvents)aEvent UsingBlock:(ActionBlock)block; 8 @end
.h文件較為簡單一看明白不多做解釋,接下來是.m文件
1 //定義添加屬性所對應的關鍵字 2 static char *overViewKey; 3 @implementation UIButton (Block) 4 5 - (void)handleClickEvent:(UIControlEvents)aEvent UsingBlock:(ActionBlock)block{ 6 //設置關聯對象 (被關聯者,關鍵字,關聯者,屬性狀態) 7 objc_setAssociatedObject(self, &overViewKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC); 8 //給button增加點擊事件 9 [self addTarget:self action:@selector(buttonClicked:) forControlEvents:aEvent]; 10 } 11 12 - (void)buttonClicked:(UIButton *)sender{ 13 //獲取關聯對象 (被關聯者,關鍵字) 14 ActionBlock clickedBlock = objc_getAssociatedObject(self, &overViewKey); 15 //如果block存在則調用block 16 if (clickedBlock != nil) { 17 clickedBlock(sender); 18 } 19 }
在這里 objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
便為runtime方法。此方法有4個參數,第一個為類別:self(給誰增加屬性),第二個參數為屬性關鍵字:&overViewKe(作為屬性的標示符來查找屬性),第三個參數為屬性:block(要添加的屬性),第四個參數為屬性修飾符:OBJC_ASSOCIATION_COPY_NONATOMIC(作為屬性的修飾符,nonatomic,copy)。通過此方法我們便可以動態的給一個類增加屬性了。
屬性增加后我們便需要獲取此屬性 objc_getAssociatedObject(<#id object#>, <#const void *key#>),此方法就較為簡單了。在這里需要注意的是屬性關鍵字,由於要保證屬性關鍵字的作用域,所以在這里使用static作為其修飾符,另外由於runtime機制屬於c語言實現,因此這里的關鍵字需要使用char類型!
這里定義好后的使用就簡單了直接一句代碼使用
1 //定義一個uibutton 並使用其方法 2 [button handleClickEvent:UIControlEventTouchUpInside UsingBlock:^(UIButton *sender) { 3 NSLog(@"你點了%@按鈕",[sender currentTitle]); 4 }];
這樣通過runtime改寫的是UIButton的點擊事件就更方便理解,閱讀了。
2.runtime之歸檔使用方法
在自定類歸檔時,我們需要將屬性一個一個的寫入,在屬性較少時可能不覺得有什么問題,但若是屬性較多時,此種寫法就顯得笨拙了,此處我們便利用runtime的方法來動態的獲取其屬性,並寫入。
首先自己新建一個類,這里筆者創建一個BQPerson類
1 BQPerson.h文件 2 @interface BQPerson : NSObject <NSCoding> 3 //假定這里有好幾十個屬性 4 @property (nonatomic, copy) NSString *name; 5 @property (nonatomic, copy) NSString *crad; 6 @property (nonatomic, copy) NSString *birthday; 7 @property (nonatomic, assign) NSInteger age; 8 @property (nonatomic, copy) NSString *iphone; 9 @property (nonatomic, copy) NSString *adress; 10 @property (nonatomic, assign) CGFloat height; 11 @property (nonatomic, assign) CGFloat weight; 12 13 @end
接下來我們便在.m文件中通過runtime來實現其歸檔
1 - (id)initWithCoder:(NSCoder *)aDecoder{ 2 if (self = [super init]) { 3 //定義長度 4 unsigned int index = 0; 5 //動態獲取成員變量數組 6 Ivar *ivars = class_copyIvarList([self class], &index); 7 for (int i = 0 ; i < index; i++) { 8 //取出變量 9 Ivar ivar = ivars[i]; 10 //獲取變量名 11 const char *name = ivar_getName(ivar); 12 //輸出屬性名字及其在內存偏移量 13 NSLog(@"%s %td",name,ivar_getOffset(ivar)); 14 //解檔:解檔調用方法decodeObjectForKey 15 NSString *key = [NSString stringWithUTF8String:name]; 16 id value = [aDecoder decodeObjectForKey:key]; 17 //賦值 18 [self setValue:value forKey:key]; 19 } 20 //由於這里是使用的C語言,需要自己手動管理內存 21 free(ivars); 22 } 23 return self; 24 } 25 26 - (void)encodeWithCoder:(NSCoder *)aCoder{ 27 //定義長度 28 unsigned int index = 0; 29 //動態獲取類成員變量數組 30 Ivar *ivars = class_copyIvarList([self class], &index); 31 for (int i = 0; i < index; i++) { 32 //取出對應的變量 33 Ivar ivar = ivars[i]; 34 //獲取取出的變量名 35 const char *name = ivar_getName(ivar); 36 //歸檔:歸檔調用方法encodeObject: forKey: 37 NSString *key = [NSString stringWithUTF8String:name]; 38 id value = [self valueForKey:key]; 39 [aCoder encodeObject:value forKey:key]; 40 } 41 //釋放獲取的變量數組 42 free(ivars); 43 }
在這里我們首先使用 class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>) 方法來獲取一個類的成員變量數組,其中第一個參數為一個類:[self class](獲取哪個類的成員變量),第二個參數為一個無符號整型指針:index(用以記錄數組長度)。接下來我們通過ivar_getName(<#Ivar v#>)來返回一個變量的名字,接着將此名字轉化為NSString類型。便可以進行解檔和歸檔了。需要注意的是這里由於使用的是C語言,所以數組需要自己手動釋放!
3.runtime之方法調換
在工程項目變得較為龐大后,由於功能需要我們要實現2個方法的交換,若是在代碼里一個一個修改相對來說較為困難,在這里我們便可以使用runtime通過幾句代碼來直接交換我們需要交換的2個方法。示例如下:
1 + (void)load{ 2 static dispatch_once_t onceToken; 3 dispatch_once(&onceToken, ^{ 4 Method testfirst = class_getInstanceMethod(self, @selector(testfirst)); 5 Method secondtest = class_getInstanceMethod(self, @selector(secondtest)); 6 if (testfirst != nil && secondtest != nil) { 7 method_exchangeImplementations(testfirst, secondtest); 8 }else{ 9 NSLog(@"方法獲取失敗"); 10 } 11 }); 12 } 13 14 - (void)viewDidLoad { 15 [super viewDidLoad]; 16 17 [self testfirst]; 18 [self secondtest]; 19 } 20 21 - (void)testfirst{ 22 NSLog(@"1===%s",__func__); 23 } 24 - (void)secondtest{ 25 NSLog(@"2===%s",__func__); 26 }
按照正常程序來講在viewDidLoad中應該是先走testfirst方法,接着再走secondtest方法,但這里我們在load(類第一次加載進內存的時候調用)方法中使用
class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)返回一個方法,其中第一個參數:self(類實例對象),第二個參數:@selector(testfirst)(方法選擇器,選擇一個方法)。最后我們使用method_exchangeImplementations(<#Method m1#>, <#Method m2#>) 將獲取的2個方法進行動態的交換,這樣在交換后的方法實現中,此2種方法都會互換,需要注意的是此處交換后,交換的是2個方法的實現地址,如調用[self testfirst]方法時會走secondtest的方法!
4.runtime之使assgin修飾符具有weak修飾符的特性(對象不存在后置為nil特性)
由於此方法實用性不強,只作為理論補充所以簡單描述即可,首先給NSObject對象增加一個屬性,若對象被銷毀前都銷毀其屬性,那么在屬性被銷毀的時候就將對象置為nil。具體其做法如下
4.1.重寫assgin的set方法
- (void)setTestView:(UIView *)testView{ _testView = testView; [testView psp_rundealloc:^{ _testView = nil; }]; }
4.2.寫一個NSObject類目,動態增加一個屬性
- (void)psp_rundealloc:(void (^)())block{ if (block) { PSPObjectBlock *pspobjc = [[PSPObjectBlock alloc] initWithBlock:block]; objc_setAssociatedObject(self, "pspobjc", pspobjc, OBJC_ASSOCIATION_RETAIN); } }
4.3.pspobjc屬性內部實現如下
@interface PSPObjectBlock(){ BlockTest _block; } @end @implementation PSPObjectBlock - (instancetype)initWithBlock:(BlockTest)block{ self = [super init]; if (self) { _block = [block copy]; } return self; } //銷毀時調用block使非weak屬性值變為nil - (void)dealloc{ if (_block) { _block(); } }
有興趣的朋友可以試一試實現此方法,關於runtime知識的記錄分享就到這里了,如果其中有什么錯誤,望大家指出,謝謝!