1. 程式人生 > >KVC、KVO、RunTime、RunLoop

KVC、KVO、RunTime、RunLoop

KVC-->Key-Value Coding: 鍵值編碼 (KVC)

原理:KVC運用了一個isa-swizzling技術. isa-swizzling就是型別混合指標機制, 將2個物件的isa指標互相調換, 就是俗稱的黑魔法.

KVC主要通過isa-swizzling, 來實現其內部查詢定位的. 預設的實現方法由NSOject提供

  isa指標, 如其名稱所指,(就是is a kind of的意思), 指向分發表物件的類. 該分發表實際上包含了指向實現類中的方法的指標, 和其它資料。

KVC是一種非正式的Protocol,提供一種機制來間接訪問物件的屬性

獲取值方式:

1.valueForKey: 傳入NSString屬性的名字。 2.valueForKeyPath: 屬性的路徑,xx.xx 3.valueForUndefinedKey 預設實現是丟擲異常,可重寫這個函式做錯誤處理

修改值方式:

1.setValue:forKey: 2.setValue:forKeyPath: 3.setValue:forUnderfinedKey: 4.setNilValueForKey: 對非類物件屬性設定nil時呼叫,預設丟擲異常。

舉例:Person物件有2個屬性  name(NSSting),age(NSInteger) KVC賦值程式碼如下

    Person *person = [[Person alloc]init];

    //KVC進行賦值

    [person setValue:[NSNumber numberWithInteger:18] forKey:@"age"];

    [person setValue:@"Jany" forKey:@"name"];

KVC字典轉模型賦值

//KVC 字典轉模型 字典要和model中的屬性一一對應  為防止閃退報錯重寫model中setValue: forUndefinedKey:   方法

    NSDictionary *dict = @{@"name":@"Jenny",@"age":@26};

    [person setValuesForKeysWithDictionary:dict];

KVO--> Key-Value Observing 鍵值觀察(KVO), KVO是觀察者模式的一種應用

原理:通過對某個物件的某個屬性新增觀察者,若該屬性值改變,就會呼叫observeValueForKeyPath:方法

給上面的Person物件person 新增觀察者  觀察name屬性變化 程式碼如下

//KVO 新增觀察者  觀察person物件中屬性name的值改變

[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];

觀察者監聽到值改變了回撥方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

    NSLog(@"keyPath: %@", keyPath);

    NSLog(@"object: %@", object);

    NSLog(@"change: %@", change);

    NSLog(@"context: %@", context);

}

全程式碼如下

#import "Person.h"

#import <objc/message.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    Person *person = [[Person alloc]init];

    //KVO 新增觀察者  觀察person物件中屬性name的值改變

    [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];

    //KVC進行賦值

    [person setValue:[NSNumber numberWithInteger:18] forKey:@"age"];

    [person setValue:@"Jany" forKey:@"name"];

    //KVC 字典轉模型 字典要和model中的屬性一一對應  為防止閃退報錯重寫model中setValue:(id)value forUndefinedKey:   方法

    NSDictionary *dict = @{@"name":@"Jenny",@"age":@26};

    [person setValuesForKeysWithDictionary:dict];

    NSLog(@"%zd",person.age);

}

// 觀察者監聽到之後回撥方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

    NSLog(@"keyPath: %@", keyPath);

    NSLog(@"object: %@", object);

    NSLog(@"change: %@", change);

    NSLog(@"context: %@", context);

}

KVO使用場景:

1、下拉重新整理、下拉載入監聽UIScrollerView的ContentOffset方法

2、webView混排監聽contentsize

3、監聽模型屬性實時更新UI

4、監聽控制器frame改變,實現抽屜效果

RunTime

簡介:RunTime簡稱執行時。OC就是執行時機制,也就是在執行時候的一些機制,其中最主要的是訊息機制。

作用:

1.傳送訊息。 objc_msgSend只有物件才能傳送訊息,因此以objc開頭。使用訊息機制前提必須匯入#import <objc/message.h>

舉例:Person中宣告兩個方法-(void)eat;  +(void)eat;

呼叫物件方法 

[person eat];

本質讓物件傳送訊息  objc_msgSend(person,@selector(eat));

呼叫類方法:兩種方式

1.[Person eat]; 

2.[[Person class] eat];

本質讓類物件傳送訊息 objc_msgSend([Person class],@selector(eat));

2.交換方法

開發使用場景:系統自帶的方法功能不夠,給系統自帶的方法擴充套件一些功能,並保持原有的功能。

方式:

1.整合系統的類,重寫方法

2.使用runtime,交換方法

舉例:建立UIImage類別Image  

.m中實現方法交換程式碼

#import <objc/message.h>

@implementation UIImage (Image)

+ (void)load{

    //獲取imageWithName方法地址

    Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));

    //獲取imageName方法地址

    Method imageName = class_getClassMethod(self, @selector(imageNamed:));

    method_exchangeImplementations(imageWithName, imageName);

}

// 不能在分類中重寫系統方法imageNamed,因為會把系統的功能給覆蓋掉,而且分類中不能呼叫super.

// 既能載入圖片又能列印

+ (instancetype)imageWithName:(NSString *)name

{

    // 這裡呼叫imageWithName,相當於呼叫imageName

    UIImage *image = [self imageWithName:name];

    if (image == nil) {

        NSLog(@"載入空的圖片");

    }

    return image;

}

實現:

 [UIImage imageNamed:@"123"];  //方法交換imageNamed-->imageWithName

3.給分類新增屬性

說明:可以為已有的類新增方法,但是卻不能直接新增屬性,因為即使你添加了@property,它既不會生成例項變數,也不會生成setter、getter方法,即使你添加了也無法使用。所以我們首先需要自己去新增setter、getter方法,這個好辦,直接在.m檔案里加就可以了,但是要真正新增可以使用的屬性,還需要利用Runtime來關聯物件

原理:給一個類宣告屬性,其實本質就是給這個類新增關聯,並不是直接把這個值的記憶體空間新增到類存空間。

實現:建立一個NSObject類別  名為Property,新增屬性為name   @property (nonatomic,strong)NSString *name;

@interface NSObject (Property)

@property (nonatomic,strong)NSString *name;

@end

#import <objc/message.h>

// 定義關聯的key

static const char *key = "name";

@implementation NSObject (Property)

- (NSString *)name{

    // 根據關聯的key,獲取關聯的值。

    return objc_getAssociatedObject(self, key);

}

- (void)setName:(NSString *)name

{

    // 第一個引數:給哪個物件新增關聯

    // 第二個引數:關聯的key,通過這個key獲取

    // 第三個引數:關聯的value

    // 第四個引數:關聯的策略

    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

呼叫:

//runtime給NSObject物件新增屬性name(通過類別property)

    NSObject *objc = [[NSObject alloc]init];

    objc.name = @"Jany";

    NSLog(@"%@",objc.name);

RunTime還有些其他的功能

4.動態新增方法

5.字典轉模型 

RunLoop  

說明:

一般來講,一個執行緒一次只能執行一個任務,執行完成後執行緒就會退出。如果我們需要一個機制,讓執行緒能隨時處理事件但並不退出.

基本作用:

1.保持程式的持續執行

2.處理App中的各種事件(比如觸控事件、定時器事件、Selector事件)

3.節約CPU資源,提高程式效能:該做事時做事,該休息時休息

. . . . . . 

main函式中的RunLoop

int main(int argc, char * argv[]) {

    @autoreleasepool {

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

    }

}

UIApplicationMain函式內部就啟動了一個RunLoop,所以UIApplicationMain函式一直沒有返回,保持了程式的持續執行,這個預設啟動的RunLoop是跟主執行緒相關聯的

RunLoop與執行緒

1.每條執行緒都有唯一的一個與之對應的RunLoop物件

2.主執行緒的RunLoop已經自動建立好了,子執行緒的RunLoop需要主動去建立

3.RunLoop在第一次獲取時建立,線上程結束時銷燬

獲得RunLoop物件

1.Foundation

NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];//獲得主執行緒的RunLoop物件

NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];//獲得當前執行緒的RunLoop物件

2.Core Foundation

CFRunLoopGetMain()

CFRunLoopGetCurrent()

NSRunLoop使用須知

1.NSLog(@"%@",[NSRunLoop currentRunLoop]);列印當前執行緒的RunLoop,懶載入模式,一條執行緒對應一個RunLoop物件,有返回,沒有建立,主執行緒的RunLoop預設建立,子執行緒的RunLoop需要手動建立,[NSRunLoop currentRunLoop],同一個執行緒中若是建立多個RunLoop,則返回的都是同一個RunLoop物件,一個RunLoop裡會有多個mode執行模式(系統提供了5個),但執行時只能指定一個RunLoop,若是切換RunLoop,則需要退出當前的RunLoop

2.定時器NSTimer問題:1:若是建立定時器用timerWithTimeInterval,則需要手動將定時器新增到NSRunLoop中,指定的執行模式為default,但是如果有滾動事件的時候,定時器就會停止工作。解決辦法:更改NSRunLoop的執行模式,UITrackingRunLoopMode介面追蹤,此模式是當只有發生滾動事件的時候才會開啟定時器。若是任何時候都會開啟定時器: NSRunLoopCommonModes,

 NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode

 佔用,標籤,凡是新增到NSRunLoopCommonModes中的事件愛你都會被同時新增到打上commmon標籤的執行模式上

3. 1:scheduledTimerWithTimeInterval此方法建立的定時器預設加到了NSRunLoop中,並且設定執行模式為預設。 2:若是想在子執行緒開啟NSRunLoop:需要手動開啟:NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];等到執行緒銷燬的時候currentRunloop物件也隨即銷燬。2:在子執行緒的定時器,需要手動加入到runloop:不要忘記呼叫run方法

程式碼案例:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    [self timer1];

//    [self timer2];

}

//在runloop中有多個執行模式,但是runloop只能選擇一種模式執行

//mode裡面至少要有一個timer或者是source

-(void)run

{

    NSLog(@"子執行緒開啟Runloop中新增timer事件,讓執行緒持續存");

}

//[NSTimer scheduledTimerWithTimeInter..]此方法建立的定時器預設加到了NSRunLoop中,並且設定執行模式為預設

-(void)timer2

{

    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

    //該方法內部自動新增到runloop中,並且設定執行模式為預設

    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    //開啟runloop

    [currentRunloop run];

}

//timerWithTimeInterval,需要手動將定時器新增到NSRunLoop中,指定的執行模式為default

- (void)timer1{

    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

    [currentRunloop addTimer:[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES] forMode:NSDefaultRunLoopMode];

    //控制迴圈時間

//    [currentRunloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];

}