Skip to main content

Adding Swift Convenience Initializers

Designated or Convenience?


First a recap on the two types of initializer:
designated initializer is the primary initializer for a class. It must fully initialize all properties introduced by its class before calling a superclass initializer. A class can have more than one designated initializer.
convenience initializer is a secondary initializer that must call a designated initializer of the same class. It is useful when you want to provide default values or other custom setup. A class does not require convenience initializers.

The Three rules

With that clear you need to remember three rules for designated and convenience initializers for class types:
  • A designated initializer must call a designated initializer from the immediate superclass.
  • A convenience initializer must call another initializer from the same class.
  • A convenience initializer must ultimately call a designated initializer.
What does this mean for us? In simple terms, do not call super from your convenience initializer. Call another initializer (convenience or designated) from the same class.

    A Practical Example

    Let’s give it a try with a practical example. Creating and configuring an image view from an image in the asset catalog ready for us to use with Auto Layout needs several lines of code:
    // Create the image
    let heartImage = UIImage(named: "Heart")
    
    // Now create the image view using the image
    let heartImageView = UIImageView(image: heartImage)
    
    // Configure the view remembering to disable
    // the autoresizing mask if we are using
    // Auto Layout
    heartImageView.translatesAutoresizingMaskIntoConstraints = false
    
    // Sometime we must also override the default content mode
    heartImageView.contentMode = .scaleAspectFit
    
    // Finally add to the view hierarchy
    view.addSubview(heartImageView)
    
    This gets boring quickly and I always end up forgetting to disable the translation of the auto resizing mask.

    Creating a Convenience Initializer

    Let’s create a new UIImageView convenience initializer that performs the configuration for us. Our initializer will take the name of the image in the asset catalog and the content mode. Since we do not always want to set the content mode we will make it default to scale to fill. Here is the code:
    extension UIImageView {
      convenience init?(named name: String, contentMode: UIViewContentMode = .scaleToFill) {
        guard let image = UIImage(named: name) else {
          return nil
        }
    
        self.init(image: image)
        self.contentMode = contentMode
        translatesAutoresizingMaskIntoConstraints = false
      }
    }
    
    Some points to note:
    • If the image name parameter is invalid our initializer will fail and should return nil. You define a failable initializer with a question mark after the init keyword (init?).
    • Once we have a valid UIImage we call the designated initializer of UIImageView to create the image view.
    • We can only set properties on the UIImageView object after we have called the designated initializer.

    Example Usage

    // Default contentMode
    let heart = UIImageView(named: "Heart")
    
    // Specifying a contentMode
    let star = UIImageView(named: "Star", contentMode: .scaleAspectFit)
    
    Note that the failable initializer means the return type is an optional (UIImageView?) so we would need to unwrap it before adding it to the superview.

    Property Access Before Initializing Self

    One final note on when you can access self to modify properties. If we try to set a property of UIImageView before we have called self.init(image: UIImage?) we get an error:
    // Does not compile
    self.contentMode = contentMode
    self.init(image: image)
    
    // Use of 'self' in property access 'contentMode'
    // before self.init initializes self
    
    If you remember that a designated initializer must fully initialize all properties of its class you can maybe see why this is not allowed. Even if the compiler allowed us to do it the designated initializer would overwrite our value with the default value.

    Comments

    Popular posts from this blog

    Understanding of Size Classes?

    Size Classes are an abstraction of how a device should be categorized depending on its screen dimensions. Apple defined two categorizations for both vertical and horizontal sizes called “Regular” and “Compact”. The former specifies a big space, while the latter specifies a small” space. How big or small exactly? Here, “big” and “small” are not intended to be measured in inches. iOS Size Classes Apple has introduced many devices like iPhone, iPad with different screen sizes and resolution. Also after iOS 8 apple has supported multitasking in iPad. So for the developers to develop a common or single UI for all the devices apple has introduced the concept of an adaptive layout by combining auto layout and size classes. What is adaptive layout: The adaptive layout is a method of building the apps based on the size and characteristics of the container instead of a targeting a particular device. We can create a single layout to work on all...

    The differences between Core Data and a Database

    Database Core Data Primary function is storing and fetching data Primary function is graph management (although reading and writing to disk is an important supporting feature) Operates on data stored on disk (or minimally and incrementally loaded) Operates on objects stored in memory (although they can be lazily loaded from disk) Stores "dumb" data Works with fully-fledged objects that self-manage a lot of their behavior and can be subclassed and customized for further behaviors Can be transactional, thread-safe, multi-user Non-transactional, single threaded, single user (unless you create an entire abstraction around Core Data which provides these things) Can drop tables and edit data without loading into memory Only operates in memory Perpetually saved to disk (and often crash resilient) Requires a save process Can be slow to create millions of new rows Can create millions of new objects in-memory very quickly (although saving these objects will be slow) Offer...

    What Is a Closure?

    Closures are self contained chunks of code that can be passed around and used in your code. Closures can capture and store references to any constants or variables from the context in which they are defined. This is know as closing over those variables, hence the name closures. Closures are use intensively in the Cocoa frameworks – which are used to develop iOS or Mac applications. Functions are a special kind of closures. There are three kinds of closures: global functions  – they have a name and cannot capture any values nested functions  – they have a name and can capture values from their enclosing functions closure expressions  – they don’t have a name and can capture values from their context The thing to keep in mind for the moment is that you already have an intuition about closures. They are almost the same as functions but don’t necessarily have a name. // a closure that has no parameters and return a String var hello: () -> ( String ) = { ...