前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS小技能: static、extern 存储类的应用(创建共享实例、申明公共方法、全局字符串常量)

iOS小技能: static、extern 存储类的应用(创建共享实例、申明公共方法、全局字符串常量)

作者头像
公众号iOS逆向
发布2022-08-22 11:14:42
8930
发布2022-08-22 11:14:42
举报
文章被收录于专栏:iOS逆向与安全

引言

在 C 语言中,程序内变量或函数的作用域和寿命是由其存储类确定的,比如static、extern。当 static 使得一个特定的文件中的函数和变量全局可见,extern 则使它们对所有文件可见。

  1. 使用static结合线程安全模式dispatch_once来创建共享实例,并使用条件编译#if进行ARC、MRC的适配。
  2. 使用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 应用场景

  1. 将登陆之后的token信息作为单利对象的属性进行存储
  2. 存储一些本次app内存销毁的变量,比如控制接口的请求标志、蓝牙打印机的连接信息。
代码语言:javascript
复制
/**
>登录账号得到的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 单例模式的基本实现

  • 一个类只有一个对象
代码语言:javascript
复制
/**
 单例模式:一个类只有一个对象
 */
@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重写,进行阻止单例对象的释放
代码语言:javascript
复制
@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和宏来实现单例的具体代码

  • 单例内容
代码语言:javascript
复制

#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:

  1. 公共头文件:Consts.h
代码语言:javascript
复制
extern NSString * _Nonnull const KNClientId;//
//key 存储到 NSUserDefaults 里(类型为 NSDictionary<NSString *, NSNumber *> *)

  1. 实现文件:定义全局字符串常量
代码语言:javascript
复制

#define KNClientId @""//宏会在编译时,将所有引用宏变量的地方,进行值的替换,造成很多相同的临时字面量,浪费内存
NSString * const KNClientId = @"";// 全局的const常量代替宏常量,节省内存空间。内存只有一份

使用字符串常量来代替宏的使用:

  1. 定义const 全局常量 ,保证只在一处定义,多处进行引用。
  2. 全局的const常量代替宏常量,节省内存空间(内存只有一份)。

当 static 使得一个特定的文件中的函数和变量全局可见,extern 则使它们对所有文件可见。

3.2 公共方法

应用场景:

  1. 仅提供辅助而与具体状态无关的方法:枚举类型转字符、蓝牙连接状态的判断、转换目标经纬度为高德坐标系、是否为海外用户、清除缓存
代码语言:javascript
复制
//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;
  }
}

  1. 法并没有被公开声明,所以你必须要自己声明:
代码语言:javascript
复制
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

  1. 转换目标经纬度为高德坐标系

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);

  1. UIRectClip
代码语言:javascript
复制

#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);

  1. 是否为海外用户

https://blog.csdn.net/z929118967/article/details/120510396 iOS设备限制境外交易(支付交易风险控制)

代码语言:javascript
复制
/**
 * 是否为海外用户...海外用户,SDK内部会屏蔽一些操作 默认为NO.
 * @warning AMapServices初始化之前,设置才能生效
 */
extern BOOL _amapLocationOverseas;

  1. 删除数据库文件
代码语言:javascript
复制
//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;
}

  1. 清除缓存
代码语言:javascript
复制
//BGFMDBConfig_h
/**
 清除缓存
 */
extern void bg_cleanCache();
//BGTool.m
/**
 清除缓存
 */
void bg_cleanCache(){
    [[NSCache bg_cache] removeAllObjects];
}

  1. 蓝牙连接状态的判断
代码语言:javascript
复制

//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;
    }
    
    
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS逆向 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • I 使用static结合线程安全模式来创建共享实例
    • 1.1 应用场景
      • 1.2 单例模式的基本实现
        • 1.3 单例模式的完善
          • 1.4 饿汉式
          • II 使用GCD结合宏来实现单例
            • 2.1 ARC 环境下的GCD实现单例
              • 2.2 非ARC的单例模式
                • 2.3 GCD和宏来实现单例的具体代码
                  • 2.4 ARC、MRC的适配(条件编译)
                  • III 使用extern申明公共方法、全局字符串常量
                    • 3.1 全局字符串常量
                      • 3.2 公共方法
                      相关产品与服务
                      对象存储
                      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档