2011-06-15 28 views
4

我們目前best-practice for custom views是:可插入的自定義視圖Nib(Nib-in-a-Nib):內存泄漏 - 爲什麼?

  1. 建立在筆尖的自定義視圖。
  2. 在視圖控制器中,以編程方式加載Nib,從加載對象數組中獲取自定義視圖(我們在UIView類別方法+loadInstanceFromNib中執行此操作)。
  3. 將自定義視圖添加爲子視圖,設置其框架。

我們實際上想要的是「嵌入」自定義視圖筆尖視圖 - 控制器筆尖內。如果不這樣做,至少我們想在視圖控制器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:,將加載從筆尖視圖(這將調用-initWithCoder:去權如果分支標記爲「步驟2」),設置其幀,並將其保留計數設置爲1.

但是,如果我們在視圖控制器Nib內部實例化自定義視圖,(確實相當難看)靜態變量loadNormally變量最初是NO:我們從「步驟1」開始,其中我們在確認我們將立即使用-initWithCoder:的「正常」if分支之後,加載並返回從它的Nib加載的實例。從自定義視圖Nib加載意味着我們回到-initWithCoder:,這次是loadNormally==YES,即我們讓Nib加載機制完成它的工作並返回自定義視圖實例。

結果,在總結:

  • 好: IT WORKS!我們在Interface Builder中有「可插入」的自定義視圖!
  • 壞:醜陋靜態變量...: -/
  • 醜陋:自定義視圖的實例被泄露!這是我愛你的幫助 - 我不明白爲什麼。有任何想法嗎?

回答

3

我們結束了一個更好的方式,這涉及到我們自定義視圖首要-awakeAfterUsingCoder:,取代從視圖 - 控制器筆尖與一個從「內嵌式」筆尖(CustomView.xib)加載加載的對象。

我在一篇廣泛的博客文章中寫了how we embed custom-view Nibs inside other Nibs

的代碼是這樣的:

// 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; 
} 
+1

後續,使這項工作在ARC下:http://blog.yangmeyer.de/blog/2012/07/09/an-update-on-nested-nib-loading/ – 2012-07-09 21:35:41

1

楊的回答是偉大的......但是「消息發送到釋放實例」尚可發生。我通過使用'自我'分配來解決這個問題。

所以如果你使用ARC,你將不得不允許這個'自我'分配。(有關更多信息,請參閱https://blog.compeople.eu/apps/?p=142

要在ARC項目中實現此目的,請在文件中添加'-fno-objc-arc'標誌編譯器設置。 然後在此文件中進行NO-ARC編碼(如dealloc設置nils,調用超級dealloc等)。

另外,客戶端nib的viewcontroller應該使用strong屬性來存放由awakeFromNib返回的實例。在我的樣本代碼的情況下,被customView這樣引用:


@屬性(,非原子)IBOutlet中CustomView * customView;


我終於加入了一些其他方面的改進,使用copyUIPropertiesTo性能處理和筆尖加載:loadNibNamed在我的UIView +的Util類別定義

所以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]; 
     } 
    }  
} 
+1

我喜歡提取屬性的想法 - 複製到一個方法(這使得它可以被子類覆蓋)。請注意,技術*確實*在ARC下工作,因爲我終於在後續文章中描述了:http://blog.yangmeyer.de/blog/2012/07/09/an-update-on-nested-筆尖加載/ – 2012-07-09 21:37:33

+0

@Yang很好,我會嘗試儘快:-) – Pascal 2012-07-10 12:19:44

0

有一種替代的方式沿這樣做:

說,你在你的Interface Builder使用View1,然後創建View2稱爲View2另一種觀點認爲,有一個相應的View2.xib文件,你在View2.mView2.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無需編寫任何代碼。