Are IBOutlets set asynchronously to instantiate(withOwner:options:)?

Howdy!

I have the following pattern peppered across my code base. Until now, it has worked without any issues.

protocol NibBased: UIView {
  static var nibName: String { get }
  static var nibBundle: Bundle { get }
  static var nibOptions: [UINib.OptionsKey: Any]? { get }
  static var nibIndex: Int { get }
  static func makeFromNib(_ nibOwner: Any?) -> Self
}

// default impl
extension NibBased {
  static var nibName: String {
    String(describing: Self.self)
  }

  static var nibBundle: Bundle {
    Bundle(for: Self.self)
  }

  static var nibOptions: [UINib.OptionsKey: Any]? {
    nil
  }

  static var nibIndex: Int {
    0
  }

  static func makeFromNib(_ nibOwner: Any? = nil) -> Self {
    UINib(nibName: nibName, bundle: nibBundle)
        .instantiate(
          withOwner: nibOwner,
          options: nibOptions
        )[nibIndex] as! Self
  }
}

final class _InternalView1: UIView, NibBased {
  // impl
}

final class _InternalView2: UIView, NibBased {
  // impl
}

final class ExternalView: UIView {
  @IBOutlet private weak var viewFromInternal1: UIView!
  @IBOutlet private weak var viewFromInternal2: UIView!

  private typealias Internal1 = _InternalView1
  private typealias Internal2 = _InternalView2

  required init?(coder: NSCoder) {
    super.init(coder: coder)
    _init()
  }

  override init(frame: CGRect) {
    super.init(frame: frame)
    _init()
  }

  private func _init() {
    let view1: InternalView1 = .makeFromNib(self)
    let view2: InternalView2 = .makeFromNib(self)
  
    // place view1 and view 2 in the view hierarchy
    // impl

    // configure the views from the internal views
    viewFromInternal1.backgroundColor = .red
    viewFromInternal2.backgroundColor = .blue
  }
}

The problem I am running into now on a particular view of mine is that some of the internal @IBOutlets remain unset even after calling .makeFromNib(self) to create the internal views and placing them in the view hierarchy. The@IBOutlets that remain unset seems mostly random and appears to be influencable by the debugger, which has be concerned... My assumption until now was that the @IBOutlets would be set when the Nib is intantiated with an owner. Has this assumption been wrong this whole time? Have I inadvertently been relying on a race condition?

Answered by smkuehnhold in 725505022

Answered over in this thread. :)

Why do you declare IBOutlets, as you create the views in code ?

You can work around this problem via property observers on the @IBOutlets, but that still leaves me with my concern.

Is the timing in which @IBOutlets are defined in the owner after a call to UINib.instantiate(withOwner:options:) specified? My assumption until now was that they were simply initialized during the call to UINib.instantiate(withOwner:options:), which seems to be mostly true in practice. But I have now run into a example where this is not the case, and I worry that my existing code might be relying on behaviors that aren't actually guaranteed 😅.

Accepted Answer

Answered over in this thread. :)

Are IBOutlets set asynchronously to instantiate(withOwner:options:)?
 
 
Q