【问题标题】:Subclassed UIView from NIB File not typed to subclass来自 NIB 文件的子类 UIView 未键入子类
【发布时间】:2011-04-06 12:09:12
【问题描述】:

我有一个 NIB 文件,其中一些类 (SomeClassView : UIView) 设置为 NIB 顶级视图的自定义类。 SomeClass 有 IBOutlets,我用它来连接我在 Interface Builder 中布置的 SomeClass 的子视图。

我像这样实例化 SomeClass:

- (id)initWithFrame:(CGRect)frame {
    self = [[[[NSBundle mainBundle] loadNibNamed:@"SomeClassView" owner:nil options:nil] objectAtIndex:0] retain]; 
    // "SomeClassView" is also the name of the nib
    if (self != nil) {
        self.frame = frame;
    }
    return self;
}

现在说我用 SubClassView 子类 SomeClass。我向 SubClassView 添加了一个名为 -(void)foo:

的方法
- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self != nil) {
        [self foo];
    }
    return self;
}

此时我收到一个运行时错误:* 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“-[SomeClassView foo:]: unrecognized selector sent to instance 0xAAAAAA”

似乎 SubClassView 的 initWithFrame 中的“self”仍设置为超级 SomeClassView。解决此问题的快速破解方法是更改​​ SubClassView initWithFrame 中的 isa 指针:

- (id)initWithFrame:(CGRect)frame {
    Class prevClass = [self class];
    self = [super initWithFrame:frame];
    if (self != nil) {
        isa = prevClass;
        [self foo]; // works now
    }
 }

这不是一个理想的解决方法,因为我每次子类化时都必须更新 isa,或者甚至可能有不同的 init 方法我也必须更新。

1) 理想情况下,有没有一种简单的方法可以完全通过代码来解决这个问题?也许我设置 self = 加载的笔尖的方式?

2) 是否有一种替代架构更适合我正在尝试做的事情?一个示例是将所有者设置为 self,但是您必须手动设置所有属性/插座映射。

【问题讨论】:

    标签: iphone objective-c cocoa-touch interface-builder nib


    【解决方案1】:

    如果您的子类的实例变量不是SomeClassView 中声明的实例变量,那么交换 isa 指针是一个问题。请注意,您的 nib 文件有一个 SomeClassView 类型的对象,这意味着,在加载 nib 文件时,nib 加载器将分配该类型的对象并将其从 nib 文件中解组。暂时将 isa 指针更改为 [SubViewClass class] 本身不会使其成为 SubViewClass 类型的对象,因为 nib 加载器分配的是 SomeClassView 对象。

    也就是说,我认为没有一种可靠且自动的方式来使用包含在加载 nib 时需要更改类型的对象的 nib 文件。

    您可以做的是让您的SomeClassView 对象声明一个符合某些协议的委托。该协议将定义SomeClassView 中可能扩展的行为方法。例如,

    @protocol SomeClassViewDelegate
    @optional
    - (void)someClassViewDidAwakeFromNib:(SomeClassView *)someClassView;
    @end
    

    不是继承SomeClassView,而是让任意对象执行您当前在SubClassView 中的任何自定义行为。例如,

    @interface SubClassViewBehaviour : NSObject <SomeClassViewDelegate>
    …
    @end
    
    @implementation SubClassViewBehaviour
    - (void)someClassViewDidAwakeFromNib:(SomeClassView *)someClassView {
        // whatever behaviour is currently in -[SubClassView foo]
    }
    @end
    

    SubClassViewBehaviour 对象将在代码中创建,并在加载 nib 文件或任何其他 IB 代理对象时设置为 nib 文件的所有者。 SomeClassView 将有一个委托出口连接到文件的所有者/代理对象,它会在适当的位置调用委托方法。例如,

    @implementation SomeClassView
    - (void)awakeFromNib {
        SEL didAwakeFromNib = @selector(someClassViewDidAwakeFromNib:);
        if ([[self delegate] respondsToSelector:didAwakeFromNib]) {
            [[self delegate] performSelector:didAwakeFromNib withObject:self];
        }
    }
    @end
    

    进一步说明:您的代码当前泄漏了一个视图对象,因为正在实例化两个对象:一个通过代码中的+alloc,另一个通过 nib 加载。您将后者分配给self,因此通过+alloc 创建的那个正在泄漏。另外,我相信您在第三个代码 sn-p 中错过了对 super 的呼叫。

    【讨论】:

    • 谢谢,我已经更新了第三个 sn-p。我想我接受这样一个事实,即通过子类化来更改笔尖类型是没有意义的。
    • @roblocop 不过会很有用。我认为有必要提交增强请求雷达。
    • @Bavarious,您是否仍然推荐这个实现,或者现在我们在 2015 年有更好的实现。
    【解决方案2】:

    为什么不从子类本身内部执行此操作,而不是在您第一次从类外部实例化它时确保它是正确的类:

    SubClass *instance = [[SubClass alloc] initWithNibName:@"SomeClassView" bundle:nil];
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-22
      • 2013-03-02
      相关资源
      最近更新 更多