A blog exploring functional programming and Swift.

A Tour of isowords

Wednesday May 12, 2021

This past month we released four completely free videos dedicated to diving into the real-world Swift code base of an iOS game we recently launched and open sourced: isowords.

In them we explore the client app and its backend Swift server to show how concepts covered in previous episodes of Point-Free can be applied to a production code base.

  • A Tour of isowords: Part 1: We start the tour by pulling down the repo and bootstrapping the iOS app. Then, we dive into the code to show off our modern approach to project management using the Swift Package Manager. We also explore how the Composable Architecture, a library we built from first principles over a number of Point-Free episodes, powers the entire application.

  • A Tour of isowords: Part 2: We explore how adopting the Composable Architecture aided in the ability to easily (and extensively) modularize the code base. This unlocked many things that would have otherwise been much more difficult, including the ability to add an onboarding experience without any changes to feature code, an App Clip experience, and even automated App Store assets.

  • A Tour of isowords: Part 3: We take a peek at the Swift server that powers the game’s backend. We get things running the server locally and explore some of the benefits of developing both client and server in Swift, such as simultaneously debugging both applications together, and how code and concepts can be shared across each application.

  • A Tour of isowords: Part 4: We wrap up our tour by showing off two powerful ways the iOS client and Swift server share code: not only does the same code that routes server requests simultaneously power the API client, but we can write integration tests that exercise the full client-server lifecycle.

We hope these episodes provide a small taste of some ideas in application development and architecture that we find interesting, and we hope you do too. If you want a go deeper in your exploration of topics related to architecture, dependency management, SwiftUI, and more, check out our ever-growing collections of episodes today!


Better Testing Bonanza

Monday Mar 22, 2021

This week on Point-Free we showed how to write tests that exhaustively describe which dependencies are needed to exercise a feature, and we did so in an ergnomic way. If a dependency is unexpectedly used in a test case then it fails the test suite and even points to the exact step of the assertion that caused the dependency to be invoked. This makes it possible to be instantly notified when a part of your feature starts accessing dependencies that you don’t expect, and it was awesome to see.

However, the ability to leverage this awesome capability hinges on being able to creating “failing” versions of dependencies, that is, instances of the dependency that simply invoke XCTFail under the hood rather than doing their actual work. And unfortunately, the moment you import XCTest into a non-test target your application will fail to build with inscrutable errors. This led us to develop a library that dynamically loads XCTFail so that it can be used in any context, not just test targets.

So, without further ado, we are open sourcing XCTDynamicOverlay today, along with updates to both the Composable Architecture and Combine Schedulers to take advantage of this new library.

XCTestDynamicOverlay

It is very common to write test support code for libraries and applications. This often comes in the form of little domain-specific functions or helpers that make it easier for users of your code to formulate assertions on behavior.

Currently there are only two options for writing test support code:

  • Put it in a test target, but then you can’t access it from multiple other test targets. For whatever reason test targets cannot be imported, and so the test support code will only be available in that one single test target.
  • Create a dedicated test support module that ships just the test-specific code. Then you can import this module into as many test targets as you want, while never letting the module interact with your regular, production code.

Neither of these options is ideal. In the first case you cannot share your test support, and the second case will lead you to a proliferation of modules. For each feature you potentially need 3 modules: MyFeature, MyFeatureTests and MyFeatureTestSupport. SPM makes managing this quite easy, but it’s still a burden.

It would be far better if we could ship the test support code right along side or actual library or application code. After all, they are intimately related. You can even fence off the test support code in #if DEBUG ... #endif if you are worried about leaking test code into production.

However, as soon as you add import XCTest to a source file in your application or a library it loads, the target becomes unbuildable:

import XCTest

🛑 ld: warning: Could not find or use auto-linked library ‘XCTestSwiftSupport’ 🛑 ld: warning: Could not find or use auto-linked framework ‘XCTest’

This is due to a confluence of problems, including test header search paths, linker issues, and more. XCTest just doesn’t seem to be built to be loaded alongside your application or library code.

Solution

That doesn’t mean we can’t try! XCTest Dynamic Overlay is a microlibrary that exposes an XCTFail function that can be invoked from anywhere. It dynamically loads XCTest functionality at runtime, which means your code will continue to compile just fine.

import XCTestDynamicOverlay // ✅

Example

A real world example of using this is in our library, the Composable Architecture. That library vends a TestStore type whose purpose is to make it easy to write tests for your application’s logic. The TestStore uses XCTFail internally, and so that forces us to move the code to a dedicated test support module. However, due to how SPM works you cannot currently have that module in the same package as the main module, and so we would be forced to extract it to a separate repo. By loading XCTFail dynamically we can keep the code where it belongs.

As another example, let’s say you have an analytics dependency that is used all over your application:

struct AnalyticsClient {
  var track: (Event) -> Void

  struct Event: Equatable {
    var name: String
    var properties: [String: String]
  }
}

If you are disciplined about injecting dependencies, you probably have a lot of objects that take an analytics client as an argument (or maybe some other fancy form of DI):

class LoginViewModel: ObservableObject {
  ...

  init(analytics: AnalyticsClient) {
    ...
  }

  ...
}

When testing this view model you will need to provide an analytics client. Typically this means you will construct some kind of “test” analytics client that buffers events into an array, rather than sending live events to a server, so that you can assert on what events were tracked during a test:

func testLogin() {
  var events: [AnalyticsClient.Event] = []
  let viewModel = LoginViewModel(
    analytics: .test { events.append($0) }
  )

  ...

  XCTAssertEqual(events, [.init(name: "Login Success")])
}

This works really well, and it’s a great way to get test coverage on something that is notoriously difficult to test.

However, some tests may not use analytics at all. It would make the test suite stronger if the tests that don’t use the client could prove that it’s never used. This would mean when new events are tracked you could be instantly notified of which test cases need to be updated.

One way to do this is to create an instance of the AnalyticsClient type that simply performs an XCTFail inside the track endpoint:

import XCTest

extension AnalyticsClient {
  static let failing = Self(
    track: { _ in XCTFail("AnalyticsClient.track is unimplemented.") }
  )
}

With this you can write a test that proves analytics are never tracked, and even better you don’t have to worry about buffering events into an array anymore:

func testValidation() {
  let viewModel = LoginViewModel(
    analytics: .failing
  )

  ...
}

However, you cannot ship this code with the target that defines AnalyticsClient. You either need to extract it out to a test support module (which means AnalyticsClient must also be extracted), or the code must be confined to a test target and thus not shareable.

However, with XCTestDynamicOverlay we can have our cake and eat it too 😋. We can define both the client type and the failing instance right next to each in application code without needing to extract out needless modules or targets:

struct AnalyticsClient {
  var track: (Event) -> Void

  struct Event: Equatable {
    var name: String
    var properties: [String: String]
  }
}

import XCTestDynamicOverlay

extension AnalyticsClient {
  static let failing = Self(
    track: { _ in XCTFail("AnalyticsClient.track is unimplemented.") }
  )
}

Composable Architecture 0.17.0

Currently the Composable Architecture dynamically loads XCTFail so that it can provide the functionality of the TestStore, which is a test helper that allows you to assert how state changes when actions are sent. We can now remove this ad hoc code and replace it with the more robust XCTestDynamicOverlay library.

In addition to this there are two new improvements to some core library types:

Effect.failing

The Effect type now vends a .failing static constructor. It’s an effect that will immediately invoke XCTFail when it is subscribed to. This is perfect for stubbing in dependency endpoints that should not be invoked during tests, giving you better guarantees about which dependencies are used and which are not.

TestStore

The TestStore has a new way of making assertions. Currently one makes assertions by calling the .assert method on TestStore and feeding it a sequence of steps that simultaneously describe a user action and how the state should have changed after that action:

store.assert(
  .send(.incrementButtonTapped) {
    $0.count = 1
  },
  .send(.numberFactButtonTapped) {
    $0.isNumberFactRequestInFlight = true
  },
  .do { self.scheduler.advance() },
  .receive(.numberFactResponse(.success("1 is a good number Brent"))) {
    $0.isNumberFactRequestInFlight = false
    $0.numberFact = "1 is a good number Brent"
  }
)

Thanks to some recent infrastructure work we have done on the TestStore we can now flatten this code by getting rid of the surrounding store.assert(...) and calling .send and .receive directly on the store:

store.send(.incrementButtonTapped) {
  $0.count = 1
}

store.send(.numberFactButtonTapped) {
  $0.isNumberFactRequestInFlight = true
}

self.scheduler.advance()

store.receive(.numberFactResponse(.success("1 is a good number Brent"))) {
  $0.isNumberFactRequestInFlight = false
  $0.numberFact = "1 is a good number Brent"
}

All the same guarantees are made, such as exhaustive checking of effect lifetimes, but now with less nesting and in fewer lines of code. Further, flattening the code in this way allows Xcode 12 to better track test failures to the .send line that caused the failure.

This change is 100% backwards compatible with the current .assert(...) method, so no need to immediately switch over, but we think there are a lot of benefits to doing so.

Combine Schedulers 0.4.0

And finally (😅) we are leveraging our new XCTestDynamicOverlay library in Combine Schedulers to provide a FailingScheduler type, which is a scheduler that immediately invokes XCTFail whenever it is asked to schedule work. This is great for testing code that requires a scheduler to be provided but for which you do not expect any asynchrony to actually take place. Just stick in a .failing instance for your scheduler and you can be sure there is no shenanigans happening internally:

func testCountUpAndDown() {
  let store = TestStore(
    initialState: EffectsBasicsState(),
    reducer: effectsBasicsReducer,
    environment: EffectsBasicsEnvironment(
      mainQueue: .failing,
      numberFact: { _ in .failing("numberFact") }
    )
  )

  store.send(.incrementButtonTapped) {
    $0.count = 1
  }
  store.send(.decrementButtonTapped) {
    $0.count = 0
  }
}

If this test passes it means definitively that there was no asynchrony involved and that the numberFact effect was not executed. This greatly strengthens what this test is capturing with very little additional work.

Try it out today!

Be sure to check out XCTestDynamicOverlay today, and update your dependencies on the Composable Architecture and/or Combine Schedulers. We think these tools will greatly strengthen your tests and their ergonomics.


Open Sourcing isowords

Wednesday Mar 17, 2021

A few months ago we announced that we were working on a new project, a word game for iOS, and we’ve even been giving little peeks at the code base in recent episodes of Point-Free. Well, we’ve now officially launched the app on the App Store and we are simultaneously open sourcing the entire code base!

isowords

isowords is a large, complex application built entirely in Swift. The iOS client’s logic is built in the Composable Architecture and the UI is built mostly in SwiftUI with a little bit in SceneKit. The server is also built in Swift using our experimental web server libraries.

The code base is currently over 45k lines of code, for both the iOS client and server, and employs a number of techniques that have been discussed on Point-Free. Here’s just a small sample of some things you might be interested in:

The Composable Architecture

The whole application is powered by the Composable Architecture, a library we built from scratch on Point-Free that provides tools for building applications with a focus on composability, modularity, and testability. This means:

  • The entire app’s state is held in a single source of truth, called a Store.
  • The entire app’s behavior is implemented by a single unit, called a Reducer, which is composed out of many other reducers.
  • All effectful operations are made explicit as values returned from reducers.
  • Dependencies are made explicit as simple data types wrapping their live implementations, along with various mock instances.

There are a ton of benefits to designing applications in this manner:

  • Large, complex features can be broken down into smaller child domains, and those domains can communicate via simple state mutations. Typically this is done in SwiftUI by accessing singletons inside ObservableObject instances, but this is not necessary in the Composable Architecture.
  • We take control of dependencies rather than allow them to take control of us. Just because you are using StoreKit, GameCenter, UserNotifications, or any other 3rd party APIs in your code, it doesn’t mean you should sacrifice your ability to run your app in the simulator, SwiftUI previews, or write concise tests.
  • Exhaustive tests can be written very quickly. We test very detailed user flows, capture subtle edge cases, and assert on how effects execute and how their outputs feed back into the application.
  • It is straightforward to write integration tests that exercise multiple independent parts of the application.

Hyper-modularization

The application is built in a hyper-modularized style. At the time of writing this README the client and server are split into 86 modules. This allows us to work on features without building the entire application, which improves compile times and SwiftUI preview stability. It also made it easy for us to ship an App Clip, whose size must be less than 10 MB uncompressed, by choosing the bare minimum of code and resources to build.

Client/Server monorepo

The code for both the iOS client and server are included in this single repository. This makes it easy to run both the client and server at the same time, and we can even debug them at the same time, e.g. set breakpoints in the server that are triggered when the simulator makes API requests.

We also share a lot of code between client and server:

  • The core types that describe players, puzzles, moves, etc.
  • Game logic, such as the random puzzle generator, puzzle verification, dictionaries, and more.
  • The router used for handling requests on the server is the exact same code the iOS client uses to make API requests to the server. New routes only have to be specified a single time and it is immediately available to both client and server.
  • We write integration tests that simultaneously test the server and iOS client. During a test, API requests made by the client are actually running real server code under the hood.
  • And more…

Automated App Store screenshots and previews

The screenshots and preview video that we upload to the App Store for this app are automatically generated.

  • The screenshots are generated by a test suite using our SnapshotTesting library, and do the work of constructing a very specific piece of state that we load into a screen, as well as framing the UI and providing the surrounding graphics.

  • The preview video is generated as a screen recording of running a slimmed-down version of the app that embeds specific letters onto a cube and runs a sequence of actions to emulate a user playing the game. The app can be run locally by selecting the TrailerPreview target in Xcode and running it in the simulator.

Preview apps

There are times that we want to test a feature in isolation without building the entire app. SwiftUI previews are great for this but also have their limitations, such as if you need to use APIs unavailable to previews, or if you need to debug more complex flows, etc.

So, we create mini-applications that build a small subset of the 86+ modules that comprise the entire application. Setting up these applications requires minimal work. You just specify what dependencies you need in the Xcode project and then create an entry point to launch the feature.

For example, here is all the code necessary to create a preview app for running the onboarding flow in isolation. If we were at the whims of the full application to test this feature we would need to constantly delete and reinstall the app since this screen is only shown on first launch.

Download today!

Check out and explore the isowords code base today. We have a lot more Point-Free episodes coming soon that dive into some of the more advanced aspects of the code base, such as API client design, integration testing, onboarding flows, automatic trailer creation and more! 😅

Also, be sure to download isowords and share with friends 😁:

Download isowords on the App Store


Older blog posts

Wednesday Mar 17, 2021

Announcing: isowords

We are excited to release isowords to the App Store, a new word search game for your phone. Download today!

Monday Mar 8, 2021

Composable Architecture Test Store Improvements

Composable Architecture 0.16.0 comes with significant improvements to its testing capabilities for tracking down effect-related failures.

Monday Feb 1, 2021

Composable Forms: Say "Bye" to Boilerplate!

Today we are releasing first-party support for concisely handling form data in the Composable Architecture.

Wednesday Dec 23, 2020

End-of-year sale: 25% off Point-Free

Through the new year, we're offering personal Point-Free subscriptions for 25% off the first year!

Wednesday Dec 23, 2020

2020 Year-in-review

The Composable Architecture, dependency management, parsers, Combine schedulers and more! Join us for a review of everything we accomplished in 2020!

Monday Dec 21, 2020

Open Sourcing Parsing

Today we are open sourcing Parsing, a library for turning nebulous data into well-structured data, with a focus on composition, performance, and generality.

Tuesday Jun 30, 2020

The Composable Architecture and SwiftUI Alerts

Today we are releasing a new version of the Composable Architecture with helpers that make working with SwiftUI alerts and action sheets a breeze.

Monday Jun 22, 2020

Core Motion support in the Composable Architecture

We are releasing our second mini-library for the Composable Architecture, which makes it easy to use Core Motion.

Monday Jun 15, 2020

Open Sourcing CombineSchedulers

Today we are open-sourcing CombineSchedulers, a library that introduces a few schedulers that makes working with Combine more testable and more versatile.

Wednesday May 27, 2020

Instrumenting features built in the Composable Architecture

Today we are releasing first-party support for instrumenting features built in the Composable Architecture.

Wednesday May 20, 2020

Core Location support in the Composable Architecture

We are releasing a mini-library that makes it easy to use Core Location inside the Composable Architecture.

Tuesday May 12, 2020

Regional Discounts

Regional discounts takes 50% off a monthly or yearly personal subscription to anyone whose credit card is issued from a particular list of countries.

Monday May 4, 2020

Composable Architecture, the library

Today we are releasing the Composable Architecture as an open-source library. It is a way to build applications in a consistent and understandable way, with composition, testing and ergonomics in mind.

Wednesday Mar 11, 2020

Announcing Episode Collections

After over two years and nearly 100 episodes we are finally launching episode collections on Point-Free!

Friday Feb 21, 2020

Share Point-Free with friends and save!

Today we're excited to announce support for referral bonuses! When friends and colleagues of yours subscribe to Point-Free with your referral link, both of you will get one month free.

Tuesday Feb 4, 2020

Open Sourcing Case Paths

Today we are open sourcing CasePaths, a library that introduces the power and ergonomics of key paths to enums!

Monday Dec 30, 2019

2019 Year-in-review

Random number generators, parsers, SwiftUI, composable architecture and more! Join us for a review of everything we accomplished in 2019!

Monday Dec 23, 2019

Snapshot Testing SwiftUI

Snapshot testing gives us broad test coverage on our SwiftUI views with very little up front work.

Wednesday Dec 18, 2019

Free Video: Testing SwiftUI

A free video exploring how to test SwiftUI.

Wednesday Nov 20, 2019

A Crash Course in Combine

Two free videos exploring Apple's new Combine framework, its core components, and how to integrate it in your code.

Thursday Nov 7, 2019

Higher-Order Snapshot Testing

How to enrich snapshot testing strategies with additional behavior using higher-order constructions.

Tuesday Jul 30, 2019

SwiftUI and State Management Corrections

Xcode 11 beta 5 has brought lots of changes to SwiftUI, and we'd like to take a moment to provide corrections to our episodes based on these changes.

Thursday May 9, 2019

Enterprise Subscriptions

Point-Free now supports enterprise subscriptions, making it easier than ever to manage a team subscription for larger organizations! For a fixed yearly rate, everyone with an email from your company's domain will get instant access to everything Point-Free has to offer. Contact us for more info!

Monday Apr 29, 2019

Open Sourcing Enum Properties

We wanted to make Swift enum data access as ergonomic as struct data access, so today we are open sourcing a code generation tool to do just that!

Monday Mar 18, 2019

Open Sourcing Gen

Today we are open sourcing Gen: a lightweight wrapper around Swift's randomness API's that makes randomness more composable, transformable and controllable!

Tuesday Jan 8, 2019

Announcing swift-html 0.2.0

Announcing swift-html 0.2.0: support for CocoaPods, Carthage, SnapshotTesting, and more!

Wednesday Dec 19, 2018

2018 Year-in-Review

41 episodes, 19 hours of video, 25 blog posts, 8 open source libraries, 3.8K stars, 36K visitors, and we’re just getting started?

Monday Dec 3, 2018

SnapshotTesting 1.0: Delightful Swift snapshot testing

Today we are open sourcing SnapshotTesting 1.0: a modern, composable snapshot testing library built entirely in Swift!

Monday Oct 29, 2018

Some news about contramap

We've seen that contramap is a powerful operation, but the name isn't fantastic. We propose a much more intuitive name for this operation, and in doing so make our code much easier to read.

Tuesday Oct 9, 2018

How to Control the World

APIs that interact with the outside world are unpredictable and make it difficult to test and simulate code paths in our apps. Existing solutions to this problem are verbose and complicated, so let's explore a simpler solution by embracing singletons and global mutation, and rejecting protocol-oriented programming and dependency injection.

Monday Oct 8, 2018

Watch episodes in your favorite podcast app!

Follow along with the newest Point-Free episodes using your favorite podcast app. We now support podcast-friendly RSS feeds for viewing all of our videos.

Thursday Sep 20, 2018

Random Zalgo Generator

Let's create a random Zalgo text generator using the simple Gen type we defined in this week's episode!

Thursday Sep 13, 2018

Type-safe HTML with Kitura

Today we're releasing a Kitura plug-in for rendering type-safe HTML. It provides a Swift compile-time API to HTML that prevents many of the runtime errors and vulnerabilities of traditional templated HTML rendering.

Thursday Sep 13, 2018

Type-safe HTML with Vapor

Today we're releasing a Vapor plug-in for rendering type-safe HTML. It provides a Swift compile-time API to HTML that prevents many of the runtime errors and vulnerabilities of traditional templated HTML rendering.

Wednesday Sep 12, 2018

Open sourcing swift-html: A Type-Safe Alternative to Templating Languages in Swift

Today we are open sourcing a new library for building HTML documents in Swift. It's extensible, transformable, type-safe, and provides many benefits over templating languages.

Friday Aug 17, 2018

Overture 0.3.0: Now with Zip

Today we are releasing Overture 0.3.0 with a bunch of useful zip functions.

Friday Aug 17, 2018

Open Sourcing Validated

Today we are open sourcing Validated, a tiny functional Swift library for handling multiple errors: functionality that you don't get from throwing functions and the Result type.

Thursday Aug 16, 2018

Solutions to Exercises: Zip Part 3

Today we solve the exercises to the third and final part of our introductory series on zip.

Wednesday Aug 15, 2018

Solutions to Exercises: Zip Part 2

Today we solve the exercises to the second part of our introductory series on zip.

Tuesday Aug 14, 2018

Solutions to Exercises: Zip Part 1

Today we solve the exercises to the first part of our introductory series on zip.

Monday Aug 6, 2018

Announcing Student Discounts

Get 50% off your Point-Free subscription with proof of enrollment at a university or coding school.

Monday Jul 30, 2018

Celebrating 6 Months

This week marks 6 months since our launch, and we’re making one of our most popular episodes free to the public!

Monday Jul 2, 2018

Conditional Coding

What happens when we combine Swift's conditional conformance with codability?

Monday Jun 25, 2018

Open Sourcing NonEmpty

Today we are open sourcing NonEmpty, a Swift library for modeling non-empty collection types. This small library can help make your code safer and more expressive with very little work.

Monday Jun 18, 2018

Tagged Seconds and Milliseconds

Let's create a type-safe interface for dealing with seconds and milliseconds in our programs. We'll use the `Tagged` type, which allows us to construct all new types in a lightweight way.

Wednesday May 30, 2018

Styling with Functions: Free for Everyone!

We are making one of our early episodes, “UIKit Styling with Functions”, free to everyone today! It’s a seminal episode that sets the foundation for some later work in the Point-Free series.

Tuesday May 15, 2018

Overture: Now with Functional Setters

Announcing Overture 0.2.0! This release is all about setters: functions that allow us to build complex transformations out of smaller units.

Monday May 7, 2018

Solutions to Exercises: Contravariance

This week we solve the exercises from our episode on contravariance, because there were _a lot_ of them!

Monday Apr 23, 2018

Case Study: Algebraic Data Types

Let’s look at a real world use for algebraic data types. We will refactor a data type that is used in the code on this very site so that the invalid states are unrepresentable by the compiler.

Monday Apr 23, 2018

Announcing Point-Free Pointers!

Today we are excited to announcement launch of Point-Free Pointers, a blog to supplement our video series for all the content we couldn’t fit in. Expect to find regularly postings here that dive even deeper into functional programming, showing real world use cases and more!