单例的实现、使用与架构

[作者原文]http://blog.csdn.net/CHENYUFENG1991/article/details/50396987

单例在我们开发中是最常用的设计模式,在iOS中也是如此。单例可以保证某个类的实例在程序中是唯一的,便于进行资源和数据的共享。使用的设计原则是单一职责原则。我们来看看在iOS中本身自带的类或者方法哪些使用了单例的模式:
(1)UIAccelerometer类和sharedAccelerometer方法,一般如果方法名中有shared这样的词,就可以认为这是一个可以整个应用程序共享的实例变量,一般是使用了单例。
(2)UIApplication类和sharedApplication方法,我们一般使用该方法来创建全局变量。
(3)NSBundle类和mainBundle方法。
(4)NSFileManager类和defaultManager方法。
(5)NSNotificationCenter类和defaultManager方法。其中NSNotificationCenter也实现了观察者模式。
(6)NSUserDefaults类和defaultUser方法。
示例代码上传至:https://github.com/chenyufeng1991/iOS-Singleton

【单例实现】
(1)新建一个普通的类,假设名字为Singleton. 在Singleton.h中声明一个类方法,到时候使用该类方法(注意:一定是类方法,而不是实例方法)可以创建该类的唯一的一个实例:

[objc] view plaincopyprint?
#import <Foundation/Foundation.h>  

@class Singleton;  

@interface Singleton : NSObject  
// "+" 表示类的方法,由类调用  
+(Singleton *)sharedInstance;  

@end2)在Singleton.m中需要实现sharedInstance方法和你其他的业务逻辑:
[objc] view plaincopyprint?
#import "Singleton.h"  

// 用static声明一个类的静态实例;  
static Singleton *_sharedInstance = nil;  

@implementation Singleton  

/** 
 *  1.使用类方法生成这个类唯一的实例; 
 */  
+(Singleton *)sharedInstance{  
    if (!_sharedInstance) {  
        _sharedInstance =[[self alloc]init];  
    }  
    return _sharedInstance;  
}  

@end  

注意:一定要声明一个static的静态变量。以后创建类的唯一实例就使用sharedInstance方法,而不是使用alloc ,init.

(3)我们使用一个简单的demo来演示一下单例:

[objc] view plaincopyprint?
#import "RootVC.h"  
#import "Singleton.h"  

@interface RootVC ()  

@end  

@implementation RootVC  

- (void)viewDidLoad  
{  
  [super viewDidLoad];  

  [self testSigleTon];  
}  

-(void)testSigleTon  
{  
  //单例的结果就是,调用类方法,只返回一个共有的对象  
  /** 
   *  single和single2是同一个对象; 
   因为返回的数据是一个静态变量,全局唯一; 
   */  
  Singleton *single = [Singleton sharedInstance];  
  Singleton *single2 = [Singleton sharedInstance];  

  if (single == single2) {  
    NSLog(@"single == single2");  
  }  

  NSLog(@"single地址:%@",single);  
  NSLog(@"single2地址:%@",single2);  


}  

@end  

[]!https://img-blog.csdn.net/20151224220106085?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

可以看到,两个对象的内存地址是一样的,表示这两个对象其实是同一个对象,单例也就实现了。这是单例最普遍也是最简单的实现方式,在项目中会经常用到,在不涉及多线程的情况下是完全正确的。但是,我们再多想一想,在多线程开发中,这种实现方式是否安全呢?那么应该如何实现。

【单例架构】
   在项目开发中,如果我们像上述实现方法一样,在每个类中都使用这样写一个方法来生成单例,会不会显得很麻烦,很冗余。这样重复在每个类中重复写代码不利于开发与架构,那么我们应该使用什么方法来进行代码抽取呢?解决方案就是使用类别(Category)。关于Category类别的简要介绍,请参考《Objective-C——类别(Category)详解》。具体的实现如下:

(1)新建一个Category,作为对NSObject类的扩展。因为NSObject类是大部分iOS类的基类,如果使用Category为NSObject增加额外方法(shareInstance方法),那么所有继承自NSObject的类都可以使用该方法。我们常用的UIViewController和UIView都是从NSObject继承的,这样就会很方便。

需要在NSObject+Singleton.h头文件中对外暴露一个生成实例的方法,供其他类调用。

[objc] view plaincopyprint?
#import <Foundation/Foundation.h>  

@interface NSObject (Singleton)  

// "+" 表示类的方法,由类调用  
+ (instancetype)sharedInstance;  

@end  

(3)在NSObject+Singleton.m中需要实现上述方法,用来生成某一个类的唯一实例。我这里使用字典来存储某一个类和该类的实例,也就是键值对的形式:键是类名,值是对象。根据类名去检索该类的对象是否已经被创建,如果检索到类名,表示已经被创建,则直接返回对象;如果没有检索到类名,则需要创建,创建完成后也存储到字典中;

[objc] view plaincopyprint?
#import "NSObject+Singleton.h"  

@implementation NSObject (Singleton)  

//使用可变字典存储每个类的单一实例,键为类名,值为该类的对象;  
//声明为静态变量,可以保存上次的值;  
static NSMutableDictionary *instanceDict;  
id instance;  

+ (instancetype)sharedInstance {  
  @synchronized(self)  
  {  
    //初始化字典;  
    if (instanceDict == nil) {  
      instanceDict = [[NSMutableDictionary alloc] init];  
    }  

    //获取类名;  
    NSString *className = NSStringFromClass([self class]);  
    if (className) {  
      //查找字典中该类的对象,使用类名去进行查找,可以确保一个类只被存储一次;  
      instance = instanceDict[className];  
      if (instance == nil) {  
        //该类的对象还没实例化,就进行初始化,并根据键值对的形式存储;  
        instance = [[self.class alloc] init];  
        [instanceDict setValue:instance forKey:className];  
      }else{  
        //该类对象已经存储在字典中,直接返回instance即可;  
      }  
    }else{  
      //没有获取类名,所以确保sharedInstance是一个类方法,用类进行调用;  
    }  

    return instance;  
  }  
}  
@end  

(4)单例的category已经写完,下面将要进行测试。我这里的测试方法如下:两个界面之间进行跳转并返回,使用相同的代码生成类对象;同时新建一个Person类和StudentModel类来测试,两个类都分别继承自NSObject,里面没有任何实现,只用来创建对象。别忘了导入头文件#import “NSObject+Singleton.h”.
第一个界面ViewController.m实现如下:

[objc] view plaincopyprint?
#import "ViewController.h"  
#import "NSObject+Singleton.h"  
#import "Person.h"  
#import "StudentModel.h"  

@interface ViewController ()  

@end  

@implementation ViewController  

- (void)viewDidLoad {  
  [super viewDidLoad];  

}  

- (void)viewDidAppear:(BOOL)animated{  

  [super viewDidAppear:true];  

  //使用sharedInstance创建类对象;  
  ViewController *vc1 = [ViewController sharedInstance];  
  ViewController *vc2 = [ViewController sharedInstance];  
  NSLog(@"ViewController---vc1地址:%@",vc1);  
  NSLog(@"ViewController---vc2地址:%@",vc2);  

  if (vc1 == vc2) {  
    NSLog(@"ViewController---vc1 == vc2");  
  }  

  //循环创建5个Person对象,5个对象都相同;  
  for (int i = 0; i < 5; i++) {  
    Person *per1 = [Person sharedInstance];  
    NSLog(@"ViewController---per1地址:%@",per1);  
  }  

  //使用alloc创建对象,每个对象都是不同的;  
  for (int i = 0; i < 5; i++) {  
    StudentModel *stud = [[StudentModel alloc] init];  
    NSLog(@"ViewController---stud地址:%@",stud);  
  }  


}  

@end  

第二个界面SecondViewController.m实现如下:

[objc] view plaincopyprint?
#import "SecondViewController.h"  
#import "NSObject+Singleton.h"  
#import "Person.h"  
#import "StudentModel.h"  
#import "ViewController.h"  

@interface SecondViewController ()  

@end  

@implementation SecondViewController  

/** 
 *  在另一个界面中做同样的测试; 
 */  
- (void)viewDidLoad {  
  [super viewDidLoad];  

}  

- (void)viewDidAppear:(BOOL)animated{  

  [super viewDidAppear:animated];  

  SecondViewController *secondVc1 = [SecondViewController sharedInstance];  
  SecondViewController *secondVc2 = [SecondViewController sharedInstance];  

  NSLog(@"SecondViewController---secondVc1地址:%@",secondVc1);  
  NSLog(@"SecondViewController---secondVc2地址:%@",secondVc2);  

  if (secondVc1 == secondVc2) {  
    NSLog(@"SecondViewController---secondVc1 == secondVc2");  
  }  

  for (int i = 0; i < 5; i++) {  
    Person *per1 = [Person sharedInstance];  
    NSLog(@"SecondViewController---per1地址:%@",per1);  
  }  

  for (int i = 0; i < 5; i++) {  
    StudentModel *stud = [[StudentModel alloc] init];  
    NSLog(@"SecondViewController---stud地址:%@",stud);  
  }  

}  

/** 
 *  返回上一界面,再次生成对象查看; 
 * 
 *  @param sender <#sender description#> 
 */  
- (IBAction)back:(id)sender {  

  [self dismissViewControllerAnimated:true completion:nil];  
}  

@end  

下面分别是三个步骤打印log:启动第一个界面、跳转到第二个界面、返回第一个界面。
启动第一个界面的输出:
[]!https://img-blog.csdn.net/20151225141656534?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

跳转到第二个界面的输出:
[]!https://img-blog.csdn.net/20151225141815787?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

再次返回到第一个界面的输出:
[]!https://img-blog.csdn.net/20151225141938019?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

总结

通过以上打印输出,使用sharedInstance创建单例后,无论在哪一个界面,每个类的对象是唯一的。而使用alloc创建的对象往往都是不同的。通过以上的设计,就不需要在每一个类中都去实现sharedInstance方法了。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值