2014年3月8日土曜日

クラスの内容をファイルに出力する方法(4)

最後にヘッダー。

//
//  saveClass
//
//  (C) 2014 by AIG-Soft
//  under Apache License

#import <Foundation/Foundation.h>

BOOL    saveClass(  id object,NSString *basePath);
BOOL    deleteClass(id object,NSString *basePath);
BOOL    loadClass(  id object,NSString *basePath);
NSArray *propertyNames(id object);
NSArray *memberNames(id object);
NSDictionary *propertiesAttributes(id object);
NSDictionary *memberAttributes(id object);






で、こんな感じで使う。
propertyNames(self);
propertiesAttributes(self);
memberNames(self);
memberAttributes(self);
saveClass(  self, makeDocumentsPath());
loadClass(  self, makeDocumentsPath());
deleteClass(self, makeDocumentsPath());
ちなみにmakeDocumentsPath()はこんな関数。
NSString *makeDocumentsPath(void)
// ディレクトリ「Documents」のフルパスを得る
// iTunesからのデータやりとりもここ
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    // paths[0]を取り出す;そこにディレクトリ名が入っているらしい
    NSString *documentsDirectory = (([paths count] > 0)?         // 見つかったら
                                    /* これ↑は単なるポインタ参照 */
                                    paths[0]                     // 最初のもの
                                    : NSTemporaryDirectory() );  // なければ一時ディレクトリ
    return (documentsDirectory);
}
使って頂いて、デバッグや改良点などあれば教えていただければありがたいかと。

2014年3月7日金曜日

クラスの内容をファイルに出力する方法(3)

さて、先の入出力関数の中では、NSArrayの中に別のクラスが存在する場合はそのまま処理できない。
それも入出力する場合は、そのクラスの中に少し処理を追加する必要がある。

追加するのはcoderという処理。これを入れるとクラスをNSDataにシリアライズして入出力出来るようになる。

以下のように実装する。


こんなクラスの場合↓
@interface TestClass : NSObject
{
    int     testInt;
    float   testFloat;
    NSArray *testArray;
}

こういうふうに実装する↓。

- (void)encodeWithCoder:(NSCoder *)aCoder
{
//  NSLog(@"encodeWithCoder");
    // メンバーのそれぞれをencode*:forKeyで変換する
    // キー名は適当でいいけど、基本はメンバー名と同じにすればいいと思う
    [aCoder encodeObject:testArray    forKey:@"testArray"];
    [aCoder encodeInt:testInt         forKey:@"testInt"];
    [aCoder encodeDouble:testFloat    forKey:@"testFloat"];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
//  NSLog(@"initWithCoder");
    self = [super init];
    if (self!=nil) {
        // メンバーのそれぞれをdecode*ForKeyで逆変換する
        // キー名はencodeで指定したもの
        testArray    = [aDecoder decodeObjectForKey: @"testArray"];
        testInt      = [aDecoder decodeIntegerForKey:@"testInt"];
        testFloat    = [aDecoder decodeDoubleForKey: @"testFloat"];
    }
    return self;
}

-(NSString *)description
// %@で表示するための文字列を返す処理
// これは必須ではない
{
    return [NSString stringWithFormat:
            @"< %@ : %p >"
            @"testArray=%@ ,"
            @"testInt=%u ,"
            @"testFloat=%f ,"
            ,NSStringFromClass([self class]),self
            ,testArray
            ,testInt
            ,testFloat
            ];
    // 再帰呼び出しになるので、"%@",selfは記述してはいけない
}

上記のメソッドを、出力したいクラスの@implementationの中に追加する。
なお、coderはカテゴリでは実装できない(無視される)。
また、coderを実装しただけでは[ary writeToFile]では出力されない様子である。









2014年3月6日木曜日

クラスの内容をファイルに出力する方法(2)

ということで、その本体のソース。
公開にあたって、一部書き換えたので動かなくなってたらごめんなさい。
「確認してから出せ」ッて言われそうだけど、時間がなくて。




//
//  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;
}

//--------------------------------------------------------------










2014年3月5日水曜日

クラスの内容をファイルに出力する方法(1)

とあるクラスの内容をファイルに出力しようと思った。
NSArrayなどNS標準クラスならwriteToFile一発だが、自分で作ったクラスの場合はそうは行かない。
そこで、いろいろ調べて&考えてそのための関数を作った。

GitHubで公開すればいいのかもしれないが、それほど完成度は高く無いと思うので、ここで公開することにする。

まずは、どんな関数を作ったか、その一覧を公開する。

・・・

BOOL saveClass(id object,NSString *basePath)
// 指定オブジェクトの全メンバーの内容を保存する
// object : 対応オブジェクト
// basePath : 保存フォルダ(この下にクラス名フォルダが出来、その下にクラス名別ファイルが作成される)
// リターン値:YES=書き込めた、NO=書き込めなかった

BOOL loadClass(id object,NSString *basePath)
// 指定オブジェクトの全メンバーの内容を復帰する
// object : 対応オブジェクト
// basePath : 保存フォルダ(この下にクラス名フォルダがあり、その下にクラス名別ファイルがあること)
// リターン値:YES=読み込めた、NO=読み込めなかった

BOOL deleteClass(id object,NSString *basePath)
// 指定オブジェクトの全メンバーの内容ファイルを削除する
// object : 対応オブジェクト
// basePath : 保存フォルダ(この下にクラス名フォルダがあり、その下にクラス名別ファイルがあること)
// クラス名ディレクトリも消す
// リターン値:YES=削除できた、NO=削除できなかった


NSArray *propertyNames(id object)
// オブジェクトの全プロパティ名を得る
// !=メンバ名なので注意

NSArray *memberNames(id object)
// オブジェクトの全メンバ名を得る

NSDictionary *propertiesAttributes(id object)
// 全プロパティ情報を得る

NSDictionary *memberAttributes(id object)
// 全メンバー情報を得る

・・・

なお予め断っておくと、

1. 標準NSクラスでも出力できないものがある
2. クラス名の最大長に制約がある

である。1は私が使わないと思うクラスまで処理を入れても大きくなるだけで無駄と判断したからである。2はプログラムを簡略化するための手抜き。定数1つ変更するだけで拡大できる。その辺りが「完成度が高くない」という理由であるが、その辺りは実際に使う人が改良すればいいのではないかと思う次第。