[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,通州

时间: 2024-10-01 03:03:08

[objective-c] 09 - 开发模式 单例 KVO 通知的相关文章

IOS开发模式——单例

单例的模式在网上有很多,今天发下我个人对单例模式的理解.整个app中只存在一个实例,也只会进行一次实例,在实例完成之后是不可以人释放的(当App关闭之后,等系统自己回收). 也就是说,如果我们写得某个类符合了上述条件,那么我们也可以称这个类为单例. 在非ARC的工程中,我们需要针对alloc,retain,copy等会增加retaincount的参数加以控制,对release和autorelease等减少retailcount的操作增加控制,以确保单一实例,绝不释放. 在ARC的工厂中,由于,内

iOS 页面间几种传值方式(属性,代理,block,单例,通知)

iOS 页面间几种传值方式(属性,代理,block,单例,通知) 姜糖水 2015-05-03 52 阅读 iOS 移动开发 第二个视图控制器如何获取第一个视图控制器的部分信息 例如 :第二个界面中的lable显示第一个界面textField中的文本 这就需要用到属性传值.block传值 那么第一个视图控制器如何获的第二个视图控制器的部分信息 例如:第一个界面中的lable显示第二个界面textField中的文本 这就需要使用代理传值 页面间传值有八大传值方式,下面我们就简单介绍下页面间常用的五

IOS开发之单例设计模式

本文将从四个方面对IOS开发中的单例设计模式进行讲解: 一.什么是单例设计模式 二.我们为什么要用单例设计模式 三.单例设计模式的基本用法 四.自定义单例设计模式代码的封装 一.什么是单例设计模式 所谓单例,即是单个的实例化对象,保证一个类有且仅有一个实例.通常情况下,当我们对一个类实例化时(如:alloc.new等),并不能保证每次实例化的对象是唯一的实例.那么为了保证该类可在多次实例化的过程中保证内存地址不变,就需要引入单例设计模式. 二.我们为什么要用单例设计模式 1.Singleton

iOS开发之单例设计模式(完整正确版本)

单例的意思从字面上就可以略知一二,所谓单例就是确保在程序运行过程中只创建一个对象实例.可以用于需要被多次广泛或者说多次使用的资源中,比如我们常见的网络请求类.工具类以及其它管理类等.比如我iOS开发中常见的系统单例[UIApplication sharedApplication].[NSUserDefaults  standardUserDefaults]等.在iOS开发中,单例模式是非常有用的一种设计模式.如下图,是一个简单的例模式的UML类图. 一.使用单例模式的作用 它可以保证某个类在程序

iOS开发之——单例的几种设计方式

单例是ios开发中常用的一种设计模式,通常用来控制器之间传值.方便.高效,全局通用. 单例模式的设计分为ARC和MRC. ARC: 方式一 1.创建一个继承自NSObject的类. 2.在这个类的.h文件中声明类方法: + (instancetype)sharedInstance; 2.在这个类的.m文件中实现以下方法: static id instance; + (instancetype)sharedInstance { static dispatch_once_t onceToken; d

iOS开发之单例头文件

// 帮助实现单例设计模式 // .h文件的实现 #define SingletonH(methodName) + (instancetype)shared##methodName; // .m文件的实现 #if __has_feature(objc_arc) // 是ARC #define SingletonM(methodName) static id _instace = nil; + (id)allocWithZone:(struct _NSZone *)zone { if (_inst

iOS 页面间几种传值方式(属性,代理,block,单例,通知)

第二个视图控制器如何获取第一个视图控制器的部分信息 例如 :第二个界面中的lable显示第一个界面textField中的文本 这就需要用到属性传值.block传值 那么第一个视图控制器如何获的第二个视图控制器的部分信息 例如:第一个界面中的lable显示第二个界面textField中的文本 这就需要使用代理传值 页面间传值有八大传值方式,下面我们就简单介绍下页面间常用的五种传值方式: (一)属性传值 第二个界面中的lable显示第一个界面textField中的文本 首先我们建立一个RootVie

iOS传值方式:属性,代理,block,单例,通知

第二个视图控制器如何获取第一个视图控制器的部分信息 例如 :第二个界面中的lable显示第一个界面textField中的文本 这需要用到属性传值.block传值 那么第一个视图控制器如何获的第二个视图控制器的部分信息 例如:第一个界面中的lable显示第二个界面textField中的文本 这就需要使用代理传值 页面间传值有八大传值方式,下面我们就简单介绍下页面间常用的五种传值方式: (一)属性传值 第二个界面中的lable显示第一个界面textField中的文本 首先我们建立一个RootView

iOS开发-一些单例的类

UIApplication(应用程序实例) NSNotificationCenter(消息中心)  NSFileManager(文件管理)    NSUserDefaults(应用程序设置)     NSURLCache(请求缓存)   NSHTTPCookieStorage(应用程序cookies池)