line4 min read

Curated summary

Large-scale iOS Settings System Unraveled through AttributedString Structure

Read original(opens in new tab)

LINE’s “Service Configuration” system lets teams deploy features dynamically without waiting for LINE’s two-week app release cycle. As the iOS app grew to roughly 700 configuration keys across 60 modules, its monolithic design created dependency, usability, concurrency, testing, and QA problems. The article argues that the original design was reasonable at small scale but needed to evolve, beginning with lessons from Foundation’s type-safe AttributedString design.

What Service Configuration Provides

  • Service operators modify values through an administration page.
  • The server notifies LINE clients, which fetch updated values.
  • Values are selected based on factors such as:
    • User region
    • Device
    • OS version
  • The system supports:
    • Feature flags
    • Rollbacks
    • A/B tests
    • Error-reporting sample rates
    • UI behavior policies
  • Configuration is delivered as a string-to-string dictionary, for example:
    • "function.media.image_medium": "1280,70"
    • "function.media.message.flow.v2.image": "Y"

Problems Caused by the Monolithic Design

The original implementation required every key to be declared in one roughly 7,000-line file. Although this was simple initially, growth in teams and modules made the structure increasingly costly.

Circular Dependencies and Weak Typing

  • Configuration values were exposed as raw strings because the configuration module could not depend on feature-specific modules.
  • For example, "1280,70" represented image dimensions and JPEG quality, but callers had to parse it into an ImageTransferQuality value themselves.
  • Defining ImageTransferQuality in the configuration module avoided repeated parsing but polluted unrelated modules with photo-specific types.
  • Defining it in the photo module preserved separation of concerns but created an impossible reverse dependency.

Incomplete and Confusing Abstractions

  • Developers had to understand server-specific encoding rules and implementation details.
  • Boolean values were sent as "Y" and "N", requiring a custom decodeBoolIfPresent(forKey:) method.
  • The custom decoder’s name resembled Swift’s standard decoding API, making incorrect implementations easy to write and review.
  • Decoding failures could silently fall back to defaults, making the underlying problem difficult to diagnose.
  • The same default value often had to be declared three times:
    • A property-group default
    • A decoding fallback
    • A global defaultConfiguration entry
  • These duplicated defaults served subtly different purposes, although the distinctions were generally unnecessary.

Lack of Thread Safety

  • Configuration groups were lazily decoded and replaced when new server values arrived.
  • Multiple services could read configuration values concurrently on different threads.
  • This caused use-after-free crashes— reportedly hundreds per day—leading to bug tickets and hotfix releases.
  • As the number of services and concurrent operations increased, this became a systemic issue rather than an occasional edge case.

No Built-in Debug Overrides

  • QA frequently needed to temporarily change configuration values.
  • Because the system had no override mechanism, each feature required custom:
    • Persistent storage
    • Debug-menu UI
    • Value-display text
  • Implementing this repeatedly required edits across several files and modules.

Fragmented Test Doubles

  • Since LineConfigurationManager was a singleton, modules created narrow protocols and custom mocks for the settings they used.
  • This resulted in dozens of duplicated protocols and test doubles.
  • These had to be updated alongside configuration keys and could fall out of sync.
  • Differences between mocks and production behavior could allow bugs to escape tests or create false failures.

Looking to Established Designs

The team first distilled the required properties of a replacement:

  • Type-safe access to a large number of key-value pairs
  • Independent key definitions by each module
  • Safe behavior under concurrency

They identified Foundation’s AttributedString as a useful precedent because it manages many typed attributes while allowing UIKit, AppKit, SwiftUI, and other frameworks to define their own attributes independently. The article presents this as the starting point for redesigning Service Configuration around a more modular and type-safe architecture.

Continue with another curated summary.