[objective-c] 09 - 开发模式 单例 KVO 通知
本章内容主要讲述OC语言中常用的开发模式,开发模式是解决某些具体问题的固定解决方案。在OC中主要有三种模式可以使用
- 单例模式
- 键值观察模式
- 消息模式
1.单例模式
在开发过程中,经常有一些共享型的数据需要储存在一个公共的地方,需要的时候,可以方便回去使用。单例模式便提供创建一个公共地方的方法。
@interface TestObj: NSObject
@property double a;
@end
例如我们声明上文中的一个对象,其中有一个a的属性可以存储数据。
TestObj * obj1 = [[TestObj alloc] init];
TestObj * obj2 = [[TestObj alloc] init];
obj1.a = 1;
obj2.a = 2;
上文中,通过两个对象指针访问a属性,是两块完全不同的内存空间,所以,两个对象储存的值也没有任何关联。但如果a是一个共享型的数据,那么在代码的任何地方获取的对象指针,应该都必须指向同一块内存,这样才能保证,通过任意对象指针获取的数据都是同一个数据。
为解决以上问题,我们需要对类进行单例改造,添加一个单例方法,通过单例方法获取的对象指针都会指向全局唯一的内存。
@interface TestObj: NSObject
@property double a;
+(instancetype)shareTestObj;
@end
单例方法为加号方法,通常以share
+类目的方式进行命名。
@implementation TestObj
+(instancetype)shareTestObj
{
static TestObj * obj = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
obj = [[TestObj alloc] init];
});
return obj;
}
@end
单例方法的实现也十分简单,首先声明一个静态对象指针,保证这个对象指针不会被释放。然后调用GCD中的只运行一次的方法,确保在任何情况下只会开辟一块内存空间,然后将对象指针的值返回。
TestObj * obj1 = [TestObj shareTestObj];
TestObj * obj2 = [TestObj shareTestObj];
经过处理的类具有单例方法,通过单例方法获取的对象指针所指向的内存为同一块内存,这样,在任何一个地方,只需要调用单例方法,便可以获取共享数据。
[代码展示]
======TestObj类的声明======
#import <Foundation/Foundation.h>
@interface TestObj : NSObject
+(instancetype) shareTestObj;
@end
======TestObj类的实现======
#import "TestObj.h"
static TestObj *obj = nil; // 声明静态全局变量
@implementation TestObj
+(instancetype)shareTestObj
{
// GCD 的方式
static dispatch_once_t onceToken;
// 1. 只允许代码块中的代码只执行一次
// 2. 它不允许并发执行
dispatch_once(&onceToken, ^{
obj = [TestObj new];
});
return obj;
}
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
// 1. 只允许代码块中的代码只执行一次
// 2. 它不允许并发执行
dispatch_once(&onceToken, ^{
obj = [super allocWithZone:zone];
});
return obj;
}
@end
======main======
#import <Foundation/Foundation.h>
#import "TestObj.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestObj *t1 = [TestObj shareTestObj];
TestObj *t2 = [TestObj shareTestObj];
TestObj *t3 = [[TestObj alloc] init];
NSLog(@"t1=%p,t2=%p,t3=%p", t1, t2, t3);
}
return 0;
}
======运行结果======
t1=0x1003049a0,t2=0x1003049a0,t3=0x1003049a0
2.键值观察模式
OC中提供一个键值观察的机制,让我们可以主动观察某一个对象的属性变化情况。
NSObject类支持键值观察机制,所有其子类创建的对象也同时支持。使用键值观察需要两步
- 注册观察者与观察路径
- 实现观察者回调
注册观察者与观察路径为NSObject提供的一个方法
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
这个方法需要被观察的对象调用,用来为自己添加其他观察者
方法参数
- observer:添加的观察者对象,意思让该对象观察自己。
- keyPath:观察属性名,例如想观察name属性,那输入@"name"即可
- options:观察值的类型,分别可选初始值,新值和旧值。
- context:回调上下文,因一个观察者可以观察多个对象,但回调方法都是这一个方法,可以通过这个参数类分辨是哪个观察操作的回调。
当被观察对象的属性发生变化时,将会回调一个固定的方法,且此方法不需要被注册,默认生效
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
通过此回调方法,可以反馈到观察者如下信息
- keyPath:被观察对象属性
- object:被观察对象
- change:改变内容
- context:注册观察者时输入的上下文信息
下面通过一个完整的案例来展现键值观察的模式
@interface Student : NSObject
@property(strong, nonatomic) NSString * name;
@end
@interface Teacher : NSObject
@property(strong, nonatomic) Student * stu;
@end
@implementation Teacher
-(void)test
{
self.stu = [[Student alloc] init];
[self.stu addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
self.stu.name = @"test";
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"%@",change);
}
-(void)dealloc
{
[self.stu removeObserver:self forKeyPath:@"name"];
}
@end
main()
{
Teacher * tea = [[Teacher alloc] init];
[tea test];
}
运行上述代码,即可看到输出结果,当stu对象的name属性发生改变时,键值观察回调方法便会被调用,同时输出change中的信息。
同时需要注意的是,在dealloc方法中一定要移除观察者,否则在对象释放之后,再产生的键值观察回调将会发送给一个野指针,产生概率性崩溃信息。
[代码展示]
======Observe类的声明======
#import <Foundation/Foundation.h>
#import "MyLable.h"
// 观察者类
@interface Observer : NSObject
@property(strong, nonatomic) MyLable *lable;
-(instancetype) initWinthObject:(MyLable *)lable;
@end
======Observe类的实现======
#import "Observer.h"
@implementation Observer
-(instancetype)initWinthObject:(MyLable *)lable
{
if (self = [super init])
{
self.lable = lable;
// 1.注册成为lable的观察者
[self.lable addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew|
NSKeyValueObservingOptionOld context:nil];
}
return self;
}
// 2.实现回调方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualTo:@"text"])
{
NSLog(@"%@,%@",change[NSKeyValueChangeNewKey], change[NSKeyValueChangeOldKey]);
}
NSLog(@"改变了");
}
// 3.移除观察者身份
-(void)dealloc
{
[self.lable removeObserver:self forKeyPath:@"text"];
}
@end
======SDK======
==MyLble类的声明==
#import <Foundation/Foundation.h>
@interface MyLable : NSObject
@property(strong, nonatomic) NSString *text;
@end
==MyLble类的实现==
#import "MyLable.h"
@implementation MyLable
@end
==MyWindow类的声明==
#import <Foundation/Foundation.h>
#import "MyLable.h"
@interface MyWindow : NSObject
@property(strong, nonatomic) MyLable *lable;
@end
==MyWindow类的实现==
#import "MyWindow.h"
@implementation MyWindow
- (instancetype) init
{
self = [super init];
if (self)
{
self.lable = [MyLable new];
}
return self;
}
@end
======main======
#import <Foundation/Foundation.h>
#import "MyWindow.h"
#import "Observer.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyWindow *window = [[MyWindow alloc] init];
Observer *observer = [[Observer alloc] initWinthObject:window.lable];
window.lable.text = @"abc";
}
return 0;
}
======运行结果======
KVO[2684:267439] abc,<null>
KVO[2684:267439] 改变了
3.消息模式
消息模式是OC中较为特殊的一种开发模式,它为两个不能相见但有需要互通消息的对象提供了一个传递信息的机制。
通常我们在使用回调或者键值观察时,都需要直接获取要产生回调事件的组件对象或需要观察的对象。
但在一些特殊情况,例如之后的App开发课程中需要对键盘对象进行监控,但却无法获取键盘对象,因键盘对象只在用户触发响应事件时才被创建。这样我们便可以采用消息的方式,接收键盘对象发出来的消息,从而实现对键盘对象行为的监控。
使用消息开发模式需要掌握以下四个步骤
- 获取消息中心
- 订阅消息
- 取消订阅
- 发送消息
获取消息中心:
NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
消息中心对象需要通过单例模式获取,不能进行alloc操作,因为全局必须保证只有一个消息中心,才能够将消息正确的传递。
订阅消息:
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;
该方法为消息中心对象的方法,其参数分别为
- observer:接收消息回调的对象,类似目标动作回调中的target对象。
- aSelector:消息到达时回调的方法,与目标动作回调中的action功能一致。
- aName:订阅消息的名称,相当于筛选器,只接收指定名称的消息,如果为nil,则接收所有消息。
- anObject:发送消息的对象
取消订阅:
- (void)removeObserver:(id)observer;
在dealloc方法中需要消息中心对象调用此方法取消订阅
发送消息分为两步,第一步是创建消息,第二步是发送消息
NSNotification * noti = [NSNotification notificationWithName:@"test" object:self userInfo:@{@"key":@"value"}];
创建一条消息需要三个参数
- 消息名
- 发送消息对象,通常填self
- 消息携带的信息,为一个字典,其中键值对可自定义。
NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
NSNotification * noti = [NSNotification notificationWithName:@"test" object:self userInfo:@{@"key":@"value"}];
[center postNotification:noti];
发送消息时消息中心的功能,调用相应的发送方法,即可把一个消息发送出去,如果在此之前,有对象订阅了相同名称的消息,那么该对象的消息回调方法会被调用。
[代码展示]
======Market类的声明======
#import <Foundation/Foundation.h>
@interface Market : NSObject
-(void) sendMessage;
@end
======Market类的实现======
#import "Market.h"
@implementation Market
-(void)sendMessage
{
// 通知中心 单例类
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
// 创建通知对象
NSNotification *notification = [NSNotification notificationWithName:@"IOS" object:self userInfo:@{@"money":@"6000", @"address":@"通州"}];
// 发送通知
[center postNotification:notification];
}
@end
======Student类的声明======
#import <Foundation/Foundation.h>
@interface Student : NSObject
@end
======Student类的实现======
#import "Student.h"
@implementation Student
-(instancetype)init
{
if (self = [super init])
{
// 1.订阅通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(method:) name:@"IOS" object:nil];
}
return self;
}
// 2.处理通知的方法
-(void) method:(NSNotification *) notification
{
NSDictionary *dic = notification.userInfo;
NSLog(@"%@,%@", dic[@"money"], dic[@"address"]);
}
-(void)dealloc
{
// 移除订阅
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
======Worker类的声明======
#import <Foundation/Foundation.h>
@interface Worker : NSObject
@end
======Worker类的实现======
#import "Worker.h"
@implementation Worker
-(instancetype)init
{
if (self = [super init])
{
// 1.订阅通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(method:) name:@"IOS" object:nil];
}
return self;
}
// 2.处理通知的方法
-(void) method:(NSNotification *) notification
{
NSDictionary *dic = notification.userInfo;
NSLog(@"%@,%@", dic[@"money"], dic[@"address"]);
}
-(void)dealloc
{
// 移除订阅
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
======main======
#import <Foundation/Foundation.h>
#import "Market.h"
#import "Student.h"
#import "Worker.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [Student new];
Market *market = [Market new];
Worker *worker = [Worker new];
[market sendMessage];
}
return 0;
}
======运行结果======
6000,通州
6000,通州