Objective-c:设计模式

单例模式

单例简介

  • 单例是生命周期与程序生命周期相同,仅能生成一次、且不能被销毁的唯一实例;

  • 需要确保应用中的一个特定类有且仅有一个实例(对象);

  • 单例可在程序任何位置被访问,且一直存在;

  • 单例实例获取方法命名规则:一般以standard…、shared…、default….开头;

  • 单例必须至少满足以下条件:

    • 只初始化一次;

    • 全局存在;

    • 唯一,对象不可改;

    • 方便获取,任何位置可访问

系统单例

  • UIApplication:应用程序单例

  • NSNotificationCenter:通知中心单例

  • NSUserDefaults:用户默认单例

  • NSFileManager:文件管理单例

单例优点

  • 共享信息;

  • 实例控制:Singleton会阻止其他对象实例化其自己的Singleton对象的副本,从而确保所有对象都访问唯一实例;

  • 灵活性:因为类控制了实例化过程,所以类可以更加灵活地修改实例化过程;

自定义单例创建常用方法

首先创建一个继承于NSObject的类Singleton,其次在Singleton.m文件中实现如下代码;

  • 方法1:一般创建
#import "Singleton.h"

// 静态变量保证单例对象始终存在
static Singleton *singleton = nil;

@implementation Singleton

+ (instancetype)defaultSingleton {

    // 仅在单例对象为空时初始化一次,之后直接返回唯一的单例对象
    if (singleton == nil) {
        singleton = [[Singleton alloc] init];
    }
    return singleton;
}

@end
  • 方法2:GCD创建(推荐使用)
#import "Singleton.h"

static Singleton *singleton = nil;

@implementation Singleton

+ (instancetype)defaultSingleton {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        singleton = [[Singleton alloc] init];
    });
    return singleton;

}

@end

自定义单例创建完整方法

  • 一般创建单例方法可能会出现一些问题,我们通过不同的途径得到不同的对象,显然是不行的。我们必须要确保对象的唯一性,所以我们就需要封锁用户通过alloc/init/copy以及release/retain等方法来构造或操作对象这条道路。

  • 我们知道,创建对象的步骤分为申请内存(alloc)、初始化(init)这两个步骤,我们要确保对象的唯一性,因此在第一步这个阶段我们就要拦截它。当我们调用alloc方法时,oc内部会调用allocWithZone这个方法来申请内存,我们覆写这个方法,然后在这个方法中调用defaultSingleton方法返回单例对象,这样就可以达到我们的目的。拷贝对象也是同样的原理,覆写copyWithZone方法,注意需遵守<NSCopying>协议,然后在这个方法中调用defaultSingleton方法返回单例对象,其他示例直接参考如下代码:

static Singleton *singleton = nil;

+ (instancetype)defaultSingleton {

    if (!singleton) {
        singleton = [[Singleton alloc] init];
    }
    return singleton;
}

// alloc
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    @synchronized(self) {
        if (!singleton) {

            singleton = [super allocWithZone:zone];

            return singleton;
        }
        return nil;
    }
}

// copy
- (id)copyWithZone:(NSZone *)zone {
    return self;
}

// retain
- (instancetype)retain {
    return self;
}

// release
- (oneway void)release {

}

// autorelease
- (instancetype)autorelease {
    return self;
}

// retainCount
- (NSUInteger)retainCount {
    return NSUIntegerMax;
}

单例使用

首先在main.m文件中,引入“Singleton.h”,其次创建两个单例实例,并且打印对象信息;

这里写图片描述

我们可以看到,两个实例对象的地址是一致的,由此可见,单例的唯一性。

可利用单例的唯一性进行传值,但必须满足一个前提条件就是,在传值时,必须保证单例存在并且单例属性确实有值。

现在在Singleton.h文件中声明属性name,其次创建一个Person类,为其设置属性name,并且重写init方法,通过单例对name直接复制,如下所示:

#import "Person.h"
#import "Singleton.h"

@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        // 获取单例属性值赋给name
        self.name = [Singleton defaultSingleton].name;
    }
    return self;
}

@end

切换到main.m文件中,创建单例实例,并且给name属性复制,其次实例化Person对象,并打印person.name,如下所示:

#import <Foundation/Foundation.h>

#import "Singleton.h"

#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

#pragma mark - 单例模式
        Singleton *singleton = [Singleton defaultSingleton];
        singleton.name = @"Admin";

        Person *person = [[Person alloc] init];

        NSLog(@"Person's name is '%@'.", person.name); //  Person's name is 'Admin'.
    }
    return 0;
}

从上述代码中可以看出,可通过单例进行传值操作。

KVC 键值编码

概述

  • KVC是KeyValueCoding的简称,它是一种可以直接通过字符串的名字(key)来访问类属性(实例变量)的机制。而不是通过调用Setter、Getter方法访问。通常我们使用valueForKey来替代getter() 方法,setValue:forKey来代替setter()方法。

  • 当使用KVO、Core Data、CocoaBindings、AppleScript(Mac支持)时,KVC是关键技术。

常用方法

  • 取值方法
// 1、
valueForKey:,传入NSString属性的名字。

// 2、
valueForKeyPath:,传入NSString属性的路径,xx.xx形式。
  • 设值方法
// 1、
- (void)setValue:(nullable id)value forKey:(NSString *)key;

// 2、
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

// 3、- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

// 4、
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

KVC 读取方式

KVC使用起来比较简单,但是它如何查找一个属性进行读取呢?具体查找规则(假设现在要利用KVC对name进行读取):

  • 如果是动态设置属性,则优先考虑调用setName方法,如果没有该方法则优先考虑搜索成员变量_name,如果仍然不存在则搜索成员变量name,如果最后仍然没搜索到则会调用这个类的setValue:forUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确设置);

  • 如果是动态读取属性,则优先考虑调用name方法(属性name的getter方法),如果没有搜索到则会优先搜索成员变量_name,如果仍然不存在则搜索成员变量name,如果最后仍然没搜索到则会调用这个类的valueforUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确读取);

案例

1、声明 KVCClass 类,并在其.h文件中声明几个全局变量,无需定义成属性及生成存(setter)取(getter)方法,代码如下:

#import <Foundation/Foundation.h>

@interface KVCClass : NSObject {

    NSString *_name;        /**< 姓名 */
    NSString *_nationality; /**< 国籍 */
    NSInteger _age;         /**< 年龄 */
}

@end

2、在 “KVCClass.m”文件中,重写description方法与dealloc方法,代码如下:

#import "KVCClass.h"

@implementation KVCClass

- (void)dealloc {
    [_name release]; _name = nil;
    [_nationality release]; _nationality = nil;

    [super dealloc];
}

- (NSString *)description {
    return [NSString stringWithFormat:@"<%p name:%@, age:%ld, address:%@>", self, _name, _age, _nationality];
}

@end

3、在main.m文件中,导入KVCClass.h,实例化对象,并通过KVC为其设值,代码如下:

#import <Foundation/Foundation.h>
#import "KVCClass.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

#pragma mark - KVC 

        // 1、逐一赋值
        KVCClass *kvcObject1 = [[KVCClass alloc] init];

        [kvcObject1 setValue:@"Charles" forKey:@"name"];
        [kvcObject1 setValue:@"China" forKey:@"nationality"];
        [kvcObject1 setValue:@22 forKey:@"age"];

        NSLog(@"%@", kvcObject1);

        // 取值
        NSLog(@"Name is '%@'.", [kvcObject1 valueForKey:@"name"]);


        // 2、统一赋值
        KVCClass *kvcObject2 = [[KVCClass alloc] init];

        NSDictionary *dic = @{@"name":@"William", @"nationality":@"USA", @"age":@30};

        [kvcObject2 setValuesForKeysWithDictionary:dic];


        NSLog(@"%@", kvcObject2);

        // 取值
        NSLog(@"Name is '%@'.", [kvcObject2 valueForKey:@"name"]);

    return 0;
}

控制台输出情况如下:

这里写图片描述

Tips

1、key的值必须正确,如果拼写错误,会出现异常;

2、当key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,如果你自己写了这个方法,key的值出错就会调用到这里来;

3、因为类key反复嵌套,所以有个keyPath的概念,keyPath就是用.号来把一个一个key链接起来,这样就可以根据这个路径访问下去;

4、NSArray/NSSet等都支持KVC;

KVO 键值监听

概述

  • KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。在ObjC中要实现KVO则必须实现NSKeyValueObServing协议,不过幸运的是NSObject已经实现了该协议,因此几乎所有的ObjC对象都可以使用KVO。

  • 在面对对象编程时,我们很多情况下直接处理某些事件会很麻烦。如常见的程序运行中电话呼入、用户突然点击home键等等,一般我们使用观察者(代理)来处理类似事件。

  • 在使用这些模式的时候,我们需要告诉这些角色,谁来响应这些事件,这就需要使用类的引用。

  • 类的引用本质上就是如何让另外一个角色,“知道”另外一个角色,对应到代码中,就是如何让某一个类的对象,顺利的把信息传递给另外一个对象。

  • 处理类的引用的时候,应注意以下几点:

    • 每个类是否能正常接收到信息(响应动作)

    • 信息传递时接收者是否存在

    • 每个类是否能正常释放(不会引起循环引用)

键值观察

  • 键值观察是基于键值编码的一种技术;

  • 利用键值观察可以注册成为一个对象的观察者,在该对象的某个属性变化时收到通知;

  • 被观察对象需要编写复合KVC标准的存取方法;

常用方法

系统框架已经支持KVO,所以程序员在使用的时候非常简单。

1、注册指定Key路径的监听器:addObserver: forKeyPath: options: context:

2、实现回调方法: observeValueForKeyPath: ofObject: change: context:

3、删除指定Key路径的监听器:removeObserver: forKeyPath、removeObserver: forKeyPath: context:

案例

此案例将KVC与KVO结合使用

1、声明 KVOClass 类,并在其.h文件中声明全局变量_weather

#import <Foundation/Foundation.h>

@interface KVOClass : NSObject {

    NSString *_weather; /**< 天气 */
}

@end

2、在“person.m”文件中实现如下代码:

#import "KVOClass.h"

@implementation KVOClass

- (void)dealloc {

    // 移除监听
    [self removeObserver:self forKeyPath:@"weather"];

    [_weather release]; _weather = nil;

    [super dealloc];
}

- (instancetype)init {
    self = [super init];
    if (self) {
        // 注册监听
        [self addObserver:self forKeyPath:@"weather" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    }
    return self;
}

// 实现监听回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

    NSLog(@"天气发生变化,此时天气情况:%@", change[@"new"]);
}

@end

3、在“main.m”文件中导入KVOClass.h文件,实现如下代码,观察控制台:

#import <Foundation/Foundation.h>
#import "KVOClass.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

#pragma mark - KVO

        KVOClass *kvoObject = [[KVOClass alloc] init];

        [kvoObject setValue:@"晴" forKey:@"weather"];

        [kvoObject release];


    }
    return 0;
}

控制台输出:

2015-11-17 23:02:00.860 DesignPatterns[1392:132353] 天气发生变化,此时天气情况:晴

Tips

使用KVC可以大大地减少我们的代码量,当遇到property的时候,可以多想想是否可以KVC来帮助我,是否可以用KVC来重构代码, 当需要加入observer模式时,可以考虑下KVO, 在高性能的observer里面,KVO会给我们很好的帮助。

数据持久化

  • iOS应用程序使用沙盒机制作为文件存储方式;

  • 应用程序有一个独立的沙盒文件夹,程序仅能访问自己沙盒路径下的文件,任何情况下都无法访问其它程序的沙盒;

  • 那我们可以通过什么方法将数据存入沙盒,同时也可以取出?可同 NSUserdefaults 或 属性列表实现。

  • 数据持久化的方法还有很多,如本地数据库(CoreData、sqlite、归档)、网络数据库(服务器)等等,会在后面的博客中介绍。

NSUserdefaults

常用方法

1、存值

// 1、获取系统单例
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

// 2、设值
[defaults setObject:@"William" forKey:@"name"];

// 3、同步数据
[defaults synchronize];

2、取值

// 1、获取系统单例
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

// 2、取值
NSString *name = [defaults objectForKey:@"name"];

// 3、打印信息
NSLog(@"Name is '%@'.", name);

3、移除值

// 1、获取系统单例
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

// 2、删除值
[defaults removeObjectForKey:@"name"];

// 3、同步数据
[defaults synchronize];

Tips

1、从上述示例中可以看出,无论是什么操作,都需获取userdefaults单例,如果是存值或删除值,一定记得调用synchronize方法,同步数据。

2、通过userdefaults同样可以进行值的传递,因为userdefaults本质也是单例。

读写文件

我们可以自己通过代码,根据一条路径自动在桌面上创建一个文件,来存取我们的数据,注意:这个数据并非存在沙河中。

获取桌面文件路径方式:直接将桌面文件拖入Xcode工程即可,Xcode会自动获取文件路径。

NSDictionary *dict = @{@"account":@"Admin", @"password":@"123456"};

// 1、写入文件
// 将 dict 存入文件,/infomation.text 为自己给存储对象文件的命名
[dict writeToFile:@"/Users/LiHongyao/Desktop/Datas/infomation.text" atomically:YES];

// 2、读取文件
NSDictionary *infomation = [NSDictionary dictionaryWithContentsOfFile:@"/Users/LiHongyao/Desktop/Datas/infomation.text"];

NSLog(@"%@", infomation);

输出情况:

2015-11-17 23:49:39.302 DesignPatterns[1562:170884] {
    account = Admin;
    password = 123456;
}
Program ended with exit code: 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值