引言
在 C 语言中,程序内变量或函数的作用域和寿命是由其存储类确定的,比如static、extern。当 static 使得一个特定的文件中的函数和变量全局可见,extern 则使它们对所有文件可见。
- 使用static结合线程安全模式
dispatch_once
来创建共享实例,并使用条件编译#if进行ARC、MRC的适配。 - 使用extern申明公共方法、全局字符串常量
I 使用static结合线程安全模式来创建共享实例
单例对象应该使用线程安全模式来创建共享实例。
代码语言:javascript复制 (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; });
return sharedInstance;
}
1.1 应用场景
- 将登陆之后的token信息作为单利对象的属性进行存储
- 存储一些本次app内存销毁的变量,比如控制接口的请求标志、蓝牙打印机的连接信息。
/**
>登录账号得到的token信息。最好不要作为一个独立的单利对象存储;而是将它作为单例对象的属性userInfo,这样便于切换账号存储token和其他账号信息
*/
@property (strong, nonatomic) UserInfoModel *userInfo;
/**
控制接口的请求标志
*/
@property (assign, nonatomic) BOOL IsreqGetCurrentSysUsering;
/**
蓝牙打印机的连接信息
*/
@property(nonatomic,strong)CBPeripheral *SelectPeripheral;
使用案例
https://kunnan.blog.csdn.net/article/details/105202605
1.2 单例模式的基本实现
- 一个类只有一个对象
/**
单例模式:一个类只有一个对象
*/
@implementation KNMusicTool
extern id _musicTool;//全局变量
/**
alloc方法内部会调用这个方法
*/
(instancetype)allocWithZone:(struct _NSZone *)zone{
NSLog(@"%s",__func__);
@synchronized(self) {//多线程
//创建对象的内存空间
if (nil == _musicTool) {
_musicTool = [super allocWithZone:zone];
}
}
return _musicTool;
}
/** 方便访问单例对象*/
(instancetype)sharedMusicTool{
@synchronized(self) {
//操作共享资源
if (nil == _musicTool) {
_musicTool = [[self alloc]init];
}
}
return _musicTool;
}
/** NSLog(@"%@",[[[HLMusicTool alloc]init] copy]);//Returns the object returned by copyWithZone:. copyWithZone 遵守协议NSCopying
*/
- (id)copyWithZone:(nullable NSZone *)zone{
return _musicTool;
}
1.3 单例模式的完善
问题1:extern 对全局变量的引用 会在全程序中查找 _musicTool,在其他类引用全局变量,其他类就可以修改全局变量的值,导致单例对象存在被修改的风险。
代码语言:javascript复制 extern id _musicTool; //引用全局变量,会在全程序中查找 _musicTool,在其他类引用全局变量,其他类就可以修改全局变量的值,导致单例对象存在被修改的风险。
// 解决方法:static 修饰的全局变量,这样其他类就无法引用
解决办法:使用static进行修饰,作用域仅限于当前的文件。
代码语言:javascript复制 static id _musicTool;
问题2: 多次加锁
代码语言:javascript复制 (instancetype)sharedMusicTool{
@synchronized(self) {
//操作共享资源
if (nil == _musicTool) {
_musicTool = [[self alloc]init];
}
}
return _musicTool;
}
解决:只有在确实需要创建对象的时候,才进行加锁。
代码语言:javascript复制 if (nil == _musicTool) {//加锁之前先进行是否满足创建对象的条件
@synchronized(self) {
//操作共享资源
if (nil == _musicTool) {//防止创建多次
_musicTool = [[self alloc]init];
}
}
}
例子:使用static代替extern,加锁之前先进行是否满足创建对象的条件。
代码语言:javascript复制/* static 修饰变量:
1)static的局部变量:保证只初始化一次,在程序运行过程中只有一份内容;--局部变量的生命周期和全局变量类似,但是不能改变作用域
2)static 修饰的全局变量:不允许本类的h文件访问。即作用域仅限于当前的文件
*/
static id _musicTool;//全局变量
/**
alloc方法内部会调用这个方法
*/
(instancetype)allocWithZone:(struct _NSZone *)zone{
NSLog(@"%s",__func__);
if (nil == _musicTool) {
@synchronized(self) {//多线程
//创建对象的内存空间
if (nil == _musicTool) {
_musicTool = [super allocWithZone:zone];
}
}
}
return _musicTool;
}
/** 方便放回单例对象*/
(instancetype)sharedMusicTool{
if (_musicTool == nil) {//防止频繁加锁
//加锁
@synchronized(self) {
//操作共享资源
if (nil == _musicTool) {//防止创建多次
_musicTool = [[self alloc]init];
}
}
}
return _musicTool;
}
/** NSLog(@"%@",[[[HLMusicTool alloc]init] copy]);//Returns the object returned by copyWithZone:. copyWithZone 遵守协议NSCopying
*/
- (id)copyWithZone:(nullable NSZone *)zone{
return _musicTool;
}
1.4 饿汉式
代码语言:javascript复制
/*
ninitailize、load方法的区别:
initailize、load都是类方法
当一个类 或者分类被装载进内存时,就会调用一次load方法(当时这个类还不可用)
当第一次使用这个类时,就会调用一次initailize方法
*/
/** Invoked whenever a class or category is added to the Objective-C runtime; 因此不存在多线程问题*/
(void)load{
_instance = [[KNSoundTool alloc]init];
NSLog(@"%s----instance%@",__func__,_instance);
}
// (void)initialize{
// NSLog(@"%s",__func__);
//}
(instancetype)allocWithZone:(struct _NSZone *)zone{
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
return _instance;
}
(instancetype)shareSoundTool{
return _instance;
}
II 使用GCD结合宏来实现单例
使用static结合线程安全模式dispatch_once
来创建共享实例,并使用条件编译#if进行ARC、MRC的适配。
缺点:宏定义的代码不好调试
2.1 ARC 环境下的GCD实现单例
代码语言:javascript复制/*
GCD 实现单例
*/
static id _dataTool;
(instancetype)shareDataTool{
//GCD
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{//一次性代码
_dataTool = [[self alloc]init];
});
return _dataTool;
}
(instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_dataTool = [super allocWithZone:zone];
});
return _dataTool;
}
- (id)copyWithZone:(NSZone *)zone{
return _dataTool;
}
2.2 非ARC的单例模式
- release重写,进行阻止单例对象的释放
@implementation HSDataTool
static id _dataTool;
(instancetype)shareDataTool{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_dataTool = [[HSDataTool alloc]init];
});
return _dataTool;
}
- (id)copyWithZone:(NSZone *)zone{
return _dataTool;
}
(instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_dataTool = [super allocWithZone:zone];
NSLog(@"%s",__func__);
});
return _dataTool;
}
#pragma mark - MRC环境的适配:重写
/*
Decrements the receiver’s reference count.
The receiver is sent a dealloc message when its reference count reaches 0.
*/
- (oneway void)release{
}
- (instancetype)retain{
return self;
}
- (NSUInteger)retainCount{
return 1;
}
2.3 GCD和宏来实现单例的具体代码
- 单例内容
#ifndef HSSingleton_h
#define HSSingleton_h
//头文件的单例内容
#define HSSingletonH(classname) (instancetype)share##classname
//.m文件的单例代码
#define HSSingletonM(classname)
static id _instance;
(instancetype)share##classname{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc]init];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
return _instance;
}
(instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
- (oneway void)release{
}
- (instancetype)retain{
return self;
}
- (NSUInteger)retainCount{
return 1;
}
#endif /* HSSingleton_h */
2.4 ARC、MRC的适配(条件编译)
代码语言:javascript复制//头文件的单例内容
#define HSSingletonH(classname) (instancetype)share##classname
//.m文件的单例代码
#if __has_feature(objc_arc)
#define HSSingletonM(classname)
static id _instance;
(instancetype)share##classname{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc]init];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
return _instance;
}
(instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
#else
#define HSSingletonM(classname)
static id _instance;
(instancetype)share##classname{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc]init];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
return _instance;
}
(instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
- (oneway void)release{
}
- (instancetype)retain{
return self;
}
- (NSUInteger)retainCount{
return 1;
}
- (instancetype)autorelease{
return self;
}
#endif
III 使用extern申明公共方法、全局字符串常量
3.1 全局字符串常量
全局的字符串常量代替宏常量,节省内存空间。
https://blog.csdn.net/z929118967/article/details/125203502
应用场景:在声明诸如 userInfo 字典,NSNotification 名称和 NSError 域的时候。
代码语言:javascript复制/// The domain for errors originating within `RACCommand`.
extern NSErrorDomain const RACCommandErrorDomain;
typedef NS_ERROR_ENUM(RACCommandErrorDomain, RACCommandError) {
/// -execute: was invoked while the command was disabled.
RACCommandErrorNotEnabled = 1,
};
实现方式:在公共头文件里申明一个 extern 的 NSString * const,并在实现文件里定义该 NSString * const:
- 公共头文件:Consts.h
extern NSString * _Nonnull const KNClientId;//
//key 存储到 NSUserDefaults 里(类型为 NSDictionary<NSString *, NSNumber *> *)
- 实现文件:定义全局字符串常量
#define KNClientId @""//宏会在编译时,将所有引用宏变量的地方,进行值的替换,造成很多相同的临时字面量,浪费内存
NSString * const KNClientId = @"";// 全局的const常量代替宏常量,节省内存空间。内存只有一份
使用字符串常量来代替宏的使用:
- 定义const 全局常量 ,保证只在一处定义,多处进行引用。
- 全局的const常量代替宏常量,节省内存空间(内存只有一份)。
当 static 使得一个特定的文件中的函数和变量全局可见,extern 则使它们对所有文件可见。
3.2 公共方法
应用场景:
- 仅提供辅助而与具体状态无关的方法:枚举类型转字符、蓝牙连接状态的判断、转换目标经纬度为高德坐标系、是否为海外用户、清除缓存
//TransactionStateMachine.h
extern NSString * NSStringFromTransactionState(TransactionStateENUM state);
//TransactionStateMachine.m
NSString * NSStringFromTransactionState(TransactionState state) {//枚举类型转字符
switch (state) {
case TransactionOpened:
return @"Opened";
case TransactionPending:
return @"Pending";
case TransactionClosed:
return @"Closed";
default:
return nil;
}
}
- 法并没有被公开声明,所以你必须要自己声明:
extern uint64_t dispatch_benchmark(size_t count, void (^block)(void));//The dispatch_benchmark function returns the average number of nanoseconds the given block takes to execute.
extern "C" MGCopyAnswer(CFStringRef prop);//可利用MGCopyAnswer(“UniqueDeviceID”)读取设备GUID
- 转换目标经纬度为高德坐标系
FOUNDATION_EXTERN
代码语言:javascript复制#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif
///AMapLocation CoordinateType
typedef NS_ENUM(NSUInteger, AMapLocationCoordinateType)
{
AMapLocationCoordinateTypeBaidu = 0, ///<Baidu
AMapLocationCoordinateTypeMapBar, ///<MapBar
AMapLocationCoordinateTypeMapABC, ///<MapABC
AMapLocationCoordinateTypeSoSoMap, ///<SoSoMap
AMapLocationCoordinateTypeAliYun, ///<AliYun
AMapLocationCoordinateTypeGoogle, ///<Google
AMapLocationCoordinateTypeGPS, ///<GPS
};
/**
* @brief 转换目标经纬度为高德坐标系
* @param coordinate 待转换的经纬度
* @param type 坐标系类型
* @return 高德坐标系经纬度
*/
FOUNDATION_EXTERN CLLocationCoordinate2D AMapLocationCoordinateConvert(CLLocationCoordinate2D coordinate, AMapLocationCoordinateType type);
FOUNDATION_EXTERN BOOL AMapLocationDataAvailableForCoordinate(CLLocationCoordinate2D coordinate);
- UIRectClip
#ifdef __cplusplus//如果定义了__cplusplus,那么当前源代码被当中C 源代码处理,
#define UIKIT_EXTERN extern "C" __attribute__((visibility ("default")))
#else//如果没有定义__cplusplus, 那么当前源代码被当作C源代码处理
#define UIKIT_EXTERN extern __attribute__((visibility ("default")))
#endif
//对指定符号增加visibility(“default”)来导出符号
UIKIT_EXTERN void UIRectClip(CGRect rect);
- 是否为海外用户
代码语言:javascript复制https://blog.csdn.net/z929118967/article/details/120510396 iOS设备限制境外交易(支付交易风险控制)
/**
* 是否为海外用户...海外用户,SDK内部会屏蔽一些操作 默认为NO.
* @warning AMapServices初始化之前,设置才能生效
*/
extern BOOL _amapLocationOverseas;
- 删除数据库文件
//BGFMDBConfig_h
/**
删除数据库文件
*/
extern BOOL bg_deleteSqlite(NSString*_Nonnull sqliteName);
//BGTool.m
/**
删除数据库文件
*/
BOOL bg_deleteSqlite(NSString*_Nonnull sqliteName){
return [BGDB deleteSqlite:sqliteName];
}
//BGDB.m
/**
删除数据库文件.
*/
(BOOL)deleteSqlite:(NSString*)sqliteName{
NSString* filePath = CachePath(([NSString stringWithFormat:@"%@.db",sqliteName]));
NSFileManager * file_manager = [NSFileManager defaultManager];
NSError* error;
if ([file_manager fileExistsAtPath:filePath]) {
[file_manager removeItemAtPath:filePath error:&error];
}
return error==nil;
}
- 清除缓存
//BGFMDBConfig_h
/**
清除缓存
*/
extern void bg_cleanCache();
//BGTool.m
/**
清除缓存
*/
void bg_cleanCache(){
[[NSCache bg_cache] removeAllObjects];
}
- 蓝牙连接状态的判断
//QCTPrinterManager.h
extern BOOL checkisBluetoothConnected();
//QCTPrinterManager.m
BOOL checkisBluetoothConnected(){
NSLog(@"SelectPeripheral.state %ld", [QCTSession shareQCTSession].SelectPeripheral.state);
switch ([QCTSession shareQCTSession].SelectPeripheral.state) {
case CBPeripheralStateConnected:
return YES;
break;
default:
return NO;
break;
}
}