大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
代理模式完成委托方交给的任务,委托方有一些任务自己不想完成,但是还需要要实现,则将该任务存放到协议中,由代理完成.但是代理并不会主动的执行任务,需要委托方通知代理。
专注于为中小企业提供成都做网站、网站制作服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业嘉鱼免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了千余家企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。
观察者模式本质上是一种发布-订阅模型,用以消除具有不同行为的对象之间的耦合,通过这一模式,不同对象可以协同工作,同时它们也可以被复用于其他地方Observer从Subject订阅通知,ConcreteObserver实现重现ObServer并将其重载其update方法。
单例模式可以保证App在程序运行中,一个类只有唯一个实例,从而做到节约内存。
在整个App程序中,这一份资源是共享的。
提供一个固定的实例创建方法。
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
代理模式:完成委托方的任务,需要声明代理对象和指定代理,相对于block,在需要传递参数的传值时优先考虑代理。
代理是一对一的关系(1个对象只能通知1个对象发生了什么事)。
应用场景:不同类之间的传值与回调。
观察者模式(通知机制,KVO机制):观察者模式本质上是一种发布-订阅模型,用以消除具有不同行为的对象之间的耦合,通过这一模式,不同对象可以协同工作。
通知是一对多的关系(1个通知可以发送给多个通知接受对象)。
应用场景:监听设备状态,自定义键盘时监听键盘的弹出和隐藏。
单例模式:可以保证App在程序运行中,一个类只有唯一个实例。
系统中的单例:UIApplication(应用程序实例)、NSNotificationCenter(消息中心)、NSFileManager(文件管理)、NSUserDefaults(应用程序设置)、NSURLCache(请求缓存)等。
应用场景:功能集中管理的模块可以封装为单例,方便外界调用。
策略模式:定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
MVC(Model View Controller):
model:数据层
view:视图层,负责页面展示
Controller:控制视图层与数据层的关联,
MVVM(model view viewModel):
Model: 数据层,不包含逻辑
View:与用户直接交互,只需处理触发事件后的转发,和告诉他如何显示
ViewModel:跟踪view的事件,处理model层传递的数据;公开方法、属性,让view保持最新的状态
iOS开发就是为装有iOS系统的设备完成应用软件或游戏软件的开发,ios开发的设计模式有代理模式、观察者模式、MVC模式、单例模式、策略模式和工厂模式。
KVO 的全称是 Key-Value Observing,俗称“键值观察/监听”,是苹果提供的一套事件通知机制,允许一个对象观察/监听另一个对象指定属性值的改变。当被观察对象属性值发生改变时,会触发 KVO 的监听方法来通知观察者。KVO 是在 MVC 应用程序中的各层之间进行通信的一种特别有用的技术。
KVO 和 NSNotification 都是 iOS 中观察者模式的一种实现。
KVO 可以监听单个属性的变化,也可以监听集合对象的变化。监听集合对象变化时,需要通过 KVC 的 mutableArrayValueForKey: 等可变代理方法获得集合代理对象,并使用代理对象进行操作,当代理对象的内部对象发生改变时,会触发 KVO 的监听方法。集合对象包含 NSArray 和 NSSet 。
先创建一个类,作为要监听的对象。
监听实现
KVO 主要用来做键值观察操作,想要一个值发生改变后通知另一个对象,则用 KVO 实现最为合适。斯坦福大学的 iOS 教程中有一个很经典的案例,通过 KVO 在 Model 和 Controller 之间进行通信。如图所示:
KVO 触发分为自动触发和手动触发两种方式。
如果是监听对象特定属性值的改变,通过以下方式改变属性值会触发 KVO:
如果是监听集合对象的改变,需要通过 KVC 的 mutableArrayValueForKey: 等方法获得代理对象,并使用代理对象进行操作,当代理对象的内部对象发生改变时,会触发 KVO。集合对象包含 NSArray 和 NSSet 。
普通对象属性或是成员变量使用:
NSArray 对象使用:
NSSet 对象使用:
observationInfo 属性是 NSKeyValueObserving.h 文件中系统通过分类给 NSObject 添加的属性,所以所有继承于 NSObject 的对象都含有该属性;
可以通过 observationInfo 属性查看被观察对象的全部观察信息,包括 observer 、 keyPath 、 options 、 context 等。
注册方法 addObserver:forKeyPath:options:context: 中的 context 可以传入任意数据,并且可以在监听方法中接收到这个数据。
context 作用:标签-区分,可以更精确的确定被观察对象属性,用于继承、 多监听;也可以用来传值。
KVO 只有一个监听回调方法 observeValueForKeyPath:ofObject:change:context: ,我们通常情况下可以在注册方法中指定 context 为 NULL ,并在监听方法中通过 object 和 keyPath 来判断触发 KVO 的来源。
但是如果存在继承的情况,比如现在有 Person 类和它的两个子类 Teacher 类和 Student 类,person、teacher 和 student 实例对象都对象的 name 属性进行观察。问题:
当 name 发生改变时,应该由谁来处理呢?
如果都由 person 来处理,那么在 Person 类的监听方法中又该怎么判断是自己的事务还是子类对象的事务呢?
这时候通过使用 context 就可以很好地解决这个问题,在注册方法中为 context 设置一个独一无二的值,然后在监听方法中对 context 值进行检验即可。
苹果的推荐用法:用 context 来精确的确定被观察对象属性,使用唯一命名的静态变量的地址作为 context 的值。可以为整个类设置一个 context ,然后在监听方法中通过 object 和 keyPath 来确定被观察属性,这样存在继承的情况就可以通过 context 来判断;也可以为每个被观察对象属性设置不同的 context ,这样使用 context 就可以精确的确定被观察对象属性。
context 优点:嵌套少、性能高、更安全、扩展性强。
context 注意点:
KVO 可以监听单个属性的变化,也可以监听集合对象的变化。监听集合对象变化时,需要通过 KVC 的 mutableArrayValueForKey: 等方法获得代理对象,并使用代理对象进行操作,当代理对象的内部对象发生改变时,会触发 KVO 的监听方法。集合对象包含 NSArray 和 NSSet 。(注意:如果直接对集合对象进行操作改变,不会触发 KVO。)
可以在被观察对象的类中重写 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 方法来控制 KVO 的自动触发。
如果我们只允许外界观察 person 的 name 属性,可以在 Person 类如下操作。这样外界就只能观察 name 属性,即使外界注册了对 person 对象其它属性的监听,那么在属性发生改变时也不会触发 KVO。
也可以实现遵循命名规则为 + (BOOL)automaticallyNotifiesObserversOfKey 的方法来单一控制属性的 KVO 自动触发,Key 为属性名(首字母大写)。
使用场景:
使用 KVO 监听成员变量值的改变;
在某些需要控制监听过程的场景下。比如:为了尽量减少不必要的触发通知操作,或者当多个更改同时具备的时候才调用属性改变的监听方法。
由于 KVO 的本质,重写 setter 方法来达到可以通知所有观察者对象的目的,所以只有通过 setter 方法或 KVC 方法去修改属性变量值的时候,才会触发 KVO,直接修改成员变量不会触发 KVO。
当我们要使用 KVO 监听成员变量值改变的时候,可以通过在为成员变量赋值的前后手动调用 willChangeValueForKey: 和 didChangeValueForKey: 两个方法来手动触发 KVO,如:
NSKeyValueObservingOptionPrior (分别在值改变前后触发方法,即一次修改有两次触发)的两次触发分别在 willChangeValueForKey: 和 didChangeValueForKey: 的时候进行的。
如果注册方法中 options 传入 NSKeyValueObservingOptionPrior ,那么可以通过只调用 willChangeValueForKey: 来触发改变前的那次 KVO,可以用于在属性值即将更改前做一些操作。
有时候我们可能会有这样的需求,KVO 监听的属性值修改前后相等的时候,不触发 KVO 的监听方法,可以结合 KVO 的自动触发控制和手动触发来实现。
例如:对 person 对象的 name 属性注册了 KVO 监听,我们希望在对 name 属性赋值时做一个判断,如果新值和旧值相等,则不触发 KVO,可以在 Person 类中如下这样实现,将 name 属性值改变的 KVO 触发方式由自动触发改为手动触发。
有些情况下我们想手动观察集合属性,下面以观察数组为例。
关键方法:
需要注意的是,根据 KVC 的 NSMutableArray 搜索模式:
有些情况下,一个属性的改变依赖于别的一个或多个属性的改变,也就是说当别的属性改了,这个属性也会跟着改变。
比如我们想要对 Download 类中的 downloadProgress 属性进行 KVO 监听,该属性的改变依赖于 writtenData 和 totalData 属性的改变。观察者监听了 downloadProgress ,当 writtenData 和 totalData 属性值改变时,观察者也应该被通知。以下有两种方法可以解决这个问题。
以上两个方法可以同时存在,且都会调用,但是最终结果会以 keyPathsForValuesAffectingValueForKey: 为准。
以上方法在观察集合属性时就不管用了。例如,假如你有一个 Department 类,它有一个装有 Employee 类的实例对象的数组,Employee 类有 salary 属性。你希望 Department 类有一个 totalSalary 属性来计算所有员工的薪水,也就是在这个关系中 Department 的 totalSalary 依赖于所有 Employee 实例对象的 salary 属性。以下有两种方法可以解决这个问题。
有时候我们难以避免多次注册和移除相同的 KVO,或者移除了一个未注册的观察者,从而产生可能会导致 Crash 的风险。
三种解决方案: 黑科技防止多次添加删除KVO出现的问题
我们在对象添加监听之前分别打印对象类型
我们看到,添加监听后,使用 object_getClass 方法获取model类型时获取到的是 NSKVONotifying_DJModel 。
这里就产生了几个问题:
从打印结果可以看出, NSKVONotifying_DJModel 是 DJModel 的子类,说明我们添加了监听之后动态创建了一个 DJModel 的子类 NSKVONotifying_DJModel ,并将对象 DJModel 的类型更改为了 NSKVONotifying_DJModel 。
我们从源码看出,实例对象调用 class 方法会返回 isa 指针,类对象调用 class 方法会返回自己,通过 object_getClass 方法获取对象的类型也会返回 isa 指针。从源码上看model对象添加监听之后使用 class 和使用 object_getClass 方法获取到的类型应该是一样的,但是这里却不同,我们猜测在添加了监听之后在 NSKVONotifying_DJModel 中重写了 class 方法。
我们打印一下添加监听前后 class 方法的 IMP 地址来确认是否重写了 class 方法。
从打印结果可以看出,添加监听之后 class 方法的地址改变了,这验证了我们之前的猜想, NSKVONotifying_DJModel 类中重写了 class 方法。
我们监听对象时调用了 set 方法,我们对监听前后的 set 方法单独分析。
我们再添加监听前后分别打印 setName 方法的 IMP 地址。
通过打印结果可以看出 setName 方法也在 NSKVONotifying_DJModel 中被重写了,我们再使用lldb来看下 setName 具体是什么
第一个地址打印的是添加监听前 setName 方法的 IMP 地址,第二个打印的是添加监听后 setName 方法的 IMP 地址。
这里看出添加监听前 setName 对应的具体方法就是 setName ,但是添加监听后, setName 对应的鸡头方法却变成了 _NSSetObjectValueAndNotify 函数。
下面我们就来研究一下 _NSSetObjectValueAndNotify 函数。
从上面与KVO相关的方法中我们可以看出,每一种数据类型都对应了一个 setXXXValueAndNotify 函数。
不过这些函数的具体实现没有公布,所以内部构造这里还是不清楚。
但是我们知道,在调用 `setXXXValueAndNotify 函数的过程中会调用另外两个方法。
测试后得出了以下几个结论:
我们还可以利用这两个方法手动触发 observeValueForKeyPath 方法:
所以我们判断在 _NSSetObjectValueAndNotify 函数内部,在调用原来的 set 方法之前插入了 willChangeValueForKey 方法,在调用原来的 set 方法之后插入了 didChangeValueForKey 方法,并根据初始化时的枚举值决定调用 observeValueForKeyPath 的时机。
(1)添加监听时,会动态创建一个监听对象类型的子类,并将监听对象的 isa 指针指向新的子类。
(2)子类中重写了 class 和监听属性的 set 方法。
(3)重写 class 方法是为了不将动态创建的类型暴露出来。
(4)重写 set 方法是将 set 方法的具体实现替换成了与属性类型相关的 __NSSetXXXValueAndNotify 函数。
(5)在 __NSSetXXXValueAndNotify 函数内部在 set 方法前后分别插入了 willChangeValueForKey 和 didChangeValueForKey 这两个方法。
(6)根据添加监听时的枚举值决定调用 observeValueForKeyPath 的具体时机。