通知
概要
- 观察者和被观察者都无需知晓对方,只需要通过标记在
NSNotificationCenter
中找到监听该通知所对应的类,从而调用该类的方法。 - 并且在
NSNotificationCenter
中,观察者可以只订阅某一特定的通知,并对其做出相应操作,而不用对某一个类发的所有通知都进行更新操作。 NSNotificationCenter
对观察者的调用不是随机的,而是遵循注册顺序一一执行的,并且在该线程内是同步的
自定义实现通知方法:
通知的原理
通知机制的核心是一个与线程关联的单例对象叫通知中心(NSNotificationCenter
)。通知中心发送通知给观察者是同步的,也可以用通知队列(NSNotificationQueue
)异步发送通知。
数据结构
通知是一个单例。
objectivec
static NSNotificationCenter *default_center = nil;
+ (NSNotificationCenter*) defaultCenter
{
return default_center;
}
通知中心NSNotificationCenter
的单例类中存放着两个表,一个存储所有注册通知信息的表的结构体,一个保存单个注册信息的节点结构体。
objectivec
typedef struct NCTbl {
Observation *wildcard; // 添加观察者时既没有传入 NotificationName ,又没有传入object,就会加在这个链表上,它里边的观察者可以接收所有的系统通知
GSIMapTable nameless; // 添加观察者时没有传入 NotificationName 的表
GSIMapTable named; // 添加观察者时传入了 NotificationName 的表
} NCTable
objectivec
typedef struct Obs {
id observer; // 观察者对象
SEL selector; // 方法信息
struct Obs *next; // 指向下一个节点
int retained; /* Retain count for structure. */
struct NCTbl *link; /* Pointer back to chunk table */
} Observation;
解释一下NCTbl中三个表的作用:
- name表:
作用:
管理通知名称和对应的观察者。
结构:
Key:通知名称(NotificationName),用于区分不同类型的通知。
Value:一个链表,保存所有注册了该通知名称的观察者。每个观察者可能会绑定一个特定的对象(object)。
对象 (object) 的作用:
当注册观察者时,可以指定一个对象(object),只监听该对象发出的通知。
如果 object 参数为 nil,系统会自动生成一个特殊的 nil 键,这个键对应的链表中保存了所有没有指定 object 的观察者。这样,nil 键可以用来管理所有未绑定特定对象的观察者。
- nameless 表
作用:管理没有指定通知名称的观察者。
结构:
Key:object,观察者对象。
Value:链表,保存所有注册了该对象的观察者。
nameless 表没有通知名称的约束,仅通过 object 来管理观察者。它的作用是处理那些未指定通知名称的情况,即所有未绑定特定通知名称的观察者。

- wildcard 表
作用:管理能够接收所有通知的观察者。
结构:
Value:链表,保存所有可以接收任何通知的观察者。
wildcard 表既没有通知名称 (NotificationName),也没有对象 (object)。它的链表存储了所有可以接收任意通知的观察者。这种设置通常用于管理那些对所有通知感兴趣的观察者。
 
为什么 wildcard 需要使用链表?
动态插入和删除:
链表允许在运行时动态插入和删除节点,这对于通知系统来说非常重要,因为观察者可能在不同的时间点注册和注销。
管理多个观察者:
一个 wildcard 链表可以存储多个观察者,这些观察者对所有类型的通知感兴趣。因此,链表是一种有效的数据结构来管理这些观察者。
高效的遍历:
链表支持顺序遍历操作,这对于触发通知时遍历所有注册的观察者非常有用。
实现原理
使用方法addObserver:selector:name:object添加观察者流程总结:
- 首先会根据传入的参数实例化一个 Observation,Observation 对象保存了观察者对象,接收到通知观察者所执行的方法,以及下一个 Observation 对象的地址。
- 根据是否传入 NotificationName 选择操作 Named Table 还是 Nameless Table。
- 若传入了 NotificationName,则会以 NotificationName 为 key 去查找对应的 Value,若找到 value,则取出对应的 value;若未找到对应的 value,则新建一个 table,然后将这个 table 以 NotificationName 为 key 添加到 Named Table 中。
- 若在保存 Observation 的 table 中,以 object(接收通知的对象) 为 key 取对应的链表。若找到了则直接在链接末尾插入之前实例化好的 Observation;若未找到则以之前实例化好的 Observation 对象作为头节点插入进去。
- 若既没有 NotificationName 也没有 object,那么就加在 wildcard 链表中
使用方法postNotification发送通知流程总结:
- 首先会创建一个数组 observerArray 用来保存需要通知的 observer。
- 遍历 wildcard 链表(为了使接收所有通知的观察者也接收到消息),将 observer 添加到 observerArray 数组中。
- 若存在 object(接收通知的对象),在 nameless table 中找到以 object 为 key 的链表,然后遍历找到的链表,将 observer 添加到 observerArray 数组中。
- 若存在 NotificationName,在 named table 中以 NotificationName 为 key 找到对应的 table,然后再在找到的 table 中以 object 为 key 找到对应的链表,遍历链表,将 observer 添加到 observerArray 数组中。如果 object 不 为nil,则以 nil 为 key 找到对应的链表,遍历链表,将 observer 添加到 observerArray 数组中。
- 至此所有关于当前通知的 observer(wildcard + nameless + named)都已经加入到了数组 observerArray 中。遍历 observerArray 数组,取出其中 的observer 节点(包含了观察者对象和 selector)其中调用观察者的方法,调用形式如下:
objc
[o->observer performSelector: o->selector withObject: notification];
移除通知的流程总结:
- 若 NotificationName 和 object 都为 nil,则清空 wildcard 链表。
- 若 NotificationName 为 nil,遍历 named table,若 object 为 nil,则清空 named table,若 object 不为 nil,则以 object 为 key 找到对应的链表,然后清空链表。在 nameless table 中以 object 为 key 找到对应的 observer 链表,然后清空,若 object 也为 nil,则清空 nameless table。
- 若 NotificationName 不为nil,在 named table 中以 NotificationName 为 key 找到对应的 table,若 object 为 nil,则清空找到的 table,若 object 不为 nil,则以 object 为 key 在找到的 table 中取出对应的链表,然后清空链表。
通知的发送时同步的,还是异步的?发送消息与接收消息的线程是同一个线程么?
通知中心发送通知给观察者是同步的,也可以用通知队列(NSNotificationQueue
)异步发送通知。
NSNotificationQueue(通知队列)
NSNotificationQueue
是notification Center
的缓冲池。如果我们使用普通的postNotification
这种方法来发送通知,那么这个通知就会直接发送到notification Center
,notification Center
则会直接将其发送给注册了该通知的观察者。但是如果我们使用NSNotificationQueue
就不一样了,通知不是直接发送给notification Center
,而是先发送给NSNotificationQueue
,然后由NSNotificationQueue
决定在当前runloop
结束或者空闲的时候转发给notification Center
,再由notification
转发给注册的观察者。通过NSNotificationQueue
,可以合并重复的通知,以便只发送一个通知。
NSNotificationQueue
遵循FIFO 的顺序,当一个通知移动到NSNotificationQueue
的最前面,它就被发送给notification Center
,然后notification Center
再将通知转发给注册了该通知的监听者。
每一个线程都有一个默认的NSNotificationQueue
,这个NSNotificationQueue
和通知中心联系在一起。当然我们也可以自己创建NSNotificationQueue
,可以为一个线程创建多个NSNotificationQueue
。
单例模式
系统为我们提供的单例类有:
UIApplication(应用程序实例类)
NSNotificationCenter(消息中心类)
NSFileManager(文件管理类)
NSUserDefaults(应用程序设置)
NSURLCache(请求缓存类)
NSHTTPCookieStorage(应用程序cookies池)
单例的GCD写法:
objectivec
#import "AModel.h"
static id _instance = nil;
@implementation AModel
+ (id)sharInstance {
return [[self alloc] init];
}
//将我们原先写在自定义初始化方法中的内容写到allocWithZone中
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
//重写 copyWithZone:方法,避免实例对象的 copy 操作导致创建新的对象
-(instancetype)copyWithZone:(NSZone *)zone
{
//由于是对象方法,说明可能存在_instance对象,直接返回即可
return _instance;
}
@end
代理模式
协议是多个类(或者说对象)之间协商的一个公共接口,提供一系列方法的声明给类们使用;而代理是协议的一个典型应用机制。
代理模式的核心思想是通过代理接口分离使用者和服务提供者,降低了模块之间的耦合度。在实际中灵活使用代理模式可以让我们写出更优质的模块结构的代码。
代理就是自己不想做的事情,让代理做。(类似老板和秘书)
代理设计模式的场合:
- 当对象A发生了一些行为,想告知对象B(让对象B成为对象A的代理对象)
- 对象B想监听对象A的一些行为(让对象B成为对象A的代理对象)
- 当对象A无法处理某些行为的时候,想让对象B帮忙处理(让对象B成为对象A的代理对象)
协议的编写规范:
- 一般情况下, 当前协议属于谁, 我们就将协议定义到谁的头文件中
- 协议的名称一般以它属于的那个类的类名开头, 后面跟上protocol或者delegate
- 协议中的方法名称一般以协议���名称protocol之前的作为开头
- 一般情况下协议中的方法会将触发该协议的对象传递出去
- 一般情况下一个类中的代理属于的名称叫做 delegate
- 当某一个类要成为另外一个类的代理的时候, 一般情况下在.h中用@protocol 协议名称;告诉当前类 这是一个协议.在.m中用#import真正的导入一个协议的声明
注意:
- 协议不能声明成员变量,不能写实现
- 只要父类遵守了某个协议,那么子类也遵守
- 协议可以遵守协议,一个协议遵守了另一个协议,就可以拥有另一份协议中的方法声明
协议中有2个关键字可以控制方法是否要实现(默认是@required,在大多数情况下,用途在于程序员之间的交流)
- @required:这个方法必须要实现(若不实现,编译器会发出警告)
- @optional:这个方法不一定要实现
代理的原理
在iOS中代理的本质就是代理对象内存的传递和操作,我们在委托类设置代理对象后,实际上只是用一个id类型的指针将代理对象进行了一个弱引用。委托方让代理方执行操作,实际上是在委托类中向这个id类型指针指向的对象发送消息,而这个id类型指针指向的对象,就是代理对象。
这里,将代理对象进行弱引用的作用是为了避免循环引用
代理的循环引用
比如以下代码:B强引用A,而A的delegate属性指向B,这里的delegate是用strong修饰的,所以A也会强引用B,这是一个比较典型的循环引用样例。所以要将代理delegate改为弱引用weak。
objc
@protocol ClssADelegate
- (void)eat;
@end
@interface ClassA : UIViewController
@property (nonatomic, strong) id delegate;
@end
//ClassB:
@interface ClassB ()
@property (nonatomic, strong) ClassA *classA;
@end
@implementation ClassB
- (void)viewDidLoad {
[super viewDidLoad];
self.classA = [[ClassA alloc] init];
self.classA.delegate = self;
}


设计模式总结
KVO/通知 -------> 观察者模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
优势:解耦合
接口隔离原则、开放-封闭原则
KVC --------> KVC模式
单例模式
利用应用程序只有一个该类的实例对象这一特殊性来实现资源共享。
优势:使用简单,延时求值,易于跨模块
劣势:这块内存知道程序退出时才能释放
单一职责原则
举例:[UIApplication sharedApplication]。
代理模式
委托方将不想完成的任务交给代理方处理,并且需要委托方通知代理方才能处理。
优势: 解耦合
开放-封闭原则
举例:tableview的数据源和代理
策略模式
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
优势:使算法的变化独立于使用算法的用户
接口隔离原则、多用组合,少用继承、针对接口编程,而非实现
举例:账号密码输入格式的判断、NSArray的sortedArrayUsingSelector等等
MVC模式
将程序书写分为三层,分别为模型、视图、控制器,每层都有各自的职责完成各自的工作。
优势: MVC模式使系统,层次清晰,职责分明,易于维护
对扩展开放-对修改封闭
MVVM模式
用于解决MVC模式下C层代码冗杂的情况(过多的网络请求以及业务逻辑处理)而出现的MVVM模式,其相比于MVC多了一层ViweModel(业务处理和数据转化)层,专门用于处理数据。
当功能简单时,MVVM反而会增加很多代码,所以对于简单的功能,MVC更加的方便。
MVP模式
MVP是MVC模式派生出来的,常用于创建用户界面,在MVP中所有页面显示逻辑都会被推送到presenter(中间人)中,它主要实现程序逻辑控制、数据的检索,并格式化数据以便在视图中显示,把Model和View完全的进行分离,MVP模式的V是View + ViewController。
三种工厂模式
通过给定参数来返回对应的实例,完全对用户隐藏其实现的原理。
优势:易于替换,面向抽象编程
依赖倒置原则