公開にあたって、一部書き換えたので動かなくなってたらごめんなさい。
「確認してから出せ」ッて言われそうだけど、時間がなくて。
// // 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; } //--------------------------------------------------------------
0 件のコメント:
コメントを投稿