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]];
}