web-dev-qa-db-ja.com

プライベートObjective-Cメソッドまたはプロパティをサブクラスに公開する

公式の話によると、Objective-Cのクラスは、そのヘッダーのパブリックメソッドとプロパティのみを公開する必要があります。

@interface MyClass : NSObject

@property (nonatomic, strong) MyPublicObject *publicObject;

- (void)publicMethod;

@end

およびプライベートメソッド/プロパティは、.mファイルのクラス拡張子に保持する必要があります。

@interface MyClass()

@property (nonatomic, strong) MyPrivateObject *privateObject;

- (void) privateMethod;

@end

そして、プライベートではあるがサブクラスからアクセスできるものにはprotected型があるとは思わない。プライベートプロパティ/メソッドを公に宣言する以外に、とにかくこれを達成する方法はあるのだろうか?

31
hzxu

これを解決する1つの方法は、サブクラスのクラス拡張でプロパティを再宣言し、@dynamicステートメント。これにより、コンパイラはそのプロパティのオーバーライド実装を作成しません。のようなもの:

@interface SuperClass ()

@property (nonatomic, strong) id someProperty;

@end

....


@interface SubClass ()

@property (nonatomic, strong) id someProperty;

@end

@implementation SubClass

@dynamic someProperty;

@end

これは明らかに、非公開の宣言を複製するため理想的ではありません。しかし、状況によっては非常に便利で役立つので、この重複に伴う危険とパブリックインターフェイスでのプロパティの公開の危険性をケースバイケースで評価します。

別の方法-UIGestureRecognizerのAppleで使用)は、「SomeClass + Protected.h」のように「private」または「protected」として明示的に名前が付けられた個別のカテゴリヘッダーファイルでプロパティを宣言することですそうすれば、他のプログラマーはファイルをインポートしてはならないことを知っていますが、継承元のコードを制御しない場合、それはオプションではありません。

34
Carl Veazey

これは、基本クラスとサブクラスの両方の実装ファイルに含めるクラス拡張(カテゴリではない)を使用することで可能です。

クラス拡張はカテゴリと同様に定義されますが、カテゴリ名はありません:

@interface MyClass ()

クラス拡張では、バッキングivarを合成できるプロパティを宣言できます(XCode> 4.4 ivarの自動合成もここで機能します)。

拡張クラスでは、プロパティをオーバーライド/リファイン(読み取り専用を読み取り書き込みなどに変更)し、実装ファイルに「表示」されるプロパティとメソッドを追加できます(ただし、プロパティとメソッドは実際にはプライベートではなく、セレクターから呼び出されます)。

このために別のヘッダーファイルMyClass_protected.hを使用することを提案した人もいますが、これは次のように#ifdefを使用してメインヘッダーファイルで行うこともできます。

例:

BaseClass.h

@interface BaseClass : NSObject

// foo is readonly for consumers of the class
@property (nonatomic, readonly) NSString *foo;

@end


#ifdef BaseClass_protected

// this is the class extension, where you define 
// the "protected" properties and methods of the class

@interface BaseClass ()

// foo is now readwrite
@property (nonatomic, readwrite) NSString *foo;

// bar is visible to implementation of subclasses
@property (nonatomic, readwrite) int bar;

-(void)baz;

@end

#endif

BaseClass.m

// this will import BaseClass.h
// with BaseClass_protected defined,
// so it will also get the protected class extension

#define BaseClass_protected
#import "BaseClass.h"

@implementation BaseClass

-(void)baz {
    self.foo = @"test";
    self.bar = 123;
}

@end

ChildClass.h

// this will import BaseClass.h without the class extension

#import "BaseClass.h"

@interface ChildClass : BaseClass

-(void)test;

@end

ChildClass.m

// this will implicitly import BaseClass.h from ChildClass.h,
// with BaseClass_protected defined,
// so it will also get the protected class extension

#define BaseClass_protected 
#import "ChildClass.h"

@implementation ChildClass

-(void)test {
    self.foo = @"test";
    self.bar = 123;

    [self baz];
}

@end

#importを呼び出すと、基本的に.hファイルをコピーして、インポート先に貼り付けます。 #ifdefがある場合、その名前の#defineが設定されている場合にのみコードが内部に含まれます。

.hファイルでは、定義を設定しないため、この.hをインポートするクラスは保護されたクラスの拡張を参照しません。基本クラスとサブクラスの.mファイルでは、#defineを使用する前に#importを使用して、コンパイラーが保護されたクラス拡張を含めるようにします。

14
d4n3

他の答えは正しいですが、追加したいと思います...

プライベート、プロテクト、パブリックareは、たとえばvariablesとして利用可能です:

@interface MyClass : NSObject {
@private
  int varA;

@protected
  int varB;

@public
  int varC;
}

@end
8
Alex Smith

唯一の選択肢は、ヘッダーファイルでパブリックとして宣言することです。少なくともメソッドの分離を維持したい場合は、カテゴリを作成し、そこにすべての保護されたメソッドと属性を設定できますが、最終的にはすべてが公開されます。

#import "MyClass.h"

@interface MyClass (Protected)

- (void) protectedMethods;

@end
1
8vius

プロパティを可視化するための良い答えはありますが、これらの答えのどれにも非常に明確に対処されたメソッドを公開することはありません。カテゴリを使用してサブクラスにプライベートメソッドを正常に公開した方法を次に示します。

SomeSuperClass.m:

@implementation SomeSuperClass

-(void)somePrivateMethod:(NSString*)someArgument {
    ...
}

SomeChildClass.h

@interface SomeChildClass : SomeSuperClass

SomeChildClass.m

@interface SomeSuperClass (exposePrivateMethod)
-(void)somePrivateMethod:(NSString*)someArgument;
@end

@implementation SomeChildClass

-(void)doSomething {
    [super somePrivateMethod:@"argument"];
}

@end
1

クラス拡張子を付けて.hファイルを作成するだけです。これを.mファイルにインポートします。ちなみに、これはカプセル化を壊さずにプライベートメンバーをテストするのに最適な方法です(プライベートメソッドをテストする必要はありません:))。

// MyClassProtectedMembers.h
@interface MyClass()

@property (nonatomic, strong) MyPrivateObject *privateObject;
- (void) privateMethod;
@end

//////////////////

#import "MyClassProtectedMembers.h"

@implementation MyClass
// implement privateMethod here and any setters or getters with computed values
@end

ここにアイデアの要点があります: https://Gist.github.com/philosopherdog/6461536b99ef73a5c32a

1
smileBot

それは、プライベートとパブリックの本当の区別さえないからです。コンパイラーは、特定のメソッドまたはインスタンス変数が欠落しているインターフェースについて警告する場合がありますが、プログラムは引き続き機能します。

0