Objective-C:カテゴリのプロパティ/インスタンス変数


122

Objective-Cのカテゴリで合成プロパティを作成できないため、次のコードを最適化する方法がわかりません。

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

@implementation MyClass (Variant)

@dynamic test;

- (NSString *)test {
    NSString *res;
    //do a lot of stuff
    return res;
}

@end

テスト方法は、実行時に複数回呼び出されて、私は結果を計算するために多くのものをやっています。通常、合成されたプロパティを使用して、メソッドが最初に呼び出されたときに値をIVar _testに格納し、次にこのIVarを返すだけです。上記のコードをどのように最適化できますか?


2
カテゴリの代わりに、MyClass基本クラスにプロパティを追加するだけで、通常は何をしませんか?さらにそれを進めるために、バックグラウンドで重いものを実行し、プロセスが通知を起動するようにするか、プロセスが完了したときにMyClassのハンドラーを呼び出します。
ジェレミー

4
MyClassはCore Dataから生成されたクラスです。生成されたクラス内にカスタムオブジェクトコードを配置した場合、コアデータからクラスを再生成すると表示されなくなります。このため、カテゴリを使用しています。
dhrm

1
タイトルに最も当てはまる質問を受け入れますか?(「カテゴリ内のプロパティ」)
hfossli 2014年

なぜサブクラスを作成しないのですか?
Scott Zhu

回答:


124

@loreanの方法は機能します(注:回答は削除されました)が、ストレージスロットは1つしかありません。したがって、これを複数のインスタンスで使用し、各インスタンスに個別の値を計算させる場合、機能しません。

幸い、Objective-Cランタイムには、関連オブジェクトと呼ばれるものがあります。

#import <objc/runtime.h>

static void *MyClassResultKey;
@implementation MyClass

- (NSString *)test {
  NSString *result = objc_getAssociatedObject(self, &MyClassResultKey);
  if (result == nil) {
    // do a lot of stuff
    result = ...;
    objc_setAssociatedObject(self, &MyClassResultKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
  return result;
}

@end


5
@DaveDeLongこのソリューションをありがとう!あまり美しくはありませんが、動作します:)
dhrm 2012年

42
「あまり美しくない」?これがObjective-Cの優れた点です。;)
Dave DeLong 2012年

6
正解です。あなたも、使用して静的変数を取り除くことができます@selector(test):として、ここで説明し、キーとしてstackoverflow.com/questions/16020918/...
ガブリエレPetronella

1
@HotFudgeSunday -それは迅速に作業を行うと思う:stackoverflow.com/questions/24133058/...
スコットCorscadden

174

.hファイル

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

.mファイル

#import <objc/runtime.h>

static void * LaserUnicornPropertyKey = &LaserUnicornPropertyKey;

@implementation NSObject (LaserUnicorn)

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, LaserUnicornPropertyKey);
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, LaserUnicornPropertyKey, unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

@end

通常のプロパティのように-ドット表記でアクセス可能

NSObject *myObject = [NSObject new];
myObject.laserUnicorn = [LaserUnicorn new];
NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);

より簡単な構文

あるいは、次の@selector(nameOfGetter)ように静的ポインタキーを作成する代わりに使用することもできます。

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, @selector(laserUnicorn));
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

詳細については、https://stackoverflow.com/a/16020927/202451を参照してください


4
良い記事。注意すべき1つのことは、記事の更新です。2011年12月22日更新:関連付けのキーは、文字列ではなくvoidポインターvoid * keyであることに注意してください。つまり、関連する参照を取得するときは、まったく同じポインタをランタイムに渡す必要があります。C文字列をキーとして使用し、その文字列をメモリ内の別の場所にコピーし、コピーした文字列へのポインタをキーとして渡して関連する参照にアクセスしようとすると、意図したとおりに機能しません。
ロジャース氏2013

4
あなたは本当に必要ありません@dynamic objectTag;@dynamicセッターとゲッターが別の場所で生成されることを意味しますが、この場合、それらはここで実装されます。
IluTov 2013年

@NSAddict true!修繕!
hfossli 2013年

1
手動メモリ管理のためのlaserUnicornsの割り当て解除はどうですか?これはメモリリークですか?
マニー2014年

自動的に解除されますstackoverflow.com/questions/6039309/...
hfossli

32

与えられた答えはうまくいき、私の提案はそれを拡張したものであり、ボイラープレートコードを書きすぎないようにしています。

カテゴリプロパティのgetterメソッドとsetterメソッドを繰り返し作成することを避けるために、この回答ではマクロを紹介しています。さらに、これらのマクロは、intやなどのプリミティブ型プロパティの使用を容易にしますBOOL

マクロを使用しない従来のアプローチ

従来、次のようなカテゴリプロパティを定義します。

@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end

次に、関連付けられたオブジェクトgetセレクターをキーとして使用して、getterメソッドとsetterメソッドを実装する必要があります(元の回答を参照)。

#import <objc/runtime.h>

@implementation MyClass (Category)
- (NSString *)text{
    return objc_getAssociatedObject(self, @selector(text));
}

- (void)setText:(NSString *)text{
    objc_setAssociatedObject(self, @selector(text), text, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

私の提案するアプローチ

ここで、代わりにマクロを使用して記述します。

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

マクロは次のように定義されています。

#import <objc/runtime.h>

#define CATEGORY_PROPERTY_GET(type, property) - (type) property { return objc_getAssociatedObject(self, @selector(property)); }
#define CATEGORY_PROPERTY_SET(type, property, setter) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), property, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
#define CATEGORY_PROPERTY_GET_SET(type, property, setter) CATEGORY_PROPERTY_GET(type, property) CATEGORY_PROPERTY_SET(type, property, setter)

#define CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(type, property, valueSelector) - (type) property { return [objc_getAssociatedObject(self, @selector(property)) valueSelector]; }
#define CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(type, property, setter, numberSelector) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), [NSNumber numberSelector: property], OBJC_ASSOCIATION_RETAIN_NONATOMIC); }

#define CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(unsigned int, property, unsignedIntValue)
#define CATEGORY_PROPERTY_SET_UINT(property, setter) CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(unsigned int, property, setter, numberWithUnsignedInt)
#define CATEGORY_PROPERTY_GET_SET_UINT(property, setter) CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_SET_UINT(property, setter)

マクロCATEGORY_PROPERTY_GET_SETは、指定されたプロパティのゲッターとセッターを追加します。読み取り専用または書き込み専用のプロパティは、それぞれCATEGORY_PROPERTY_GETCATEGORY_PROPERTY_SETマクロを使用します。

プリミティブ型にはもう少し注意が必要です

プリミティブ型はオブジェクトではないため、上記のマクロにはunsigned int、プロパティの型として使用する例が含まれています。これは、整数値をNSNumberオブジェクトにラップすることによって行われます。したがって、その使用法は前の例と類似しています。

@interface ...
@property unsigned int value;
@end

@implementation ...
CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
@end

このパターンに続き、あなたは単にもサポートするために、より多くのマクロを追加することができsigned intBOOLなどを...

制限事項

  1. OBJC_ASSOCIATION_RETAIN_NONATOMICデフォルトでは、すべてのマクロが使用されています。

  2. App CodeのようなIDE は現在、プロパティの名前をリファクタリングするときにセッターの名前を認識しません。自分で名前を変更する必要があります。


1
#import <objc/runtime.h>それ以外の場合は、カテゴリ.mファイルを作成することを忘れないでください。コンパイル時エラー:C99では関数 'objc_getAssociatedObject'の暗黙の宣言が無効 です。stackoverflow.com/questions/9408934/...
Sathe_Nagaraja

7

libextobjcライブラリを使用するだけです。

hファイル:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

mファイル:

#import <extobjc.h>
@implementation MyClass (Variant)

@synthesizeAssociation (MyClass, test);

@end

@synthesizeAssociationの詳細


これでアクセサをオーバーライドする正しい方法は何ですか(たとえば、プロパティの遅延読み込み)
David James

3

iOS 9でのみテスト済み例:UIViewプロパティをUINavigationBar(カテゴリ)に追加

UINavigationBar + Helper.h

#import <UIKit/UIKit.h>

@interface UINavigationBar (Helper)
@property (nonatomic, strong) UIView *tkLogoView;
@end

UINavigationBar + Helper.m

#import "UINavigationBar+Helper.h"
#import <objc/runtime.h>

#define kTKLogoViewKey @"tkLogoView"

@implementation UINavigationBar (Helper)

- (void)setTkLogoView:(UIView *)tkLogoView {
    objc_setAssociatedObject(self, kTKLogoViewKey, tkLogoView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)tkLogoView {
    return objc_getAssociatedObject(self, kTKLogoViewKey);
}

@end

-2

おそらくより簡単で、使用しない別の可能な解決策は、Associated Objects次のようにカテゴリ実装ファイルで変数を宣言することです。

@interface UIAlertView (UIAlertViewAdditions)

- (void)setObject:(id)anObject;
- (id)object;

@end


@implementation UIAlertView (UIAlertViewAdditions)

id _object = nil;

- (id)object
{
    return _object;
}

- (void)setObject:(id)anObject
{
    _object = anObject;
}
@end

この種の実装の欠点は、オブジェクトがインスタンス変数としてではなく、クラス変数として機能することです。また、プロパティ属性を割り当てることはできません(OBJC_ASSOCIATION_RETAIN_NONATOMICなどの関連オブジェクトで使用されるなど)


質問はインスタンス変数について尋ねます。あなたのソリューションはロリアンのようです
hfossli

1
本当に??あなたはあなたが何をしているのか知っていましたか?ファイルに依存しない内部変数にアクセスするゲッター/セッターメソッドのペアを作成しましたが、クラスオブジェクトです。つまり、2つのUIAlertViewクラスオブジェクトを割り当てた場合、それらのオブジェクト値は同じです!
イタチ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.