4

init ヘッダーに省略記号構文を持つオブジェクトをサブクラス化したいと考えています。すなわち

-(void) initObjectWith:(NSString*)argument arguments:(NSString*)someArgument,...;

この場合、引数配列を渡す方法がわかりません。私はそれが次のようなものになると思います:

- (void) initObjectWithCustomInitializer:(NSString*)argument additionalArgument:(NSString*)additionalArgument argument:(NSString*) someArgument,... {
  self = [super initObjectWith:argument arguments:someArgument,...];
  if (self) {
     //custom init code here
  }
  return self
}

これはコンパイルされますが、nil で終わる 'arguments' 配列は最初の引数しか取得していません。nil で終わる配列のオブジェクトを渡すにはどうすればよいですか?

4

3 に答える 3

9

可変個引数イニシャライザを宣言するスーパークラスは、a を取る非可変個引数も宣言する必要があります (たとえば、 how hasにva_list類似しています)。スーパークラスに次の両方がある場合を想定します。printfvprintf

-(void)init:(id)a arguments:(id)b, ...;

-(void)init:(id)a arguments:(id)b variadicArgs:(va_list)args;

あなたは次のようなことをします:

- (void)myInit:(id)a newArg:(id)c arguments:(id)b, ...
{
    va_list v;
    va_start(v, b);

    self = [super init:a arguments:b variadicArgs:v];
    if (self) {
        //custom init code here
    }

    va_end(v);
    return self;
}

もちろん、新しい初期化子の非可変長バージョンも必ず用意する必要があります!

于 2013-01-25T22:18:40.563 に答える
7

varargs が実際に実装される方法と C 言語の制限により、次の場合を除き、呼び出す関数を...取得せずに引数をコールチェーンに渡すことはできません。va_list

  1. コードを実行できるすべてのプラットフォームに適したアセンブリ言語を使用する
  2. コンパイラがどのように実装するかなどの詳細を知ってva_listいる、または
  3. 引数の型のすべての可能な組み合わせを何らかの方法で計算し、それらを手動で渡す関数を作成してみてください。

これらのオプションのうち、(3) は現実的なケースでは明らかに非現実的であり、(2) はいつでも予告なしに変更される可能性があります。残りは (1)、コードが実行される各プラットフォームのアセンブリ言語です。

内部的には、varargs は各アーキテクチャの ABI 固有の方法で実装されます。概念的に...は、「必要なすべての引数を、それらの引数を取る関数を呼び出しているかのように渡します。各引数をどこから取得するかは、あなた次第です」と述べています。i386OS X や iOS シミュレータなど、すべての引数をスタックに渡すアーキテクチャの例を見てみましょう。

次の関数プロトタイプと呼び出しがあるとします。

void f(const char * const format, ...);
/* ... */
f("lUf", 0L, 1ULL, 1.0);

コンパイラは次のアセンブリを生成します (私が書いたとおりです。実際のコンパイラは、おそらく同じ効果を持つ多少異なる呼び出しシーケンスを生成します)。

leal L_str, %eax
pushl %eax
movl $0x3f800000, %eax
pushl %eax
movl $0x00000000, %eax
pushl %eax
movl $0x00000001, %eax
pushl %eax
movl $0x00000000, %eax
pushl %eax
call _f

これにより、各パラメーターが逆の順序でスタックにプッシュされます。秘密のトリックは次のとおりです。次のように宣言されていれば、コンパイラは同じことを行います。f()

void f(const char * const format, long arg1, unsigned long long arg2, float arg3);

これは、関数がスタックのパラメーター領域をコピーして vararg 取得関数を呼び出すことができる場合、引数は単純に通過することを意味します。問題: このパラメータ領域の大きさを把握する一般的な方法がありません! i386では、フレーム ポインターを持つ関数からも呼び出されるフレーム ポインターを持つ関数では、バイトをチートしてコピーできますが、これrbp - *rbpは非効率的であり、すべてのケースで機能しません (特に、パラメーターを受け取る関数やsをstruct返す関数)。 struct)。

次に、 と のようなアーキテクチャがarmv6ありarmv7、ほとんどのパラメーターは慎重に保存する必要があるレジスターに渡されx86_64ます。パラメーターはレジスターに渡されレジスターxmmカウントは に渡されます。スタック ロケーションとレジスターの%al両方パラメーターにマップされます。ppc

a を使用せずに引数を転送する唯一の確実なva_list方法は、コンパイラが行うのと同じ方法で、アーキテクチャごとにアセンブリを使用してコード内のアーキテクチャ ABI ロジック全体を再実装することです。

これも本質的に同じ問題をobjc_msgSend()解決します。

「だから待って!」あなたは今言う。objc_msgSend「このようにアセンブリをいじる代わりに、なぜ呼び出すことができないのですか?!」

回答: コンパイラに「スタック上で何かを壊したり、私が使用していないレジスタを消去したりしないでください」と伝える方法がないためです。サブクラスの実装でなんらかの作業を行う前に、呼び出しをスーパークラスの実装に転送するアセンブリ ルーチンを作成するobjc_msgSend()必要が_stretあり_fpretます。少なくとも 3 つのアーキテクチャ ( armv7i386x86_64- および後方互換性と前方互換性の必要性に応じて、潜在的ppcppc64armv6、 およびarmv7s) での実装。

単純な varargs の場合、コンパイラは、呼び出しに関する詳細な知識とターゲットの呼び出し規則を使用して、va_list. C では、この情報に直接アクセスすることはできません。そしてobjc_msgSend()、Objective-C コンパイラとランタイムがすべてやり直すので、常に使用せずにメソッド呼び出しを記述できますva_list。(また、一部のアーキテクチャでは、varargs 規則を使用するよりも、既知の呼び出しリストにパラメーターを渡すことができる方が効率的です)。

したがって、残念ながら、その価値よりもはるかに多くの労力を費やさなければ、それを行うことはできません。クラスの実装者は、これを教訓にしましょう。可変引数を取るメソッドを提供するときはいつでも、 のva_list代わりに...を受け入れる同じメソッドのバージョンも提供してください。とNSStringを使用した良い例です。initWithFormat:initWithFormat:arguments:

于 2013-01-26T05:10:53.083 に答える
0

好奇心旺盛な人のために、答えを見つけました!簡単に言えば、ストックの「init」初期化子を使用してから、通常の NSArray を介して引数を渡し、スーパークラス セッターを使用しました。

- (id) initWithCustomInitializer:(NSString *)argument arguments:(NSArray*)moreArguments {

    self = [super init];
    if (self) {
        self.argument = argument;

        for (int i = 0; i < [moreArguments count]; i++) {
            [self addArgument:[moreArguments objectAtIndex:i]];
        }
    }
    return self;
}

これは次のように呼ばれます:

NSArray *moreArguments = [NSArray arrayWithObjects:@"argument0", @"argument1", @"argument2", nil];

CustomObject *myObject = [[CustomObject alloc] initWithCustomInitializer:@"argument" arguments:moreArguments];

注: Carl は以下でいくつかの良い点を挙げています。この解決策は一般的ではない可能性があり、単純な init は常に initWith... メソッドが行う追加の初期化を行うとは限りません。ただし、このソリューションは私にとってはうまくいきました。

于 2013-01-25T22:35:02.700 に答える