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