web-dev-qa-db-ja.com

目的Cカテゴリのインスタンス変数

カテゴリにインスタンス変数を追加する必要があるように見える状況がありますが、Appleのドキュメントから、それができないことを知っています。だから私は最善の代替策または回避策が何であるかと思っています。

私がやりたいことは、UIViewControllersに機能を追加するカテゴリを追加することです。拡張する特定のUIViewControllerサブクラスに関係なく、すべてのさまざまなUIViewControllersでそれが役立つと思うので、カテゴリが最良のソリューションだと思います。この機能を実装するには、いくつかの異なるメソッドが必要であり、それらの間のデータを追跡する必要があるため、インスタンスメソッドを作成したいと思いました。

参考までに、私が特にやりたいことは次のとおりです。ビューでコンテンツのサイズを変更できるように、ソフトウェアキーボードの非表示と表示を簡単に追跡できるようにしたいと考えています。これを確実に行う唯一の方法は、コードを4つの異なるUIViewControllerメソッドに配置し、インスタンス変数の追加データを追跡することです。したがって、これらのメソッドとインスタンス変数をカテゴリに入れたいので、ソフトウェアキーボードを処理する必要があるたびにコピーアンドペーストする必要はありません。 (この正確な問題のより簡単な解決策があれば、それも問題ありませんが、将来の参照のために、カテゴリインスタンス変数への回答を知りたいと思います!)

32
Josh Justice

はい、あなたはこれを行うことができますが、あなたが尋ねているので、私は尋ねなければなりません:あなたがする必要があることを本当に確信していますか? (「はい」と言った場合は、戻って何をしたいのかを理解し、別の方法があるかどうかを確認してください)

ただし、制御できないクラスに本当にストレージを注入したい場合は、 連想参照 を使用します。

28
Dave DeLong

最近、これを行う必要がありました(カテゴリーに状態を追加します)。 @Dave DeLongはこれについて正しい見解を持っています。最善のアプローチを調査したところ、トムハリントンによるすばらしい ブログ投稿 を見つけました。カテゴリーで@property宣言を使用するという@JeremyPのアイデアは好きですが、彼の特定の実装は好きではありません(グローバルシングルトンのファンやグローバル参照を保持しているわけではありません)。連想参照は、進むべき道です。

以下は、ivarを(どのように見えるか)カテゴリに追加するコードです。これについて詳しくブログに書きました here

File.hでは、呼び出し元はクリーンで高レベルの抽象化のみを確認します。

@interface UIViewController (MyCategory)
@property (retain,nonatomic) NSUInteger someObject;
@end

File.mで、@ propertyを実装できます(注:これらを@synthesizeすることはできません)。

@implementation UIViewController (MyCategory)

- (NSUInteger)someObject
{
  return [MyCategoryIVars fetch:self].someObject;
}

- (void)setSomeObject:(NSUInteger)obj
{
  [MyCategoryIVars fetch:self].someObject = obj;
}

また、クラスMyCategoryIVarsを宣言および定義する必要があります。理解を容易にするために、これを適切なコンパイル順序から外して説明しました。 @インターフェイスは、カテゴリ@実装の前に配置する必要があります。

@interface MyCategoryIVars : NSObject
@property (retain,nonatomic) NSUInteger someObject;
+ (MyCategoryIVars*)fetch:(id)targetInstance;
@end

@implementation MyCategoryIVars

@synthesize someObject;

+ (MyCategoryIVars*)fetch:(id)targetInstance
{
  static void *compactFetchIVarKey = &compactFetchIVarKey;
  MyCategoryIVars *ivars = objc_getAssociatedObject(targetInstance, &compactFetchIVarKey);
  if (ivars == nil) {
    ivars = [[MyCategoryIVars alloc] init];
    objc_setAssociatedObject(targetInstance, &compactFetchIVarKey, ivars, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [ivars release];
  } 
  return ivars;
}

- (id)init
{
  self = [super init];
  return self;
}

- (void)dealloc
{
  self.someObject = nil;
  [super dealloc];
}

@end

上記のコードは、ivar(someObject)を保持するクラスを宣言して実装します。 UIViewControllerを実際に拡張することはできないため、これを行う必要があります。

14
Andrew Philips

合成されたプロパティをカテゴリに追加できるようになり、インスタンス変数が自動的に作成されると思いますが、試したことがないので、機能するかどうかはわかりません。

よりハッキーなソリューション:

UIViewControllerをキーとして(または、NSValueとしてラップされたアドレス)、プロパティの値を値として持つシングルトンNSDictionaryを作成します。

プロパティを取得/設定するためにディクショナリに実際に行くプロパティのゲッターとセッターを作成します。

@interface UIViewController(MyProperty)

@property (nonatomic, retain) id myProperty;
@property (nonatomic, readonly, retain) NSMutableDcitionary* propertyDictionary;

@end

@implementation  UIViewController(MyProperty)

-(NSMutableDictionary*) propertyDictionary
{
    static NSMutableDictionary* theDictionary = nil;
    if (theDictionary == nil)
    {
        theDictioanry = [[NSMutableDictionary alloc] init];
    }
    return theDictionary;
}


-(id) myProperty
{
    NSValue* key = [NSValue valueWithPointer: self];
    return [[self propertyDictionary] objectForKey: key];
}

-(void) setMyProperty: (id) newValue
{
    NSValue* key = [NSValue valueWithPointer: self];
    [[self propertyDictionary] setObject: newValue forKey: key];    
}

@end

上記のアプローチには2つの潜在的な問題があります。

  • 割り当てが解除されたView Controllerのキーを削除する方法はありません。あなたがほんの一握りを追跡している限り、それは問題ではありません。または、キーを使い終わったら、ディクショナリからキーを削除するメソッドを追加できます。
  • NSValueのisEqual:メソッドがコンテンツ(つまり、ラップされたポインター)を比較して等しいかどうかを判断するか、それとも自身を比較して比較オブジェクトがまったく同じNSValueかどうかを確認するかどうかは、100%確実ではありません。後者の場合は、キーにNSValueではなくNSNumberを使用する必要があります(NSNumber numberWithUnsignedLong:は、32ビットと64ビットの両方のプラットフォームでトリックを実行します。
6
JeremyP

これは、組み込みのObjC機能、関連オブジェクト(別名、関連参照)を使用して最適に達成されます。以下の例では、カテゴリに変更して、関連オブジェクトを変数名に置き換えます。

NSObject + AssociatedObject.h

@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject + AssociatedObject.m

#import <objc/runtime.h>

@implementation NSObject (AssociatedObject)
@dynamic associatedObject;

- (void)setAssociatedObject:(id)object {
     objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)associatedObject {
    return objc_getAssociatedObject(self, @selector(associatedObject));
}

完全なチュートリアルはこちらをご覧ください:

http://nshipster.com/associated-objects/

6
malhal

多くのドキュメントのオンラインで、カテゴリに新しい変数を作成することはできないと述べましたが、私はそれを達成する非常に簡単な方法を見つけました。これは、カテゴリで新しい変数を宣言する方法です。

.hファイル

@interface UIButton (Default)

   @property(nonatomic) UIColor *borderColor;

@end  

.mファイル

#import <objc/runtime.h>
static char borderColorKey;

@implementation UIButton (Default)

- (UIColor *)borderColor
{
    return objc_getAssociatedObject(self, &borderColorKey);
}

- (void)setBorderColor:(UIColor *)borderColor
{
    objc_setAssociatedObject(self, &borderColorKey,
                             borderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    self.layer.borderColor=borderColor.CGColor;
}

@end

これで、新しい変数が作成されました。

3
Varun Naharia

単にUIViewControllerのサブクラスを作成し、それに機能を追加してから、代わりにそのクラス(またはそのサブクラス)を使用しないのはなぜですか?

0
Joshua Nozzi

実行していることに応じて、静的カテゴリメソッドを使用することができます。

だから、私はあなたがこの種の問題を抱えていると思います:

ScrollViewには、いくつかのテキスト編集があります。ユーザーがテキスト編集を入力すると、スクロールビューをスクロールして、キーボードの上にテキスト編集が表示されるようにします。

+ (void) staticScrollView: (ScrollView*)sv scrollsTo:(id)someView
{
  // scroll view to someviews's position or some such.
}

これから戻るには、必ずしもビューを戻す必要はありません。そのため、何も格納する必要はありません。

しかし、コード例がなくても、これですべてを理解できます。申し訳ありません。

0
Stephen Furlani

Obj-Cランタイムを使用してクラスに変数を追加することは可能だと思います。
このディスカッション も見つかりました。

0
Richard