【问题标题】:Instance Variables for Objective C CategoriesObjective C 类别的实例变量
【发布时间】:2011-05-07 23:12:21
【问题描述】:

我有一种情况,我似乎需要将实例变量添加到一个类别,但我从 Apple 的文档中知道我不能这样做。所以我想知道最好的替代方案或解决方法是什么。

我想做的是添加一个为 UIViewControllers 添加功能的类别。我会发现它在我所有不同的 UIViewController 中都很有用,无论它们扩展了什么特定的 UIViewController 子类,所以我认为类别是最好的解决方案。为了实现这个功能,我需要几种不同的方法,并且我需要跟踪它们之间的数据,这就是我想要创建实例方法的原因。

如果它有帮助,这就是我特别想做的事情。我希望更容易跟踪软件键盘何时隐藏和显示,以便我可以在我的视图中调整内容的大小。我发现可靠地做到这一点的唯一方法是将代码放入四个不同的 UIViewController 方法中,并在实例变量中跟踪额外的数据。所以这些方法和实例变量是我想放入一个类别的,所以我不必每次需要处理软件键盘时都复制粘贴它们。 (如果对于这个确切的问题有更简单的解决方案,那也很好——但我仍然想知道类别实例变量的答案以供将来参考!)

【问题讨论】:

  • 你能做一个子类吗?

标签: objective-c cocoa oop categories


【解决方案1】:

是的,你可以这样做,但既然你在问,我必须问:你绝对确定你需要这样做吗? (如果你说“是”,那就回去,弄清楚你想做什么,看看有没有不同的方法)

但是,如果您真的想将存储注入到您无法控制的类中,请使用associative reference

【讨论】:

【解决方案2】:

最近,我需要这样做(将状态添加到类别)。 @Dave DeLong 对此有正确的看法。在研究最佳方法时,我发现了 Tom Harrington 的出色 blog post。我喜欢@JeremyP 在类别上使用@property 声明的想法,但不喜欢他的特定实现(不喜欢全局单例或持有全局引用)。关联引用是必经之路。

这里是添加(看起来是)ivars 到您的类别的代码。我已经在博客上详细介绍了这一点here

在 File.h 中,调用者只能看到干净的高级抽象:

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

在 File.m 中,我们可以实现@property(注意:这些不能是@synthesize'd):

@implementation UIViewController (MyCategory)

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

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

我们还需要声明和定义 MyCategoryIVars 类。为了便于理解,我已经按照正确的编译顺序对此进行了解释。 @interface 需要放在 Category @implementation 之前。

@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

上面的代码声明并实现了保存我们的 ivars (someObject) 的类。由于我们无法真正扩展 UIViewController,因此必须这样做。

【讨论】:

  • 在您的示例实现中,-init 覆盖的目的是什么?这似乎是一个无操作。谢谢!
  • @Olie,你说得对,它不需要。我的编码倾向于有点强迫症。由于有一个 dealloc,我喜欢提供一个匹配的 init 来提醒自己如果/当向对象添加资源时触摸两者。您可以安全地消除它。
  • 没问题。因为我是 objc_setAssociatedObject() 的新手,所以我不确定那里是否有一些奇怪的、理想的副作用。谢谢!
【解决方案3】:

我相信现在可以将合成属性添加到类别中,并且实例变量是自动创建的,但我从未尝试过,所以我不确定它是否会起作用。

一个更hacky的解决方案:

创建一个单例 NSDictionary,它将 UIViewController 作为键(或者更确切地说,将其地址包装为 NSValue)并将您的属性的值作为其值。

为实际进入字典以获取/设置属性的属性创建 getter 和 setter。

@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

上述方法的两个潜在问题:

  • 无法删除已释放的视图控制器的键。只要您只跟踪少数几个,那应该不是问题。或者,您可以添加一个方法,在您知道已完成操作后从字典中删除一个键。
  • 我不能 100% 确定 NSValue 的 isEqual: 方法比较内容(即包装的指针)以确定相等性,或者它是否只是比较 self 以查看比较对象是否是完全相同的 NSValue。如果是后者,您必须使用 NSNumber 而不是 NSValue 作为键(NSNumber numberWithUnsignedLong: 将在 32 位和 64 位平台上都可以使用)。

【讨论】:

    【解决方案4】:

    这最好使用内置的 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/

    【讨论】:

      【解决方案5】:

      在许多在线文档中都提到您无法在类别中创建新变量,但我找到了一种非常简单的方法来实现这一点。这是让我们在类别中声明新变量的方式。

      在您的 .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
      

      现在你有了新的变量。

      【讨论】:

        【解决方案6】:

        为什么不简单地创建UIViewController 的子类,向其中添加功能,然后改用该类(或其子类)?

        【讨论】:

        • UITableViewControllerUIViewController 的子类。类别将为两者添加功能,而子类不会。
        • 我怀疑 OP 遇到了他们无法控制的 UIViewController 子类。
        • 我明白你的意思。关于措辞的一些东西让我认为这些纯粹是 OP 自己的直接 UIViewController 子类。我怀疑 KennyTM(根据他的评论)以同样的方式阅读它。 :-)
        • 正确,我想扩展 UIViewController 和 UITableViewController 子类。
        • 斯蒂芬+1。这是一个很棒的评论。是的,一个类别将为两者添加功能。
        【解决方案7】:

        根据您的工作,您可能需要使用静态类别方法。

        所以,我假设你遇到了这种问题:

        ScrollView 中有几个文本编辑器。用户在文本编辑时键入,您希望滚动滚动视图,以便在键盘上方看到文本编辑。

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

        从这里返回并不一定需要视图向后移动,因此它不需要存储任何东西。

        但如果没有代码示例,我只能想到这些,抱歉。

        【讨论】:

          【解决方案8】:

          我相信可以使用 Obj-C 运行时向类添加变量。
          我也找到了this discussion

          【讨论】:

          • 这是进入 iOS 了吗?我没有跟上这个新功能(对我来说似乎很脏:-))。
          猜你喜欢
          • 2012-02-02
          • 1970-01-01
          • 1970-01-01
          • 2013-02-02
          • 2023-04-04
          • 1970-01-01
          • 1970-01-01
          • 2020-05-09
          • 1970-01-01
          相关资源
          最近更新 更多