runtime運行機制方法學習


  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知識的記錄分享就到這里了,如果其中有什么錯誤,望大家指出,謝謝!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM