IOSアプリとObjective Cで作業を開始して以来、変数を宣言および定義できるさまざまな場所に本当に困惑していました。一方には従来のCアプローチがあり、もう一方にはOOを追加する新しいObjectiveCディレクティブがあります。これらの場所を変数に使用して、現在の理解を修正したい場合のベストプラクティスと状況を理解するのに役立ちますか?
サンプルクラス(.hおよび.m)は次のとおりです。
#import <Foundation/Foundation.h>
// 1) What do I declare here?
@interface SampleClass : NSObject
{
// 2) ivar declarations
// Pretty much never used?
}
// 3) class-specific method / property declarations
@end
そして
#import "SampleClass.h"
// 4) what goes here?
@interface SampleClass()
// 5) private interface, can define private methods and properties here
@end
@implementation SampleClass
{
// 6) define ivars
}
// 7) define methods and synthesize properties from both public and private
// interfaces
@end
たくさんの人、ありがとう!
あなたの混乱を理解できます。特にXcodeと新しいLLVMコンパイラの最近の更新により、ivarとプロパティの宣言方法が変更されたためです。
「現代の」Objective-C(「古い」Obj-C 2.0)では、多くの選択肢がありませんでした。インスタンス変数は、中括弧{ }
の間のヘッダーで宣言されていました。
// MyClass.h
@interface MyClass : NSObject {
int myVar;
}
@end
実装でのみこれらの変数にアクセスできましたが、他のクラスからはアクセスできませんでした。そのためには、次のようなアクセサメソッドを宣言する必要がありました。
// MyClass.h
@interface MyClass : NSObject {
int myVar;
}
- (int)myVar;
- (void)setMyVar:(int)newVar;
@end
// MyClass.m
@implementation MyClass
- (int)myVar {
return myVar;
}
- (void)setMyVar:(int)newVar {
if (newVar != myVar) {
myVar = newVar;
}
}
@end
このようにして、通常の角括弧構文を使用してメッセージを送信する(メソッドを呼び出す)ことにより、他のクラスからもこのインスタンス変数を取得および設定できました。
// OtherClass.m
int v = [myClass myVar]; // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];
すべてのアクセサメソッドを手動で宣言して実装するのは非常に面倒なので、@property
と@synthesize
が導入されて、アクセサメソッドが自動的に生成されました。
// MyClass.h
@interface MyClass : NSObject {
int myVar;
}
@property (nonatomic) int myVar;
@end
// MyClass.m
@implementation MyClass
@synthesize myVar;
@end
結果は、はるかに明確で短いコードです。アクセサメソッドは自動的に実装され、以前と同様にブラケット構文を使用できます。ただし、さらに、ドット構文を使用してプロパティにアクセスすることもできます。
// OtherClass.m
int v = myClass.myVar; // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;
Xcode 4.4以降では、インスタンス変数を自分で宣言する必要がなくなり、@synthesize
もスキップできます。 ivarを宣言しない場合、コンパイラはそれを追加し、@synthesize
を使用せずにアクセサメソッドも生成します。
自動生成されたivarのデフォルト名は、アンダースコアで始まる名前またはプロパティです。 @synthesize myVar = iVarName;
を使用して、生成されたivarの名前を変更できます
// MyClass.h
@interface MyClass : NSObject
@property (nonatomic) int myVar;
@end
// MyClass.m
@implementation MyClass
@end
これは上記のコードとまったく同じように機能します。互換性の理由から、ヘッダーでivarを宣言できます。しかし、それを行う(プロパティを宣言しない)唯一の理由はプライベート変数を作成することであるため、実装ファイルでも同様に行うことができ、これが好ましい方法です。
実装ファイルの@interface
ブロックは、実際には Extension であり、メソッドの宣言(不要)の転送やプロパティの(再)宣言に使用できます。たとえば、ヘッダーでreadonly
プロパティを宣言できます。
@property (nonatomic, readonly) myReadOnlyVar;
実装ファイルでreadwrite
として再宣言して、ivarへの直接アクセスだけでなく、プロパティ構文を使用して設定できるようにします。
@interface
または@implementation
ブロックの完全に外側で変数を宣言することに関しては、はい、それらは単純なC変数であり、まったく同じように機能します。
まず、@ DrummerBの答えを読んでください。理由と一般的にすべきことの概要です。それを念頭に置いて、あなたの特定の質問に:
#import <Foundation/Foundation.h>
// 1) What do I declare here?
ここには実際の変数定義はありません(実行していることを正確に知っているが、絶対に実行しない場合は、技術的には合法です)。他のいくつかの種類のものを定義できます。
外部は変数宣言のように見えますが、実際には別の場所で宣言する約束です。 ObjCでは、定数を宣言するためにのみ使用し、通常は文字列定数のみを使用する必要があります。例えば:
extern NSString * const MYSomethingHappenedNotification;
次に、.m
ファイルで実際の定数を宣言します。
NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";
@interface SampleClass : NSObject
{
// 2) ivar declarations
// Pretty much never used?
}
DrummerBが指摘したように、これはレガシーです。ここには何も入れないでください。
// 3) class-specific method / property declarations
@end
うん。
#import "SampleClass.h"
// 4) what goes here?
上記の外部定数。ファイルの静的変数もここに移動できます。これらは、他の言語のクラス変数と同等です。
@interface SampleClass()
// 5) private interface, can define private methods and properties here
@end
うん
@implementation SampleClass
{
// 6) define ivars
}
しかし、めったにありません。ほとんどの場合、clang(Xcode)が変数を作成できるようにする必要があります。例外は通常、ObjC以外のivar(Core Foundationオブジェクト、特にObjC++クラスの場合はC++オブジェクトなど)、または奇妙なストレージセマンティクスを持つivar(何らかの理由でプロパティと一致しないivarなど)に関するものです。
// 7) define methods and synthesize properties from both public and private
// interfaces
一般的に、@ synthesizeはもうすべきではありません。 Clang(Xcode)があなたのためにそれを行います。
過去数年間で、物事は劇的に簡単になりました。副作用は、3つの異なる時代(脆弱なABI、非脆弱なABI、非脆弱なABI +自動合成)が存在することです。したがって、古いコードを見ると、少し混乱する可能性があります。したがって、単純さから生じる混乱:D
私もかなり新しいので、うまくいけば何も台無しにしないでください。
1および4:Cスタイルのグローバル変数:ファイル全体のスコープを持ちます。 2つの違いは、ファイル幅が広いため、最初のヘッダーはヘッダーをインポートするすべてのユーザーが使用できるのに対し、2番目はヘッダーをインポートできないことです。
2:インスタンス変数。ほとんどのインスタンス変数は、プロパティを使用してアクセサを介して合成および取得/設定されます。これは、メモリ管理がわかりやすく簡単になり、わかりやすいドット表記ができるためです。
6:実装ivarはやや新しいものです。パブリックヘッダーに必要なものだけを公開したいが、サブクラスはそれらを継承しないため、プライベートivarを配置するのに適した場所です。
3と7:パブリックメソッドとプロパティの宣言、そして実装。
5:プライベートインターフェイス。私は物事をきれいに保ち、一種のブラックボックス効果を作り出すことができるときはいつでも常にプライベートインターフェイスを使用します。彼らがそれについて知る必要がなければ、そこに置いてください。私は読みやすさのためにそれをします、他の理由があるかどうかわかりません。
これは、Objective-Cで宣言されたすべての種類の変数の例です。変数名はそのアクセスを示します。
ファイル:Animal.h
@interface Animal : NSObject
{
NSObject *iProtected;
@package
NSObject *iPackage;
@private
NSObject *iPrivate;
@protected
NSObject *iProtected2; // default access. Only visible to subclasses.
@public
NSObject *iPublic;
}
@property (nonatomic,strong) NSObject *iPublic2;
@end
ファイル:Animal.m
#import "Animal.h"
// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end
@implementation Animal {
@public
NSString *iNotVisible3;
}
-(id) init {
self = [super init];
if (self){
iProtected = @"iProtected";
iPackage = @"iPackage";
iPrivate = @"iPrivate";
iProtected2 = @"iProtected2";
iPublic = @"iPublic";
_iPublic2 = @"iPublic2";
iNotVisible = @"iNotVisible";
_iNotVisible2 = @"iNotVisible2";
iNotVisible3 = @"iNotVisible3";
}
return self;
}
@end
INotVisible変数は他のクラスからは見えないことに注意してください。これは可視性の問題であるため、@property
または@public
で宣言しても変更されません。
コンストラクタ内では、副作用を避けるために、self
の代わりにアンダースコアを使用して、@property
で宣言された変数にアクセスすることをお勧めします。
変数にアクセスしてみましょう。
ファイル:Cow.h
#import "Animal.h"
@interface Cow : Animal
@end
ファイル:Cow.m
#import "Cow.h"
#include <objc/runtime.h>
@implementation Cow
-(id)init {
self=[super init];
if (self){
iProtected = @"iProtected";
iPackage = @"iPackage";
//iPrivate = @"iPrivate"; // compiler error: variable is private
iProtected2 = @"iProtected2";
iPublic = @"iPublic";
self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private
//iNotVisible = @"iNotVisible"; // compiler error: undeclared identifier
//_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
//iNotVisible3 = @"iNotVisible3"; // compiler error: undeclared identifier
}
return self;
}
@end
ランタイムを使用して、表示されない変数に引き続きアクセスできます。
ファイル:Cow.m(パート2)
@implementation Cow(blindAcess)
- (void) setIvar:(NSString*)name value:(id)value {
Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
object_setIvar(self, ivar, value);
}
- (id) getIvar:(NSString*)name {
Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
id thing = object_getIvar(self, ivar);
return thing;
}
-(void) blindAccess {
[self setIvar:@"iNotVisible" value:@"iMadeVisible"];
[self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
[self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
NSLog(@"\n%@ \n%@ \n%@",
[self getIvar:@"iNotVisible"],
[self getIvar:@"_iNotVisible2"],
[self getIvar:@"iNotVisible3"]);
}
@end
表示されない変数にアクセスしてみましょう。
ファイル:main.m
#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
Cow *cow = [Cow new];
[cow performSelector:@selector(blindAccess)];
}
}
これは印刷します
iMadeVisible
iMadeVisible2
iMadeVisible3
サブクラス専用のバッキングivar _iNotVisible2
にアクセスできたことに注意してください。 Objective-Cでは、@private
とマークされているものも含め、すべての変数を読み取りまたは設定できます。例外はありません。
関連するオブジェクトやC変数は別の鳥なので、それらは含めませんでした。 C変数については、@interface X{}
または@implementation X{}
の外部で定義された変数は、ファイルスコープと静的ストレージを持つC変数です。
メモリ管理属性、または読み取り専用/読み取り書き込み、getter/setter属性については説明しませんでした。