可插拔的自定义视图笔尖(Nib-in-a-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中实例化自定义视图,则(相当丑陋的)静态
loadNormally
变量最初是ѭ5We:我们从\“ Step 1 \”开始,在该处加载并返回从其Nib加载的实例,之后确保我们立即使用\“ normal \” if-branch
-initWithCoder:
。从自定义视图Nib加载意味着我们回到
-initWithCoder:
,这次是
loadNormally==YES
,即我们让Nib加载机制完成其工作并返回自定义视图实例。 结果总结: 优点:它有效!!!在Interface Builder中,我们具有“可插入”自定义视图! 坏处:丑陋的静态变量…:-/ 丑陋:自定义视图的实例被泄漏!这就是我希望您的帮助的地方-我不明白为什么。有任何想法吗?     
已邀请:
        最后,我们采用了一种更好的方法,即在自定义视图中覆盖ѭ9,将视图控制器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;
}
    
        Yang的回答很好,但是仍然可能发生“发送到已释放实例的消息”。我通过使用'self \'分配解决了这个问题。 因此,如果使用ARC,则必须允许此\\ self \分配。 (有关更多信息,请阅读https://blog.compeople.eu/apps/?p=142) 要在ARC项目中实现此目的,请在文件上添加\'-fno-objc-arc \'标志编译器设置。 然后在此文件中进行NO-ARC编码(如dealloc设置nils,调用super dealloc等)。 同样,客户端nib的viewcontroller应该使用strong属性来保存awakeFromNib返回的实例。在我的示例代码中,customView的引用如下: @属性(强,非原子)IBOutlet CustomView * customView; 最后,我使用UIView + Util类别中定义的copyUIPropertiesTo:和loadNibNamed对属性处理和笔尖加载进行了其他一些改进。 所以awakeAfterUsingCoder:代码现在
#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];
        }
    }    
}
    
        有另一种方法可以做到这一点: 假设您在
Interface Builder
中使用
View1
,然后创建另一个名为ѭ16view的视图,
View2
有一个对应的
View2.xib
文件,您已在linked19ѭ和
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];
}
这样,您就可以在需要将自定义视图放在ѭ15中的位置使用
View1
,从而使
View1
在ѭ15中可以重用,而无需编写任何其他代码。     

要回复问题请先登录注册