ということで、その本体のソース。
公開にあたって、一部書き換えたので動かなくなってたらごめんなさい。
「確認してから出せ」ッて言われそうだけど、時間がなくて。
//
// saveClass.m : 全クラス内容を得る、保存する
//
// (C) 2014 by AIG-Soft
// under Apache License
/* この辺りを参照
クラスのメンバ名を文字列で指定する
http://program.station.ez-net.jp/special/handbook/objective-c/id/ivar.asp
オブジェクトが持つプロパティの型と名前のリストを取得する
http://d.hatena.ne.jp/shu223/20120226/1330231240
クラス名を取得する
http://lab.dolice.net/blog/2013/04/17/objc-ns-string-from-class/
シリアライズする
http://nagano.monalisa-au.org/archives/64
*/
#import "saveClass.h"
#import "objc/runtime.h"
#include <sys/stat.h> // mkdir()
#include <sys/types.h> // mkdir()のmode
#include <unistd.h> // rmdir()
#define MAX_CLASS_NAME (128) // クラス名はこれbytes以下にすること
//--------------------------------------------------------------
static BOOL saveClassSub(id object,Ivar *ivars,unsigned int cnt,NSString *basePath)
// 全メンバーの内容を保存する
// メモリ確保の関係でサブルーチンにする
// 未対応型クラスは実装を追加すること
{
NSLog(@"saveClassSub:%d",cnt);
for (int i = 0; i < cnt; i++) {
// 属性取得
const char *encode = ivar_getTypeEncoding(ivars[i]);
const char *name = ivar_getName(ivars[i]);
// パス名=basePath/クラス名/プロパティ
NSString *path=[NSString stringWithFormat:@"%@/%@/%s",basePath,NSStringFromClass([object class]),name];
NSLog(@"パス名=%@",path);
// 型別出力処理
FILE *fp;
// クラスの場合はwriteToFileを使うが、ファイルはとりあえずopenしておく。これにより、内容がnilだった場合も空ファイルができるようになる。
fp=fopen(nsStringTocString(path),"w");
if (fp==NULL) {
// ファイルが作成できない
NSLog(@"ファイルが作成できない");
return(NO);
}
switch (encode[0]) {
default:
NSLog(@"不明型:%s",encode);
break;
// 小文字はsigned,大文字はunsigned
case 'c':
case 'C': // char系(BOOL/char)
{
unsigned char result;
object_getInstanceVariable(object, name, (void**)&result);
fwrite(&result,sizeof(result),1,fp);
NSLog(@"char系:%d/%u",result,result);
}
break;
case 's':
case 'S': // short系
{
unsigned short result;
object_getInstanceVariable(object, name, (void**)&result);
fwrite(&result,sizeof(result),1,fp);
NSLog(@"short系:%d/%u",result,result);
}
break;
case 'i':
case 'I': // interger系
{
unsigned int result;
object_getInstanceVariable(object, name, (void**)&result);
fwrite(&result,sizeof(result),1,fp);
NSLog(@"interger系:%d/%u",result,result);
}
break;
case 'l':
case 'L': // long系 : iOSではintと同じはずだけど念のため
{
unsigned long result;
object_getInstanceVariable(object, name, (void**)&result);
fwrite(&result,sizeof(result),1,fp);
NSLog(@"long系:%ld/%lu",result,result);
}
break;
case 'q':
case 'Q': // long long
{
unsigned long long result;
#if 0
// 64bitでは以下の方法は正常動作しない
// http://stackoverflow.com/questions/1219081/object-getinstancevariable-works-for-float-int-bool-but-not-for-double
object_getInstanceVariable(object, name, (void**)&result);
#else
// なので、一旦メンバーの存在位置を取得して、そこから直接読み出す方法を使う
Ivar ivar = object_getInstanceVariable(object, name, NULL);
if (ivar) {
result= *(long long*)((char *)object + ivar_getOffset(ivar));
}
#endif
fwrite(&result,sizeof(result),1,fp);
NSLog(@"long long系:%lld/%llu",result,result);
}
break;
case 'f': // float
{
float result;
object_getInstanceVariable(object, name, (void**)&result);
fwrite(&result,sizeof(result),1,fp);
NSLog(@"float:%f",result);
}
break;
case 'd': // double
{
double result;
#if 0
// 64bitでは以下の方法は正常動作しない
object_getInstanceVariable(object, name, (void**)&result);
#else
// なので、一旦メンバーの存在位置を取得して、そこから直接読み出す方法を使う
Ivar ivar = object_getInstanceVariable(object, name, NULL);
if (ivar) {
result= *(double *)((char *)object + ivar_getOffset(ivar));
}
#endif
fwrite(&result,sizeof(result),1,fp);
NSLog(@"double=%.15f",result);
}
break;
// 汎用化を進めるときは以下に出力処理を追記すること
case '@': // クラス名
if (encode[1]=='"') {
// 以下に"クラス名"がある
// クラス名のみ切り出す
char className[MAX_CLASS_NAME+1]; // +1 for EOS
int j=0;
char c;
while ((c=encode[2+j])!='"') {
className[j]=c;
if (++j>=MAX_CLASS_NAME) {
break;
}
}
className[j]='\0';
NSLog(@"クラス=%s",className);
if (strcmp(className,"NSString")==0||strcmp(className,"NSMutableString")==0) {
// クラスはfopen()に頼らない出力をするので閉じてしまう
// これでとりあえずサイズ0のファイルができるので、オブジェクトの実体がnilの場合はそれが残る。
fclose(fp);
fp=NULL; // 閉じた印
//
NSString *str;
object_getInstanceVariable(object, name, (void**)&str);
NSLog(@"NSString=%@",str);
if (str!=nil) {
NSError *error=nil;
if (![str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error]) return(NO);
}
break;
}
else
if (strcmp(className,"NSData") ==0||strcmp(className,"NSMutableData") ==0) {
// クラスはfopen()に頼らない出力をするので閉じてしまう
// これでとりあえずサイズ0のファイルができるので、オブジェクトの実体がnilの場合はそれが残る。
fclose(fp);
fp=NULL; // 閉じた印
//
NSData *data;
object_getInstanceVariable(object, name, (void**)&data);
// NSLog(@"NSData=%@",data);
if (data!=nil) {
if (![data writeToFile:path atomically:YES]) return(NO);
}
break;
}
else
if (strcmp(className,"NSArray") ==0||strcmp(className,"NSMutableArray") ==0) {
// クラスはfopen()に頼らない出力をするので閉じてしまう
// これでとりあえずサイズ0のファイルができるので、オブジェクトの実体がnilの場合はそれが残る。
fclose(fp);
fp=NULL; // 閉じた印
//
NSArray *ary;
object_getInstanceVariable(object, name, (void**)&ary);
NSLog(@"NSArray=%@",ary);
if (ary!=nil) {
#if 0
// aryの中にwriteToFileをサポートしてないクラスがあると失敗するので、無視するためエラーは取らない
// if (!
[ary writeToFile:path atomically:YES]
;
// ) return(NO);
#else
// aryの中に別のクラスが存在し、それも出力したい場合は、coderを実装した上、NSDataにシリアライズして出力する
// 注意
// 1.coderを実装しただけでは[ary writeToFile]では出力されない様子
// 2.coderはカテゴリでは実装できない(無視される)
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:ary];
// NSLog(@"data=%@",data);
[data writeToFile:path atomically:YES];
#endif
}
break;
}
else
if (strcmp(className,"NSDate") ==0) {
NSDate *date;
object_getInstanceVariable(object, name, (void**)&date);
NSLog(@"NSDate=%@",date);
if (date!=nil) {
NSTimeInterval dt=[date timeIntervalSince1970]; // 1970/1/1からの相対時間(数値)に変換する
fwrite(&dt,sizeof(dt),1,fp);
NSLog(@"dt=%f",dt);
}
break;
}
else {
NSLog(@"未サポートクラスは出力しない");
// NSSetはwriteToFileがない
}
} else {
// クラス名文字列がないときはid型
// 型が特定できないので出力対象としない
NSLog(@"id型は出力しない");
}
break;
}
if (fp!=NULL) fclose(fp);
// printf("型=%s/名前=%s\n",attributes,property_getName(properties[i]));
}
// 全出力終了
return(YES);
}
// basePath=makeDocumentsPath();
BOOL saveClass(id object,NSString *basePath)
// 指定オブジェクトの全メンバーの内容を保存する
// object : 対応オブジェクト
// basePath : 保存フォルダ(この下にクラス名フォルダが出来、その下にクラス名別ファイルが作成される)
{
NSLog(@"saveClass:%@",object);
// 全メンバ情報を得る
unsigned int cnt;
Ivar *ivar = class_copyIvarList([object class], &cnt);
//
// 先に保存ディレクトリを作成する : basePath/クラス名
NSString *dir=[NSString stringWithFormat:@"%@/%@",basePath,NSStringFromClass([object class])];
NSLog(@"保存ディレクトリ:%@",dir);
#if 1
mkdir(nsStringTocString(dir),S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IXOTH|S_IXOTH);
#else
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error=nil;
[fileManager createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:&error];
#endif
// ここで失敗してディレクトリが出来なくても、あとのファイル書き出しでエラーが出るのでとりあえず無視する
// 既存の場合のエラーコードがわからないので。
//
BOOL ret=saveClassSub(object,ivar,cnt,basePath);
//
free(ivar);
NSLog(@"---------------");
return (ret);
}
//--------------------------------------------------------------
// saveとloadは必ず同じ内容のクラスで行うこと。
// 変わっている場合、動作は保証されない。
static BOOL loadClassSub(id object,Ivar *ivars,unsigned int cnt,NSString *basePath)
// 全メンバーの内容を復帰する
// メモリ確保の関係でサブルーチンにする
// 未対応型クラスは実装を追加すること
{
NSLog(@"loadClassSub:%d",cnt);
for (int i = 0; i < cnt; i++) {
// 属性取得
const char *encode = ivar_getTypeEncoding(ivars[i]);
const char *name = ivar_getName(ivars[i]);
// パス名=basePath/クラス名/メンバ名
NSString *path=[NSString stringWithFormat:@"%@/%@/%s",basePath,NSStringFromClass([object class]),name];
NSLog(@"パス名=%@",path);
// 型別読み込み処理
// 出力時に型までは保存してないので、同名の別クラスがあったりすると誤動作する
FILE *fp;
fp=fopen(nsStringTocString(path),"r");
if (fp==NULL) {
// ファイルがない
continue; // 無視するだけ
}
switch (encode[0]) {
default:
NSLog(@"不明型:%s",encode);
break;
// 小文字はsigned,大文字はunsigned
case 'c':
case 'C': // char系(BOOL/char)
{
unsigned char result;
if (fread(&result,sizeof(result),1,fp)<1) result=0;
object_setInstanceVariable(object, name, (void*)&result);
NSLog(@"char系:%d/%u",result,result);
}
break;
case 's':
case 'S': // short系
{
unsigned short result;
fread(&result,sizeof(result),1,fp);
object_setInstanceVariable(object, name, (void*)&result);
NSLog(@"short系:%d/%u",result,result);
}
break;
case 'i':
case 'I': // interger系
{
unsigned int result;
if (fread(&result,sizeof(result),1,fp)<1) result=0;
object_setInstanceVariable(object, name, (void*)&result);
NSLog(@"interger系:%d/%u",result,result);
}
break;
case 'l':
case 'L': // long系 : iOSではintと同じはずだけど念のため
{
unsigned long result;
if (fread(&result,sizeof(result),1,fp)<1) result=0;
object_setInstanceVariable(object, name, (void*)&result);
NSLog(@"long系:%ld/%lu",result,result);
}
break;
case 'q':
case 'Q': // long long
{
unsigned long long result;
if (fread(&result,sizeof(result),1,fp)<1) result=0;
#if 0
// 64bitでは以下の方法は正常動作しない
object_setInstanceVariable(object, name, (void*)&result);
#else
// なので、一旦メンバーの存在位置を取得して、そこから直接読み出す方法を使う
Ivar ivar = object_getInstanceVariable(object, name, NULL);
if (ivar) {
*(long long *)((char *)object + ivar_getOffset(ivar))=result;
}
#endif
NSLog(@"long long系:%lld/%llu",result,result);
}
break;
case 'f': // float
{
float result;
if (fread(&result,sizeof(result),1,fp)<1) result=0;
object_setInstanceVariable(object, name, (void*)&result);
NSLog(@"float:%f",result);
}
break;
case 'd': // double
{
double result=0;
if (fread(&result,sizeof(result),1,fp)<1) result=0;
#if 0
// 64bitでは以下の方法は正常動作しない
object_setInstanceVariable(object, name, (void*)&result);
#else
// なので、一旦メンバーの存在位置を取得して、そこから直接読み出す方法を使う
Ivar ivar = object_getInstanceVariable(object, name, NULL);
if (ivar) {
*(double *)((char *)object + ivar_getOffset(ivar))=result;
}
#endif
NSLog(@"double=%.15f",result);
// doubleの表示は%f。しかし、標準ではfloatと同じ精度(7桁)までしか表示しないので、double精度(15桁)表示させるには上記のように桁数を指定する必要がある。
// ただし、15桁は少数以下だけでなく整数位も含めた全体なので注意。要するにこの記述は必ずしも正しくはない。
}
break;
// 汎用化を進めるときは以下に読み込み処理を追記すること
case '@': // クラス名
if (encode[1]=='"') {
// 以下に"クラス名"がある
// クラス名のみ切り出す
char className[MAX_CLASS_NAME+1]; // +1 for EOS
int j=0;
char c;
while ((c=encode[2+j])!='"') {
className[j]=c;
if (++j>=MAX_CLASS_NAME) {
break;
}
}
className[j]='\0';
NSLog(@"クラス=%s",className);
if (strcmp(className,"NSString")==0||strcmp(className,"NSMutableString")==0) {
NSError *error=nil;
NSString *str=[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error==nil) {
str=nil;
}
object_setInstanceVariable(object, name, (void*)str);
NSLog(@"NSString=%@",str);
break;
}
else
if (strcmp(className,"NSData") ==0||strcmp(className,"NSMutableData") ==0) {
NSData *data=[NSData dataWithContentsOfFile:path];
object_setInstanceVariable(object, name, (void*)data);
NSLog(@"NSData=%@",data);
break;
}
else
if (strcmp(className,"NSArray") ==0||strcmp(className,"NSMutableArray") ==0) {
#if 1
NSData *data = [NSData dataWithContentsOfFile:path];
NSArray *ary = [NSKeyedUnarchiver unarchiveObjectWithData:data];
#else
NSArray *ary=[NSArray arrayWithContentsOfFile:path];
#endif
object_setInstanceVariable(object, name, (void*)ary);
NSLog(@"NSArray=%@",ary);
break;
}
else
if (strcmp(className,"NSDate") ==0) {
NSTimeInterval dt; // 1970/1/1からの相対時間(数値)で保存されている
if (fread(&dt,sizeof(dt),1,fp)<1) dt=0;
NSDate *date=[NSDate dateWithTimeIntervalSince1970:dt];
object_setInstanceVariable(object, name, (void*)date);
NSLog(@"dt=%f/date=%@",dt,date);
break;
}
else {
NSLog(@"未サポートクラスは読み込まない");
}
} else {
// クラス名文字列がないときはid型
// 型が特定できないので読み込み対象としない
NSLog(@"id型は読み込めない");
}
break;
}
fclose(fp);
// printf("型=%s/名前=%s\n",attributes,property_getName(properties[i]));
}
// 全読み込み終了
return(YES);
}
BOOL loadClass(id object,NSString *basePath)
// 指定オブジェクトの全メンバーの内容を復帰する
// object : 対応オブジェクト
// basePath : 保存フォルダ(この下にクラス名フォルダがあり、その下にクラス名別ファイルがあること)
{
NSLog(@"loadClass:%@",object);
// 全メンバ情報を得る
unsigned int cnt;
Ivar *ivar = class_copyIvarList([object class], &cnt);
//
BOOL ret=loadClassSub(object,ivar,cnt,basePath);
//
free(ivar);
NSLog(@"---------------");
return (ret);
}
//--------------------------------------------------------------
static void delete1(id object,NSString *basePath,const char *name)
// 1ファイル削除
{
NSString *path=[NSString stringWithFormat:@"%@/%@/%s",basePath,NSStringFromClass([object class]),name];
unlink(nsStringTocString(path));
}
static BOOL deleteClassSub(id object,Ivar *ivars,unsigned int cnt,NSString *basePath)
// 全メンバー名ファイルを削除する
// メモリ確保の関係でサブルーチンにする(deleteでは関係ないけど他と合わせている)
{
NSLog(@"deleteClassSub:%d",cnt);
for (int i = 0; i < cnt; i++) {
delete1(object,basePath,ivar_getName(ivars[i]));
}
// 全出力終了
return(YES);
}
BOOL deleteClass(id object,NSString *basePath)
// 指定オブジェクトの全メンバーの内容ファイルを削除する
// object : 対応オブジェクト
// basePath : 保存フォルダ(この下にクラス名フォルダがあり、その下にクラス名別ファイルがあること)
// クラス名ディレクトリも消す
{
NSLog(@"deleteClass:%@",object);
// 全メンバ情報を得る
unsigned int cnt;
Ivar *ivar = class_copyIvarList([object class], &cnt);
//
BOOL ret=deleteClassSub(object,ivar,cnt,basePath);
//
// 保存ディレクトリも削除する : basePath/クラス名
NSString *dir=[NSString stringWithFormat:@"%@/%@",basePath,NSStringFromClass([object class])];
rmdir(nsStringTocString(dir));
// unlink(nsStringTocString(dir)); // これではディレクトリは削除できない
NSLog(@"削除ディレクトリ:%@",dir);
//
free(ivar);
NSLog(@"---------------");
return (ret);
}
//--------------------------------------------------------------
#if 0
static const char * getPropertyType(objc_property_t property)
// 正しく取り出せないことがあるので封印(未解析)
{
const char *attributes = property_getAttributes(property);
printf("型=%s\n",attributes);
char buffer[1 + strlen(attributes)];
strcpy(buffer, attributes);
char *state = buffer, *attribute;
while ((attribute = strsep(&state, ",")) != NULL) {
if (attribute[0] == 'T' && attribute[1] != '@') {
return (const char *)[[NSData dataWithBytes:(attribute + 1) length:strlen(attribute) - 1] bytes];
}
else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
return "id";
}
else if (attribute[0] == 'T' && attribute[1] == '@') {
return (const char *)[[NSData dataWithBytes:(attribute + 3) length:strlen(attribute) - 4] bytes];
}
}
return "";
}
#endif
NSArray *propertyNames(id object)
// オブジェクトの全プロパティ名を得る
// !=メンバ名なので注意
{
unsigned int cnt;
NSMutableArray *ary = [NSMutableArray array];
objc_property_t *properties = class_copyPropertyList([object class], &cnt);
for (int i = 0; i < cnt; i++) {
objc_property_t property = properties[i];
const char *name = property_getName(property);
if (name) {
[ary addObject:CstringToNSString(name)];
}
}
free(properties);
NSLog(@"プロパティ群=%@",ary);
return ary;
}
NSArray *memberNames(id object)
// オブジェクトの全メンバ名を得る
{
unsigned int cnt;
NSMutableArray *ary = [NSMutableArray array];
Ivar *ivar = class_copyIvarList([object class], &cnt);
for (int i = 0; i < cnt; i++) {
Ivar iv = ivar[i];
const char *name = ivar_getName(iv);
if (name) {
[ary addObject:CstringToNSString(name)];
}
}
free(ivar);
NSLog(@"メンバ名群=%@",ary);
return ary;
}
/*
property_getAttributes()の返してくる文字列
返ってくるのは@propertyされているもののみ
T 先頭
@"〜" クラス名 "〜"がないときはid
i signed int/NSInterger
I unsigned int
c signed char/BOOL
C unsigned char
q long long
Q unsigned long long
f float
d double
N nonatomic
C copy
& retain
R readonly
何もなしはassign,readwrite,atomic
Vの直後からメンバ名
*/
NSDictionary *propertiesAttributes(id object)
// 全プロパティ情報を得る
{
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
unsigned int cnt;
objc_property_t *properties = class_copyPropertyList([object class], &cnt);
for (int i = 0; i < cnt; i++) {
const char *attributes = property_getAttributes(properties[i]);
const char *name = property_getName(properties[i]);
printf("型=%s/名前=%s\n",attributes,name);
[dic setObject:CstringToNSString(attributes) forKey:CstringToNSString(name)];
}
free(properties);
NSLog(@"プロパティ=%@",dic);
NSLog(@"-------------------------");
return dic;
}
NSDictionary *memberAttributes(id object)
// 全メンバー情報を得る
{
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
unsigned int cnt;
Ivar *ivar = class_copyIvarList([object class], &cnt);
for (int i = 0; i < cnt; i++) {
const char *encode = ivar_getTypeEncoding(ivar[i]);
const char *name = ivar_getName(ivar[i]);
printf("型=%s/名前=%s\n",encode,name);
[dic setObject:CstringToNSString(encode) forKey:CstringToNSString(name)];
}
free(ivar);
NSLog(@"メンバー=%@",dic);
NSLog(@"-------------------------");
return dic;
}
//--------------------------------------------------------------