当前位置: 首页 > news >正文

福建设备公司网站天津中小企业建设网站

福建设备公司网站,天津中小企业建设网站,wordpress新增目录,天津网站开发学校通知 概要 观察者和被观察者都无需知晓对方#xff0c;只需要通过标记在NSNotificationCenter中找到监听该通知所对应的类#xff0c;从而调用该类的方法。并且在NSNotificationCenter中#xff0c;观察者可以只订阅某一特定的通知#xff0c;并对齐做出相应操作#xf…通知 概要 观察者和被观察者都无需知晓对方只需要通过标记在NSNotificationCenter中找到监听该通知所对应的类从而调用该类的方法。并且在NSNotificationCenter中观察者可以只订阅某一特定的通知并对齐做出相应操作而不用对某一个类发的所有通知都进行更新操作。NSNotificationCenter对观察者的调用不是随机的而是遵循注册顺序一一执行的并且在该线程内是同步的 使用步骤 在要传递参数的地方发送通知给通知中心 [[NSNotificationCenter defaultCenter] postNotificationName:temp object:nil userInfo:{content: self.myTextField.text}];在接收参数的地方注册通知并实现定义方法 [[NSNotificationCenter defaultCenter] addObserver:self selector:selector(Notificate:) name:temp object:nil];在不需要通知的时候移除通知 [[NSNotificationCenter defaultCenter] removeObserver:self];自定义实现通知功能 首先创建一个自定义文件NotificationCenter继承自NSObject用作自定义通知的类因为通知是可以实现多对多关系的所以我们在这个类中还需要定义一个可变的字典属性用来存储注册的通知。又因为注册的通知数据需要一直保存下来所以我们使用单例来完成这一操作保证我们在想要访问已经注册的通知的时候其数据是存在的。 interface NotificationCenter ()// 因为通知是多对多的关系所以这里定义一个可变字典用来存储对应关系 property (nonatomic, strong) NSMutableDictionary *classDictionary;end implementation NotificationCenter// 实现默认的通知中心是个单例防止其自动销毁(instancetype)defaultCenter {// 定义一个锁static dispatch_once_t onceToken;// 创建通知中心的单例同时初始化其中数据static NotificationCenter *notificationCenter nil;dispatch_once(onceToken, ^{notificationCenter [NotificationCenter new];notificationCenter.classDictionary [NSMutableDictionary dictionary];});return notificationCenter; } end因为我们使用通知的目的就是为了传递参数供别的类来使用所以我们这里再定义一个专门保存通知的类ZJQNotification其中包含通知的必要信息通知的名称、通知传递的参数信息、以及一个id类型的object。因为这是需要给外界透露的接口外界不能对其进行写操作所以为只读readOnly属性除了这三个参数当然还得需要一个快速的初始化类的方法 // 通知类用来保存通知及其参数 interface ZJQNotification : NSObject// 对外有三个只读属性 property (readonly, copy) NSNotificationName name; // 通知名 property (nullable, readonly, copy) NSDictionary *userInfo; // 参数信息 property (nullable, readonly, retain) id object; // 接收通知的对象// 快速初始化类参数的方法 - (instancetype)initWithName:(NSString *)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo; end该通知类中的三个参数是对外只读的内部可以进行修改所以我们在内部重写属性实现可读可写同时实现该类快速初始化方法 interface ZJQNotification() // 在内部可修改这三个属性 property (nonatomic, copy) NSString *name; // 通知名 property (nonatomic, copy) NSDictionary *userInfo; // 参数信息 property (nonatomic, retain) id object; // 接收通知的对象end// 通知类用来保存通知及其参数 implementation ZJQNotification// 快速初始化类参数的方法 - (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo {ZJQNotification *notification [ZJQNotification new];notification.name name;notification.object object;notification.userInfo userInfo;return notification; }end接着我们在之前定义的通知中心ZJQNotificationCenter中再定义通知常用的对外开放的接口注册通知、发送通知、删除通知 // 通知中心 interface ZJQNotificationCenter : NSObject// 默认的通知中心(instancetype)defaultCenter;// 添加通知中心 - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;// 发送通知 - (void)postNotificationName:(NSString *)aName object:(nullable id)anObject; - (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;// 移除通知 - (void)removeObserver:(id)observer; - (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;end接着就一个一个实现首先是添加通知的逻辑我们在添加、调用通知的时候需要知道三个必要信息一个是创建该通知的实例类一个是通知要调用的自定义方法以及一个object。所以我们需要将这三个信息保存起来一起保存到刚刚我们创建的通知中心单例的classDictionary属性中为了方便我们后续的查找所以该属性的key我们使用注册通知时通知的名称标识因为一个通知可能对应多个类所以这里我们value使用一个数组该数组中的每个变量又是一个字典其中的内容就是上边所说的三个参数 // 添加通知中心 - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject {// 以通知名为key来设置value并保存在通知中心// 从通知中心获取该通知名的所有注册信息NSMutableArray *array self.classDictionary[aName];// 如果通知中心没有储存过该通知名的信息就新建if (!array) {array [NSMutableArray array];}// 向数组中添加传递过来的信息[array addObject:{class: observer, selector: NSStringFromSelector(aSelector), object: anObject ? : [NSNull null]}];// 将更新过的数组重新添加到通知中心[self.classDictionary setObject:array forKey:aName]; }发送通知的逻辑其实就是通过发送过来的通知名标识aName在classDictionary属性中查找对应的通知信息然后依次使用objc_msgSend发送消息从而达到传值的目的这里我们用系统封装好的NSInvocation类进行调用 // 发送通知 - (void)postNotificationName:(NSString *)aName object:(nullable id)anObject {[self postNotificationName:aName object:anObject userInfo:nil]; }- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo {// 通过发送通知的通知名找到在通知中心保存的该通知名的所有注册的类的信息NSMutableArray *array self.classDictionary[aName];// 通过获取到的信息中的方法名和类信息来逐一使用msgSend发送消息给目标类for (NSDictionary *mapDictionary in array) {// 当mapDictionary中的object与anObject一致或者接收者为null时才调用方法确保信息无误不会发错if ([mapDictionary[object] isEqual:anObject] || [mapDictionary[object] isKindOfClass:[NSNull class]]) {// NSInvocation;用来包装方法和对应的对象它可以存储方法的名称对应的对象对应的参数,/*NSMethodSignature签名再创建NSMethodSignature的时候必须传递一个签名对象签名对象的作用用于获取参数的个数和方法的返回值*/// 创建签名对象的时候不是使用NSMethodSignature这个类创建而是方法属于谁就用谁来创建创建当前访问信息的class类中的selector方法的签名NSMethodSignature *signature [[mapDictionary[class] class] instanceMethodSignatureForSelector:NSSelectorFromString(mapDictionary[selector])];// 1、通过创建的方法签名创建NSInvocation对象NSInvocation *invocation [NSInvocation invocationWithMethodSignature:signature];// 给NSInvocation对象设置实现该selector方法的类信息invocation.target mapDictionary[class];// 给NSInvocation对象设置调用的方法的信息invocation.selector NSSelectorFromString(mapDictionary[selector]);// 包装要使用通知传递的信息通过消息转发机制来实现跨界面传值// 注意设置参数的索引时不能从0开始因为0已经被self占用1已经被_cmd占用ZJQNotification *notification [[ZJQNotification alloc] initWithName:aName object:anObject userInfo:aUserInfo];[invocation setArgument:notification atIndex:2];/* 第一个参数需要给指定方法传递的值第一个参数需要接收一个指针也就是传递值的时候需要传递地址 */// 第二个参数需要给指定方法的第几个参数传值// 2、调用NSInvocation对象的invoke方法// 只要调用invocation的invoke方法就代表需要执行NSInvocation对象中制定对象的指定方法并且传递指定的参数[invocation invoke];}} }移除通知时通过给定的通知的信息在classDictionary中删除对应的数据即可 // 移除通知 // 找到对应的类在classDictionary中删除即可 // 全部移除 - (void)removeObserver:(id)observer {// 创建一个临时字典存放删除完了的数据最后用这个字典更新classDictionary数据NSMutableDictionary *tempDictionary [NSMutableDictionary dictionary];// 枚举遍历[self.classDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {NSMutableArray *tempArray [obj mutableCopy];for (NSDictionary * mapDictionary in obj) {// 判断该类是不是observer的类是就删除if ([mapDictionary[class] isKindOfClass:[observer class]]) {[tempArray removeObject:mapDictionary];}}// 删除完了添加到tempDictionary中[tempDictionary setObject:tempArray forKey:key];}];// 更新classDictionary数据self.classDictionary tempDictionary; }// 根据通知名移除 - (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject {// 获取该通知名注册的所有信息NSMutableArray *array self.classDictionary[aName];NSMutableArray *tempArray [array mutableCopy];for (NSDictionary *mapDictionary in array) {// 判断该类是不是observer的类if ([mapDictionary[class] isKindOfClass:[observer class]]) {// 如果该通知的接收方相等或者接收方不存在再删除确保不会误删if ([mapDictionary[object] isEqual:anObject] || !anObject) {[tempArray removeObject:mapDictionary];}}}// 更新classDictionary数据[self.classDictionary setValue:tempArray forKey:aName]; }通知原理 通知机制的核心是一个与线程关联的单例对象叫通知中心NSNotificationCenter。通知中心发送通知给观察者是同步的也可以用通知队列NSNotificationQueue异步发送通知。 因为苹果的通知源码没有开源所以我们看看GNUStep的源码。 数据结构 单例类 从我们之前使用通知的流程和代码来看通知其实就是一个单例方便随时访问。 static NSNotificationCenter *default_center nil; (NSNotificationCenter*) defaultCenter {return default_center; }NSNotificationCenter消息中心 这个单例类中主要定义了两个表一个存储所有注册通知信息的表的结构体一个保存单个注册信息的节点结构体。 typedef struct NCTbl {Observation *wildcard; // 添加观察者时既没有传入 NotificationName 又没有传入object就会加在这个链表上它里边的观察者可以接收所有的系统通知GSIMapTable nameless; // 添加观察者时没有传入 NotificationName 的表GSIMapTable named; // 添加观察者时传入了 NotificationName 的表 } NCTable保存了观察者的信息 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;以及一些宏定义 #define TABLE ((NCTable*)_table) #define WILDCARD (TABLE-wildcard) #define NAMELESS (TABLE-nameless) #define NAMED (TABLE-named) #define LOCKCOUNT (TABLE-lockCount)named表 在 named 表中NotifcationName 作为表的 key因为我们在注册观察者的时候是可以传入一个参数 object 用于只监听指定该对象发出的通知并且一个通知可以添加多个观察者所以还需要一张表来保存 object 和 Observer 的对应关系。这张表的是 key、Value 分别是以 object 为 KeyObserver 为 value。用了链表这种数据结构实现保存多个观察者的情况。 在实际开发过程中 object 参数我们经常传 nil这时候系统会根据 nil 自动生成一个 key相当于这个 key 对应的 value链表保存的就是当前通知传入了 NotificationName 没有传入 object 的所有观察者。 nameless表 上边说了 named 表那么 nameless 表就不难想象了他注册时没有 NotificationName 即没有了最外边一层键值对的约束了其中就只有 object 和 Observation 所对应的键值对结构了 wildcard表 这个表既没有 NotificationName 也没有 object 了所以他就会在 nameless基础上在脱去一层键值对那么它就只剩下一个链表了该练表存储了可以接收所有通知的类的信息 添加观察者 使用方法addObserver:selector:name:object添加观察者根据 GNUStep 的源码分析 - (void) addObserver: (id)observerselector: (SEL)selectorname: (NSString*)nameobject: (id)object {Observation *list;Observation *o;GSIMapTable m;GSIMapNode n; // observer为空时的报错if (observer nil)[NSException raise: NSInvalidArgumentExceptionformat: Nil observer passed to addObserver ...]; // selector为空时的报错if (selector 0)[NSException raise: NSInvalidArgumentExceptionformat: Null selector passed to addObserver ...]; // observer不能响应selector时的报错if ([observer respondsToSelector: selector] NO){[NSException raise: NSInvalidArgumentExceptionformat: [%-%] Observer % does not respond to selector %,NSStringFromClass([self class]), NSStringFromSelector(_cmd),observer, NSStringFromSelector(selector)];} // 给表上锁lockNCTable(TABLE); // 建立一个新Observation存储这次注册的信息o obsNew(TABLE, selector, observer);// 如果有nameif (name) {// 在named表中 以name为key寻找valuen GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);// named表中没有找到对应的valueif (n 0) {// 新建一个表m mapNew(TABLE);// 由于这是对给定名称的首次观察因此我们对该名称进行了复制以便在map中无法对其进行更改来自GNUStep的注释name [name copyWithZone: NSDefaultMallocZone()];// 新建表作为name的value添加在named表中GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);GS_CONSUMED(name)} else { //named表中有对应的value// 取出对应的valuem (GSIMapTable)n-value.ptr;}// 将observation添加到正确object的列表中// 获取添加完后name对应的value的object对应的链表n GSIMapNodeForSimpleKey(m, (GSIMapKey)object);// n是object的valueif (n 0) { // 如果object对应value没有数据o-next ENDOBS;// 将o作为object的value链表的头结点插入GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);} else { // 如果有object对应的value那么就直接添加到原练表的尾部// 在链表尾部加入olist (Observation*)n-value.ptr;o-next list-next;list-next o;}// 这个else if 就是没有name有object的Observation对object进行的操作相同} else if (object) {// 直接获取object对应的value链表n GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);if (n 0) { // 这个对应链表如果没有数据o-next ENDOBS;// 将该observation作为头节点插入GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);} else { // 有数据将obsevation直接插在原链表的后面list (Observation*)n-value.ptr;o-next list-next;list-next o;}} else {// 既没有name又没有object就加在WILDCARD链表中o-next WILDCARD;WILDCARD o;}// 解锁unlockNCTable(TABLE); }流程总结 1.首先会根据传入的参数实例化一个 ObservationObservation 对象保存了观察者对象接收到通知观察者所执行的方法以及下一个 Observation 对象的地址。2.根据是否传入 NotificationName 选择操作 Named Table 还是 Nameless Table。3.若传入了 NotificationName则会以 NotificationName 为 key 去查找对应的 Value若找到 value则取出对应的 value若未找到对应的 value则新建一个 table然后将这个 table 以 NotificationName 为 key 添加到 Named Table 中。4.若在保存 Observation 的 table 中以 object 为 key 取对应的链表。若找到了则直接在链接末尾插入之前实例化好的 Observation若未找到则以之前实例化好的 Observation 对象作为头节点插入进去。5.若既没有 NotificationName 也没有 object那么就加在 WILDCARD 链表中 发送通知 使用方法postNotification: postNotificationName:object:userInfo或者postNotificationName:object:发送通知后者默认userInfo为nil同样使用GNUStep源码进行分析 - (void) postNotification: (NSNotification*)notification {if (notification nil) {[NSException raise: NSInvalidArgumentExceptionformat: Tried to post a nil notification.];}[self _postAndRelease: RETAIN(notification)]; }- (void) postNotificationName: (NSString*)nameobject: (id)object {[self postNotificationName: name object: object userInfo: nil]; }- (void) postNotificationName: (NSString*)nameobject: (id)objectuserInfo: (NSDictionary*)info {GSNotification *notification;notification (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());notification-_name [name copyWithZone: [self zone]];notification-_object [object retain];notification-_info [info retain];[self _postAndRelease: notification]; }我们发现经典的源码书写最终都只会调用 _postAndRelease:方法。不同的是postNotification:方法外部直接传了一个NSNotification对象其他两个方法都是内部进行了处理包装 成为了一个NSNotification对象我们再看看_postAndRelease:方法做了什么 - (void) _postAndRelease: (NSNotification*)notification {Observation *o;unsigned count;NSString *name [notification name];id object;GSIMapNode n;GSIMapTable m;GSIArrayItem i[64];GSIArray_t b;GSIArray a b;// name为空的报错注册时可以注册无名注册无名就等于说是所有的通知都能接收但是发送通知时不可以if (name nil) {RELEASE(notification);[NSException raise: NSInvalidArgumentExceptionformat: Tried to post a notification with no name.];}object [notification object];GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);lockNCTable(TABLE);// 查找所有未指定name或object的观察者加在a数组中即将wildcard表中的数据都加在新建链表中for (o WILDCARD purgeCollected(WILDCARD); o ! ENDOBS; o o-next){GSIArrayAddItem(a, (GSIArrayItem)o);}// 查找与通知的object相同但是没有name的观察者加在a数组中if (object) {// 在nameless中找object对应的数据节点n GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);if (n ! 0) { // 将其加入到新建链表中o purgeCollectedFromMapNode(NAMELESS, n);while (o ! ENDOBS) {GSIArrayAddItem(a, (GSIArrayItem)o);o o-next;}}}// 查找name的观察者但观察者的非零对象与通知的object不匹配时除外加在a数组中if (name) {// 先匹配namen GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));if (n) { // m指向name匹配到的数据m (GSIMapTable)n-value.ptr;} else {m 0;}if (m ! 0) { // 如果上述name查找到了数据// 首先查找与通知的object相同的观察者n GSIMapNodeForSimpleKey(m, (GSIMapKey)object);if (n ! 0) { // 找到了与通知的object相同的观察者就加入到新建链表中o purgeCollectedFromMapNode(m, n);while (o ! ENDOBS) {GSIArrayAddItem(a, (GSIArrayItem)o);o o-next;}}if (object ! nil) {// 接着是没有object的观察者都加在新建链表中n GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);if (n ! 0) { // 如果没有object并且有数据就把其中的数据加到新建链表中o purgeCollectedFromMapNode(m, n);while (o ! ENDOBS) {GSIArrayAddItem(a, (GSIArrayItem)o);o o-next;}}}}}unlockNCTable(TABLE);// 发送通知给之前新建链表中的所有数据count GSIArrayCount(a);while (count-- 0) {o GSIArrayItemAtIndex(a, count).ext;if (o-next ! 0) {NS_DURING {// 给observer发送selector让其处理[o-observer performSelector: o-selectorwithObject: notification];}NS_HANDLER {BOOL logged;// 尝试将通知与异常一起报告但是如果通知本身有问题我们只记录异常。NS_DURINGNSLog(Problem posting %: %, notification, localException);logged YES;NS_HANDLERlogged NO;NS_ENDHANDLERif (NO logged){ NSLog(Problem posting notification: %, localException);} }NS_ENDHANDLER}}lockNCTable(TABLE);GSIArrayEmpty(a);unlockNCTable(TABLE);RELEASE(notification); }流程总结 1.首先会创建一个数组 observerArray 用来保存需要通知的 observer。2.遍历 wildcard 链表将 observer 添加到 observerArray 数组中。3.若存在 object在 nameless table 中找到以 object 为 key 的链表然后遍历找到的链表将 observer 添加到 observerArray 数组中。4.若存在 NotificationName在 named table 中以 NotificationName 为 key 找到对应的 table然后再在找到的 table 中以 object 为 key 找到对应的链表遍历链表将 observer 添加到 observerArray 数组中。如果 object 不 为nil则以 nil 为 key 找到对应的链表遍历链表将 observer 添加到 observerArray 数组中。5.至此所有关于当前通知的 observerwildcard nameless named都已经加入到了数组 observerArray 中。遍历 observerArray 数组取出其中 的observer 节点包含了观察者对象和 selector其中调用观察者的方法调用形式如下 [o-observer performSelector: o-selector withObject: notification];移除通知 GNUStep源码 - (void) removeObserver: (id)observer {if (observer nil)return;[self removeObserver: observer name: nil object: nil]; }- (void) removeObserver: (id)observername: (NSString*)nameobject: (id)object {// 当其要移除的信息都为空时直接返回if (name nil object nil observer nil)return;lockNCTable(TABLE);// name和object都为nil就在wildcard链表里删除对应observer的注册信息if (name nil object nil) {WILDCARD listPurge(WILDCARD, observer);}// name为空时if (name nil) {GSIMapEnumerator_t e0;GSIMapNode n0;// 首先尝试删除为此object对应的所有命名项目// 在named表中e0 GSIMapEnumeratorForMap(NAMED);n0 GSIMapEnumeratorNextNode(e0);while (n0 ! 0) {GSIMapTable m (GSIMapTable)n0-value.ptr;NSString *thisName (NSString*)n0-key.obj;n0 GSIMapEnumeratorNextNode(e0);if (object nil) { // 如果object为空直接清除named表// 清空named表GSIMapEnumerator_t e1 GSIMapEnumeratorForMap(m);GSIMapNode n1 GSIMapEnumeratorNextNode(e1);while (n1 ! 0) {GSIMapNode next GSIMapEnumeratorNextNode(e1);purgeMapNode(m, n1, observer);n1 next;}} else {// 以object为key找到对应链表清空该链表GSIMapNode n1;n1 GSIMapNodeForSimpleKey(m, (GSIMapKey)object);if (n1 ! 0) {purgeMapNode(m, n1, observer);}}if (m-nodeCount 0) {mapFree(TABLE, m);GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName);}}// 开始操作nameless表if (object nil) { // object为空时// 清空nameless表e0 GSIMapEnumeratorForMap(NAMELESS);n0 GSIMapEnumeratorNextNode(e0);while (n0 ! 0) {GSIMapNode next GSIMapEnumeratorNextNode(e0);purgeMapNode(NAMELESS, n0, observer);n0 next;}} else { // object不为空// 找到对应的observer链表清空该链表n0 GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);if (n0 ! 0) {purgeMapNode(NAMELESS, n0, observer);}}} else { // name不为空GSIMapTable m;GSIMapEnumerator_t e0;GSIMapNode n0;n0 GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));// 如果没有和这个name相同的key直接返回if (n0 0) {unlockNCTable(TABLE);return; /* Nothing to do. */}m (GSIMapTable)n0-value.ptr; // 找到name作为key对应的数据信息if (object nil) {// 如果object为nil就清空刚才找到的name对应的数据信息e0 GSIMapEnumeratorForMap(m);n0 GSIMapEnumeratorNextNode(e0);while (n0 ! 0) {GSIMapNode next GSIMapEnumeratorNextNode(e0);purgeMapNode(m, n0, observer);n0 next;}} else {// 如果object不为空清空object对应的链表n0 GSIMapNodeForSimpleKey(m, (GSIMapKey)object);if (n0 ! 0) {purgeMapNode(m, n0, observer);}}// 因为其中的数据清除完了所以记得清除named表中的作为key的nameif (m-nodeCount 0) {mapFree(TABLE, m);GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));}}unlockNCTable(TABLE); }流程总结 1.若 NotificationName 和 object 都为 nil则清空 wildcard 链表。2.若 NotificationName 为 nil遍历 named table若 object 为 nil则清空 named table若 object 不为 nil则以 object 为 key 找到对应的链表然后清空链表。在 nameless table 中以 object 为 key 找到对应的 observer 链表然后清空若 object 也为 nil则清空 nameless table。3.若 NotificationName 不为nil在 named table 中以 NotificationName 为 key 找到对应的 table若 object 为 nil则清空找到的 table若 object 不为 nil则以 object 为 key 在找到的 table 中取出对应的链表然后清空链表。 一些问题 通知的发送时同步的还是异步的发送消息与接收消息的线程是同一个线程么 通知中心发送通知给观察者是同步的也可以用通知队列NSNotificationQueue异步发送通知。 在抛出通知以后观察者在通知事件处理完成以后可以通过休眠3秒来测试抛出者才会往下继续执行也就是说这个过程默认是同步的当发送通知时通知中心会一直等待所有的 observer 都收到并且处理了通知才会返回到 poster。 接收通知的线程和发送通知所处的线程是同一个线程。也就是说如果要在接收通知的时候更新 UI需要注意发送通知的线程是否为主线程。 - (void)viewDidLoad {[super viewDidLoad];[[NSNotificationCenter defaultCenter] addObserver:self selector:selector(test) name:NotificationName object:nil]; }- (void)touchesBegan:(NSSetUITouch * *)touches withEvent:(UIEvent *)event {dispatch_queue_t queue dispatch_queue_create(test.queue, DISPATCH_QUEUE_SERIAL);dispatch_async(queue, ^{ // 异步执行 串行队列NSLog(--current thread: %, [NSThread currentThread]);NSLog(Begin post notification);[[NSNotificationCenter defaultCenter] postNotificationName:NotificationName object:nil];NSLog(End);}); }- (void)test {NSLog(--current thread: %, [NSThread currentThread]);NSLog(Handle notification and sleep 3s);sleep(3); }如何使用异步发送通知 1.让通知事件处理方法在子线程中执行 - (void)viewDidLoad {[super viewDidLoad];[[NSNotificationCenter defaultCenter] addObserver:self selector:selector(test) name:NotificationName object:nil]; }- (void)touchesBegan:(NSSetUITouch * *)touches withEvent:(UIEvent *)event {NSLog(--current thread: %, [NSThread currentThread]);NSLog(Begin post notification);[[NSNotificationCenter defaultCenter] postNotificationName:NotificationName object:nil];NSLog(End); }- (void)test {dispatch_queue_t queue dispatch_queue_create(test.queue, DISPATCH_QUEUE_SERIAL);dispatch_async(queue, ^{ // 异步执行 串行队列NSLog(--current thread: %, [NSThread currentThread]);NSLog(Handle notification and sleep 3s);sleep(3);}); }2.可以通过 NSNotificationQueue 的 enqueueNotification: postingStyle: 和 enqueueNotification: postingStyle: coalesceMask: forModes: 方法将通告放入队列实现异步发送在把通告放入队列之后这些方法会立即将控制权返回给调用对象。 - (void)viewDidLoad {[super viewDidLoad];[[NSNotificationCenter defaultCenter] addObserver:self selector:selector(test) name:NotificationName object:nil]; }- (void)touchesBegan:(NSSetUITouch * *)touches withEvent:(UIEvent *)event {NSLog(--current thread: %, [NSThread currentThread]);NSLog(Begin post notification);NSNotification *notification [NSNotification notificationWithName:NotificationName object:nil];[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];NSLog(End); }- (void)test {NSLog(--current thread: %, [NSThread currentThread]);NSLog(Handle notification and sleep 3s);sleep(3); }NSNotificationQueue 和 runloop 的关系 postringStyle 参数就是定义通知调用和 runloop 状态之间关系。 该参数的三个可选参数 1.NSPostWhenIdle通知回调方法是等待到当下线程 runloop 进入等待状态才会调用。2.NSPostASAP通知回调方法是等待到当下线程 runloop 开始接收事件源的时候就会调用。3.NSPostNow其实和直接用默认的通知中心添加通知是一样的通知马上调用回调方法 页面销毁时不移除通知会崩溃吗 在观察者对象释放之前需要调用removeOberver方法将观察者从通知中心移除否则程序可能会出现崩溃。但从 iOS9 开始即使不移除观察者对象程序也不会出现异常。这是因为在 iOS9 以后通知中心持有的观察者由 unsafe_unretained 引用变为weak引用。即使不对观察者手动移除持有的观察者的引用也会在观察者被回收后自动置空。但是通过 addObserverForName:object: queue:usingBlock: 方法注册的观察者需要手动释放因为通知中心持有的是它们的强引用。 多次添加同一个通知会是什么结果多次移除通知呢多次添加同一个通知观察者方法会调用多次多次移除没关系。应该和源码有关系我的理解是它在表中找不到同时源码也没写这样会报错那就自然没有关系。 下面的方法不会接收到通知 // 添加观察 [[NSNotificationCenter defaultCenter] addObserver:self selector:selector(handleNotification:) name:TestNotification object:1]; // 通知发送 [NSNotificationCenter.defaultCenter postNotificationName:TestNotification object:nil];不会上文介绍 NSNotificationCenter 时介绍了 center 的结构。 注册通知在添加observer时路径为 TestNotification - 1 - self发送通知在查找observer时路径为 TestNotification - nil - observer list object是干嘛的?是不是可以用来传值? object是用来过滤Notification的只接收指定的sender所发的Notification传值请用userInfo而不是object。 单例模式 什么是单例模式 单例模式在整个工程中相当于一个全局变量就是不论在哪里需要用到这个类的实例变量都可以通过单例方法来取得而且一旦你创建了一个单例类不论你在多少个界面中初始化调用了这个单例方法取得对象它们所有的对象都是指向的同一块内存的存储空间即单例类保证了该类的实例对象是唯一存在的一个。 系统为我们提供的单例类有 UIApplication(应用程序实例类) NSNotificationCenter(消息中心类) NSFileManager(文件管理类) NSUserDefaults(应用程序设置) NSURLCache(请求缓存类) NSHTTPCookieStorage(应用程序cookies池)单例模式的优缺点 优点 一个类只被实例化一次提供了对唯一实例的受控访问。节省系统资源。允许可变数目的实例 缺点 一个类只有一个对象可能造成责任过重在一定程度上违背了“单一职责原则”。由于单例模式中没有抽象层因此单例类的扩展有很大的困难。滥用单例将带来一些负面问题如为了节省资源将数据库连接池对象设计为单例类可能会导致共享连接池对象的程序过多而出现连接池溢出如果实例化的对象长时间不被利用系统会认为是垃圾而被回收这将导致对象状态的丢失。 单例的实现 单例的实现分为两种懒汉式和饿汉式。 懒汉式顾名思义不到万不得已就不会去实例化类也就是说在第一次用到类实例的时候才会去实例化。饿汉式饿了肯定会饥不择食所以在单例类加载的时候就进行实例化。 特点和选择 由于要进行线程同步所以在访问量比较大或者可能访问的线程比较多时采用饿汉实现可以实现更好的性能这是以空间换时间。在访问量较小时采用懒汉实现这是以时间换空间。 3.1 懒汉式 单线程下实现单例 static id manager nil;(instancetype)shareInstance {if (!manager) {manager [[super allocWithZone:NULL] init];}return manager; }以上代码在单线程下不会出问题但是如果在多线程下可能会出现多个线程共同进入 if 条件中创建出多个对象所以为了防止此类问题的发生我们选择给他上锁保证每次访问该代码都只能一个线程进行访问 static id manager nil;(instancetype)shareInstance {synchronized (self) {if (!manager) {manager [[super allocWithZone:NULL] init];}}return manager; }添加完synchronized锁之后确实可以实现单线程访问该代码了但是这样的代码无论我们有没有创建该类的单例对象他都会进来执行一次锁就很没有必要我们创建过了这个对象之后就不用进来了那么我们在对其进行改造 static id manager nil;(instancetype)shareInstance {// 防止多次加锁if (!manager) {synchronized (self) {if (!manager) {manager [[super allocWithZone:NULL] init];}}}return manager; }这样写也可以达到目的但是相比GCD给出的轻量级的dispatch_once来说还是很繁琐dispatch_once它没有使用重量级的同步机制性能也优于前者并且更加高效所以单例推荐写法如下 static id manager nil; (instancetype)shareInstance {static dispatch_once_t onceToken;dispatch_once(onceToken, ^{manager [[super allocWithZone:NULL] init];});return manager; }- (instancetype)copyWithZone:(NSZone *)zone {return [LazyType shareInstance]; }- (instancetype)mutableCopyWithZone:(NSZone *)zone {return [LazyType shareInstance]; } (instancetype)allocWithZone:(struct _NSZone *)zone {return [LazyType shareInstance]; }dispatch_once无论使用多线程还是单线程都只执行一次在安全的前提下也保证了性能。 dispatch_once主要是根据onceToken的值来决定怎么执行代码 当onceToken为0时线程执行dispatch_once的block中的代码当onceToken为-1时线程跳过dispatch_once的block中的代码当onceToken为其他值时线程被阻塞等待onceToken值改变 dispatch_once的执行流程 当线程调用shareInstance此时onceToken为0执行dispatch_once的block中的代码此时onceToken中的值为其他值这时如果有其他线程再调用shareInstance方法时onceToken值为其他值线程阻塞当block线程执行完block后onceToken变为-1。其他线程不再阻塞跳过block下次再调用shareInstance时onceToken为-1直接跳过block 饿汉式 当类被加载的时候就创建因为一个类在整个生命周期中只会被加载一次所以它肯定只有一个线程对其进行访问此时再创建他就是线程安全的就不需要使用线程锁来保证其不会被多次创建。 static id manager nil; (void)load {[super load];manager [[super allocWithZone:NULL] init]; } (instancetype)shareInstance {return manager; }- (instancetype)copyWithZone:(NSZone *)zone {return manager; }- (instancetype)mutableCopyWithZone:(NSZone *)zone {return manager; } (instancetype)allocWithZone:(struct _NSZone *)zone {return manager; }关于复写 我们在写单例的时候一定要记着复写该类的其他方法例如alloc、copy、mutablecopy、new等但一般不复写alloc方法就像上面我书写的代码一样。 因为这些方法都可以创建一个全新的该单例对象但是根据单例的定义我们只能允许存在一个该单例对象所以我们要扼制其创建出新的该单例类的对象保证对象的唯一性。 关于XXXWithZone 我们重写这些XXXWithZone都是为了使我们的单例返回的是同一个对象但是为甚么非要重写这些方法重写alloc、copy、mutableCopy方法不行吗 答案肯定是不行的为了保持单例类实例的唯一性需要覆盖所有会生成新的实例的方法如果我们只重写了alloc、copy、mutableCopy方法那要是有人初始化这个单例类的时候不走[[Class alloc] init]而是直接allocWithZone:方法那么这个单例就不再是单例了其他的copy、mutableCopy方法原因也相同所以我们必须把这个方法也堵上。 代理 代理我们一般用于相邻两个界面之间的回传值用起来很顺手下面我就来说说 代理传值的使用步骤 我们的使用场景如下现在有两个视图控制器A和B我们现在想要在视图B中回传一个值给视图A同时改变A视图中UILabel显示的字符串B中我们使用UITextField来实现数据的输入传回的就是这个UITextField中的值 第一步在B视图控制器声明一份协议 protocol MyViewControllerDelegate NSObject - (void)changeUILabelText:(NSString *)string; end第二步在B视图控制器中声明一个代理属性 property (nonatomic, weak) idMyViewControllerDelegate delegate;第三步在B视图控制器中想要回传值的地方写代理执行的方法 [self.delegate changeUILabelText:self.myTextField.text];第四步在A视图控制器里签订代理协议 MyViewControllerDelegate第五步A中签订代理人 self.myView [[MyViewController alloc] init]; self.myView.delegate self;第六步A中实现代理方法 - (void)changeUILabelText:(NSString *)string {self.myLabel.text [string copy]; }代理传值的原理 代理传值其实就是利用了可以在遵守代理协议的视图中实现代理方法的原理上述例子中我们自己先定义了一个代理协议然后在B中定义一个代理对象delegate同时在需要传值的地方实现了代理对象delegate对代理方法的调用好了关键来了这个代理对象delegate其实就是代理传值的核心因为我们在A视图中创建B视图的时候有行这样的代码 self.myView.delegate self;在此之前B视图已经初始化创建出来了同时你将A视图自身的地址赋值给了B视图中的delegate属性那么此时B视图中的delegate属性是不是就等同于是A视图了并且该delegate还遵循了代理协议MyViewControllerDelegate那不就是等同于遵循该代理协议的A视图嘛那不就有了self.delegate 遵循MyViewControllerDelegate协议的A视图控制器这不就是完全成立的吗现在我们在看在B视图中写的回传值的方法 [self.delegate changeUILabelText:self.myTextField.text];这不就纯纯是A视图在进行该方法的调用吗到此想必大家都已经想明白了代理传值的原理了。 综上所述代理传值就是将一个视图的地址传递给另一个视图的delegate属性使用该属性进行操作其实就是使用那个视图在进行操作。 代理的循环引用 上述我们说了代理传值的原理我们不难发现其中的delegate属性是weak进行修饰的为什么使用weak进行修饰下面我们就来讲讲这其中的道理 上面说了发送方的delegate等同于接收方其实就是它引用了接收方视图并且我们在接收方中定义发送方视图的时候接收方这不又引用了发送方吗两个视图之间互相引用这不就是典型的循环引用案例所以我们为了打破这个循环引用的问题所以才在发送方的delegate使用weak进行修饰即发送方delegate属性弱引用接收方接收方强引用发送方这不就打破了循环引用。 因为 weak 不会使对象的引用计数增加如果使用它来修饰发送方我们不知道它会在什么时候将发送方对象置 nil 了程序说不定还会 crash但是使用其修饰 delegate 就不一样了所以我们使用 weak 来修饰 delegate。 KVO\KVC\单例模式\通知\代理\Block 代理和通知的区别 效率代理比通知高关联代理是强关联委托和代理双方互相知道。通知是弱关联不需要知道是谁发也不需要知道是谁接收代理是一对一的关系通知是一对多的关系代理要实现对多个类发出消息可以通过将代理者添加入集合类后遍历或通过消息转发来实现。代理一般行为需要别人来完成通知是全局通知 KVO和通知的区别 相同都是一对多的关系不同通知是需要被观察者先主动发出通知观察者注册监听再响应比KVO多了发送通知这一步监听范围KVO是监听一个值的变化通知不局限于监听属性的变化还可以对多种多样的状态变化进行监听通知的监听范围广使用更灵活使用场景KVO的一般使用场景是监听数据变化通知是全局通知 block和代理的区别 相同点 block和代理都是回调的方式。使用场景相同。 不同点 block集中代码块而代理分散代码块所以 block 更适用于轻便、简单的回调如网络传输代理适用于公共接口较多的情况这样做也更易于解耦代码架构block运行成本高block出栈时需要将使用的数据从栈内存拷贝到堆内存。当然如果是对象就是加计数使用完或block置为 nil 后才消除而代理只是保存了一个对象指针直接回调并没有额外消耗相对C的函数指针只是多做了一个查表动作 设计模式总结 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。 三种工厂模式 通过给定参数来返回对应的实例完全对用户隐藏其实现的原理。 优势易于替换面向抽象编程 依赖倒置原则
http://www.dnsts.com.cn/news/160975.html

相关文章:

  • 邯郸做网站的公司哪家好百度seo软件优化
  • html教程 pdf杭州新站整站seo
  • 网站建设情况说明书杭州外贸建站
  • 网站后台动态播放怎么做的wordpress4.7发布模块
  • 辽宁省城乡建设厅网站广州seo网站推广顾问
  • 兴宁区住房和城乡建设局网站网站建设的指导书
  • 如何将数据库导入网站龙岗高端网站建设
  • 做性的视频网站南沙建设局网站
  • dw网站建设的基本流程土巴兔网站开发方案
  • 哪家网站建设好网页设计与制作app
  • 猪八戒网做网站如何付款怎么上传网站模板
  • 免费创业平台大连seo外包公司
  • 免费建立平台网站茶网站设计素材下载
  • 网站建设教程aspwordpress163邮件
  • 网站推广业务广州网站开发软件平台
  • 美食网站建设策划方案如何给wordpress文章部分内容加密
  • 门头沟石家庄网站建设温江做网站哪家好
  • 做公司网站需要多长时间网站建设 紧急检查工作
  • 口碑好网站建设安徽和住房建设厅网站
  • 如何做网站title小标图软件技术和计算机应用技术哪个好
  • 友点企业网站管理系统模板上海诚杰华建设工程咨询有限公司网站
  • 网站建设制作深圳西安市规划建设局网站
  • 门户网站的自身的特性透明主题wordpress
  • 网站备案机构网站中超链接怎么做
  • 变更icp备案网站信息阿里云建设网站教程
  • 加强政务公开与网站建设建设局全称是什么
  • 网站水晶头怎么做国内精美网站界面网址
  • 网站建设和安全管理制度怎么查网站找谁做的
  • 辽宁网站网站建设内蒙古创意网站开发
  • 一级页面的网站怎么做的企业网络推广网站建设