MVC
Published: 4 January 2014
Tagged:
Pattern

I saved this article in 2014 because I was building iOS apps and kept asking myself: why does Apple’s MVC feel wrong? ViewControllers were growing to thousands of lines, the model was doing too much, and nobody could agree on what the controller was actually supposed to do.

The answer was in the original. What Apple shipped as “MVC” for UIKit isn’t what was designed in the 1970s. The original had a clean observer loop, direct input handling through the controller, and nested views. UIKit collapsed all of that into one massive class.

Below is a translation of Bill Sanders’ breakdown of the original Smalltalk-80 implementation. Worth reading once — it explains a lot about why everything that came after (MVVM, MVP, TCA) had to be invented.



A translation of Bill Sanders’ article on implementing the classic MVC meta-pattern in Smalltalk-80



Preface

This is not an introduction to the MVC model, but an overview of its implementation in Smalltalk-80 to understand the original intentions and functions of the triad. Classic MVC will help better understand the subsequent evolution of patterns that appeared later. We will look at how a simple MVC application works in Smalltalk-80 and study how it can be implemented in ActionScript.


Model-view-controller (MVC) — a software architecture pattern in which the data model, user interface, and control logic of an application are separated into three distinct components, so that modification of one component has minimal impact on the others.


Implementing Model-View-Controller in Smalltalk-80

The MVC model facilitates separation of concerns in the development of interactive graphical applications. Application logic and state, how users interact with the application, and how the application state is presented to the user are all handled by separate elements of the MVC triad. Smalltalk-80 uses the MVC metaphor, providing built-in support for interactive application development.


The idea was to provide a set of built-in user interface components, such as buttons, menus, and lists, that can be plugged into a GUI application. To effectively use these built-in interface elements, the implementation had to be built according to MVC. Let’s look at a conceptual diagram of a Smalltalk-80 application.



MVC diagram in Smalltalk-80 (Krasner & Pope). All objects in Smalltalk communicate with each other through messages, which is the way of calling object methods. At first glance, the MVC diagram looks a bit strange. Model-View and Controller-Model have bidirectional dependencies. The second interesting aspect was that user input goes directly through the controller. This is a significant change — we usually expect the user to interact with interface elements in the View, not in the Controller.


In Smalltalk-80, all data from input devices is fed directly to the controller. Let’s briefly look at how MVC is supported in Smalltalk-80.



MVC classes in Smalltalk-80

There are three abstract classes called Model, View, and Controller. All concrete implementations of models, views, and controllers must be subclasses of these abstract superclasses. Let’s look at the classes (this is an abbreviated description — see Krasner & Pope).


  • Model: the abstract superclass Model implements common model behavior. It implements the dependency maintenance mechanism.

A View can register with a concrete model to become a subscriber and receive change notifications. If a concrete model broadcasts its messages, they will be automatically sent to all its subscribed views. This is an implementation of the Observer pattern (a behavioral object pattern). A concrete model does not directly know about the views that depend on it.


  • View: the abstract superclass View implements common view behaviors. A built-in set of view subclass components (e.g., StandardSystemView — a standard window, and TextEditorView — a text editor).

Views can be nested to develop complex user interfaces. For example, StandardSystemView (i.e., a window) contains a view component called TextEditorView. Each view can have one model and one controller. Built-in components have a predefined default controller class to implement their standard behavior. To instantiate a component, you need to configure your model and display the component. The component then initializes the default controller with its model instance and registers with it for messages. When a component is closed, all nested components are released. When a view is released, it removes its message subscriptions from the model. If you want components to have unique behavior, you can create a custom controller for them.


  • Controller: the abstract superclass Controller implementing common behaviors. Each controller has a reference to one model, one view, and a global variable called a sensor, which provides an interaction interface for input devices (mouse, keyboard). The abstract controller class implements its common behaviors for defining a concrete controller and its corresponding view. If a concrete view is active (currently being used by the user), its controller manages all user input.


The controller must have a direct reference to the view — for example, to know whether the mouse cursor is currently positioned over a particular view. Importantly, only controllers, not views, receive input data (from keyboard or mouse).



Key features of MVC implementation in Smalltalk-80

  • views can be nested;
  • each built-in component (e.g. ListBox) is a view;
  • each view has a corresponding controller;
  • a controller notifies its view when it becomes active;
  • the active controller (whose view is currently active) receives data changed by the user via input devices;
  • a controller can update the view (component) based on user actions;
  • a controller modifies the model based on user actions;
  • a view updates itself via the observer, synchronized with the model.



Fast forward to 2026

Smalltalk-80 is gone. But the ideas aren’t.

SwiftUI’s @Observable + @State model is actually closer to the original observer loop than UIKit’s MVC ever was. The view subscribes to the model and re-renders automatically. The controller disappears — user events are just closures.

TCA (The Composable Architecture) takes it further: Store → Reducer → State is the 2024 version of Model → Controller → View, with unidirectional flow added to prevent the feedback loops that made the original Smalltalk diagram look confusing.

The separation of concerns Krasner and Pope defined in 1988 is still the goal. We just have better tools to enforce it now.