单例模式--设计模式

本人已迁移博客至掘进,以后会在掘进平台更新最新的文章也会有更多的干货,欢迎大家关注!!!https://juejin.im/user/588993965333309

 

最近大约15天左右,自己想整理设计模式方面的问题,毕竟在研发过程中,对书写代码的质量还是有很大的提高的。本篇将讲述23中设计模式中的第一种----单例模式。读下来大约10-15分钟,前面讲述单例模式的创建方式,后面讲述项目中的实际用处,欢迎大家指正。

 

单例模式

一、定义

所谓的单例模式,就是一个单例类,在整个应用程序中都只有一个实例,在其中,并且提供一个类方法来供全局调用,在编译期间,会一直存储在内存中,直到App程序退出,系统自动释放此内存。

 

二、生命周期

在应用程序中,一个单例类在应用程序中就只能初始化一次,为了达到在使用过程中一直存在该单例,所以单例是存储在存储器的全局局域,在编译分配内存,在应用程序退出结束后由系统本身释放这部分内存。

下面是关于不同变量在手机中的存储位置

栈:临时变量(由编译器自动管理创建,分配以及释放的,栈中的内存被调用是处于存储空间中,调用完毕后会有系统自动的释放内存)

:通过calloc,malloc,alloc以及new来申请内存,释放也由开发者手动调用free或者delete来释放,在ARC模式下,是由系统自动管理的。

全局区域:静态变量(是由编译时来分配,App结束后由系统本身释放)

常量:常量(是由编译时分配,app结束由系统本身释放)

代码区:存放代码

 

三、创建方式

3.1 GCD方式(dispatch_once_t)苹果官方推荐使用(保证线程和数据安全

#import "Upload_Image.h"

static Upload_Image *uploadImage = nil;

@implementation Upload_Image
//单例方法
+ (Upload_Image *)shareUploadImage{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        uploadImage = [[Upload_Image alloc]init];
    });
    return uploadImage;
}

 

3.2 线程锁

#import "ViewController.h"

static ViewController * _singletonVC ;

@interface ViewController ()

@end

@implementation ViewController

+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    @synchronized (self){
        if (_singletonVC) {
            _singletonVC = [super allocWithZone:zone];
        }
    }
    return _singletonVC;
}

+ (instancetype)share{
    
    return  [[self alloc] init];
}

 

发现一个项目可能会有很多单例,不能每个类都手动的写一遍代码,因此可以把单例的创建写成宏,如下:

#define DJ_SINGLETON_DEF(_type_) + (_type_ *)sharedInstance;\
+(instancetype) alloc __attribute__((unavailable("call sharedInstance instead")));\
+(instancetype) new __attribute__((unavailable("call sharedInstance instead")));\
-(instancetype) copy __attribute__((unavailable("call sharedInstance instead")));\
-(instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead")));\
#define DJ_SINGLETON_IMP(_type_) + (_type_ *)sharedInstance{\
static _type_ *theSharedInstance = nil;\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
theSharedInstance = [[super alloc] init];\
});\
return theSharedInstance;\
}

我们可以按照下面使用方法使用如下:

@interface DJSingleton : NSObject
    DJ_SINGLETON_DEF(DJSingleton);
@end
@implementation DJSingleton
    DJ_SINGLETON_IMP(DJSingleton);
@end

 

四、项目使用

我们下面以我公司的上传图片和拍照的工具类为主,介绍一下单例模式的使用(也可以直接拿走用于拍照选择图片上传)直接导入Upload_Image这个类,具体demo会上传到github中

4.1 我们看一下Upload_Image.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

/**
 定义一个单例,方便外部使用
 */
#define UPLOAD_IMAGE [Upload_Image shareUploadImage]

/**
 代理方法
 */
@protocol Upload_ImageDelegate<NSObject>

//将图片上传到阿里云服务器
- (void)uploadImageToServerWithImage:(UIImage *)image;

@optional
- (void)uploadImageDidClickCancel;

@end

@interface Upload_Image : NSObject<UIActionSheetDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate,UIAlertViewDelegate>

@property (nonatomic,weak)id<Upload_ImageDelegate> delegate;

@property (nonatomic,strong)UIViewController *fatherViewController;

//单例方法
+ (Upload_Image *)shareUploadImage;

//弹出选择项窗口的方法
- (void)showActionSheetInFatherViewController:(UIViewController *)fatherVC delegate:(id<Upload_ImageDelegate>)aDelegate;


@end

我们看一下实现方法

#import "Upload_Image.h"

#define WS(weakSelf)  __weak __typeof(&*self)weakSelf = self

static Upload_Image *uploadImage = nil;

@implementation Upload_Image
//单例方法
+ (Upload_Image *)shareUploadImage{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        uploadImage = [[Upload_Image alloc]init];
    });
    return uploadImage;
}

//显示ActionSheet方法
- (void)showActionSheetInFatherViewController:(UIViewController *)fatherVC delegate:(id<Upload_ImageDelegate>)aDelegate{
    uploadImage.delegate = aDelegate;
    self.fatherViewController = fatherVC;
    
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
    WS(weakSelf);
    UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        [fatherVC dismissViewControllerAnimated:YES completion:nil];
        weakSelf.fatherViewController = nil;
    }];
    UIAlertAction* firstAction = [UIAlertAction actionWithTitle:@"使用相机拍照" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [fatherVC dismissViewControllerAnimated:YES completion:nil];
        [self createPhotoView];

    }];
    UIAlertAction* secondAction = [UIAlertAction actionWithTitle:@"使用相册照片" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [fatherVC dismissViewControllerAnimated:YES completion:nil];
         [self fromPhotos];
    }];
    [alert addAction:cancelAction];
    [alert addAction:firstAction];
    [alert addAction:secondAction];
    [fatherVC presentViewController:alert animated:YES completion:^{
        nil;
    }];
}

#pragma mark - 头像图片(从相机中选择得到)
- (void)createPhotoView {
    // ** 设置相机模式
    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
        UIImagePickerController *imagePC = [[UIImagePickerController alloc] init];
        imagePC.sourceType  = UIImagePickerControllerSourceTypeCamera;
        imagePC.delegate = self;
        imagePC.allowsEditing = YES;
        [_fatherViewController presentViewController:imagePC animated:YES completion:nil];
    } else {
        UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"提示"
                                                         message:@"该设备没有照相机"
                                                        delegate:nil
                                               cancelButtonTitle:@"确定"
                                               otherButtonTitles:nil];
        [alert show];
    }
}

#pragma mark - 图片库方法(从手机的图片库中查找图片)
- (void)fromPhotos {
    UIImagePickerController *imagePC = [[UIImagePickerController alloc] init];
    imagePC.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    imagePC.delegate = self;
    imagePC.allowsEditing = YES;
    [_fatherViewController presentViewController:imagePC animated:YES completion:nil];
}

#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    [picker dismissViewControllerAnimated:YES completion:nil];
    UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
/**开始上传图片*/
    if (self.delegate && [self.delegate respondsToSelector:@selector(uploadImageToServerWithImage:)]) {
        [self.delegate uploadImageToServerWithImage:image];
    }
    self.fatherViewController = nil;
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    [picker dismissViewControllerAnimated:YES completion:nil];
//    if (self.delegate && [self.delegate respondsToSelector:@selector(uploadImageDidClickCancel)]) {
//        [self.delegate uploadImageDidClickCancel];
//    }
    self.fatherViewController = nil;
}

//- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(nullable NSDictionary<NSString *,id> *)editingInfo NS_DEPRECATED_IOS(2_0, 3_0);


//- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary<NSString *,id> *)editingInfo{
//    [picker dismissViewControllerAnimated:YES completion:nil];
//}

@end

 

4.2 具体使用

倒入头文件,遵守代理

 

在使用地方拉起弹框

选择好图片之后,然后就上传到阿里云服务器

 

大家可以下载demo github地址:https://github.com/zxy1829760/uploadImageSingleton

 

 

五、单例模式存在的问题

5.1 内存问题

从上面知道,单例对象在程序的整个生命周期都会存在,如果单例比较大时,就会存在占据更多的内存。还有如果单例引用了另外的对象,也是个问题,别的对象引用因为单例对象不能释放从而不能释放。参看上面4.1中代码里面标红处。

对于这个问题,我们可以这样,在需要的时候加载出来,使用完之后再释放,从而不会有强引用现象,如果再次需要,下次再重新加载回来即可。

 

5.2 循环依赖

在我们实际开发中,单例对象可能会有属性,这些属性都是在init的时候创建以及初始化。一个单例M的m属性依赖于单例N,而单例N的属性n又依赖于单例M,就这样在初始化会出现一种情况-循环依赖问题,问题出现在dispatch_once中:如下面:

@interface DJSingletonM : NSObject
DJ_SINGLETON_DEF(DJSingletonM);
@end
@interface DJSingletonN : NSObject
DJ_SINGLETON_DEF(DJSingletonN);
@end
@interface DJSingletonM()
@property(nonatomic, strong) id someObj;
@end
@implementation DJSingletonM
DJ_SINGLETON_IMP(DJSingletonM);
-(id)init{
    if (self = [super init]) {
        _someObj = [DJSingletonN sharedInstance];
    }
    return self;
}
@end
@interface DJSingletonN()
@property(nonatomic, strong) id someObj;
@end
@implementation DJSingletonN
DJ_SINGLETON_IMP(DJSingletonN);
-(id)init{
    if (self = [super init]) {
        _someObj = [DJSingletonM sharedInstance];
    }
    return self;
}
@end
//---------------------------------------
DJSingletonM * s1 = [DJSingletonM sharedInstance];

如果这样写会报错误,EXC_BREAKPOINT()错误

对于可能 这样的错误,我们在设计的时候,就要考虑清楚,初始化的过程中,不依赖其它对象。如果必须要依赖,我们可以考虑异步初始化的方式,或者在内部做个标识也可。

 

上面就是23中设计模式的一种--单例模式,赋有变量内存,项目demo使用,以及潜在的问题讲解等,希望可以帮助大家对单例模式的使用和理解有个更好的理解,欢迎大家指正!!!

上面的demo地址:https://github.com/zxy1829760/uploadImageSingleton

posted @ 2018-07-10 16:39  国孩  阅读(1604)  评论(2编辑  收藏  举报