「iOS」通過CoreLocation Framework深入了解MVC架構

「iOS」通過CoreLocation Framework重新了解多界面傳值以及MVC架構

文章目錄

  • 「iOS」通過CoreLocation Framework重新了解多界面傳值以及MVC架構
    • 前言
    • CoreLocation了解
      • 根據需求建模
      • 設計屬性
      • 方法設計
        • 協議傳值
        • Block傳值
        • KVO
        • Notification通知方式
    • 總結
    • 參考文章

前言

在這個學期的前段時間進行了MVC的相關學習,并且使用MVC完成了知乎日報奧的項目,加上學習的一些博客,開始對MVC這個架構有著更加深刻的理解,也體會到了MVC架構之中的缺點,這篇文章就是利用CoreLocation 這個原生關于定位的架構,來總結MVC的一些使用技巧和理解。

CoreLocation了解

根據需求建模

我們可以先來認識CoreLocation實現定位的相關內容,我們知道CoreLocation是一個關于位置定位的庫,我們分析一下相關的功能,要想實現定位首先就需要一個位置類來對位置進行描述,屬于它的屬性應該有經緯度,海拔等相關信息;一個地標類來描述所處位置的城市街道;一個管理器來控制位置的變更,以及位置的獲取;一個解析器根據當前的位置信息轉化為地標。那么邏輯如此,對應的圖片關系如下

img

我們可以在CoreLocation Framework之中找到了對應的這幾個類的相關屬性,這里我簡單的給出這些類之中的部分頭文件

CLLocation:

@interface CLLocation : NSObject <NSCopying, NSSecureCoding> @property(readonly, nonatomic) CLLocationCoordinate2D coordinate;//返回當前位置的坐標。
@property(readonly, nonatomic) CLLocationDistance altitude;// 返回位置的高度,正值表示海平面上,負值表示海平面下。- (instancetype)initWithLatitude:(CLLocationDegrees)latitudelongitude:(CLLocationDegrees)longitude;//初始化指定經緯度的位置。@end

CLPlacemark:

@interface CLPlacemark : NSObject <NSCopying, NSSecureCoding>// 通過已有地標初始化新地標并復制其數據
- (instancetype)initWithPlacemark:(CLPlacemark *) placemark;// 獲取與地標相關聯的地理位置信息
@property (nonatomic, readonly, copy, nullable) CLLocation *location;// 城市名稱(如Cupertino)
@property (nonatomic, readonly, copy, nullable) NSString *locality; 
......
// 一系列地標相關的具體屬性,用于更詳細地描述位置信息

CLLocationManager:

@interface CLLocationManager : NSObject 
// 獲取調用應用的當前授權狀態
@property (nonatomic, readonly) CLAuthorizationStatus authorizationStatus API_AVAILABLE(ios(14.0), macos(11.0), watchos(7.0), tvos(14.0));// 獲取最后接收到的位置信息,在接收到位置前為nil
@property(readonly, nonatomic, copy, nullable) CLLocation *location;// 判斷用戶是否啟用了位置服務
+ (BOOL)locationServicesEnabled API_AVAILABLE(ios(4.0), macos(10.7));// 開始更新位置信息
- (void)startUpdatingLocation API_AVAILABLE(watchos(3.0)) API_UNAVAILABLE(tvos);// 停止更新位置信息
- (void)stopUpdatingLocation;@end

CLGeocoder:

// 定義地理編碼完成后的回調處理塊,CLPlacemarks按可信度從高到低排列
@interface CLGeocoder : NSObject
typedef void (^CLGeocodeCompletionHandler)(NSArray< CLPlacemark *> * __nullable placemarks, NSError * __nullable error);@interface CLGeocoder : NSObject
// 判斷是否正在進行地理編碼操作
@property (nonatomic, readonly, getter=isGeocoding) BOOL geocoding;// 反向地理編碼請求,根據給定位置獲取對應的地標信息
- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;// 用地址字符串進行地理編碼(默認無區域和首選語言環境設置)
- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;
@end

CLLocationManagerDelegate:

@protocol CLLocationManagerDelegate<NSObject>- (void)locationManager:(CLLocationManager *)managerdidUpdateLocations:(NSArray<CLLocation *> *)locations;@end

設計屬性

我們可以從這些類的設計學習一些MVC架構的設計思想

從剛剛的頭文件我們可以看到,其實頭文件之中的許多屬性是被標注為只讀,標志為只讀其實對于解耦合實現規范代碼有著很大的幫助,比如CLLocationManager之中的location屬性,因為這個位置管理器只是用來管理這個當前的位置,對于外部的使用者來說,只需要在適當的時候進行訪問而不是修改。對應數據的更新和維護其實就是這個類內部的負責。

我們就可以提煉出一個設計準則:外部使用者只要負責讀取數據,具體的數據更新則是由提供者來完成

這種設計思想其實很清晰的將層次分開了,我們這樣不僅成功保護了相關數據的安全,也能進一步的減少相關屬性值的濫用。

具體的操作大佬的博客歸結為:

  1. 業務類中的屬性設計為只讀。使用者只能通過屬性來讀取數據。而由業務類中的方法內部來更新這些屬性的值。
  2. 數據模型類中的屬性定義最好也設置為只讀,因為數據模型的建立是在業務類方法內部完成并通過通知或者異步回調的方式交給使用者。而不應該交由使用者來創建和更新。
  3. 數據模型類一般提供一個帶有所有屬性的init初始化方法,而初始化后這些屬性原則上是不能被再次改變,所以應該設置為只讀屬性。

這里的類全部指的是暴露在頭文件之中的屬性聲明

至于對于類內容,我們需要在內部(即.m文件)修改相關的聲明

@interface CLLocationManager ()@property(nonatomic, copy, nullable) CLLocation *location;
@end//也可以改用以下形式
@implementation CLLocationManager {CLLocation *_location;
}@end

方法設計

協議傳值

當我們類設計結束之后,隨之而來的就是類方法的設計類。無論如何,我們的業務模型最后總是會走向網絡請求或者數據庫調用這種需要,在獲取操作之后再進行異步的操作。在這里CoreLocation的框架,使用的是Delegate和Blockzhe這兩種方式進行回調

先看CLLocationManager定義的屬性

 @property(assign, nonatomic, nullable) id<CLLocationManagerDelegate> delegate;

這個協議實現了

@protocol CLLocationManagerDelegate<NSObject>@optional
- (void)locationManager:(CLLocationManager *)managerdidUpdateLocations:(NSArray<CLLocation *> *)locations API_AVAILABLE(ios(6.0), macos(10.9));@end

當位置管理器對象更新了當前的位置后就會調用delegate屬性所指對象的didUpdateLocations方法來通知。

這就產生了幾個問題:

  1. 誰來創建M層的位置管理對象?
    C層,這個其實很簡單,控制器是負責控制和協調M層,所以C層具有負責創建并持有M層對象的責任,C層也是一個使用觀察者。

  2. M層如何來實現實時的更新和停止更新?
    對于位置管理器之中,有以下兩個方法

- (void)startUpdatingLocation API_AVAILABLE(watchos(3.0)) __TVOS_PROHIBITED;- (void)stopUpdatingLocation;

好像是在tableView也有相似的兩個方法beginUpdatesendUpdates,通過方法通知tableView的數據源發生更新進而更新cell。位置管理器之中的這兩個方法通過通知管理器,位置類的數據(即M層)在內部發生變化。至于這個兩個方法是如何實現位置管理器之中持有的位置類的變化,就是內部實現的內容,相當于一個黑盒,我們作為M層的使用者不需要知道里面的實現原理。

  1. 誰來負責調用M層提供的那些方法?
    答案是: 控制器C層。因為控制器既然負責M層對象的構建,那他當然也是負責M層方法的調用了。

  2. 誰來觀察M層的數據變化通知并進行相應的處理?
    答案是: 控制器C層。因為C層既然負責調用M層所提供的方法,那么他理所當然的也要負責對方法的返回以及更新進行處理。在這里我們的C層控制器需要實現CLLocationManagerDelegate接口,并賦值給位置管理器對象的delegate屬性。

這里引用大佬博客之中的內容

我們知道MVC結構中,C層是負責協調和調度M和V層的一個非常關鍵的角色。而C和M以及V之間的交互協調方式用的最多的也是通過Delegate這種模式,Delegate這種模式并不局限在M和C之間,同樣也可以應用在V和C之間。Delegate的本質其實是一種雙方之間通信的接口,而通過接口來進行通信則可以最大限度的減少對象之間交互的耦合性。

Block傳值

除了用Delegate外,我們還可以用Block回調這種方式來實現方法調用的異步通知處理。標準格式如下:

  typedef void (^BlockHandler)(id obj, NSError * error);

在地標解析器CLGeocoder之中,采用的就是block回調這種方式來實現異步通知的。我們來看看類的部分定義:

typedef void (^CLGeocodeCompletionHandler)(NSArray< CLPlacemark *> * __nullable placemarks, NSError * __nullable error);// 反向地理編碼請求,根據給定位置獲取對應的地標信息
- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;// 用地址字符串進行地理編碼(默認無區域和首選語言環境設置)
- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;

從反向地理編碼請求可以看出來,我們根據一個CLLocation方向解碼出一個CLPlacemark的對象,用一個block來完成內容的回調

//VC中的某個點擊按鈕事件:-(void)ClickHandle:(UIButton*)sender
{sender.userInteractionEnabled = NO;__weak XXXVC  *weakSelf = self;//geocoder也可以是XXXVC里面的一個屬性,從而可以避免重復建立CLGeocoder  *geocoder = [CLGeocoder new];//假設知道了某個位置對象location[geocoder  reverseGeocodeLocation:location completionHandler:^(NSArray< CLPlacemark *> * placemarks, NSError * error)){if (weakSelf == nil)return;sender.userInteractionEnabled = YES;if (error == nil){//處理placemarks對象}else{//處理錯誤}}];  
}

這里的block回調,其實在沒有異步的情況下(即讀取本地庫)是可以不需要寫的,但是在我們實際的編寫過程當中,還是盡可能的遵循統一的相關的模式,有時候需求是會改變的,如果我們這個操作要改為異步操作的話,那么代碼需要整段修改,還包括C層的代碼,修改起來很麻煩。那么不如就在一開始就使用block進行回調,有統一的格式以及便于理解的優點。

KVO

上面簡單展示了Delegate與Block機制用于實現M層數據的更新,前面介紹了這兩個機制的優點,下面引用博客的內容,概括這兩者的缺點,順帶引入對應的KVO機制監聽的內容:

Delegate的方式必須要事先定義出一個接口協議來,并且調用者和實現者都需要按照這個接口規則來進行通知和數據處理交互,這樣無形中就產生了一定的耦合性。也就是二者之間還是具有隱式的依賴形式。不利于擴展和進行完全自定義處理。

block方式的缺點則是使用不好則會產生循環引用的問題從而產生內存泄露,另外就是用block機制在出錯后難以調試以及難以進行問題跟蹤。 而且block機制其實也是需要在調用者和實現之間預先定義一個標準的BlockHandler接口來進行交互和處理。block機制還有一個缺陷是會在代碼中產生多重嵌套,從而影響代碼的美觀和可讀性。

Delegate和block方式雖然都是一種觀察者實現,但卻不是標準和經典的觀察者模式。因為這兩種模式是無法實現多觀察者的。也就是說當數據更新而進行通知時,只能有一個觀察者進行監聽和處理,不能實現多個觀察者的通知更新處理。

可惜的是在CoreLocation Framework并不支持KVO之中方式,下面是大佬假設其支持KVO寫出的相關代碼。

//再次申明的是CCLocationManager是不支持KVO來監聽位置變化的,這里只是一個假設支持的話的使用方法。@interface AppDelegate@property(nonatomic, strong)  CLLocationManager *locationManager;
@end@implementation  AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {self.locationManager = [CLLocationManager new];[self.locationManager  startUpdatingLocation];  //開始監聽位置變化return YES;
}
@end//第一個頁面
@implementation  VC1-(void)viewWillAppear:(BOOL)animated
{[  [UIApplication sharedApplication].delegate.locationManager  addObserver:self  forKeyPath:@"location" options:NSKeyValueObservingOptionNew context:NULL];
}-(void)viewWillDisappear:(BOOL)animated
{[ [UIApplication sharedApplication].delegate.locationManager  removeObserver:self  forKeyPath:@"location" ];}- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context
{//這里處理位置變化時的邏輯。
}
@end//第二個頁面
@implementation  VC2-(void)viewWillAppear:(BOOL)animated
{[  [UIApplication sharedApplication].delegate.locationManager  addObserver:self  forKeyPath:@"location" options:NSKeyValueObservingOptionNew context:NULL];
}-(void)viewWillDisappear:(BOOL)animated
{[ [UIApplication sharedApplication].delegate.locationManager  removeObserver:self  forKeyPath:@"location" ];}- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context
{//這里處理位置變化時的邏輯。
}
@end//.. 其他頁面

接下來分析一下在什么情況下使用KVO:

  1. 最顯而易見的,當我這個數據更新可能會引起多個依賴該對象的對象更新時使用KVO
  2. 當某個對象,在對應流程之中會創建多個副本,而且在這個副本的狀態會不斷產生變化,當副本增多的情況下,我們就需要使用KVO機制來根據新的狀態來處理。

下面是我們使用多副本且不使用KVO的相關流程

img

使用KVO+單例,KVO在這里的意義就是,通知這個單例屬性狀態已經被改變,進行對應的更新

img

Notification通知方式

KVO模式實現了一種對屬性變化的通知觀察機制。而且這種機制由系統來完成,缺點就是他只是對屬性的變化進行觀察,而不能對某些異步方法調用進行通知處理。而如果我們想要正真的實現觀察者模式而不局限于屬性呢?答案就是iOS的NSNotificationCenter

但是這個模式對應的也存在一些缺點,就使用通知的代碼較為松散,在一定程度上,不利于程序的解讀。看前面的內容,通過Delegate或者block時來設計業務層方法的回調時,可以很清楚的知道業務調用方法和實現機制的上下文,因為這些東西在代碼定義里面就已經固化了,我們必須額外的去學習和了解哪些業務層的方法需要添加觀察者哪些不需要,而且代碼中不管在什么時候需要都要在初始化時添加一段代碼上去。通知處理邏輯的可讀寫性以及代碼的可讀性也比較差。

總結

其實這篇文章寫著寫著,和前面寫的五大傳值方法其實有些重合,主要就是當時在學習傳值技巧的時候還沒有學到MVC架構,之前的學習還是停留在較為表面的,通過這次學習加深了對傳值和MVC架構結合的理解

參考文章

論MVVM偽框架結構和MVC中M的實現機制

控制層的設計方法

模型層設計方法

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/63008.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/63008.shtml
英文地址,請注明出處:http://en.pswp.cn/web/63008.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Debezium系列之:使用Debezium采集oceanbase數據庫

Debezium系列之:使用Debezium采集oceanbase數據庫 一、oceanbase數據庫二、安裝OceanBase三、安裝oblogproxy四、基于Docker的簡單采集案例五、生產實際應用案例Debezium 是一個開源的分布式平臺,用于監控數據庫變化和捕捉數據變動事件,并以事件流的形式導出到各種消費者。D…

線程sleep的時候會釋放鎖嗎

來看一段代碼&#xff1a; void task1(mutex &m) {cout << "thread 1 init..." << endl;{std::unique_lock<mutex> lock(m);cout << "thread 1 getLock" << endl;sleep(5);}cout << "thread 1 freeLock&quo…

wordpress建站--如何用Let‘s Encrypt給網站添加免費ssl證書,支持https訪問

本文首發網站&#xff1a;https://www.click234.com 默認情況下我們的網站是http訪問&#xff0c;為了增加訪問安全性&#xff0c;我們需要添加ssl證書&#xff0c;支持采用https方式訪問&#xff0c;今天我們來看下怎么創建免費的ssl證書--Lets Encrypt 使用 Certbot 自動化工…

青少年編程與數學 02-004 Go語言Web編程 02課題、依賴管理

青少年編程與數學 02-004 Go語言Web編程 02課題、依賴管理 課題摘要:一、項目結構各目錄說明&#xff1a; 二、依賴項三、依賴管理任務四、依賴管理步驟1. 初始化Go Modules項目2. 添加依賴3. 指定依賴版本4. 更新依賴5. 清理未使用的依賴6. 離線工作7. 模塊隔離8. 可重現構建 …

Debezium OracleConnection 分析

Debezium OracleConnection 分析 目錄 1. 概述2. 核心功能3. 實現分析4. 使用場景5. 示例分析6. 最佳實踐7. 總結1. 概述 OracleConnection 是 Debezium Oracle 連接器中的數據庫連接管理組件,主要負責: 數據庫連接的建立和管理事務控制查詢執行元數據操作LogMiner 會話管理…

【每日一練 基礎題】[藍橋杯 2022 省 A] 求和

[藍橋杯 2022 省 A] 求和 暴力破解會超時,用因式分解的平方差公式 a2 2abb2(a)2 a-2abb2(a-b)2 輸出整數((a1a2a3…an)-a1-a2-a3-…-an)/2 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);l…

ArrayList源碼分析、擴容機制面試題,數組和List的相互轉換,ArrayList與LinkedList的區別

目錄 1.java集合框架體系 2. 前置知識-數組 2.1 數組 2.1.1 定義&#xff1a; 2.1.2 數組如何獲取其他元素的地址值&#xff1f;&#xff08;尋址公式&#xff09; 2.1.3 為什么數組索引從0開始呢&#xff1f;從1開始不行嗎&#xff1f; 3. ArrayList 3.1 ArrayList和和…

【C++】- 掌握STL List類:帶你探索雙向鏈表的魅力

文章目錄 前言&#xff1a;一.list的介紹及使用1. list的介紹2. list的使用2.1 list的構造2.2 list iterator的使用2.3 list capacity2.4 list element access2.5 list modi?ers2.6 list的迭代器失效 二.list的模擬實現1. list的節點2. list的成員變量3.list迭代器相關問題3.1…

Docker--Docker Container(容器) 之容器實戰

對docker容器的前兩篇文章 Docker–Docker Container(容器) 之 操作實例 Docker–Docker Container(容器&#xff09; Mysql容器化安裝 我們可以先在Docker Hub上查看對應的Mysql鏡像,拉取對應的鏡像&#xff1a; 拉取mysql5.7版本的鏡像&#xff1a; docker pull mysql:5.7…

ModuleNotFoundError: No module named ‘torchvision.transforms.functional_tensor‘

問題&#xff1a; 運行代碼時&#xff0c;報錯&#xff1a; … File “/home/xzy/anaconda3/envs/groundinggpt/lib/python3.10/site-packages/pytorchvideo/transforms/augmix.py”, line 6, in from pytorchvideo.transforms.augmentations import ( File “/home/xzy/anac…

【匯編語言】內中斷(二) —— 安裝自己的中斷處理程序:你也能控制0號中斷

文章目錄 前言1. 編程處理0號中斷1.1 效果演示1.2 分析所要編寫的中斷處理程序1.2.1 引發中斷1.2.2 中斷處理程序1.2.3 中斷處理程序do0應該存放的位置1.2.4 中斷向量表的修改1.2.5 總結 1.3 程序框架1.4 注意事項1.5 從CPU的角度看中斷處理程序1.6 一些問題的思考與解答 2. 安…

華為OD E卷(100分)23-連續字母長度

前言 工作了十幾年&#xff0c;從普通的研發工程師一路成長為研發經理、研發總監。臨近40歲&#xff0c;本想辭職后換一個相對穩定的工作環境一直干到老, 沒想到離職后三個多月了還沒找到工作&#xff0c;愁腸百結。為了讓自己有點事情做&#xff0c;也算提高一下自己的編程能力…

VS2019中無法跳轉定義_其中之一情況

我習慣了使用VS2019看stm的代碼&#xff1b; 遇到的問題&#xff0c;在導入代碼后&#xff0c;發現有些函數調用不能跳轉到定義&#xff1b; 問題描述步驟 1、導入代碼 2、跳轉&#xff0c;無法跳轉 1、中文路徑 2、刪除.vs文件 和網上查的都沒辦法解決 最后發現是VS不支持 …

讓 Win10 上網本 Debug 模式 QUDPSocket 信號槽 收發不丟包的方法總結

在前兩篇文章里&#xff0c;我們探討了不少UDP丟包的解決方案。經過幾年的摸索測試&#xff0c;其實方法非常簡單, 無需修改代碼。 1. Windows 下設置UDP緩存 這個方法可以一勞永逸解決UDP的收發丟包問題&#xff0c;只要添加注冊表項目并重啟即可。即使用Qt的信號與槽&#…

【設計模式】觀察者模式深度講解

文章目錄 概覽一、定義與特點二、角色與職責三、實現方式四、應用場景五、優缺點 Java實現Python實現 概覽 觀察者模式&#xff08;Observer Pattern&#xff09;是一種行為型設計模式&#xff0c;它定義了一種一對多的依賴關系&#xff0c;讓多個觀察者對象同時監聽某一個主題…

Elasticsearch:ES|QL 中的全文搜索 - 8.17

細心的開發者如果已經閱讀我前兩天發布的文章 “Elastic 8.17&#xff1a;Elasticsearch logsdb 索引模式、Elastic Rerank 等”&#xff0c;你就會發現在 8.17 的發布版中&#xff0c;有一個重要的功能發布。那就是 ES|QL 開始支持全文搜索了。在今天的文章中我們來嘗試一下。…

SQL和Python 哪個更容易自學?

SQL和Python不是一個物種&#xff0c;Python肯定更難學習。如果你從事數據工作&#xff0c;我建議先學SQL、有余力再學Python。因為SQL不光容易學&#xff0c;而且前期的投入產出比更大。 SQL是數據查詢語言&#xff0c;場景限于數據查詢和數據庫的管理&#xff0c;對大部分數據…

【unity】從零開始制作平臺跳躍游戲--界面的認識,添加第一個角色!

在上一篇文章中&#xff0c;我們已經完成了unity的環境配置與安裝?? 【Unity】環境配置與安裝-CSDN博客 接下來&#xff0c;讓我們開始新建一個項目吧&#xff01; 新建項目 首先進入unityHub的項目頁面&#xff0c;點擊“新項目”&#xff1a; 我們這個系列將會以2D平臺…

怎么禁用 vscode 中點擊 go 包名時自動打開瀏覽器跳轉到 pkg.go.dev

本文引用怎么禁用 vscode 中點擊 go 包名時自動打開瀏覽器跳轉到 pkg.go.dev 在 vscode 設置項中配置 gopls 的 ui.navigation.importShortcut 為 Definition 即可。 "gopls": {"ui.navigation.importShortcut": "Definition" }ui.navigation.i…

Unity3D實現抽象類的應用場景例子

系列文章目錄 unity知識點 文章目錄 系列文章目錄??前言??一、示例??二、使用步驟??三、抽象類和接口的區別??3-1、抽象類??3-2、接口類??壁紙分享??總結??前言 假設我們正在制作一個游戲,游戲中有多種不同類型的角色,這些角色都有一些共同的行為(比如移…