「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.mzph.cn/web/63008.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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 modifiers2.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…

【汇编语言】内中断(二) —— 安装自己的中断处理程序:你也能控制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. 安…

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的信号与槽&#…

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、接口类👉壁纸分享👉总结👉前言 假设我们正在制作一个游戏,游戏中有多种不同类型的角色,这些角色都有一些共同的行为(比如移…

数据仓库工具箱—读书笔记01(数据仓库、商业智能及维度建模初步)

数据仓库、商业智能及维度建模初步 记录一下读《数据仓库工具箱》时的思考&#xff0c;摘录一些书中关于维度建模比较重要的思想与大家分享&#x1f923;&#x1f923;&#x1f923; 博主在这里先把这本书"变薄"~有时间的小伙伴可以亲自再读一读&#xff0c;感受一下…

docker启动一个helloworld(公司内网服务器)

这里写目录标题 容易遇到的问题&#xff1a;1、docker连接问题 我来介绍几种启动 Docker Hello World 的方法&#xff1a; 最简单的方式&#xff1a; docker run hello-world这会自动下载并运行官方的 hello-world 镜像。 使用 Nginx 作为 Hello World&#xff1a; docker…

基于IEEE 802.1Qci的时间敏感网络(TSN)主干架构安全分析及异常检测系统设计

中文标题&#xff1a;基于IEEE 802.1Qci的时间敏感网络&#xff08;TSN&#xff09;主干架构安全分析及异常检测系统设计 英文标题&#xff1a;Security Analysis of the TSN Backbone Architecture and Anomaly Detection System Design Based on IEEE 802.1Qci 作者信息&…

怎样提升企业网络的性能?

企业网络的稳定性和高效性直接影响员工的工作效率。以下从多维度分析了一些有效策略&#xff0c;帮助公司提升网络性能&#xff0c;营造更高效的办公环境。 1. 升级网络设备 采用性能更高的网络硬件是优化网络体验的重要基础。选择支持高吞吐量、低延迟的设备&#xff08;如企业…

力扣239.滑动窗口最大值

文章目录 一、前言二、单调队列 一、前言 力扣239.滑动窗口最大值 滑动窗口最大值&#xff0c;这道题给定一个数组&#xff0c;以及一个窗口的长度&#xff0c;这个窗口会往后滑动&#xff0c;直到数组最后一个元素。 要求每个滑动窗口的中的最大值。对于这道题&#xff0c;我…

mac 安装CosyVoice (cpu版本)

CosyVoice 介绍 CosyVoice 是阿里研发的一个tts大模型 官方项目地址&#xff1a;https://github.com/FunAudioLLM/CosyVoice.git 下载项目&#xff08;非官方&#xff09; git clone --recursive https://github.com/v3ucn/CosyVoice_for_MacOs.git 进入项目 cd CosyVoic…

Maven 安装配置(详细教程)

文章目录 一、Maven 简介二、下载 Maven三、配置 Maven3.1 配置环境变量3.2 Maven 配置3.3 IDEA 配置 四、结语 一、Maven 简介 Maven 是一个基于项目对象模型&#xff08;POM&#xff09;的项目管理和自动化构建工具。它主要服务于 Java 平台&#xff0c;但也支持其他编程语言…

Scala中的泛型特质

代码如下&#xff1a; package test41 //泛型特质 object test3 { //定义一个日志//泛型特质&#xff0c;X是泛型名称&#xff0c;可以更改。trait Logger[X] {val content: Xdef show():Unit }class FileLogger extends Logger[String] {override val content: String "…

ASP.NET |日常开发中读写XML详解

ASP.NET &#xff5c;日常开发中读写XML详解 前言一、XML 概述1.1 定义和结构1.2 应用场景 二、读取 XML 文件2.1 使用XmlDocument类&#xff08;DOM 方式&#xff09;2.2 使用XmlReader类&#xff08;流方式&#xff09; 三、写入 XML 文件3.1 使用XmlDocument类3.2 使用XmlWr…