單例模式--設計模式


最近大約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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM