单例模式

作者: 952625a28d0d | 来源:发表于2015-11-06 19:59 被阅读209次
    • 什么是单例模式
    • 编写严格的单例
    • 用单例优化本地存储
    Paste_Image.png
    • 单例模式的基本原理:
      系统中的类只有一个实例且易于外界访问。

    • 单例模式用于解决何种问题?

    • 单例模式的优缺点

    • 系统中的单例举例

    [[UIApplication sharedApplication] statusBarStyle];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notififationAction) name:@"noti" object:nil];
        [[NSUserDefaults standardUserDefaults] setObject:@"user" forKey:@"user"];
        [NSFileManager defaultManager];```
    
    观察系统的单例类之后,我们都会发现单例的类都会通过一个方法来获取这个类实例,然后通过这个实例来做一些事情。单例一般都是用来管理某种资源,某种对象,而这个对象拥有某些属性供全局使用。
    大部分情况,我们使用单例是为了共享信息。
    缺点:因为他共享了信息,破坏了最少支持原则
    破坏了封装性。
    但是解决实际问题的时候也是可以忽略的。
    
    - 我们来写一个用户信息管理中心单例
    
    

    import <Foundation/Foundation.h>

    @interface UserInfoManagerCenter : NSObject

    @property(nonatomic, copy) NSString * name;
    @property(nonatomic, strong) NSNumber * age;

    • (instancetype)managerCenter;

    @end```

    h:

    #import "UserInfoManagerCenter.h"
    
    @implementation UserInfoManagerCenter
    
    #pragma mark - 第三种创建单例的方法,不好,即写在这个类里面,因为此类是任何一个类在调用类方法之前必须调用的方法
    + (void)initialize{
        static UserInfoManagerCenter *center = nil;
        if (self == [UserInfoManagerCenter class]) {
            center = [[UserInfoManagerCenter alloc] init];
        }
        
    }
    
    #pragma mark - 一般解决方案,不好,如果AppDelegate和ViewController同时访问这个单例,如果同时进入方法进行初始化,那么就会出现问题
    + (instancetype)managerCenter{
        
        // 用静态变量持有对象
        static UserInfoManagerCenter *center = nil;
        if (center == nil) {
            center = [[UserInfoManagerCenter alloc] init];
        }
        return center;
    }
    
    #pragma mark - 好的解决方案,用dispatch_once来解决他们的竞争问题
    + (instancetype)managerCenter{
        static UserInfoManagerCenter *center = nil;
        static dispatch_once_t predicate;
        dispatch_once(&predicate, ^{
            center = [[UserInfoManagerCenter alloc] init];
        });
        return center;
    }
    
    @end```
    
    - 使用单例
    AppDelegate中赋值
    
    
    • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      // Override point for customization after application launch.

      UserInfoManagerCenter *center = [UserInfoManagerCenter managerCenter];
      center.name = @"贾元发";
      center.age = @23;
      return YES;
      }```

    viewController中取出

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        [[UIApplication sharedApplication] statusBarStyle];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notififationAction) name:@"noti" object:nil];
        [[NSUserDefaults standardUserDefaults] setObject:@"user" forKey:@"user"];
        [NSFileManager defaultManager];
        
        UserInfoManagerCenter *center = [UserInfoManagerCenter managerCenter];
        NSLog(@"%@%@",center.name,center.age);
        
    }```
    
    
    ![Paste_Image.png](http:https://img.haomeiwen.com/i189984/61a92c34d4be032a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    以上是一个单例的典型用法,但是并不是完美单例,因为我们还能通过alloc init方法再次创建对象,严格来说是不行的。
    
    - 那么如何实现一个严格的单例模式?
    
    我们可以写一个方法,以检测单例方法实现只有UserInfoManager类在调用,如果是别的类在调用会直接导致崩溃,这个怎么实现呢?
    
    - 如何防止子类继承?
    
    

    pragma mark - 好的解决方案,用dispatch_once来解决他们的竞争问题

    • (instancetype)managerCenter{
      static UserInfoManagerCenter *center = nil;
      static dispatch_once_t predicate;
      dispatch_once(&predicate, ^{
      center = [[UserInfoManagerCenter alloc] init];
      });

      // 防止子类使用
      // 获取类名
      NSString *classString = NSStringFromClass([self class]);
      // 判断是否与本类类名相同
      if ([classString isEqualToString:@"UserInfoManagerCenter"] == NO) {
      // 如果不直接崩溃
      NSParameterAssert(nil);
      }
      return center;
      }```

    • 如何确保实例对象只出现一个
    Paste_Image.png

    上图我们用两种方法创建了单例类的实例对象,发现打印出来的地址并不一样,这就造成了单例对象不唯一的问题

    在一个系统中,单例作为一个管理中心对象,他的开销是非常大的,所以保持唯一性非常重要,我已我们不能让单例通过alloc init方法初始化,换句话说我们要让这种初始化方法失效。

    • 第一步:重写单例类的init方法
      声明类全局静态
    Paste_Image.png

    在单例里面

    Paste_Image.png
    #pragma mark - 重写单例类的初始化方法
    - (instancetype)init{
        // 用字符串接收静态变量
        NSString *string = (NSString *)center;
        // 判断是否是字符串类并且等于创建单例方法中的字符串
        if ([string isKindOfClass:[NSString class]] && [string isEqualToString:@"UserInfoManagerCenter"]) {
            self = [super init];
            if (self) {
                
            }
            return self;
        }else{
            // 如果不,则初始化返回为nil
            return nil;
        }
    }
    @end```
    
    - 运行结果
    
    ![Paste_Image.png](http:https://img.haomeiwen.com/i189984/f9501aa9dc52cfd6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    发现用alloc init创建的对象已经为nil
    当然我们也需要在init方法中实现子类的alloc init滞空
    
    

    pragma mark - 重写单例类的初始化方法

    • (instancetype)init{
      // 用字符串接收静态变量
      NSString *string = (NSString *)center;
      // 判断是否是字符串类并且等于创建单例方法中的字符串
      if ([string isKindOfClass:[NSString class]] && [string isEqualToString:@"UserInfoManagerCenter"]) {
      self = [super init];
      if (self) {
      // 防止子类使用
      // 获取类名
      NSString *classString = NSStringFromClass([self class]);
      // 判断是否与本类类名相同
      if ([classString isEqualToString:@"UserInfoManagerCenter"] == NO) {
      // 如果不直接崩溃
      NSParameterAssert(nil);
      }

        }
        return self;
      

      }else{
      // 如果不,则初始化返回为nil
      return nil;
      }
      }
      @end```

    • 优化本地存储
      1:用单例设计存储数据接口
      2:用单例接口隔离实现细节
      3:在单例提供接口的基础上进行上层封装

    这里给大家推荐一个第三方: FastCoding
    github地址为:https://github.com/nicklockwood/FastCoding
    这个第三方的强大之处在于能够不遵守系统的NSCoding协议对模型、数组、等等数据进行存储!下面我们用单例结合这个第三方对数据存储封装一下吧!
    首先,引入引入第三方运行之后发现一个警告!

    Paste_Image.png Paste_Image.png

    警告的意思是我们的项目使用了ARC,这个第三方在ARC下运行缓慢,我们在这个文件后面添加-fno-objc-arc即可

    Paste_Image.png

    再次运行发现警告消失

    开始编写代码:
    首先写一个单例用来存储数据:

    #import <Foundation/Foundation.h>
    
    @interface StoreValue : NSObject
    
    + (StoreValue *)shareInstance;  // 单例初始化
    
    - (void)storeValue:(id)object withKey:(NSString *)key;  // 存储值
    - (id)valueWithKey:(NSString *)key; // 取出
    
    @end```
    
    m文件
    
    

    import "StoreValue.h"

    import "FastCoder.h" // 引入头文件

    @implementation StoreValue

    • (StoreValue *)shareInstance{
      static StoreValue *storeValue = nil;
      static dispatch_once_t predicate;
      dispatch_once(&predicate, ^{
      storeValue = [[StoreValue alloc] init];
      });
      return storeValue;
      }
    • (void)storeValue:(id)object withKey:(NSString *)key{
      NSParameterAssert(object);
      NSParameterAssert(key); // 空值崩溃
      // 把任意类型的对象用FastCodingl类转换为NSData类型存储
      NSData *data = [FastCoder dataWithRootObject:object];
      [[NSUserDefaults standardUserDefaults] setObject:data forKey:key];

    }

    • (id)valueWithKey:(NSString *)key{
      NSParameterAssert(key);
      NSData *data = [[NSUserDefaults standardUserDefaults] valueForKey:key];
      return [FastCoder objectWithData:data];
      }

    @end```

    • 测试:
    Paste_Image.png

    已经有了值
    下面我们来写一个复杂的存储,看看能不能实现,首先我们来写两个模型

    Paste_Image.png
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
    //    NSArray *array = @[@"贾元发"];
    //    [[StoreValue shareInstance] storeValue:array withKey:@"jia"];
    //    NSLog(@"%@",[[StoreValue shareInstance] valueWithKey:@"jia"]);
        Student *student = [[Student alloc] init];
        student.name = @"jiayuanfa";
        NSArray *array = @[[Teacher new],[Teacher new],[Teacher new]];
        student.teachersArray = array;
        [[StoreValue shareInstance] storeValue:student withKey:@"stu"];
        Student *student1 = [[StoreValue shareInstance] valueWithKey:@"stu"];
        NSLog(@"%@",student1.name);
        NSLog(@"%@",student1.teachersArray);
    
    }```
    
    运行结果:
    
    ![Paste_Image.png](http:https://img.haomeiwen.com/i189984/bd2dbc0d84fbd362.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    发现复杂的数据我们也能存储
    
    - 用类目对单例存储进行进一步上层的封装以便我们用对象直接调用存储的方法
    
    ![Paste_Image.png](http:https://img.haomeiwen.com/i189984/97c7257f1ce76cd2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    

    import <Foundation/Foundation.h>

    @interface NSObject (StoreValue)

    // 添加类目方法

    • (void)storeValueWithKey:(NSString *)key;
    • (id)valueWithKey:(NSString *)key;

    @end```

    m

    #import "NSObject+StoreValue.h"
    #import "StoreValue.h"
    
    @implementation NSObject (StoreValue)
    
    - (void)storeValueWithKey:(NSString *)key{
        [[StoreValue shareInstance] storeValue:self withKey:key];
    }
    
    + (id)valueWithKey:(NSString *)key{
        return [[StoreValue shareInstance] valueWithKey:key];
    }
    
    @end```
    
    

    import "ViewController.h"

    import "StoreValue.h"

    import "Teacher.h"

    import "Student.h"

    import "NSObject+StoreValue.h"

    @interface ViewController ()

    @end

    @implementation ViewController

    • (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.

    // NSArray *array = @[@"贾元发"];
    // [[StoreValue shareInstance] storeValue:array withKey:@"jia"];
    // NSLog(@"%@",[[StoreValue shareInstance] valueWithKey:@"jia"]);
    Student *student = [[Student alloc] init];
    student.name = @"jiayuanfa";
    NSArray *array = @[[Teacher new],[Teacher new],[Teacher new]];
    student.teachersArray = array;
    // [[StoreValue shareInstance] storeValue:student withKey:@"stu"];
    // 模型调用直接存储
    [student storeValueWithKey:@"stu"];
    Student *student1 = [Student valueWithKey:@"stu"];
    NSLog(@"%@",student1.name);
    NSLog(@"%@",student1.teachersArray);

    }```

    结果:

    Paste_Image.png

    以上我们对单例存储用类目进行了进一步的封装。

    需要掌握的技术:
    1:单例核心原理
    2:严格单例思想
    3:用单例优化存储模式

    相关文章

      网友评论

      本文标题:单例模式

      本文链接:https://www.haomeiwen.com/subject/irschttx.html