Masonry鏈式程式設計思想的基本思路以及KVO底層的響應式程式設計
Masonry基本使用
[self.enterButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.moviePlayer.view);
make.centerY.equalTo(self.moviePlayer.view);
make.height.mas_equalTo(@(50));
make.width.mas_equalTo(@(0));
}];
我們平時已經用習慣了物件導向的程式設計思想,基本上理解為萬物皆物件的概念,那麼如果用Autolayout佈局的話,Masonry就肯定會用過,每個優秀的框架都有一套自己的程式設計思想,這就是經典的鏈式程式設計思想,既然都會用了,那麼學習下人家的程式設計思想也是非常好的提高。
上述程式碼思想是通過.符號把多行程式碼連結成一句程式碼,增強了可讀性,例如a(1).b(2).c(3)一系列操作,那麼在OC中要實現a(引數)這種呼叫方式,那麼必然就是Block了。沒錯,鏈式程式設計的方法呼叫返回的是Block,而且呼叫完Block之後必然會有返回值(該返回值就是操作物件本身),這樣才能鏈式呼叫,而且Block的引數就是需要內部物件操作的值
1.通過鏈式來實現簡單的計算器
從後面往前面推,先來看看我們最終需要的呼叫鏈式程式碼manager.add(10).add(20).add(30).add(40)
,只要這句程式碼,我們就能完成加法運算,一步一步來分析,首先呼叫的是manager.add(10)
,這是第一個加法運算區塊,這裡還能分兩個呼叫,第一個是manager.add
,後面緊跟著是(10),這種呼叫方式必然是Block,因此add方法返回值就是一個Block,該block的引數就是括號裡面的值,那麼假定沒有返回值,呼叫完第一波區塊的時候,就不能再呼叫add
了,因此,該Block既有引數,又有返回值,返回值就是呼叫的物件
程式碼如下
1.建立CalculateManager物件和方法
#define 加法
#define 減法
#define 乘法
#define 除法
@interface CalculateManager : NSObject
// 內部用來儲存計算的結果
@property (nonatomic,assign) int result;
加法
- (CalculateManager *(^)(int num))add;
乘法
- (CalculateManager *(^)(int num))multiply;
減法
- (CalculateManager *(^)(int num))minus;
除法
- (CalculateManager *(^)(int num))div;
2.實現暴露出來的方法
- (CalculateManager *(^)(int num))add
{
// 把block返回出去,在外面進行資料處理,呼叫完之後返回物件
return ^CalculateManager *(int num){
self.result += num;
return self;
};
}
- (CalculateManager *(^)(int))minus
{
return ^CalculateManager *(int num){
self.result -= num;
return self;
};
}
- (CalculateManager * (^)(int num))div{
return ^CalculateManager *(int num){
self.result /= num;
return self;
};
}
- (CalculateManager * (^)(int num))multiply{
return ^CalculateManager *(int num){
self.result *= num;
return self;
};
}
3.最基礎的呼叫
CalculateManager *manager = [[CalculateManager alloc] init];
int result = [manager.add(10).add(20).add(30).add(40) result];
NSLog(@"result = %d",result);
可以看到上面的基本呼叫已經完成了,雖然也是鏈式的,結果也有,但是總感覺和Masonry不一樣,OK,在來一層
@interface NSObject (Calculate)
+ (int)mkj_makeCalculates:(void(^)(CalculateManager *))block;
@end
+ (int)mkj_makeCalculates:(void(^)(CalculateManager *))block {
CalculateManager *manager = [[CalculateManager alloc] init];
block(manager);
return manager.result;
}
4.封裝後的呼叫,現在看起來和Masonry有點像了
int result = [CalculateManager mkj_makeCalculates:^(CalculateManager *manager) {
manager.add(10).add(10).add(10);
manager.add(30).add(40);
manager.div(2);
manager.multiply(4).minus(12);
}];
NSLog(@"result111 = %d",result);
稍微概括下,首先呼叫Category的類方法,引數是Block,該Block根據實際呼叫地方的引數進行方法鏈式呼叫,Category裡面先建立一個計算物件,然後呼叫Block,把物件當做引數進行Block回撥,在外部進行一套鏈式呼叫,鏈式的思想是,物件的方法呼叫沒有引數返回值必須是Block,然後呼叫Block,Block的返回值就是物件,迴圈進行再次呼叫,最終內部傳參的物件把結果儲存在屬性欄位中,可以根據函式返回值返回出來,列印最終需要的結果
2.通過KVO研究下響應式程式設計思路
鏈式程式設計思想就是,不考慮呼叫順序,只在乎結果,事件分發出去,只要有人監聽,就能進行下一步操作,KVO就是這樣,稍微分析下
簡單的KVO如下
// KVO內部原理
CalculateManager *manager1 = [[CalculateManager alloc] init];
NSLog(@"列印類%@,通過runtime獲取類資訊%@",manager1,object_getClass(manager1));
[manager1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"列印類%@,通過runtime獲取類資訊%@",manager1,object_getClass(manager1));
manager1.name = @"mikejing";
NSLog(@"列印類%@,通過runtime獲取類資訊%@",manager1,object_getClass(manager1));
[manager1 removeObserver:self forKeyPath:@"name"];
NSLog(@"列印類%@,通過runtime獲取類資訊%@",manager1,object_getClass(manager1));
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"%@",change);
}
列印結果如下
2016-12-31 11:21:11.182 鏈式程式設計[43681:1390993] 列印類<CalculateManager: 0x60000002e800>,通過runtime獲取類資訊CalculateManager
2016-12-31 11:21:11.183 鏈式程式設計[43681:1390993] 列印類<CalculateManager: 0x60000002e800>,通過runtime獲取類資訊NSKVONotifying_CalculateManager
2016-12-31 11:21:11.183 鏈式程式設計[43681:1390993] {
kind = 1;
new = mikejing;
}
2016-12-31 11:21:11.184 鏈式程式設計[43681:1390993] 列印類<CalculateManager: 0x60000002e800>,通過runtime獲取類資訊NSKVONotifying_CalculateManager
2016-12-31 11:21:11.184 鏈式程式設計[43681:1390993] 列印類<CalculateManager: 0x60000002e800>,通過runtime獲取類資訊CalculateManager
注意看:在給物件新增觀察者之前,物件的class、runtime真實class和isa指標都是CalculateManager
物件,這很正常,但是增加觀察者之後呢,注意看,class還是CalculateManager
,但是isa指標和runtime的getclass暴露了一切,已經變了一個子類NSKVONotifying_CalculateManager
這裡好像在哪看到過,叫做門面設計模式,對外隱藏了一切類資訊,就連直接列印class都被重寫了,還是返回原本的父類,但是內在確確實實被動態改變了,當我們監聽的屬性改變的時候,其實就是監聽到set方法的呼叫,重寫動態建立那個子類的set方法,然後由子類通知觀察者,我已經被改變,呵呵,你該幹嘛幹嘛
既然監聽的是屬性的set方法,那來驗證下
@interface CalculateManager : NSObject
// 暴露一個外部變數直接訪問
{
@public
NSString *_name;
}
// 然後把屬性的變化換個方法,直接訪問
// manager1.name = @"mikejing";
manager1->_name = @"mikejing";
// 然後這個觀察者就不會被呼叫了
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
// 不再被列印,說明沒監聽到,有興趣可以試試
NSLog(@"%@",change);
}
3.模仿下KVO自己實現下響應式程式設計思路
回顧下KVO實現的思路
1.動態建立NSKVONotifying_類名,該類是原來那個類的子類,用來最終同時觀察者
2. 修改當前物件的isa指標,當呼叫set方法的時候先去建立的子類找,而且子類重寫了class方法,返回原來父類,來掩蓋犯罪證據
3.子類重寫set,當外部呼叫set,就會進來子類的方法
4.重寫set方法裡面先呼叫super set:
方法,然後通知觀察者
第一步,category一個方法
@interface NSObject (Calculate)
- (void)mkj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
第二步動態修改類的isa指標,並且儲存觀察者物件和呼叫者繫結
- (void)mkj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
// 修改呼叫者的isa指標為動態建立的子類,能讓set方法改變的時候,去子類進行操作
object_setClass(self, [MKJKVONotifying_CalculateManager class]);
// 由於observr沒有任何儲存,子類set方法改變之後如何呼叫observe實現的方法?runtime
// 動態給物件關聯一個屬性
objc_setAssociatedObject(self, "hehe", observer, OBJC_ASSOCIATION_RETAIN);
}
第三步重寫class,隱藏子類,通過runtime拿出對應的觀察者,通知觀察者改變
- (Class)class
{
return [CalculateManager class];
}
- (void)setName:(NSString *)name
{
// 呼叫父類
[super setName:name];
// 根據關聯欄位拿出關聯的觀察者
id obj = objc_getAssociatedObject(self, "hehe");
// 子類最終通知呼叫觀察者的observer方法
[obj observeValueForKeyPath:@"name" ofObject:self change:@{@"change":name} context:nil];
}
第四步外部呼叫自己實現的類似的KVO
// 自己實現KVO
CalculateManager *manager2 = [[CalculateManager alloc] init];
NSLog(@"列印類%@,通過runtime獲取類資訊%@",manager2,object_getClass(manager2));
[manager2 mkj_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"列印類%@,通過runtime獲取類資訊%@",manager2,object_getClass(manager2));
manager2.name = @"jiaojiao";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"%@",change);
}
// 列印資訊如下
2016-12-31 12:10:02.409 鏈式程式設計[44785:1429519] 列印類<CalculateManager: 0x60800003eba0>,通過runtime獲取類資訊CalculateManager
2016-12-31 12:10:02.410 鏈式程式設計[44785:1429519] 列印類<CalculateManager: 0x60800003eba0>,通過runtime獲取類資訊MKJKVONotifying_CalculateManager
2016-12-31 12:10:02.410 鏈式程式設計[44785:1429519] {
change = jiaojiao;
}
通過上述列印可以看出,實際的呼叫是在子類進行的,而且,外部呼叫還只是能看到父類的資訊
上面的只是一些簡單的思路而已,記錄一下,內部實現其實還需要進一步學習,畢竟i love study,it make me happy,哈哈哈,其實思路這種東西,研究久了,只是提供了很多解決問題的方案和設計思路,專案中就能自然的想到解決方案,畢竟好的程式設計可讀性非常高,而且能做到牛B的高聚合,低耦合,讓讀程式碼的人深深感嘆,大哥你好牛B啊,就像讀SDWebImage,Masonry等優質框架,你肯定會覺得,臥槽,大哥,你好牛b啊,這個時候,如果你是作者,你就能淡定的丟擲:略懂,略懂!!!
相關文章
- iOS框架·Masonry原始碼深度解析及學習啟示:設計模式與鏈式程式設計思想iOS框架原始碼設計模式程式設計
- 揚帆起航:從指令式程式設計到函式響應式程式設計程式設計函式
- 使用Reactor響應式程式設計React程式設計
- 函式響應式程式設計與RxSwift函式程式設計Swift
- 淺析iOS-MAS&鏈式程式設計思想iOS程式設計
- 對響應式程式設計的懷疑 - lukaseder程式設計
- 響應式程式設計入門(RxJava)程式設計RxJava
- 響應式程式設計庫RxJava初探程式設計RxJava
- 響應式程式設計簡介之:Reactor程式設計React
- 響應式程式設計機制總結程式設計
- Spring 5與Spring cloud的響應式程式設計之旅SpringCloud程式設計
- 函數語言程式設計-鏈式程式設計RAC函數程式設計
- 完美解釋 Javascript 響應式程式設計原理JavaScript程式設計
- RxJS 系列故事(1)——理解響應式程式設計JS程式設計
- Kotlin Flow響應式程式設計,StateFlow和SharedFlowKotlin程式設計
- Spring Boot 中的響應式程式設計和 WebFlux 入門Spring Boot程式設計WebUX
- 響應式程式設計在Android 中的一些探索程式設計Android
- RxDart——Dart和Flutter中的響應式程式設計入門DartFlutter程式設計
- Spring響應式Reactive程式設計的10個陷阱 -Jeroen RosenbergSpringReact程式設計ROS
- Vue3設計思想及響應式原始碼剖析Vue原始碼
- 程式設計思想 面向切面程式設計程式設計
- 使用Java 9 Flow進行響應式程式設計Java程式設計
- 不用任何賦值的程式設計稱為*函式式*程式設計賦值程式設計函式
- 響應式設計?響應式設計的基本原理是什麼?如何做?
- Spring Cloud Stream的函式式和響應式Reactive程式設計特點 - spring.ioSpringCloud函式React程式設計
- 簡單學懂鏈式程式設計程式設計
- 響應式程式設計與MVVM架構—理論篇程式設計MVVM架構
- 前端RxJs響應式程式設計之運算子實踐前端JS程式設計
- spring AOP 程式設計式應用Spring程式設計
- 程式設計中的自頂向下設計思想程式設計
- 阻塞式程式設計和非阻塞式程式設計區別程式設計
- 函式程式設計函式程式設計
- 【程式設計素質】程式設計思想總結程式設計
- 《Go 語言程式設計》讀書筆記(十一)底層程式設計Go程式設計筆記
- go 模仿JAVA,面向介面/鏈式程式設計GoJava程式設計
- 響應式程式設計基礎教程:Spring Boot 與 Lettuce 整合程式設計Spring Boot
- 什麼是反應式程式設計?程式設計
- 聊聊Spring Reactor反應式程式設計SpringReact程式設計
- Java 網路程式設計 —— 非阻塞式程式設計Java程式設計