3
votes

According to Apple's documentation, Interface Builder instantiates objects, which then are serialized and packaged into a NIB/XIB file at design time. At runtime, the whole object graph is deserialized via initWithCoder:.

Here's the documentation:

View instances that are created in Interface Builder don't call initWithFrame: when their nib files are loaded, which often causes confusion. Remember that Interface Builder archives an object when it saves a nib file, so the view instance will already have been created and initWithFrame: will already have been called.

As the objects are instantiated via initWithCoder:, I thought that the object graph is serialized via its counterpart encodeWithCoder:. However, consider the following code:

@interface CustomView : UIView
@end

@implementation CustomView

-(id)initWithCoder:(NSCoder *)aDecoder{
    if(self = [super initWithCoder:aDecoder]){
        NSNumber * n = [aDecoder decodeObjectForKey:@"DummyKey"];
        NSLog(@"%@", n);
    }
    return self;
}

-(void)encodeWithCoder:(NSCoder *)aCoder{
    [super encodeWithCoder:aCoder];
    [aCoder encodeObject:@YES forKey:@"DummyKey"];
}

@end

If I place this CustomView on a storyboard in Interface Builder, it prints "(null)" instead of "YES". Why is this happening? How does Interface Builder serialize the object graph? Am I misinterpreting the documentation?

3
Note that you can add "User Defined Runtime Attributes" in IB (select the view and go to the 3rd tab of the inspector). These will be set on your object when it is unarchived IIRC (using the setter method of your property).Taum

3 Answers

1
votes

encoderWithCoder: method won't be called because the time when XCode encode the object graph into xib is not running time, it can't call your encoderWithCoder: method.

How does Interface Builder serialize the object graph?

If you open Xib as source code, you can see it like this:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="4514" systemVersion="13B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
    <dependencies>
        <deployment defaultVersion="1536" identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3747"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="EMXibViewController">
            <connections>
                <outlet property="view" destination="1" id="3"/>
            </connections>
        </placeholder>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <view contentMode="scaleToFill" id="1">
            <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
            <subviews>
                <view contentMode="scaleToFill" id="XyJ-CF-vRx">
                    <rect key="frame" x="68" y="102" width="204" height="403"/>
                    <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                    <color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
                </view>
            </subviews>
            <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
            <simulatedStatusBarMetrics key="simulatedStatusBarMetrics"/>
            <simulatedScreenMetrics key="simulatedDestinationMetrics" type="retina4"/>
        </view>
    </objects>
</document>

We can see xib only keeps the non-default value of object and the relationships between them.

The xib an UIViewController and has only two UI object , one is the UIViewController's view, the other is it's subview let's say it mysubview and talks about how XCode archive mysubview.

I have changed its backgroundColor、frame and autoresizingMask property of mysubview, take backgroundColor property as example, the backgroundColor property is saved as "color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"". It will archive the backgroundColor of mysubview.

Xib just archive the backgroundColor and autoresizingMask and frame property of the mysubview object and when load the xib, it will call UIView's initWithCoder method. In the method, it will get the UIColor object for the key @"backgroundColor" and CGRect for the key @"frame" and UIViewAutoresizing for the key @"autoresizingMask", any other property of UIView assigned default value.

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder] ;
    UIColor * backgroundColor = [aDecoder decodeIntegerForKey:@"backgroundColor"] ;
    if (backgroundColor) {
        self.backgroundColor = backgroundColor ;
    } else {
        self.backgroundColor = $(defaultColor) ; // if no key in xib, use the default value.
    }
    // more...
    return self ;
}

It's a nice question and make me think more.

0
votes

Interface Builder doesn't use your code to generate xibs. It's as simple as that. encodeWithCoder is never called by the Interface Builder. The Interface Builder doesn't run your code.

0
votes

In simple word if you create your object in xib or Storybord then initWithFrame and initWithCoder will not be able to override by you, they will be called by framework and create the object property from xib or Storybord.