Uiview 可插拔自定义视图Nib(Nib-in-a-Nib):内存泄漏–;为什么?

Uiview 可插拔自定义视图Nib(Nib-in-a-Nib):内存泄漏–;为什么?,uiview,memory-leaks,custom-controls,nib,nscoding,Uiview,Memory Leaks,Custom Controls,Nib,Nscoding,我们目前的目标是: 在Nib中构建自定义视图 在视图控制器中,以编程方式加载Nib,从加载的对象数组中获取自定义视图(我们在UIView类别方法+loadInstanceFromNib中执行此操作) 将自定义视图添加为子视图,设置其边框 实际上,我们希望将自定义视图Nib“嵌入”到视图控制器Nib中。否则,至少我们希望在视图控制器Nib中添加并定位自定义视图实例(而不查看其内容) 我们非常接近于以下解决方案: @implementation CustomView static BOOL loa

我们目前的目标是:

  • 在Nib中构建自定义视图
  • 在视图控制器中,以编程方式加载Nib,从加载的对象数组中获取自定义视图(我们在UIView类别方法
    +loadInstanceFromNib
    中执行此操作)
  • 将自定义视图添加为子视图,设置其边框
  • 实际上,我们希望将自定义视图Nib“嵌入”到视图控制器Nib中。否则,至少我们希望在视图控制器Nib中添加并定位自定义视图实例(而不查看其内容)

    我们非常接近于以下解决方案:

    @implementation CustomView
    
    static BOOL loadNormally;
    
    - (id) initWithCoder:(NSCoder*)aDecoder {
        id returnValue = nil;
        if (loadNormally) { // Step 2
            returnValue = [super initWithCoder:aDecoder];
            loadNormally = !loadNormally;
        } else {            // Step 1
            loadNormally = !loadNormally;
            returnValue = [CustomView loadInstanceFromNib];
        }
        return returnValue;
    }
    
    - (id) initWithFrame:(CGRect)frame {
        loadNormally = YES;
        self = (id) [[CustomView loadInstanceFromNib] retain];
        self.frame = frame;
        return self;
    }
    // ...
    @end
    
    如果我们以编程方式实例化自定义视图,我们将使用
    -initWithFrame:
    ,它将从Nib加载视图(它将调用
    -initWithCoder:
    ,并直接转到标记为“步骤2”的If分支),设置其帧,并将其保留计数设置为1

    但是,如果我们在视图控制器Nib中实例化自定义视图,静态
    loadNormal
    变量(公认相当难看)最初是
    NO
    :我们从“步骤1”开始,在确保立即使用“normal”后,加载并返回从其Nib加载的实例如果
    -initWithCoder:
    的分支。从自定义视图Nib加载意味着我们回到
    -initWithCoder:
    ,这次是
    loadnormal==YES
    ,也就是说,我们让Nib加载机制完成它的工作并返回自定义视图实例

    结果总结如下:

    • 好的:它有效!!!我们在Interface Builder中有“可插拔”自定义视图
    • 坏:一个丑陋的静态变量…:-/
    • 丑陋:自定义视图的实例泄漏!这就是我想要你帮助的地方——我不明白为什么。有什么想法吗

    我们最终得到了一个更好的方法,即在自定义视图中使用coder:覆盖
    -awakefter,将从视图控制器Nib加载的对象替换为从“嵌入式”Nib(CustomView.xib)加载的对象

    我在一篇博文中写道

    代码是这样的:

    // CustomView.m
    - (id) awakeAfterUsingCoder:(NSCoder*)aDecoder {
        BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
        if (theThingThatGotLoadedWasJustAPlaceholder) {
            // load the embedded view from its Nib
            CustomView* theRealThing = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([CustomView class]) owner:nil options:nil] objectAtIndex:0];
    
            // pass properties through
            theRealThing.frame = self.frame;
            theRealThing.autoresizingMask = self.autoresizingMask;
    
            [self release];
            self = [theRealThing retain];
        }
        return self;
    }
    

    杨的答案很好。。。但“发送到解除分配实例的消息”仍然会发生。我通过“自我”分配解决了这个问题

    因此,如果使用ARC,则必须允许这种“自我”分配。(阅读了解更多信息)

    要在ARC项目中实现这一点,请在文件中添加“-fno objc ARC”标志编译器设置。 然后在此文件中进行无弧编码(如dealoc设置nils、调用super dealoc等)

    此外,客户端nib的viewcontroller应该使用strong属性来保存awakeFromNib返回的实例。在我的示例代码中,customView的引用方式如下:

    // CustomView.m
    - (id) awakeAfterUsingCoder:(NSCoder*)aDecoder {
        BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
        if (theThingThatGotLoadedWasJustAPlaceholder) {
            // load the embedded view from its Nib
            CustomView* theRealThing = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([CustomView class]) owner:nil options:nil] objectAtIndex:0];
    
            // pass properties through
            theRealThing.frame = self.frame;
            theRealThing.autoresizingMask = self.autoresizingMask;
    
            [self release];
            self = [theRealThing retain];
        }
        return self;
    }
    

    @属性(strong,非原子)IBOutlet CustomView*CustomView


    最后,我使用在我的UIView+Util类别中定义的copyupropertiesto:loadNibNamed对属性处理和nib加载添加了一些其他改进

    所以使用编码器后醒来:代码现在是

    #import "UIView+Util.h"
    ...
    - (id) awakeAfterUsingCoder:(NSCoder*)aDecoder
    {
        // are we loading an empty “placeholder” or the real thing?
        BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
    
        if (theThingThatGotLoadedWasJustAPlaceholder)
        {
            CustomView* customView = (id) [CustomView loadInstanceFromNib];
            // copy all UI properties from self to new view!
            // if not, property that were set using Interface buider are lost!
            [self copyUIPropertiesTo:customView];
    
            [self release];
            // need retain to avoid deallocation
            self = [customView retain];
        }
        return self;
    }
    
    UIView+Util类别代码为

    @interface UIView (Util)
       +(UIView*) loadInstanceFromNib;
       -(void) copyUIPropertiesTo:(UIView *)view;
    @end
    
    以及它的实施

    #import "UIView+Util.h"
    #import "Log.h"
    
    @implementation UIView (Util)
    
    +(UIView*) loadInstanceFromNib
    { 
        UIView *result = nil; 
        NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner: nil options: nil];
        for (id anObject in elements)
        { 
            if ([anObject isKindOfClass:[self class]])
            { 
                result = anObject;
                break; 
            } 
        }
        return result; 
    }
    
    -(void) copyUIPropertiesTo:(UIView *)view
    {
        // reflection did not work to get those lists, so I hardcoded them
        // any suggestions are welcome here
    
        NSArray *properties =
        [NSArray arrayWithObjects: @"frame",@"bounds", @"center", @"transform", @"contentScaleFactor", @"multipleTouchEnabled", @"exclusiveTouch", @"autoresizesSubviews", @"autoresizingMask", @"clipsToBounds", @"backgroundColor", @"alpha", @"opaque", @"clearsContextBeforeDrawing", @"hidden", @"contentMode", @"contentStretch", nil];
    
        // some getters have 'is' prefix
        NSArray *getters =
        [NSArray arrayWithObjects: @"frame", @"bounds", @"center", @"transform", @"contentScaleFactor", @"isMultipleTouchEnabled", @"isExclusiveTouch", @"autoresizesSubviews", @"autoresizingMask", @"clipsToBounds", @"backgroundColor", @"alpha", @"isOpaque", @"clearsContextBeforeDrawing", @"isHidden", @"contentMode", @"contentStretch", nil];
    
        for (int i=0; i<[properties count]; i++)
        {
            NSString * propertyName = [properties objectAtIndex:i];
            NSString * getter = [getters objectAtIndex:i];
    
            SEL getPropertySelector = NSSelectorFromString(getter);
    
            NSString *setterSelectorName =
                [propertyName stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[propertyName substringToIndex:1] capitalizedString]];
    
            setterSelectorName = [NSString stringWithFormat:@"set%@:", setterSelectorName];
    
            SEL setPropertySelector = NSSelectorFromString(setterSelectorName);
    
            if ([self respondsToSelector:getPropertySelector] && [view respondsToSelector:setPropertySelector])
            {
                NSObject * propertyValue = [self valueForKey:propertyName];
    
                [view setValue:propertyValue forKey:propertyName];
            }
        }    
    }
    
    #导入“UIView+Util.h”
    #导入“Log.h”
    @实现UIView(Util)
    +(UIView*)loadInstanceFromNib
    { 
    UIView*结果=零;
    NSArray*元素=[[NSBundle mainBundle]loadNibNamed:NSStringFromClass([self class])所有者:nil选项:nil];
    用于(id元素中的对象)
    { 
    if([anObject isKindOfClass:[self class]]))
    { 
    结果=一个对象;
    打破
    } 
    }
    返回结果;
    }
    -(void)copyUIPropertiesTo:(UIView*)视图
    {
    //反射无法获取这些列表,所以我硬编码了它们
    //欢迎提出任何建议
    NSArray*属性=
    [NSArray阵列包含以下对象:@“框架”@“边界”@“中心”@“变换”@“内容缩放因子”@“多重接触”@“排他性接触”@“自动ResizesSuBViews”@“自动ResisingMask”@“剪贴簿”@“背景色”@“alpha”@“不透明”@“在绘图前清除内容”@“隐藏”@“内容模式”@“内容拉伸”,零];
    //某些getter具有“is”前缀
    NSArray*吸气剂=
    [n包含以下对象的阵列阵列:@“帧”@“边界”@“中心”@“变换”@“内容缩放因子”@“isMultipleTouchEnabled”@“isExclusiveTouch”@“autoresizesSubviews”@“autoresizingMask”@“clipsToBounds”@“backgroundColor”@“alpha”@“等水线”@“在绘图之前清除内容”@“isHidden”@“内容模式”@“内容拉伸”,零];
    
    对于(int i=0;i有另一种方法:

    假设您在
    界面生成器中使用
    View1
    ,然后创建另一个名为
    View2
    View2
    有一个相应的
    View2.xib
    文件,您已经链接了
    View2.m
    View2.xib
    中的插座

    然后,在
    View1.m
    中,写下以下内容:

    -(void)awakeFromNib
    {
        NSArray *topObjects = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil];
        self.subContentView = topObjects.firstObject]
        [self addSubview:self.subContentView];
    }
    

    有了它,您可以在需要将自定义视图放入
    Interface Builder
    的地方使用
    View1
    ,从而使
    View1
    Interface Builder
    中可重用,而无需再编写任何代码。

    接下来,这将在ARC下工作:我喜欢提取属性复制到方法的想法(这使得它可以被子类覆盖)。请注意,该技术在ARC下确实有效,正如我最后在后续帖子中所描述的:@Yang great,我将尽快尝试:-)