一、前言
extension app 和 宿主app 是在两个进程里~当我们运行宿主app时,在extension app中打印的log在Xcode控制台中是不打印的,反之,运行extension app时,Xcode控制台也不会打印宿主app的log。
在平时的开发阶段,我们可以通过运行extension app来分析extension app的问题。但在发包给测试时,我们需要收集宿主app的log以及extension app的log,以便更好的分析问题。
二、如何收集App log
有一个很简单的方法:将NSlog打印信息保存到Document目录下的文件中。
// 将log输入到文件
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
这个方法就可以将NSLog打印的信息存储到指定的文件下
但还存在一个问题,extension app 和 宿主app是两个进程,它们分别有自己的沙盒,也就是说,extension app中存储log的文件,在宿主app中获取不到~也就是说我们需要在宿主app中获取extension app中的日志。
针对上述问题,我们可以有以下三种方案
1.通过socket,将extension app中的日志,实时传输到宿主app中,再在宿主app中存储log
2.通过接口,将log上传到服务器。宿主app和extension app分别上传log
3.通过AppGroup实现数据共享,在extension app中,将log信息存储到共享文件中,在宿主app中获取共享文件中的log信息
经测试,方案1会比较耗性能,方案2太依赖网络,也不可行
方案3可行
三、通过AppGroup实现数据共享,存储日志
1.项目中配置App Group(注意:要用到数据共享的工程都要配置 extension app 和宿主app 都需要配置)
TARGETS->Signing & Capabilities ->Capability 选中 App Group
选择或者新增一个groups,比如我的是group.com.company.test。
2.代码实现
//运行日志
- (void)redirectNSlogToDocumentFolder:(NSString *)fileName
{
// 日志文件path
NSURL *url = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupId];
NSURL *fileURL = [url URLByAppendingPathComponent:fileName];
NSString *logFilePath = [fileURL path];
// 先删除已经存在的文件
NSFileManager *defaultManager = [NSFileManager defaultManager];
[defaultManager removeItemAtPath:logFilePath error:nil];
// 将log输入到文件 记录当前文件流
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
}
// 读取日志
- (NSString*)readLogFromFile:(NSString *)fileName{
NSURL *url = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupId];
NSURL *fileURL = [url URLByAppendingPathComponent:fileName];
BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]];
if (isExist) {
NSString *str = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];
return str;
}else{
return nil;
}
}
四、优化日志存储
我们需要对日志存储进行优化,比如自动清除之前的日志,将日志排序,以及按一定的大小分割日志。
直接上代码
EDExtNSLOGManager 用来存储extension app中的日志
EDExtNSLOGManager.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface EDExtNSLOGManager : NSObject
// 初始化 manager
+ (instancetype)sharedInstance;
// 开始存储NSLOG
- (void)startSaveNSlog;
// 获取所有日志文件Path
+ (NSArray *)getAllLogFilePath;
// 删除所有日志文件
+ (void)deletAllLog;
@end
NS_ASSUME_NONNULL_END
EDExtNSLOGManager.m
#import "EDExtNSLOGManager.h"
#import "EDAppGroupManager.h"
#import <UIKit/UIKit.h>
#import "sys/utsname.h"
#include <sys/param.h>
#include <sys/mount.h>
#include <stdio.h>
static NSString * const groupId = @"group.com.company.test"; // 换成自己开发者账号对应的groups
static NSString * const EDNSLOGDocumentDirectory = @"RUNNINGLOG";
static float const EDFreeDiskLimit = 500 *1024 *1024; //500M
static float const EDSingleFileLimit = 100 * 1024 *1024; //100M
@interface EDExtNSLOGManager ()
{
FILE * _currentStdout;
FILE * _currentStderr;
}
@property (nonatomic, strong) NSString *currentLogFilePath; // 当前日志文件Path
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) NSString *timeFlag; // 标示
@property (nonatomic, strong) NSString *appLaunchTime; // App启动时间
@end
@implementation EDExtNSLOGManager
static EDExtNSLOGManager *manager = nil;
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[EDExtNSLOGManager alloc] init];
});
return manager;
}
- (instancetype)init {
self = [super init];
if (self) {
NSDateFormatter *format=[[NSDateFormatter alloc] init];
format.timeZone=[NSTimeZone localTimeZone];
format.dateFormat=@"HH.mm";
self.timeFlag = [format stringFromDate:[NSDate date]];
NSDateFormatter *format2=[[NSDateFormatter alloc] init];
format2.timeZone=[NSTimeZone localTimeZone];
format2.dateFormat=@"YYYY-MM-DD HH:mm";
self.appLaunchTime = [format2 stringFromDate:[NSDate date]];
NSFileManager *fmManager = [NSFileManager defaultManager];
BOOL isExist = [fmManager fileExistsAtPath:[self getLogDirectory]];
if (!isExist) {
[fmManager createDirectoryAtPath:[self getLogDirectory] withIntermediateDirectories:YES attributes:nil error:nil];
}
}
return self;
}
// 开启日志
- (void)startSaveNSlog {
// 清除7天前的日志
[self cleanBeforeLogWithDays:7];
long long freeDiskSize = [self checkFreeDiskSpaceInBytes];
if (freeDiskSize > EDFreeDiskLimit) {
// 开启日志
[self redirectNSlogToDocumentFolder];
}else {
// 关闭日志
NSLog(@"存储空间不足,关闭日志存储");
[self closeSaveNSLog];
}
// 打印app及系统信息
[self printAppAndSystemInfo];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:30*60 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong typeof(self) self = weakSelf;
// 如果当前文件大小 大于100M 另起文件
long long fileSize = [self getFileSizeForPath:self.currentLogFilePath];
if (fileSize > EDSingleFileLimit) {
long long freeDiskSize = [self checkFreeDiskSpaceInBytes];
if (freeDiskSize > EDFreeDiskLimit) {
NSLog(@"当前文件大小达到上限 切换文件");
[self closeSaveNSLog];
[self redirectNSlogToDocumentFolder];
NSLog(@"App启动时间:%@",self.appLaunchTime);
}else{
NSLog(@"存储空间不足,关闭日志存储");
[self closeSaveNSLog];
}
}
}];
}
//运行日志
- (void)redirectNSlogToDocumentFolder
{
// 日志文件path
NSString *documentDirectory = [self getLogDirectory];
NSString *fileName = [self getLogFileName];
NSString *logFilePath = [documentDirectory stringByAppendingPathComponent:fileName];
// 先删除已经存在的文件
NSFileManager *defaultManager = [NSFileManager defaultManager];
[defaultManager removeItemAtPath:logFilePath error:nil];
// 将log输入到文件 记录当前文件流
_currentStdout = freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
_currentStderr = freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
// 记录当前日志文件Path
self.currentLogFilePath = logFilePath;
[[EDAppGroupManager sharedInstance] setObject:logFilePath forKey:@"EDExtNSLOG_CurrentLogFilePath_Key"];
}
// 获取日志文件夹Path
- (NSString *)getLogDirectory {
NSURL *appGroupUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupId];
NSString *docmentPath= [[appGroupUrl URLByAppendingPathComponent:EDNSLOGDocumentDirectory] path];
return docmentPath;
}
// 获取日志文件名
- (NSString *)getLogFileName {
NSDateFormatter *format=[[NSDateFormatter alloc] init];
format.timeZone=[NSTimeZone localTimeZone];
format.dateFormat=@"yyyy.MM.dd.HH.mm.ss";
NSString *time=[format stringFromDate:[NSDate date]];
NSString *fileName = [NSString stringWithFormat:@"RUNLOG %@&%@&Ext.txt",time,self.timeFlag];
return fileName;
}
// 关闭日志存储
- (void)closeSaveNSLog {
if (_currentStdout) {
fclose(_currentStdout);
}
if (_currentStderr) {
fclose(_currentStderr);
}
}
#pragma mark - - 日志管理 获取/删除
// 为什么不使用实例方法
// 避免EDExtNSLOGManager在两个进程里各初始化一遍
// EDExtNSLOGManager 在extension app内初始化了,如果在app内使用EDExtNSLOGManager的实例方法,
// 也会再次初始化EDExtNSLOGManager,这样会造成一定的资源浪费
// 获取所有日志Path
+ (NSArray *)getAllLogFilePath {
NSURL *appGroupUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupId];
NSString *documentsPath = [[appGroupUrl URLByAppendingPathComponent:EDNSLOGDocumentDirectory] path];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *allLogNameArray = [[[fileManager contentsOfDirectoryAtPath:documentsPath error:&error] reverseObjectEnumerator] allObjects];
NSMutableArray *array = [[NSMutableArray alloc] init];
for (NSString *fileName in allLogNameArray) {
if ([[fileName pathExtension] isEqualToString:@"txt"]){
[array addObject:[documentsPath stringByAppendingPathComponent:fileName]];
}
}
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
NSArray *fileArray = [NSMutableArray arrayWithArray:[array sortedArrayUsingDescriptors:@[sortDescriptor]]];
return fileArray;
}
// 删除所有日志
+ (void)deletAllLog{
NSURL *appGroupUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupId];
NSString *documentsPath = [[appGroupUrl URLByAppendingPathComponent:EDNSLOGDocumentDirectory] path];
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *allPath = [[[fm contentsOfDirectoryAtPath:documentsPath error:nil] reverseObjectEnumerator] allObjects];
// 在App内执行delete方法时,重新初始化了manager,因而currentLogFilePath直接获取不到
NSString *currentLog = [[EDAppGroupManager sharedInstance] objectForKey:@"EDExtNSLOG_CurrentLogFilePath_Key"];
// 删除当前所有日志,除正在写入的日志外
for (NSString *fileName in allPath) {
if ([fileName containsString:@"RUNLOG"] && ![currentLog containsString:fileName]){
NSString *logPath = [documentsPath stringByAppendingPathComponent:fileName];
[fm removeItemAtPath:logPath error:nil];
}
}
}
#pragma mark - - 打印系统信息
// 打印系统信息
- (void)printAppAndSystemInfo {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSLog(@"App版本 VersionName:%@ , VersionCode:%@ , 包名:%@",[infoDictionary objectForKey:@"CFBundleShortVersionString"],[infoDictionary objectForKey:@"CFBundleVersion"],[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]);
struct utsname systemInfo;
uname(&systemInfo);
NSString *deviceString = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
NSLog(@"iOS系统版本:%@ , iPhone手机型号:%@",[[UIDevice currentDevice] systemVersion],deviceString);
}
#pragma mark - - 清除几天前的日志
// 清除几天前的日志
- (void)cleanBeforeLogWithDays:(int)days {
NSString *documentsPath = [self getLogDirectory];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *allLogNameArray = [[[fileManager contentsOfDirectoryAtPath:documentsPath error:nil] reverseObjectEnumerator] allObjects];
for (NSString *logName in allLogNameArray) {
if ([logName containsString:@"RUNLOG"]){
NSString *logPath = [documentsPath stringByAppendingPathComponent:logName];
if ([fileManager fileExistsAtPath:logPath]){
NSDate *logCreateDate = [[fileManager attributesOfItemAtPath:logPath error:nil] fileCreationDate];
NSDate *currentDate = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier: NSCalendarIdentifierGregorian];
NSDateComponents *delta = [calendar components:NSCalendarUnitDay fromDate:logCreateDate toDate:currentDate options:0];
if (delta.day>days) {
[[NSFileManager defaultManager] removeItemAtPath:logPath error:nil];
}
}
}
}
}
#pragma mark - - 文件大小
// 获取文件的大小
- (long long)getFileSizeForPath:(NSString *)logPath {
long long fileSize = 0;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:logPath]){
fileSize = [[fileManager attributesOfItemAtPath:logPath error:nil] fileSize];
}
NSLog(@"当前文件大小:%lld",fileSize);
return fileSize;
}
// 获取手机存储空间
- (long long)checkFreeDiskSpaceInBytes{
struct statfs buf;
long long freeSpace = -1;
if (statfs("/var", &buf) >= 0) {
freeSpace = (long long)(buf.f_bsize * buf.f_bavail);
}
NSLog(@"当前存储空间:%lld",freeSpace);
return freeSpace;
}
@end
EDNSLogManager 用于存储宿主app日志
EDNSLogManager.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface EDNSLogManager : NSObject
// 初始化 manager
+ (instancetype)sharedInstance;
// 开始存储NSLOG
- (void)startSaveNSlog;
// 获取所有日志文件Path
- (NSArray *)getAllLogFilePath;
// 删除所有日志文件
- (void)deletAllLog;
@end
NS_ASSUME_NONNULL_END
EDNSLogManager.m
#import "EDNSLogManager.h"
#import "sys/utsname.h"
#include <sys/param.h>
#include <sys/mount.h>
#include <stdio.h>
static NSString * const EDNSLOGDocumentDirectory = @"RUNNINGLOG";
static float const EDFreeDiskLimit = 500 *1024 *1024; //500M
static float const EDSingleFileLimit = 100 * 1024 *1024; //100M
@interface EDNSLogManager ()
{
FILE * _currentStdout;
FILE * _currentStderr;
}
@property (nonatomic, strong) NSString *currentLogFilePath; // 当前日志文件Path
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) NSString *timeFlag; // 标示
@property (nonatomic, strong) NSString *appLaunchTime; // App启动时间
@end
@implementation EDNSLogManager
static EDNSLogManager *manager = nil;
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[EDNSLogManager alloc] init];
});
return manager;
}
- (instancetype)init {
self = [super init];
if (self) {
NSDateFormatter *format=[[NSDateFormatter alloc] init];
format.timeZone=[NSTimeZone localTimeZone];
format.dateFormat=@"HH.mm";
self.timeFlag = [format stringFromDate:[NSDate date]];
NSDateFormatter *format2=[[NSDateFormatter alloc] init];
format2.timeZone=[NSTimeZone localTimeZone];
format2.dateFormat=@"YYYY-MM-DD HH:mm";
self.appLaunchTime = [format2 stringFromDate:[NSDate date]];
NSFileManager *fmManager = [NSFileManager defaultManager];
BOOL isExist = [fmManager fileExistsAtPath:[self getLogDirectory]];
if (!isExist) {
[fmManager createDirectoryAtPath:[self getLogDirectory] withIntermediateDirectories:YES attributes:nil error:nil];
}
}
return self;
}
- (void)startSaveNSlog {
// 清除7天前的日志
[self cleanBeforeLogWithDays:7];
long long freeDiskSize = [self checkFreeDiskSpaceInBytes];
if (freeDiskSize > EDFreeDiskLimit) {
// 开启日志
[self redirectNSlogToDocumentFolder];
}else {
// 关闭日志
NSLog(@"存储空间不足,关闭日志存储");
[self closeSaveNSLog];
}
// 打印app及系统信息
[self printAppAndSystemInfo];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:30*60 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong typeof(self) self = weakSelf;
// 如果当前文件大小 大于100M 另起文件
long long fileSize = [self getFileSizeForPath:self.currentLogFilePath];
if (fileSize > EDSingleFileLimit) {
long long freeDiskSize = [self checkFreeDiskSpaceInBytes];
if (freeDiskSize > EDFreeDiskLimit) {
NSLog(@"当前文件大小达到上限 切换文件");
[self closeSaveNSLog];
[self redirectNSlogToDocumentFolder];
NSLog(@"App启动时间:%@",self.appLaunchTime);
}else{
NSLog(@"存储空间不足,关闭日志存储");
[self closeSaveNSLog];
}
}
}];
}
//运行日志
- (void)redirectNSlogToDocumentFolder
{
// 日志文件path
NSString *documentDirectory = [self getLogDirectory];
NSString *fileName = [self getLogFileName];
NSString *logFilePath = [documentDirectory stringByAppendingPathComponent:fileName];
// 先删除已经存在的文件
NSFileManager *defaultManager = [NSFileManager defaultManager];
[defaultManager removeItemAtPath:logFilePath error:nil];
// 将log输入到文件 记录当前文件流
_currentStdout = freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
_currentStderr = freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
// 记录当前日志文件Path
self.currentLogFilePath = logFilePath;
}
// 获取日志文件夹Path
- (NSString *)getLogDirectory {
// NSString *docmentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) safeObjectAtIndex:0];
NSString *docmentPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:EDNSLOGDocumentDirectory];
return docmentPath;
}
// 获取日志文件名
- (NSString *)getLogFileName {
NSDateFormatter *format=[[NSDateFormatter alloc] init];
format.timeZone=[NSTimeZone localTimeZone];
format.dateFormat=@"yyyy.MM.dd.HH.mm.ss";
NSString *time=[format stringFromDate:[NSDate date]];
NSString *fileName = [NSString stringWithFormat:@"RUNLOG %@&%@.txt",time,self.timeFlag];
return fileName;
}
// 关闭日志存储
- (void)closeSaveNSLog {
if (_currentStdout) {
fclose(_currentStdout);
}
if (_currentStderr) {
fclose(_currentStderr);
}
}
#pragma mark - - 日志管理 获取/删除
// 获取所有日志Path
- (NSArray *)getAllLogFilePath {
NSString *documentsPath = [self getLogDirectory];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *allLogNameArray = [[[fileManager contentsOfDirectoryAtPath:documentsPath error:&error] reverseObjectEnumerator] allObjects];
NSMutableArray *array = [[NSMutableArray alloc] init];
for (NSString *fileName in allLogNameArray) {
if ([[fileName pathExtension] isEqualToString:@"txt"]){
[array addObject:[documentsPath stringByAppendingPathComponent:fileName]];
}
}
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
NSArray *fileArray = [NSMutableArray arrayWithArray:[array sortedArrayUsingDescriptors:@[sortDescriptor]]];
return fileArray;
}
// 删除所有日志
- (void)deletAllLog{
NSString *documentsPath = [self getLogDirectory];
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *allPath = [[[fm contentsOfDirectoryAtPath:documentsPath error:nil] reverseObjectEnumerator] allObjects];
// 删除当前所有日志,除正在写入的日志外
for (NSString *fileName in allPath) {
if ([fileName containsString:@"RUNLOG"] && ![self.currentLogFilePath containsString:fileName]){
NSString *logPath = [documentsPath stringByAppendingPathComponent:fileName];
[fm removeItemAtPath:logPath error:nil];
}
}
}
#pragma mark - - 清除几天前的日志
// 清除几天前的日志
- (void)cleanBeforeLogWithDays:(int)days {
NSString *documentsPath = [self getLogDirectory];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *allLogNameArray = [[[fileManager contentsOfDirectoryAtPath:documentsPath error:nil] reverseObjectEnumerator] allObjects];
for (NSString *logName in allLogNameArray) {
if ([logName containsString:@"RUNLOG"]){
NSString *logPath = [documentsPath stringByAppendingPathComponent:logName];
if ([fileManager fileExistsAtPath:logPath]){
NSDate *logCreateDate = [[fileManager attributesOfItemAtPath:logPath error:nil] fileCreationDate];
NSDate *currentDate = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier: NSCalendarIdentifierGregorian];
NSDateComponents *delta = [calendar components:NSCalendarUnitDay fromDate:logCreateDate toDate:currentDate options:0];
if (delta.day>days) {
[[NSFileManager defaultManager] removeItemAtPath:logPath error:nil];
}
}
}
}
}
#pragma mark - -打印系统信息
// 打印系统信息
- (void)printAppAndSystemInfo {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSLog(@"App版本 VersionName:%@ , VersionCode:%@ , 包名:%@",[infoDictionary objectForKey:@"CFBundleShortVersionString"],[infoDictionary objectForKey:@"CFBundleVersion"],[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]);
struct utsname systemInfo;
uname(&systemInfo);
NSString *deviceString = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
NSLog(@"iOS系统版本:%@ , iPhone手机型号:%@",[[UIDevice currentDevice] systemVersion],deviceString);
}
#pragma mark - - 文件大小
// 获取文件的大小
- (long long)getFileSizeForPath:(NSString *)logPath {
long long fileSize = 0;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:logPath]){
fileSize = [[fileManager attributesOfItemAtPath:logPath error:nil] fileSize];
}
NSLog(@"当前文件大小:%lld",fileSize);
return fileSize;
}
// 获取手机存储空间
- (long long)checkFreeDiskSpaceInBytes{
struct statfs buf;
long long freeSpace = -1;
if (statfs("/var", &buf) >= 0) {
freeSpace = (long long)(buf.f_bsize * buf.f_bavail);
}
NSLog(@"当前存储空间:%lld",freeSpace);
return freeSpace;
}
@end
EDAppGroupManager 用于宿主app和extension app简单的数据共享
EDAppGroupManager.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
static NSString *const AppGroupUserDefault_SaveLog_Key = @"AppGroupUserDefault_SaveLog_Key";
@interface EDAppGroupManager : NSObject
// 初始化 manager
+ (instancetype)sharedInstance;
#pragma mark - - UserDefaults
- (BOOL)setObject:(nullable id)value forKey:(NSString *_Nullable)defaultName;
- (nullable id)objectForKey:(NSString *_Nullable)defaultName;
@end
NS_ASSUME_NONNULL_END
EDAppGroupManager.m
#import "EDAppGroupManager.h"
static NSString * const groupId = @"group.com.company.test"; // 换成自己开发者账号对应的groups
@interface EDAppGroupManager ()
@property (nonatomic, strong) NSUserDefaults * appGroupUserDefaults;
@end
@implementation EDAppGroupManager
// 初始化 manager
static EDAppGroupManager *manager = nil;
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[EDAppGroupManager alloc] init];
});
return manager;
}
- (instancetype)init {
self = [super init];
if (self) {
self.appGroupUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:groupId];
}
return self;
}
- (BOOL)setObject:(nullable id)value forKey:(NSString *_Nullable)defaultName{
if (!value || !defaultName) {
return NO;
}
[self.appGroupUserDefaults setObject:value forKey:defaultName];
return YES;
}
- (nullable id)objectForKey:(NSString *_Nullable)defaultName{
if (!defaultName) {
return nil;
}
return [self.appGroupUserDefaults objectForKey:defaultName];
}
@end