1640682874
In this video, I describe 3 techniques behind finding residues of a complex function: 1) Using the Laurent series, 2) A residue-finding approach for simple poles, and 3) A residue-finding approach for non-simple poles.
I also prove/verify these techniques, which are ultimately going to be used to calculate complex integrals (and even real integrals) when applying the Residue Theorem.
Lecture Notes: https://drive.google.com/file/d/0B_urJu4cgDhMNHN4YjNRZ3NfNzA/view?usp=sharing&resourcekey=0--Hv2rTFHVvF0qSYeq0_dnw
Support my Patreon: https://www.patreon.com/user?u=4354534
Subscribe: https://www.youtube.com/c/FacultyofKhan/featured
1661592007
⚠️ This list is no longer being updated. For my latest Swift tips, checkout the "Tips" section on Swift by Sundell.
One of the things I really love about Swift is how I keep finding interesting ways to use it in various situations, and when I do - I usually share them on Twitter. Here's a collection of all the tips & tricks that I've shared so far. Each entry has a link to the original tweet, if you want to respond with some feedback or question, which is always super welcome! 🚀
Also make sure to check out all of my other Swift content:
🚀 Here are some quick tips to make async tests faster & more stable:
// BEFORE:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
detector.detectMentions(in: string) { mentions in
XCTAssertEqual(mentions, ["johnsundell"])
}
sleep(2)
}
}
// AFTER:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
var mentions: [String]?
let expectation = self.expectation(description: #function)
detector.detectMentions(in: string) {
mentions = $0
expectation.fulfill()
}
waitForExpectations(timeout: 10)
XCTAssertEqual(mentions, ["johnsundell"])
}
}
For more on async testing, check out "Unit testing asynchronous Swift code".
✍️ Adding support for the new Apple Pencil double-tap feature is super easy! All you have to do is to create a UIPencilInteraction
, add it to a view, and implement one delegate method. Hopefully all pencil-compatible apps will soon adopt this.
let interaction = UIPencilInteraction()
interaction.delegate = self
view.addInteraction(interaction)
extension ViewController: UIPencilInteractionDelegate {
func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
// Handle pencil double-tap
}
}
For more on using this and other iPad Pro features, check out "Building iPad Pro features in Swift".
😎 Here's a cool function that combines a value with a function to return a closure that captures that value, so that it can be called without any arguments. Super useful when working with closure-based APIs and we want to use some of our properties without having to capture self
.
func combine<A, B>(_ value: A, with closure: @escaping (A) -> B) -> () -> B {
return { closure(value) }
}
// BEFORE:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = { [weak self] in
guard let self = self else {
return
}
self.productManager.startCheckout(for: self.product)
}
}
}
// AFTER:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = combine(product, with: productManager.startCheckout)
}
}
💉 When I'm only using a single function from a dependency, I love to inject that function as a closure, instead of having to create a protocol and inject the whole object. Makes dependency injection & testing super simple.
final class ArticleLoader {
typealias Networking = (Endpoint) -> Future<Data>
private let networking: Networking
init(networking: @escaping Networking = URLSession.shared.load) {
self.networking = networking
}
func loadLatest() -> Future<[Article]> {
return networking(.latestArticles).decode()
}
}
For more on this technique, check out "Simple Swift dependency injection with functions".
💥 It's cool that you can easily assign a closure as a custom NSException
handler. This is super useful when building things in Playgrounds - since you can't use breakpoints - so instead of just signal SIGABRT
, you'll get the full exception description if something goes wrong.
NSSetUncaughtExceptionHandler { exception in
print(exception)
}
❤️ I love that in Swift, we can use the type system to make our code so much more self-documenting - one way of doing so is to use type aliases to give the primitive types that we use a more semantic meaning.
extension List.Item {
// Using type aliases, we can give semantic meaning to the
// primitive types that we use, without having to introduce
// wrapper types.
typealias Index = Int
}
extension List {
enum Mutation {
// Our enum cases now become a lot more self-documenting,
// without having to add additional parameter labels to
// explain them.
case add(Item, Item.Index)
case update(Item, Item.Index)
case remove(Item.Index)
}
}
For more on self-documenting code, check out "Writing self-documenting Swift code".
🤯 A little late night prototyping session reveals that protocol constraints can not only be applied to extensions - they can also be added to protocol definitions!
This is awesome, since it lets us easily define specialized protocols based on more generic ones.
protocol Component {
associatedtype Container
func add(to container: Container)
}
// Protocols that inherit from other protocols can include
// constraints to further specialize them.
protocol ViewComponent: Component where Container == UIView {
associatedtype View: UIView
var view: View { get }
}
extension ViewComponent {
func add(to container: UIView) {
container.addSubview(view)
}
}
For more on specializing protocols, check out "Specializing protocols in Swift".
📦 Here's a super handy extension on Swift's Optional
type, which gives us a really nice API for easily unwrapping an optional, or throwing an error in case the value turned out to be nil
:
extension Optional {
func orThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
switch self {
case .some(let value):
return value
case .none:
throw errorExpression()
}
}
}
let file = try loadFile(at: path).orThrow(MissingFileError())
For more ways that optionals can be extended, check out "Extending optionals in Swift".
👩🔬 Testing code that uses static APIs can be really tricky, but there's a way that it can often be done - using Swift's first class function capabilities!
Instead of accessing that static API directly, we can inject the function we want to use, which enables us to mock it!
// BEFORE
class FriendsLoader {
func loadFriends(then handler: @escaping (Result<[Friend]>) -> Void) {
Networking.loadData(from: .friends) { result in
...
}
}
}
// AFTER
class FriendsLoader {
typealias Handler<T> = (Result<T>) -> Void
typealias DataLoadingFunction = (Endpoint, @escaping Handler<Data>) -> Void
func loadFriends(using dataLoading: DataLoadingFunction = Networking.loadData,
then handler: @escaping Handler<[Friend]>) {
dataLoading(.friends) { result in
...
}
}
}
// MOCKING IN TESTS
let dataLoading: FriendsLoader.DataLoadingFunction = { _, handler in
handler(.success(mockData))
}
friendsLoader.loadFriends(using: dataLoading) { result in
...
}
🐾 Swift's pattern matching capabilities are so powerful! Two enum cases with associated values can even be matched and handled by the same switch case - which is super useful when handling state changes with similar data.
enum DownloadState {
case inProgress(progress: Double)
case paused(progress: Double)
case cancelled
case finished(Data)
}
func downloadStateDidChange(to state: DownloadState) {
switch state {
case .inProgress(let progress), .paused(let progress):
updateProgressView(with: progress)
case .cancelled:
showCancelledMessage()
case .finished(let data):
process(data)
}
}
🅰 One really nice benefit of Swift multiline string literals - even for single lines of text - is that they don't require quotes to be escaped. Perfect when working with things like HTML, or creating a custom description for an object.
let html = highlighter.highlight("Array<String>")
XCTAssertEqual(html, """
<span class="type">Array</span><<span class="type">String</span>>
""")
💎 While it's very common in functional programming, the reduce
function might be a bit of a hidden gem in Swift. It provides a super useful way to transform a sequence into a single value.
extension Sequence where Element: Equatable {
func numberOfOccurrences(of target: Element) -> Int {
return reduce(0) { result, element in
guard element == target else {
return result
}
return result + 1
}
}
}
You can read more about transforming collections in "Transforming collections in Swift".
📦 When I use Codable in Swift, I want to avoid manual implementations as much as possible, even when there's a mismatch between my code structure and the JSON I'm decoding.
One way that can often be achieved is to use private data containers combined with computed properties.
struct User: Codable {
let name: String
let age: Int
var homeTown: String { return originPlace.name }
private let originPlace: Place
}
private extension User {
struct Place: Codable {
let name: String
}
}
extension User {
struct Container: Codable {
let user: User
}
}
🚢 Instead of using feature branches, I merge almost all of my code directly into master - and then I use feature flags to conditionally enable features when they're ready. That way I can avoid merge conflicts and keep shipping!
extension ListViewController {
func addSearchIfNeeded() {
// Rather than having to keep maintaining a separate
// feature branch for a new feature, we can use a flag
// to conditionally turn it on.
guard FeatureFlags.searchEnabled else {
return
}
let resultsVC = SearchResultsViewController()
let searchVC = UISearchController(
searchResultsController: resultsVC
)
searchVC.searchResultsUpdater = resultsVC
navigationItem.searchController = searchVC
}
}
You can read more about feature flags in "Feature flags in Swift".
💾 Here I'm using tuples to create a lightweight hierarchy for my data, giving me a nice structure without having to introduce any additional types.
struct CodeSegment {
var tokens: (
previous: String?,
current: String
)
var delimiters: (
previous: Character?
next: Character?
)
}
handle(segment.tokens.current)
You can read more about tuples in "Using tuples as lightweight types in Swift"
3️⃣ Whenever I have 3 properties or local variables that share the same prefix, I usually try to extract them into their own method or type. That way I can avoid massive types & methods, and also increase readability, without falling into a "premature optimization" trap.
Before
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articleFolder = try contentFolder.subfolder(named: "posts")
let articleProcessor = ContentProcessor(folder: articleFolder)
let articles = try articleProcessor.process()
...
}
After
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articles = try processArticles(in: contentFolder)
...
}
private func processArticles(in folder: Folder) throws -> [ContentItem] {
let folder = try folder.subfolder(named: "posts")
let processor = ContentProcessor(folder: folder)
return try processor.process()
}
👨🔧 Here's two extensions that I always add to the Encodable
& Decodable
protocols, which for me really make the Codable API nicer to use. By using type inference for decoding, a lot of boilerplate can be removed when the compiler is already able to infer the resulting type.
extension Encodable {
func encoded() throws -> Data {
return try JSONEncoder().encode(self)
}
}
extension Data {
func decoded<T: Decodable>() throws -> T {
return try JSONDecoder().decode(T.self, from: self)
}
}
let data = try user.encoded()
// By using a generic type in the decoded() method, the
// compiler can often infer the type we want to decode
// from the current context.
try userDidLogin(data.decoded())
// And if not, we can always supply the type, still making
// the call site read very nicely.
let otherUser = try data.decoded() as User
📦 UserDefaults
is a lot more powerful than what it first might seem like. Not only can it store more complex values (like dates & dictionaries) and parse command line arguments - it also enables easy sharing of settings & lightweight data between apps in the same App Group.
let sharedDefaults = UserDefaults(suiteName: "my-app-group")!
let useDarkMode = sharedDefaults.bool(forKey: "dark-mode")
// This value is put into the shared suite.
sharedDefaults.set(true, forKey: "dark-mode")
// If you want to treat the shared settings as read-only (and add
// local overrides on top of them), you can simply add the shared
// suite to the standard UserDefaults.
let combinedDefaults = UserDefaults.standard
combinedDefaults.addSuite(named: "my-app-group")
// This value is a local override, not added to the shared suite.
combinedDefaults.set(true, forKey: "app-specific-override")
🎨 By overriding layerClass
you can tell UIKit what CALayer
class to use for a UIView
's backing layer. That way you can reduce the amount of layers, and don't have to do any manual layout.
final class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
var colors: (start: UIColor, end: UIColor)? {
didSet { updateLayer() }
}
private func updateLayer() {
let layer = self.layer as! CAGradientLayer
layer.colors = colors.map { [$0.start.cgColor, $0.end.cgColor] }
}
}
✅ That the compiler now automatically synthesizes Equatable conformances is such a huge upgrade for Swift! And the cool thing is that it works for all kinds of types - even for enums with associated values! Especially useful when using enums for verification in unit tests.
struct Article: Equatable {
let title: String
let text: String
}
struct User: Equatable {
let name: String
let age: Int
}
extension Navigator {
enum Destination: Equatable {
case profile(User)
case article(Article)
}
}
func testNavigatingToArticle() {
let article = Article(title: "Title", text: "Text")
controller.select(article)
XCTAssertEqual(navigator.destinations, [.article(article)])
}
🤝 Associated types can have defaults in Swift - which is super useful for types that are not easily inferred (for example when they're not used for a specific instance method or property).
protocol Identifiable {
associatedtype RawIdentifier: Codable = String
var id: Identifier<Self> { get }
}
struct User: Identifiable {
let id: Identifier<User>
let name: String
}
struct Group: Identifiable {
typealias RawIdentifier = Int
let id: Identifier<Group>
let name: String
}
🆔 If you want to avoid using plain strings as identifiers (which can increase both type safety & readability), it's really easy to create a custom Identifier type that feels just like a native Swift type, thanks to protocols!
More on this topic in "Type-safe identifiers in Swift".
struct Identifier: Hashable {
let string: String
}
extension Identifier: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
string = value
}
}
extension Identifier: CustomStringConvertible {
var description: String {
return string
}
}
extension Identifier: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
string = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
}
}
struct Article: Codable {
let id: Identifier
let title: String
}
let article = Article(id: "my-article", title: "Hello world!")
🙌 A really cool thing about using tuples to model the internal state of a Swift type, is that you can unwrap an optional tuple's members directly into local variables.
Very useful in order to group multiple optional values together for easy unwrapping & handling.
class ImageTransformer {
private var queue = [(image: UIImage, transform: Transform)]()
private func processNext() {
// When unwrapping an optional tuple, you can assign the members
// directly to local variables.
guard let (image, transform) = queue.first else {
return
}
let context = Context()
context.draw(image)
context.apply(transform)
...
}
}
❤️ I love to structure my code using extensions in Swift. One big benefit of doing so when it comes to struct initializers, is that defining a convenience initializer doesn't remove the default one the compiler generates - best of both worlds!
struct Article {
let date: Date
var title: String
var text: String
var comments: [Comment]
}
extension Article {
init(title: String, text: String) {
self.init(date: Date(), title: title, text: text, comments: [])
}
}
let articleA = Article(title: "Best Cupcake Recipe", text: "...")
let articleB = Article(
date: Date(),
title: "Best Cupcake Recipe",
text: "...",
comments: [
Comment(user: currentUser, text: "Yep, can confirm!")
]
)
🏈 A big benefit of using throwing functions for synchronous Swift APIs is that the caller can decide whether they want to treat the return value as optional (try?
) or required (try
).
func loadFile(named name: String) throws -> File {
guard let url = urlForFile(named: name) else {
throw File.Error.missing
}
do {
let data = try Data(contentsOf: url)
return File(url: url, data: data)
} catch {
throw File.Error.invalidData(error)
}
}
let requiredFile = try loadFile(named: "AppConfig.json")
let optionalFile = try? loadFile(named: "UserSettings.json")
🐝 Types that are nested in generics automatically inherit their parent's generic types - which is super useful when defining accessory types (for things like states or outcomes).
struct Task<Input, Output> {
typealias Closure = (Input) throws -> Output
let closure: Closure
}
extension Task {
enum Result {
case success(Output)
case failure(Error)
}
}
🤖 Now that the Swift compiler automatically synthesizes Equatable & Hashable conformances for value types, it's easier than ever to setup model structures with nested types that are all Equatable
/Hashable
!
typealias Value = Hashable & Codable
struct User: Value {
var name: String
var age: Int
var lastLoginDate: Date?
var settings: Settings
}
extension User {
struct Settings: Value {
var itemsPerPage: Int
var theme: Theme
}
}
extension User.Settings {
enum Theme: String, Value {
case light
case dark
}
}
You can read more about using nested types in Swift here.
🎉 Swift 4.1 is here! One of the key features it brings is conditional conformances, which lets you have a type only conform to a protocol under certain constraints.
protocol UnboxTransformable {
associatedtype RawValue
static func transform(_ value: RawValue) throws -> Self?
}
extension Array: UnboxTransformable where Element: UnboxTransformable {
typealias RawValue = [Element.RawValue]
static func transform(_ value: RawValue) throws -> [Element]? {
return try value.compactMap(Element.transform)
}
}
I also have an article with lots of more info on conditional conformances here. Paul Hudson also has a great overview of all Swift 4.1 features here.
🕵️♀️ A cool thing about Swift type aliases is that they can be generic! Combine that with tuples and you can easily define simple generic types.
typealias Pair<T> = (T, T)
extension Game {
func calculateScore(for players: Pair<Player>) -> Int {
...
}
}
You can read more about using tuples as lightweight types here.
☑️ A really cool "hidden" feature of UserDefaults is that it contains any arguments that were passed to the app at launch!
Super useful both in Swift command line tools & scripts, but also to temporarily override a value when debugging iOS apps.
let defaults = UserDefaults.standard
let query = defaults.string(forKey: "query")
let resultCount = defaults.integer(forKey: "results")
👏 Swift's &
operator is awesome! Not only can you use it to compose protocols, you can compose other types too! Very useful if you want to hide concrete types & implementation details.
protocol LoadableFromURL {
func load(from url: URL)
}
class ContentViewController: UIViewController, LoadableFromURL {
func load(from url: URL) {
...
}
}
class ViewControllerFactory {
func makeContentViewController() -> UIViewController & LoadableFromURL {
return ContentViewController()
}
}
🤗 When capturing values in mocks, using an array (instead of just a single value) makes it easy to verify that only a certain number of values were passed.
Perfect for protecting against "over-calling" something.
class UserManagerTests: XCTestCase {
func testObserversCalledWhenUserFirstLogsIn() {
let manager = UserManager()
let observer = ObserverMock()
manager.addObserver(observer)
// First login, observers should be notified
let user = User(id: 123, name: "John")
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
// If the same user logs in again, observers shouldn't be notified
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
}
}
private extension UserManagerTests {
class ObserverMock: UserManagerObserver {
private(set) var users = [User]()
func userDidChange(to user: User) {
users.append(user)
}
}
}
👋 When writing tests, you don't always need to create mocks - you can create stubs using real instances of things like errors, URLs & UserDefaults.
Here's how to do that for some common tasks/object types in Swift:
// Create errors using NSError (#function can be used to reference the name of the test)
let error = NSError(domain: #function, code: 1, userInfo: nil)
// Create non-optional URLs using file paths
let url = URL(fileURLWithPath: "Some/URL")
// Reference the test bundle using Bundle(for:)
let bundle = Bundle(for: type(of: self))
// Create an explicit UserDefaults object (instead of having to use a mock)
let userDefaults = UserDefaults(suiteName: #function)
// Create queues to control/await concurrent operations
let queue = DispatchQueue(label: #function)
For when you actually do need mocking, check out "Mocking in Swift".
⏱ I've started using "then" as an external parameter label for completion handlers. Makes the call site read really nicely (Because I do ❤️ conversational API design) regardless of whether trailing closure syntax is used or not.
protocol DataLoader {
// Adding type aliases to protocols can be a great way to
// reduce verbosity for parameter types.
typealias Handler = (Result<Data>) -> Void
associatedtype Endpoint
func loadData(from endpoint: Endpoint, then handler: @escaping Handler)
}
loader.loadData(from: .messages) { result in
...
}
loader.loadData(from: .messages, then: { result in
...
})
😴 Combining lazily evaluated sequences with builder pattern-like properties can lead to some pretty sweet APIs for configurable sequences in Swift.
Also useful for queries & other things you "build up" and then execute.
// Extension adding builder pattern-like properties that return
// a new sequence value with the given configuration applied
extension FileSequence {
var recursive: FileSequence {
var sequence = self
sequence.isRecursive = true
return sequence
}
var includingHidden: FileSequence {
var sequence = self
sequence.includeHidden = true
return sequence
}
}
// BEFORE
let files = folder.makeFileSequence(recursive: true, includeHidden: true)
// AFTER
let files = folder.files.recursive.includingHidden
Want an intro to lazy sequences? Check out "Swift sequences: The art of being lazy".
My top 3 tips for faster & more stable UI tests:
📱 Reset the app's state at the beginning of every test.
🆔 Use accessibility identifiers instead of UI strings.
⏱ Use expectations instead of waiting time.
func testOpeningArticle() {
// Launch the app with an argument that tells it to reset its state
let app = XCUIApplication()
app.launchArguments.append("--uitesting")
app.launch()
// Check that the app is displaying an activity indicator
let activityIndicator = app.activityIndicator.element
XCTAssertTrue(activityIndicator.exists)
// Wait for the loading indicator to disappear = content is ready
expectation(for: NSPredicate(format: "exists == 0"),
evaluatedWith: activityIndicator)
// Use a generous timeout in case the network is slow
waitForExpectations(timeout: 10)
// Tap the cell for the first article
app.tables.cells["Article.0"].tap()
// Assert that a label with the accessibility identifier "Article.Title" exists
let label = app.staticTexts["Article.Title"]
XCTAssertTrue(label.exists)
}
📋 It's super easy to access the contents of the clipboard from a Swift script. A big benefit of Swift scripting is being able to use Cocoa's powerful APIs for Mac apps.
import Cocoa
let clipboard = NSPasteboard.general.string(forType: .string)
🎯 Using Swift tuples for view state can be a super nice way to group multiple properties together and render them reactively using the layout system.
By using a tuple we don't have to either introduce a new type or make our view model-aware.
class TextView: UIView {
var state: (title: String?, text: String?) {
// By telling UIKit that our view needs layout and binding our
// state in layoutSubviews, we can react to state changes without
// doing unnecessary layout work.
didSet { setNeedsLayout() }
}
private let titleLabel = UILabel()
private let textLabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.text = state.title
textLabel.text = state.text
...
}
}
⚾️ Swift tests can throw, which is super useful in order to avoid complicated logic or force unwrapping. By making errors conform to LocalizedError
, you can also get a nice error message in Xcode if there's a failure.
class ImageCacheTests: XCTestCase {
func testCachingAndLoadingImage() throws {
let bundle = Bundle(for: type(of: self))
let cache = ImageCache(bundle: bundle)
// Bonus tip: You can easily load images from your test
// bundle using this UIImage initializer
let image = try require(UIImage(named: "sample", in: bundle, compatibleWith: nil))
try cache.cache(image, forKey: "key")
let cachedImage = try cache.image(forKey: "key")
XCTAssertEqual(image, cachedImage)
}
}
enum ImageCacheError {
case emptyKey
case dataConversionFailed
}
// When using throwing tests, making your errors conform to
// LocalizedError will render a much nicer error message in
// Xcode (per default only the error code is shown).
extension ImageCacheError: LocalizedError {
var errorDescription: String? {
switch self {
case .emptyKey:
return "An empty key was given"
case .dataConversionFailed:
return "Failed to convert the given image to Data"
}
}
}
For more information, and the implementation of the require
method used above, check out "Avoiding force unwrapping in Swift unit tests".
✍️ Unlike static
properties, class
properties can be overridden by subclasses (however, they can't be stored, only computed).
class TableViewCell: UITableViewCell {
class var preferredHeight: CGFloat { return 60 }
}
class TallTableViewCell: TableViewCell {
override class var preferredHeight: CGFloat { return 100 }
}
👨🎨 Creating extensions with static factory methods can be a great alternative to subclassing in Swift, especially for things like setting up UIViews, CALayers or other kinds of styling.
It also lets you remove a lot of styling & setup from your view controllers.
extension UILabel {
static func makeForTitle() -> UILabel {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 24)
label.textColor = .darkGray
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.75
return label
}
static func makeForText() -> UILabel {
let label = UILabel()
label.font = .systemFont(ofSize: 16)
label.textColor = .black
label.numberOfLines = 0
return label
}
}
class ArticleViewController: UIViewController {
lazy var titleLabel = UILabel.makeForTitle()
lazy var textLabel = UILabel.makeForText()
}
🧒 An awesome thing about child view controllers is that they're automatically resized to match their parent, making them a super nice solution for things like loading & error views.
class ListViewController: UIViewController {
func loadItems() {
let loadingViewController = LoadingViewController()
add(loadingViewController)
dataLoader.loadItems { [weak self] result in
loadingViewController.remove()
self?.handle(result)
}
}
}
For more about child view controller (including the add
and remove
methods used above), check out "Using child view controllers as plugins in Swift".
🤐 Using the zip function in Swift you can easily combine two sequences. Super useful when using two sequences to do some work, since zip takes care of all the bounds-checking.
func render(titles: [String]) {
for (label, text) in zip(titleLabels, titles) {
print(text)
label.text = text
}
}
🎛 The awesome thing about option sets in Swift is that they can automatically either be passed as a single member or as a set. Even cooler is that you can easily define your own option sets as well, perfect for options and other non-exclusive values.
// Option sets are awesome, because you can easily pass them
// both using dot syntax and array literal syntax, like when
// using the UIView animation API:
UIView.animate(withDuration: 0.3,
delay: 0,
options: .allowUserInteraction,
animations: animations)
UIView.animate(withDuration: 0.3,
delay: 0,
options: [.allowUserInteraction, .layoutSubviews],
animations: animations)
// The cool thing is that you can easily define your own option
// sets as well, by defining a struct that has an Int rawValue,
// that will be used as a bit mask.
extension Cache {
struct Options: OptionSet {
static let saveToDisk = Options(rawValue: 1)
static let clearOnMemoryWarning = Options(rawValue: 1 << 1)
static let clearDaily = Options(rawValue: 1 << 2)
let rawValue: Int
}
}
// We can now use Cache.Options just like UIViewAnimationOptions:
Cache(options: .saveToDisk)
Cache(options: [.saveToDisk, .clearDaily])
🙌 Using the where
clause when designing protocol-oriented APIs in Swift can let your implementations (or others' if it's open source) have a lot more freedom, especially when it comes to collections.
See "Using generic type constraints in Swift 4" for more info.
public protocol PathFinderMap {
associatedtype Node
// Using the 'where' clause for associated types, we can
// ensure that a type meets certain requirements (in this
// case that it's a sequence with Node elements).
associatedtype NodeSequence: Sequence where NodeSequence.Element == Node
// Instead of using a concrete type (like [Node]) here, we
// give implementors of this protocol more freedom while
// still meeting our requirements. For example, one
// implementation might use Set<Node>.
func neighbors(of node: Node) -> NodeSequence
}
👨🍳 Combine first class functions in Swift with the fact that Dictionary elements are (Key, Value) tuples and you can build yourself some pretty awesome functional chains when iterating over a Dictionary.
func makeActor(at coordinate: Coordinate, for building: Building) -> Actor {
let actor = Actor()
actor.position = coordinate.point
actor.animation = building.animation
return actor
}
func render(_ buildings: [Coordinate : Building]) {
buildings.map(makeActor).forEach(add)
}
😎 In Swift, you can call any instance method as a static function and it will return a closure representing that method. This is how running tests using SPM on Linux works.
More about this topic in my blog post "First class functions in Swift".
// This produces a '() -> Void' closure which is a reference to the
// given view's 'removeFromSuperview' method.
let closure = UIView.removeFromSuperview(view)
// We can now call it just like we would any other closure, and it
// will run 'view.removeFromSuperview()'
closure()
// This is how running tests using the Swift Package Manager on Linux
// works, you return your test functions as closures:
extension UserManagerTests {
static var allTests = [
("testLoggingIn", testLoggingIn),
("testLoggingOut", testLoggingOut),
("testUserPermissions", testUserPermissions)
]
}
👏 One really nice benefit of dropping suffixes from method names (and just using verbs, when possible) is that it becomes super easy to support both single and multiple arguments, and it works really well semantically.
extension UIView {
func add(_ subviews: UIView...) {
subviews.forEach(addSubview)
}
}
view.add(button)
view.add(label)
// By dropping the "Subview" suffix from the method name, both
// single and multiple arguments work really well semantically.
view.add(button, label)
👽 Using the AnyObject
(or class
) constraint on protocols is not only useful when defining delegates (or other weak references), but also when you always want instances to be mutable without copying.
// By constraining a protocol with 'AnyObject' it can only be adopted
// by classes, which means all instances will always be mutable, and
// that it's the original instance (not a copy) that will be mutated.
protocol DataContainer: AnyObject {
var data: Data? { get set }
}
class UserSettingsManager {
private var settings: Settings
private let dataContainer: DataContainer
// Since DataContainer is a protocol, we an easily mock it in
// tests if we use dependency injection
init(settings: Settings, dataContainer: DataContainer) {
self.settings = settings
self.dataContainer = dataContainer
}
func saveSettings() throws {
let data = try settings.serialize()
// We can now assign properties on an instance of our protocol
// because the compiler knows it's always going to be a class
dataContainer.data = data
}
}
🍣 Even if you define a custom raw value for a string-based enum in Swift, the full case name will be used in string interpolation.
Super useful when using separate raw values for JSON, while still wanting to use the full case name in other contexts.
extension Building {
// This enum has custom raw values that are used when decoding
// a value, for example from JSON.
enum Kind: String {
case castle = "C"
case town = "T"
case barracks = "B"
case goldMine = "G"
case camp = "CA"
case blacksmith = "BL"
}
var animation: Animation {
return Animation(
// When used in string interpolation, the full case name is still used.
// For 'castle' this will be 'buildings/castle'.
name: "buildings/\(kind)",
frameCount: frameCount,
frameDuration: frameDuration
)
}
}
👨🔬 Continuing to experiment with expressive ways of comparing a value with a list of candidates in Swift. Adding an extension on Equatable is probably my favorite approach so far.
extension Equatable {
func isAny(of candidates: Self...) -> Bool {
return candidates.contains(self)
}
}
let isHorizontal = direction.isAny(of: .left, .right)
See tip #35 for my previous experiment.
📐 A really interesting side-effect of a UIView
's bounds
being its rect within its own coordinate system is that transforms don't affect it at all. That's why it's usually a better fit than frame
when doing layout calculations of subviews.
let view = UIView()
view.frame.size = CGSize(width: 100, height: 100)
view.transform = CGAffineTransform(scaleX: 2, y: 2)
print(view.frame) // (-50.0, -50.0, 200.0, 200.0)
print(view.bounds) // (0.0, 0.0, 100.0, 100.0)
👏 It's awesome that many UIKit APIs with completion handlers and other optional parameters import into Swift with default arguments (even though they are written in Objective-C). Getting rid of all those nil arguments is so nice!
// BEFORE: All parameters are specified, just like in Objective-C
viewController.present(modalViewController, animated: true, completion: nil)
modalViewController.dismiss(animated: true, completion: nil)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
options: [],
animations: animations,
completion: nil)
// AFTER: Since many UIKit APIs with completion handlers and other
// optional parameters import into Swift with default arguments,
// we can make our calls shorter
viewController.present(modalViewController, animated: true)
modalViewController.dismiss(animated: true)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
animations: animations)
✂️ Avoiding Massive View Controllers is all about finding the right levels of abstraction and splitting things up.
My personal rule of thumb is that as soon as I have 3 methods or properties that have the same prefix, I break them out into their own type.
// BEFORE
class LoginViewController: UIViewController {
private lazy var signUpLabel = UILabel()
private lazy var signUpImageView = UIImageView()
private lazy var signUpButton = UIButton()
}
// AFTER
class LoginViewController: UIViewController {
private lazy var signUpView = SignUpView()
}
class SignUpView: UIView {
private lazy var label = UILabel()
private lazy var imageView = UIImageView()
private lazy var button = UIButton()
}
❤️ I love the fact that optionals are enums in Swift - it makes it so easy to extend them with convenience APIs for certain types. Especially useful when doing things like data validation on optional values.
func validateTextFields() -> Bool {
guard !usernameTextField.text.isNilOrEmpty else {
return false
}
...
return true
}
// Since all optionals are actual enum values in Swift, we can easily
// extend them for certain types, to add our own convenience APIs
extension Optional where Wrapped == String {
var isNilOrEmpty: Bool {
switch self {
case let string?:
return string.isEmpty
case nil:
return true
}
}
}
// Since strings are now Collections in Swift 4, you can even
// add this property to all optional collections:
extension Optional where Wrapped: Collection {
var isNilOrEmpty: Bool {
switch self {
case let collection?:
return collection.isEmpty
case nil:
return true
}
}
}
🗺 Using the where
keyword can be a super nice way to quickly apply a filter in a for
-loop in Swift. You can of course use map
, filter
and forEach
, or guard
, but for simple loops I think this is very expressive and nice.
func archiveMarkedPosts() {
for post in posts where post.isMarked {
archive(post)
}
}
func healAllies() {
for player in players where player.isAllied(to: currentPlayer) {
player.heal()
}
}
👻 Variable shadowing can be super useful in Swift, especially when you want to create a local copy of a parameter value in order to use it as state within a closure.
init(repeatMode: RepeatMode, closure: @escaping () -> UpdateOutcome) {
// Shadow the argument with a local, mutable copy
var repeatMode = repeatMode
self.closure = {
// With shadowing, there's no risk of accidentially
// referring to the immutable version
switch repeatMode {
case .forever:
break
case .times(let count):
guard count > 0 else {
return .finished
}
// We can now capture the mutable version and use
// it for state in a closure
repeatMode = .times(count - 1)
}
return closure()
}
}
✒️ Dot syntax is one of my favorite features of Swift. What's really cool is that it's not only for enums, any static method or property can be used with dot syntax - even initializers! Perfect for convenience APIs and default parameters.
public enum RepeatMode {
case times(Int)
case forever
}
public extension RepeatMode {
static var never: RepeatMode {
return .times(0)
}
static var once: RepeatMode {
return .times(1)
}
}
view.perform(animation, repeated: .once)
// To make default parameters more compact, you can even use init with dot syntax
class ImageLoader {
init(cache: Cache = .init(), decoder: ImageDecoder = .init()) {
...
}
}
🚀 One really cool aspect of Swift having first class functions is that you can pass any function (or even initializer) as a closure, and even call it with a tuple containing its parameters!
// This function lets us treat any "normal" function or method as
// a closure and run it with a tuple that contains its parameters
func call<Input, Output>(_ function: (Input) -> Output, with input: Input) -> Output {
return function(input)
}
class ViewFactory {
func makeHeaderView() -> HeaderView {
// We can now pass an initializer as a closure, and a tuple
// containing its parameters
return call(HeaderView.init, with: loadTextStyles())
}
private func loadTextStyles() -> (font: UIFont, color: UIColor) {
return (theme.font, theme.textColor)
}
}
class HeaderView {
init(font: UIFont, textColor: UIColor) {
...
}
}
💉 If you've been struggling to test code that uses static APIs, here's a technique you can use to enable static dependency injection without having to modify any call sites:
// Before: Almost impossible to test due to the use of singletons
class Analytics {
static func log(_ event: Event) {
Database.shared.save(event)
let dictionary = event.serialize()
NetworkManager.shared.post(dictionary, to: eventURL)
}
}
// After: Much easier to test, since we can inject mocks as arguments
class Analytics {
static func log(_ event: Event,
database: Database = .shared,
networkManager: NetworkManager = .shared) {
database.save(event)
let dictionary = event.serialize()
networkManager.post(dictionary, to: eventURL)
}
}
🎉 In Swift 4, type inference works for lazy properties and you don't need to explicitly refer to self
!
// Swift 3
class PurchaseView: UIView {
private lazy var buyButton: UIButton = self.makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
// Swift 4
class PurchaseView: UIView {
private lazy var buyButton = makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
😎 You can turn any Swift Error
into an NSError
, which is super useful when pattern matching with a code 👍. Also, switching on optionals is pretty cool!
let task = urlSession.dataTask(with: url) { data, _, error in
switch error {
case .some(let error as NSError) where error.code == NSURLErrorNotConnectedToInternet:
presenter.showOfflineView()
case .some(let error):
presenter.showGenericErrorView()
case .none:
presenter.renderContent(from: data)
}
}
task.resume()
Also make sure to check out Kostas Kremizas' tip about how you can pattern match directly against a member of URLError
.
🖥 Here's an easy way to make iOS model code that uses UIImage
macOS compatible - like me and Gui Rambo discussed on the Swift by Sundell Podcast.
// Either put this in a separate file that you only include in your macOS target or wrap the code in #if os(macOS) / #endif
import Cocoa
// Step 1: Typealias UIImage to NSImage
typealias UIImage = NSImage
// Step 2: You might want to add these APIs that UIImage has but NSImage doesn't.
extension NSImage {
var cgImage: CGImage? {
var proposedRect = CGRect(origin: .zero, size: size)
return cgImage(forProposedRect: &proposedRect,
context: nil,
hints: nil)
}
convenience init?(named name: String) {
self.init(named: Name(name))
}
}
// Step 3: Profit - you can now make your model code that uses UIImage cross-platform!
struct User {
let name: String
let profileImage: UIImage
}
🤖 You can easily define a protocol-oriented API that can only be mutated internally, by using an internal protocol that extends a public one.
// Declare a public protocol that acts as your immutable API
public protocol ModelHolder {
associatedtype Model
var model: Model { get }
}
// Declare an extended, internal protocol that provides a mutable API
internal protocol MutableModelHolder: ModelHolder {
var model: Model { get set }
}
// You can now implement the requirements using 'public internal(set)'
public class UserHolder: MutableModelHolder {
public internal(set) var model: User
internal init(model: User) {
self.model = model
}
}
🎛 You can switch on a set using array literals as cases in Swift! Can be really useful to avoid many if
/else if
statements.
class RoadTile: Tile {
var connectedDirections = Set<Direction>()
func render() {
switch connectedDirections {
case [.up, .down]:
image = UIImage(named: "road-vertical")
case [.left, .right]:
image = UIImage(named: "road-horizontal")
default:
image = UIImage(named: "road")
}
}
}
🌍 When caching localized content in an app, it's a good idea to add the current locale to all keys, to prevent bugs when switching languages.
func cache(_ content: Content, forKey key: String) throws {
let data = try wrap(content) as Data
let key = localize(key: key)
try storage.store(data, forKey: key)
}
func loadCachedContent(forKey key: String) -> Content? {
let key = localize(key: key)
let data = storage.loadData(forKey: key)
return data.flatMap { try? unbox(data: $0) }
}
private func localize(key: String) -> String {
return key + "-" + Bundle.main.preferredLocalizations[0]
}
🚳 Here's an easy way to setup a test to avoid accidental retain cycles with object relationships (like weak delegates & observers) in Swift:
func testDelegateNotRetained() {
// Assign the delegate (weak) and also retain it using a local var
var delegate: Delegate? = DelegateMock()
controller.delegate = delegate
XCTAssertNotNil(controller.delegate)
// Release the local var, which should also release the weak reference
delegate = nil
XCTAssertNil(controller.delegate)
}
👨🔬 Playing around with an expressive way to check if a value matches any of a list of candidates in Swift:
// Instead of multiple conditions like this:
if string == "One" || string == "Two" || string == "Three" {
}
// You can now do:
if string == any(of: "One", "Two", "Three") {
}
You can find a gist with the implementation here.
👪 APIs in a Swift extension automatically inherit its access control level, making it a neat way to organize public, internal & private APIs.
public extension Animation {
init(textureNamed textureName: String) {
frames = [Texture(name: textureName)]
}
init(texturesNamed textureNames: [String], frameDuration: TimeInterval = 1) {
frames = textureNames.map(Texture.init)
self.frameDuration = frameDuration
}
init(image: Image) {
frames = [Texture(image: image)]
}
}
internal extension Animation {
func loadFrameImages() -> [Image] {
return frames.map { $0.loadImageIfNeeded() }
}
}
🗺 Using map
you can transform an optional value into an optional Result
type by simply passing in the enum case.
enum Result<Value> {
case value(Value)
case error(Error)
}
class Promise<Value> {
private var result: Result<Value>?
init(value: Value? = nil) {
result = value.map(Result.value)
}
}
👌 It's so nice that you can assign directly to self
in struct
initializers in Swift. Very useful when adding conformance to protocols.
extension Bool: AnswerConvertible {
public init(input: String) throws {
switch input.lowercased() {
case "y", "yes", "👍":
self = true
default:
self = false
}
}
}
☎️ Defining Swift closures as inline functions enables you to recursively call them, which is super useful in things like custom sequences.
class Database {
func records(matching query: Query) -> AnySequence<Record> {
var recordIterator = loadRecords().makeIterator()
func iterate() -> Record? {
guard let nextRecord = recordIterator.next() else {
return nil
}
guard nextRecord.matches(query) else {
// Since the closure is an inline function, it can be recursively called,
// in this case in order to advance to the next item.
return iterate()
}
return nextRecord
}
// AnySequence/AnyIterator are part of the standard library and provide an easy way
// to define custom sequences using closures.
return AnySequence { AnyIterator(iterate) }
}
}
Rob Napier points out that using the above might cause crashes if used on a large databaset, since Swift has no guaranteed Tail Call Optimization (TCO).
Slava Pestov also points out that another benefit of inline functions vs closures is that they can have their own generic parameter list.
🏖 Using lazy properties in Swift, you can pass self
to required Objective-C dependencies without having to use force-unwrapped optionals.
class DataLoader: NSObject {
lazy var urlSession: URLSession = self.makeURLSession()
private func makeURLSession() -> URLSession {
return URLSession(configuration: .default, delegate: self, delegateQueue: .main)
}
}
class Renderer {
lazy var displayLink: CADisplayLink = self.makeDisplayLink()
private func makeDisplayLink() -> CADisplayLink {
return CADisplayLink(target: self, selector: #selector(screenDidRefresh))
}
}
👓 If you have a property in Swift that needs to be weak
or lazy
, you can still make it readonly by using private(set)
.
class Node {
private(set) weak var parent: Node?
private(set) lazy var children = [Node]()
func add(child: Node) {
children.append(child)
child.parent = self
}
}
🌏 Tired of using URL(string: "url")!
for static URLs? Make URL
conform to ExpressibleByStringLiteral
and you can now simply use "url"
instead.
extension URL: ExpressibleByStringLiteral {
// By using 'StaticString' we disable string interpolation, for safety
public init(stringLiteral value: StaticString) {
self = URL(string: "\(value)").require(hint: "Invalid URL string literal: \(value)")
}
}
// We can now define URLs using static string literals 🎉
let url: URL = "https://www.swiftbysundell.com"
let task = URLSession.shared.dataTask(with: "https://www.swiftbysundell.com")
// In Swift 3 or earlier, you also have to implement 2 additional initializers
extension URL {
public init(extendedGraphemeClusterLiteral value: StaticString) {
self.init(stringLiteral: value)
}
public init(unicodeScalarLiteral value: StaticString) {
self.init(stringLiteral: value)
}
}
To find the extension that adds the require()
method on Optional
that I use above, check out Require.
✚ I'm always careful with operator overloading, but for manipulating things like sizes, points & frames I find them super useful.
extension CGSize {
static func *(lhs: CGSize, rhs: CGFloat) -> CGSize {
return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
}
}
button.frame.size = image.size * 2
If you like the above idea, check out CGOperators, which contains math operator overloads for all Core Graphics' vector types.
🔗 You can use closure types in generic constraints in Swift. Enables nice APIs for handling sequences of closures.
extension Sequence where Element == () -> Void {
func callAll() {
forEach { $0() }
}
}
extension Sequence where Element == () -> String {
func joinedResults(separator: String) -> String {
return map { $0() }.joined(separator: separator)
}
}
callbacks.callAll()
let names = nameProviders.joinedResults(separator: ", ")
(If you're using Swift 3, you have to change Element
to Iterator.Element
)
🎉 Using associated enum values is a super nice way to encapsulate mutually exclusive state info (and avoiding state-specific optionals).
// BEFORE: Lots of state-specific, optional properties
class Player {
var isWaitingForMatchMaking: Bool
var invitingUser: User?
var numberOfLives: Int
var playerDefeatedBy: Player?
var roundDefeatedIn: Int?
}
// AFTER: All state-specific information is encapsulated in enum cases
class Player {
enum State {
case waitingForMatchMaking
case waitingForInviteResponse(from: User)
case active(numberOfLives: Int)
case defeated(by: Player, roundNumber: Int)
}
var state: State
}
👍 I really like using enums for all async result types, even boolean ones. Self-documenting, and makes the call site a lot nicer to read too!
protocol PushNotificationService {
// Before
func enablePushNotifications(completionHandler: @escaping (Bool) -> Void)
// After
func enablePushNotifications(completionHandler: @escaping (PushNotificationStatus) -> Void)
}
enum PushNotificationStatus {
case enabled
case disabled
}
service.enablePushNotifications { status in
if status == .enabled {
enableNotificationsButton.removeFromSuperview()
}
}
🏃 Want to work on your async code in a Swift Playground? Just set needsIndefiniteExecution
to true to keep it running:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let greeting = "Hello after 3 seconds"
print(greeting)
}
To stop the playground from executing, simply call PlaygroundPage.current.finishExecution()
.
💦 Avoid memory leaks when accidentially refering to self
in closures by overriding it locally with a weak reference:
Swift >= 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let self = self else {
return
}
self.cache(result)
...
Swift < 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let `self` = self else {
return
}
self.cache(result)
...
Note that the reason the above currently works is because of a compiler bug (which I hope gets turned into a properly supported feature soon).
🕓 Using dispatch work items you can easily cancel a delayed asynchronous GCD task if you no longer need it:
let workItem = DispatchWorkItem {
// Your async code goes in here
}
// Execute the work item after 1 second
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem)
// You can cancel the work item if you no longer need it
workItem.cancel()
➕ While working on a new Swift developer tool (to be open sourced soon 😉), I came up with a pretty neat way of organizing its sequence of operations, by combining their functions into a closure:
internal func +<A, B, C>(lhs: @escaping (A) throws -> B,
rhs: @escaping (B) throws -> C) -> (A) throws -> C {
return { try rhs(lhs($0)) }
}
public func run() throws {
try (determineTarget + build + analyze + output)()
}
If you're familiar with the functional programming world, you might know the above technique as the pipe operator (thanks to Alexey Demedreckiy for pointing this out!)
🗺 Using map()
and flatMap()
on optionals you can chain multiple operations without having to use lengthy if lets
or guards
:
// BEFORE
guard let string = argument(at: 1) else {
return
}
guard let url = URL(string: string) else {
return
}
handle(url)
// AFTER
argument(at: 1).flatMap(URL.init).map(handle)
🚀 Using self-executing closures is a great way to encapsulate lazy property initialization:
class StoreViewController: UIViewController {
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let view = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
view.delegate = self
view.dataSource = self
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
}
}
⚡️ You can speed up your Swift package tests using the --parallel
flag. For Marathon, the tests execute 3 times faster that way!
swift test --parallel
🛠 Struggling with mocking UserDefaults
in a test? The good news is: you don't need mocking - just create a real instance:
class LoginTests: XCTestCase {
private var userDefaults: UserDefaults!
private var manager: LoginManager!
override func setUp() {
super.setup()
userDefaults = UserDefaults(suiteName: #file)
userDefaults.removePersistentDomain(forName: #file)
manager = LoginManager(userDefaults: userDefaults)
}
}
👍 Using variadic parameters in Swift, you can create some really nice APIs that take a list of objects without having to use an array:
extension Canvas {
func add(_ shapes: Shape...) {
shapes.forEach(add)
}
}
let circle = Circle(center: CGPoint(x: 5, y: 5), radius: 5)
let lineA = Line(start: .zero, end: CGPoint(x: 10, y: 10))
let lineB = Line(start: CGPoint(x: 0, y: 10), end: CGPoint(x: 10, y: 0))
let canvas = Canvas()
canvas.add(circle, lineA, lineB)
canvas.render()
😮 Just like you can refer to a Swift function as a closure, you can do the same thing with enum cases with associated values:
enum UnboxPath {
case key(String)
case keyPath(String)
}
struct UserSchema {
static let name = key("name")
static let age = key("age")
static let posts = key("posts")
private static let key = UnboxPath.key
}
📈 The ===
operator lets you check if two objects are the same instance. Very useful when verifying that an array contains an instance in a test:
protocol InstanceEquatable: class, Equatable {}
extension InstanceEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs === rhs
}
}
extension Enemy: InstanceEquatable {}
func testDestroyingEnemy() {
player.attack(enemy)
XCTAssertTrue(player.destroyedEnemies.contains(enemy))
}
😎 Cool thing about Swift initializers: you can call them using dot syntax and pass them as closures! Perfect for mocking dates in tests.
class Logger {
private let storage: LogStorage
private let dateProvider: () -> Date
init(storage: LogStorage = .init(), dateProvider: @escaping () -> Date = Date.init) {
self.storage = storage
self.dateProvider = dateProvider
}
func log(event: Event) {
storage.store(event: event, date: dateProvider())
}
}
📱 Most of my UI testing logic is now categories on XCUIApplication
. Makes the test cases really easy to read:
func testLoggingInAndOut() {
XCTAssertFalse(app.userIsLoggedIn)
app.launch()
app.login()
XCTAssertTrue(app.userIsLoggedIn)
app.logout()
XCTAssertFalse(app.userIsLoggedIn)
}
func testDisplayingCategories() {
XCTAssertFalse(app.isDisplayingCategories)
app.launch()
app.login()
app.goToCategories()
XCTAssertTrue(app.isDisplayingCategories)
}
🙂 It’s a good idea to avoid “default” cases when switching on Swift enums - it’ll “force you” to update your logic when a new case is added:
enum State {
case loggedIn
case loggedOut
case onboarding
}
func handle(_ state: State) {
switch state {
case .loggedIn:
showMainUI()
case .loggedOut:
showLoginUI()
// Compiler error: Switch must be exhaustive
}
}
💂 It's really cool that you can use Swift's 'guard' statement to exit out of pretty much any scope, not only return from functions:
// You can use the 'guard' statement to...
for string in strings {
// ...continue an iteration
guard shouldProcess(string) else {
continue
}
// ...or break it
guard !shouldBreak(for: string) else {
break
}
// ...or return
guard !shouldReturn(for: string) else {
return
}
// ..or throw an error
guard string.isValid else {
throw StringError.invalid(string)
}
// ...or exit the program
guard !shouldExit(for: string) else {
exit(1)
}
}
❤️ Love how you can pass functions & operators as closures in Swift. For example, it makes the syntax for sorting arrays really nice!
let array = [3, 9, 1, 4, 6, 2]
let sorted = array.sorted(by: <)
🗝 Here's a neat little trick I use to get UserDefault key consistency in Swift (#function expands to the property name in getters/setters). Just remember to write a good suite of tests that'll guard you against bugs when changing property names.
extension UserDefaults {
var onboardingCompleted: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
}
📛 Want to use a name already taken by the standard library for a nested type? No problem - just use Swift.
to disambiguate:
extension Command {
enum Error: Swift.Error {
case missing
case invalid(String)
}
}
📦 Playing around with using Wrap to implement Equatable
for any type, primarily for testing:
protocol AutoEquatable: Equatable {}
extension AutoEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
let lhsData = try! wrap(lhs) as Data
let rhsData = try! wrap(rhs) as Data
return lhsData == rhsData
}
}
📏 One thing that I find really useful in Swift is to use typealiases to reduce the length of method signatures in generic types:
public class PathFinder<Object: PathFinderObject> {
public typealias Map = Object.Map
public typealias Node = Map.Node
public typealias Path = PathFinderPath<Object>
public static func possiblePaths(for object: Object, at rootNode: Node, on map: Map) -> Path.Sequence {
return .init(object: object, rootNode: rootNode, map: map)
}
}
📖 You can reference either the external or internal parameter label when writing Swift docs - and they get parsed the same:
// EITHER:
class Foo {
/**
* - parameter string: A string
*/
func bar(with string: String) {}
}
// OR:
class Foo {
/**
* - parameter with: A string
*/
func bar(with string: String) {}
}
👍 Finding more and more uses for auto closures in Swift. Can enable some pretty nice APIs:
extension Dictionary {
mutating func value(for key: Key, orAdd valueClosure: @autoclosure () -> Value) -> Value {
if let value = self[key] {
return value
}
let value = valueClosure()
self[key] = value
return value
}
}
🚀 I’ve started to become a really big fan of nested types in Swift. Love the additional namespacing it gives you!
public struct Map {
public struct Model {
public let size: Size
public let theme: Theme
public var terrain: [Position : Terrain.Model]
public var units: [Position : Unit.Model]
public var buildings: [Position : Building.Model]
}
public enum Direction {
case up
case right
case down
case left
}
public struct Position {
public var x: Int
public var y: Int
}
public enum Size: String {
case small = "S"
case medium = "M"
case large = "L"
case extraLarge = "XL"
}
}
Author: JohnSundell
Source code: https://github.com/JohnSundell/SwiftTips
License: MIT license
#swift
1666245660
One of the things I really love about Swift is how I keep finding interesting ways to use it in various situations, and when I do - I usually share them on Twitter. Here's a collection of all the tips & tricks that I've shared so far. Each entry has a link to the original tweet, if you want to respond with some feedback or question, which is always super welcome! 🚀
⚠️ This list is no longer being updated. For my latest Swift tips, checkout the "Tips" section on Swift by Sundell.
Also make sure to check out all of my other Swift content:
🚀 Here are some quick tips to make async tests faster & more stable:
// BEFORE:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
detector.detectMentions(in: string) { mentions in
XCTAssertEqual(mentions, ["johnsundell"])
}
sleep(2)
}
}
// AFTER:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
var mentions: [String]?
let expectation = self.expectation(description: #function)
detector.detectMentions(in: string) {
mentions = $0
expectation.fulfill()
}
waitForExpectations(timeout: 10)
XCTAssertEqual(mentions, ["johnsundell"])
}
}
For more on async testing, check out "Unit testing asynchronous Swift code".
✍️ Adding support for the new Apple Pencil double-tap feature is super easy! All you have to do is to create a UIPencilInteraction
, add it to a view, and implement one delegate method. Hopefully all pencil-compatible apps will soon adopt this.
let interaction = UIPencilInteraction()
interaction.delegate = self
view.addInteraction(interaction)
extension ViewController: UIPencilInteractionDelegate {
func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
// Handle pencil double-tap
}
}
For more on using this and other iPad Pro features, check out "Building iPad Pro features in Swift".
😎 Here's a cool function that combines a value with a function to return a closure that captures that value, so that it can be called without any arguments. Super useful when working with closure-based APIs and we want to use some of our properties without having to capture self
.
func combine<A, B>(_ value: A, with closure: @escaping (A) -> B) -> () -> B {
return { closure(value) }
}
// BEFORE:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = { [weak self] in
guard let self = self else {
return
}
self.productManager.startCheckout(for: self.product)
}
}
}
// AFTER:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = combine(product, with: productManager.startCheckout)
}
}
💉 When I'm only using a single function from a dependency, I love to inject that function as a closure, instead of having to create a protocol and inject the whole object. Makes dependency injection & testing super simple.
final class ArticleLoader {
typealias Networking = (Endpoint) -> Future<Data>
private let networking: Networking
init(networking: @escaping Networking = URLSession.shared.load) {
self.networking = networking
}
func loadLatest() -> Future<[Article]> {
return networking(.latestArticles).decode()
}
}
For more on this technique, check out "Simple Swift dependency injection with functions".
💥 It's cool that you can easily assign a closure as a custom NSException
handler. This is super useful when building things in Playgrounds - since you can't use breakpoints - so instead of just signal SIGABRT
, you'll get the full exception description if something goes wrong.
NSSetUncaughtExceptionHandler { exception in
print(exception)
}
❤️ I love that in Swift, we can use the type system to make our code so much more self-documenting - one way of doing so is to use type aliases to give the primitive types that we use a more semantic meaning.
extension List.Item {
// Using type aliases, we can give semantic meaning to the
// primitive types that we use, without having to introduce
// wrapper types.
typealias Index = Int
}
extension List {
enum Mutation {
// Our enum cases now become a lot more self-documenting,
// without having to add additional parameter labels to
// explain them.
case add(Item, Item.Index)
case update(Item, Item.Index)
case remove(Item.Index)
}
}
For more on self-documenting code, check out "Writing self-documenting Swift code".
🤯 A little late night prototyping session reveals that protocol constraints can not only be applied to extensions - they can also be added to protocol definitions!
This is awesome, since it lets us easily define specialized protocols based on more generic ones.
protocol Component {
associatedtype Container
func add(to container: Container)
}
// Protocols that inherit from other protocols can include
// constraints to further specialize them.
protocol ViewComponent: Component where Container == UIView {
associatedtype View: UIView
var view: View { get }
}
extension ViewComponent {
func add(to container: UIView) {
container.addSubview(view)
}
}
For more on specializing protocols, check out "Specializing protocols in Swift".
📦 Here's a super handy extension on Swift's Optional
type, which gives us a really nice API for easily unwrapping an optional, or throwing an error in case the value turned out to be nil
:
extension Optional {
func orThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
switch self {
case .some(let value):
return value
case .none:
throw errorExpression()
}
}
}
let file = try loadFile(at: path).orThrow(MissingFileError())
For more ways that optionals can be extended, check out "Extending optionals in Swift".
👩🔬 Testing code that uses static APIs can be really tricky, but there's a way that it can often be done - using Swift's first class function capabilities!
Instead of accessing that static API directly, we can inject the function we want to use, which enables us to mock it!
// BEFORE
class FriendsLoader {
func loadFriends(then handler: @escaping (Result<[Friend]>) -> Void) {
Networking.loadData(from: .friends) { result in
...
}
}
}
// AFTER
class FriendsLoader {
typealias Handler<T> = (Result<T>) -> Void
typealias DataLoadingFunction = (Endpoint, @escaping Handler<Data>) -> Void
func loadFriends(using dataLoading: DataLoadingFunction = Networking.loadData,
then handler: @escaping Handler<[Friend]>) {
dataLoading(.friends) { result in
...
}
}
}
// MOCKING IN TESTS
let dataLoading: FriendsLoader.DataLoadingFunction = { _, handler in
handler(.success(mockData))
}
friendsLoader.loadFriends(using: dataLoading) { result in
...
}
🐾 Swift's pattern matching capabilities are so powerful! Two enum cases with associated values can even be matched and handled by the same switch case - which is super useful when handling state changes with similar data.
enum DownloadState {
case inProgress(progress: Double)
case paused(progress: Double)
case cancelled
case finished(Data)
}
func downloadStateDidChange(to state: DownloadState) {
switch state {
case .inProgress(let progress), .paused(let progress):
updateProgressView(with: progress)
case .cancelled:
showCancelledMessage()
case .finished(let data):
process(data)
}
}
🅰 One really nice benefit of Swift multiline string literals - even for single lines of text - is that they don't require quotes to be escaped. Perfect when working with things like HTML, or creating a custom description for an object.
let html = highlighter.highlight("Array<String>")
XCTAssertEqual(html, """
<span class="type">Array</span><<span class="type">String</span>>
""")
💎 While it's very common in functional programming, the reduce
function might be a bit of a hidden gem in Swift. It provides a super useful way to transform a sequence into a single value.
extension Sequence where Element: Equatable {
func numberOfOccurrences(of target: Element) -> Int {
return reduce(0) { result, element in
guard element == target else {
return result
}
return result + 1
}
}
}
You can read more about transforming collections in "Transforming collections in Swift".
📦 When I use Codable in Swift, I want to avoid manual implementations as much as possible, even when there's a mismatch between my code structure and the JSON I'm decoding.
One way that can often be achieved is to use private data containers combined with computed properties.
struct User: Codable {
let name: String
let age: Int
var homeTown: String { return originPlace.name }
private let originPlace: Place
}
private extension User {
struct Place: Codable {
let name: String
}
}
extension User {
struct Container: Codable {
let user: User
}
}
🚢 Instead of using feature branches, I merge almost all of my code directly into master - and then I use feature flags to conditionally enable features when they're ready. That way I can avoid merge conflicts and keep shipping!
extension ListViewController {
func addSearchIfNeeded() {
// Rather than having to keep maintaining a separate
// feature branch for a new feature, we can use a flag
// to conditionally turn it on.
guard FeatureFlags.searchEnabled else {
return
}
let resultsVC = SearchResultsViewController()
let searchVC = UISearchController(
searchResultsController: resultsVC
)
searchVC.searchResultsUpdater = resultsVC
navigationItem.searchController = searchVC
}
}
You can read more about feature flags in "Feature flags in Swift".
💾 Here I'm using tuples to create a lightweight hierarchy for my data, giving me a nice structure without having to introduce any additional types.
struct CodeSegment {
var tokens: (
previous: String?,
current: String
)
var delimiters: (
previous: Character?
next: Character?
)
}
handle(segment.tokens.current)
You can read more about tuples in "Using tuples as lightweight types in Swift"
3️⃣ Whenever I have 3 properties or local variables that share the same prefix, I usually try to extract them into their own method or type. That way I can avoid massive types & methods, and also increase readability, without falling into a "premature optimization" trap.
Before
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articleFolder = try contentFolder.subfolder(named: "posts")
let articleProcessor = ContentProcessor(folder: articleFolder)
let articles = try articleProcessor.process()
...
}
After
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articles = try processArticles(in: contentFolder)
...
}
private func processArticles(in folder: Folder) throws -> [ContentItem] {
let folder = try folder.subfolder(named: "posts")
let processor = ContentProcessor(folder: folder)
return try processor.process()
}
👨🔧 Here's two extensions that I always add to the Encodable
& Decodable
protocols, which for me really make the Codable API nicer to use. By using type inference for decoding, a lot of boilerplate can be removed when the compiler is already able to infer the resulting type.
extension Encodable {
func encoded() throws -> Data {
return try JSONEncoder().encode(self)
}
}
extension Data {
func decoded<T: Decodable>() throws -> T {
return try JSONDecoder().decode(T.self, from: self)
}
}
let data = try user.encoded()
// By using a generic type in the decoded() method, the
// compiler can often infer the type we want to decode
// from the current context.
try userDidLogin(data.decoded())
// And if not, we can always supply the type, still making
// the call site read very nicely.
let otherUser = try data.decoded() as User
📦 UserDefaults
is a lot more powerful than what it first might seem like. Not only can it store more complex values (like dates & dictionaries) and parse command line arguments - it also enables easy sharing of settings & lightweight data between apps in the same App Group.
let sharedDefaults = UserDefaults(suiteName: "my-app-group")!
let useDarkMode = sharedDefaults.bool(forKey: "dark-mode")
// This value is put into the shared suite.
sharedDefaults.set(true, forKey: "dark-mode")
// If you want to treat the shared settings as read-only (and add
// local overrides on top of them), you can simply add the shared
// suite to the standard UserDefaults.
let combinedDefaults = UserDefaults.standard
combinedDefaults.addSuite(named: "my-app-group")
// This value is a local override, not added to the shared suite.
combinedDefaults.set(true, forKey: "app-specific-override")
🎨 By overriding layerClass
you can tell UIKit what CALayer
class to use for a UIView
's backing layer. That way you can reduce the amount of layers, and don't have to do any manual layout.
final class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
var colors: (start: UIColor, end: UIColor)? {
didSet { updateLayer() }
}
private func updateLayer() {
let layer = self.layer as! CAGradientLayer
layer.colors = colors.map { [$0.start.cgColor, $0.end.cgColor] }
}
}
✅ That the compiler now automatically synthesizes Equatable conformances is such a huge upgrade for Swift! And the cool thing is that it works for all kinds of types - even for enums with associated values! Especially useful when using enums for verification in unit tests.
struct Article: Equatable {
let title: String
let text: String
}
struct User: Equatable {
let name: String
let age: Int
}
extension Navigator {
enum Destination: Equatable {
case profile(User)
case article(Article)
}
}
func testNavigatingToArticle() {
let article = Article(title: "Title", text: "Text")
controller.select(article)
XCTAssertEqual(navigator.destinations, [.article(article)])
}
🤝 Associated types can have defaults in Swift - which is super useful for types that are not easily inferred (for example when they're not used for a specific instance method or property).
protocol Identifiable {
associatedtype RawIdentifier: Codable = String
var id: Identifier<Self> { get }
}
struct User: Identifiable {
let id: Identifier<User>
let name: String
}
struct Group: Identifiable {
typealias RawIdentifier = Int
let id: Identifier<Group>
let name: String
}
🆔 If you want to avoid using plain strings as identifiers (which can increase both type safety & readability), it's really easy to create a custom Identifier type that feels just like a native Swift type, thanks to protocols!
More on this topic in "Type-safe identifiers in Swift".
struct Identifier: Hashable {
let string: String
}
extension Identifier: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
string = value
}
}
extension Identifier: CustomStringConvertible {
var description: String {
return string
}
}
extension Identifier: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
string = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
}
}
struct Article: Codable {
let id: Identifier
let title: String
}
let article = Article(id: "my-article", title: "Hello world!")
🙌 A really cool thing about using tuples to model the internal state of a Swift type, is that you can unwrap an optional tuple's members directly into local variables.
Very useful in order to group multiple optional values together for easy unwrapping & handling.
class ImageTransformer {
private var queue = [(image: UIImage, transform: Transform)]()
private func processNext() {
// When unwrapping an optional tuple, you can assign the members
// directly to local variables.
guard let (image, transform) = queue.first else {
return
}
let context = Context()
context.draw(image)
context.apply(transform)
...
}
}
❤️ I love to structure my code using extensions in Swift. One big benefit of doing so when it comes to struct initializers, is that defining a convenience initializer doesn't remove the default one the compiler generates - best of both worlds!
struct Article {
let date: Date
var title: String
var text: String
var comments: [Comment]
}
extension Article {
init(title: String, text: String) {
self.init(date: Date(), title: title, text: text, comments: [])
}
}
let articleA = Article(title: "Best Cupcake Recipe", text: "...")
let articleB = Article(
date: Date(),
title: "Best Cupcake Recipe",
text: "...",
comments: [
Comment(user: currentUser, text: "Yep, can confirm!")
]
)
🏈 A big benefit of using throwing functions for synchronous Swift APIs is that the caller can decide whether they want to treat the return value as optional (try?
) or required (try
).
func loadFile(named name: String) throws -> File {
guard let url = urlForFile(named: name) else {
throw File.Error.missing
}
do {
let data = try Data(contentsOf: url)
return File(url: url, data: data)
} catch {
throw File.Error.invalidData(error)
}
}
let requiredFile = try loadFile(named: "AppConfig.json")
let optionalFile = try? loadFile(named: "UserSettings.json")
🐝 Types that are nested in generics automatically inherit their parent's generic types - which is super useful when defining accessory types (for things like states or outcomes).
struct Task<Input, Output> {
typealias Closure = (Input) throws -> Output
let closure: Closure
}
extension Task {
enum Result {
case success(Output)
case failure(Error)
}
}
🤖 Now that the Swift compiler automatically synthesizes Equatable & Hashable conformances for value types, it's easier than ever to setup model structures with nested types that are all Equatable
/Hashable
!
typealias Value = Hashable & Codable
struct User: Value {
var name: String
var age: Int
var lastLoginDate: Date?
var settings: Settings
}
extension User {
struct Settings: Value {
var itemsPerPage: Int
var theme: Theme
}
}
extension User.Settings {
enum Theme: String, Value {
case light
case dark
}
}
You can read more about using nested types in Swift here.
🎉 Swift 4.1 is here! One of the key features it brings is conditional conformances, which lets you have a type only conform to a protocol under certain constraints.
protocol UnboxTransformable {
associatedtype RawValue
static func transform(_ value: RawValue) throws -> Self?
}
extension Array: UnboxTransformable where Element: UnboxTransformable {
typealias RawValue = [Element.RawValue]
static func transform(_ value: RawValue) throws -> [Element]? {
return try value.compactMap(Element.transform)
}
}
I also have an article with lots of more info on conditional conformances here. Paul Hudson also has a great overview of all Swift 4.1 features here.
🕵️♀️ A cool thing about Swift type aliases is that they can be generic! Combine that with tuples and you can easily define simple generic types.
typealias Pair<T> = (T, T)
extension Game {
func calculateScore(for players: Pair<Player>) -> Int {
...
}
}
You can read more about using tuples as lightweight types here.
☑️ A really cool "hidden" feature of UserDefaults is that it contains any arguments that were passed to the app at launch!
Super useful both in Swift command line tools & scripts, but also to temporarily override a value when debugging iOS apps.
let defaults = UserDefaults.standard
let query = defaults.string(forKey: "query")
let resultCount = defaults.integer(forKey: "results")
👏 Swift's &
operator is awesome! Not only can you use it to compose protocols, you can compose other types too! Very useful if you want to hide concrete types & implementation details.
protocol LoadableFromURL {
func load(from url: URL)
}
class ContentViewController: UIViewController, LoadableFromURL {
func load(from url: URL) {
...
}
}
class ViewControllerFactory {
func makeContentViewController() -> UIViewController & LoadableFromURL {
return ContentViewController()
}
}
🤗 When capturing values in mocks, using an array (instead of just a single value) makes it easy to verify that only a certain number of values were passed.
Perfect for protecting against "over-calling" something.
class UserManagerTests: XCTestCase {
func testObserversCalledWhenUserFirstLogsIn() {
let manager = UserManager()
let observer = ObserverMock()
manager.addObserver(observer)
// First login, observers should be notified
let user = User(id: 123, name: "John")
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
// If the same user logs in again, observers shouldn't be notified
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
}
}
private extension UserManagerTests {
class ObserverMock: UserManagerObserver {
private(set) var users = [User]()
func userDidChange(to user: User) {
users.append(user)
}
}
}
👋 When writing tests, you don't always need to create mocks - you can create stubs using real instances of things like errors, URLs & UserDefaults.
Here's how to do that for some common tasks/object types in Swift:
// Create errors using NSError (#function can be used to reference the name of the test)
let error = NSError(domain: #function, code: 1, userInfo: nil)
// Create non-optional URLs using file paths
let url = URL(fileURLWithPath: "Some/URL")
// Reference the test bundle using Bundle(for:)
let bundle = Bundle(for: type(of: self))
// Create an explicit UserDefaults object (instead of having to use a mock)
let userDefaults = UserDefaults(suiteName: #function)
// Create queues to control/await concurrent operations
let queue = DispatchQueue(label: #function)
For when you actually do need mocking, check out "Mocking in Swift".
⏱ I've started using "then" as an external parameter label for completion handlers. Makes the call site read really nicely (Because I do ❤️ conversational API design) regardless of whether trailing closure syntax is used or not.
protocol DataLoader {
// Adding type aliases to protocols can be a great way to
// reduce verbosity for parameter types.
typealias Handler = (Result<Data>) -> Void
associatedtype Endpoint
func loadData(from endpoint: Endpoint, then handler: @escaping Handler)
}
loader.loadData(from: .messages) { result in
...
}
loader.loadData(from: .messages, then: { result in
...
})
😴 Combining lazily evaluated sequences with builder pattern-like properties can lead to some pretty sweet APIs for configurable sequences in Swift.
Also useful for queries & other things you "build up" and then execute.
// Extension adding builder pattern-like properties that return
// a new sequence value with the given configuration applied
extension FileSequence {
var recursive: FileSequence {
var sequence = self
sequence.isRecursive = true
return sequence
}
var includingHidden: FileSequence {
var sequence = self
sequence.includeHidden = true
return sequence
}
}
// BEFORE
let files = folder.makeFileSequence(recursive: true, includeHidden: true)
// AFTER
let files = folder.files.recursive.includingHidden
Want an intro to lazy sequences? Check out "Swift sequences: The art of being lazy".
My top 3 tips for faster & more stable UI tests:
📱 Reset the app's state at the beginning of every test.
🆔 Use accessibility identifiers instead of UI strings.
⏱ Use expectations instead of waiting time.
func testOpeningArticle() {
// Launch the app with an argument that tells it to reset its state
let app = XCUIApplication()
app.launchArguments.append("--uitesting")
app.launch()
// Check that the app is displaying an activity indicator
let activityIndicator = app.activityIndicator.element
XCTAssertTrue(activityIndicator.exists)
// Wait for the loading indicator to disappear = content is ready
expectation(for: NSPredicate(format: "exists == 0"),
evaluatedWith: activityIndicator)
// Use a generous timeout in case the network is slow
waitForExpectations(timeout: 10)
// Tap the cell for the first article
app.tables.cells["Article.0"].tap()
// Assert that a label with the accessibility identifier "Article.Title" exists
let label = app.staticTexts["Article.Title"]
XCTAssertTrue(label.exists)
}
📋 It's super easy to access the contents of the clipboard from a Swift script. A big benefit of Swift scripting is being able to use Cocoa's powerful APIs for Mac apps.
import Cocoa
let clipboard = NSPasteboard.general.string(forType: .string)
🎯 Using Swift tuples for view state can be a super nice way to group multiple properties together and render them reactively using the layout system.
By using a tuple we don't have to either introduce a new type or make our view model-aware.
class TextView: UIView {
var state: (title: String?, text: String?) {
// By telling UIKit that our view needs layout and binding our
// state in layoutSubviews, we can react to state changes without
// doing unnecessary layout work.
didSet { setNeedsLayout() }
}
private let titleLabel = UILabel()
private let textLabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.text = state.title
textLabel.text = state.text
...
}
}
⚾️ Swift tests can throw, which is super useful in order to avoid complicated logic or force unwrapping. By making errors conform to LocalizedError
, you can also get a nice error message in Xcode if there's a failure.
class ImageCacheTests: XCTestCase {
func testCachingAndLoadingImage() throws {
let bundle = Bundle(for: type(of: self))
let cache = ImageCache(bundle: bundle)
// Bonus tip: You can easily load images from your test
// bundle using this UIImage initializer
let image = try require(UIImage(named: "sample", in: bundle, compatibleWith: nil))
try cache.cache(image, forKey: "key")
let cachedImage = try cache.image(forKey: "key")
XCTAssertEqual(image, cachedImage)
}
}
enum ImageCacheError {
case emptyKey
case dataConversionFailed
}
// When using throwing tests, making your errors conform to
// LocalizedError will render a much nicer error message in
// Xcode (per default only the error code is shown).
extension ImageCacheError: LocalizedError {
var errorDescription: String? {
switch self {
case .emptyKey:
return "An empty key was given"
case .dataConversionFailed:
return "Failed to convert the given image to Data"
}
}
}
For more information, and the implementation of the require
method used above, check out "Avoiding force unwrapping in Swift unit tests".
✍️ Unlike static
properties, class
properties can be overridden by subclasses (however, they can't be stored, only computed).
class TableViewCell: UITableViewCell {
class var preferredHeight: CGFloat { return 60 }
}
class TallTableViewCell: TableViewCell {
override class var preferredHeight: CGFloat { return 100 }
}
👨🎨 Creating extensions with static factory methods can be a great alternative to subclassing in Swift, especially for things like setting up UIViews, CALayers or other kinds of styling.
It also lets you remove a lot of styling & setup from your view controllers.
extension UILabel {
static func makeForTitle() -> UILabel {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 24)
label.textColor = .darkGray
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.75
return label
}
static func makeForText() -> UILabel {
let label = UILabel()
label.font = .systemFont(ofSize: 16)
label.textColor = .black
label.numberOfLines = 0
return label
}
}
class ArticleViewController: UIViewController {
lazy var titleLabel = UILabel.makeForTitle()
lazy var textLabel = UILabel.makeForText()
}
🧒 An awesome thing about child view controllers is that they're automatically resized to match their parent, making them a super nice solution for things like loading & error views.
class ListViewController: UIViewController {
func loadItems() {
let loadingViewController = LoadingViewController()
add(loadingViewController)
dataLoader.loadItems { [weak self] result in
loadingViewController.remove()
self?.handle(result)
}
}
}
For more about child view controller (including the add
and remove
methods used above), check out "Using child view controllers as plugins in Swift".
🤐 Using the zip function in Swift you can easily combine two sequences. Super useful when using two sequences to do some work, since zip takes care of all the bounds-checking.
func render(titles: [String]) {
for (label, text) in zip(titleLabels, titles) {
print(text)
label.text = text
}
}
🎛 The awesome thing about option sets in Swift is that they can automatically either be passed as a single member or as a set. Even cooler is that you can easily define your own option sets as well, perfect for options and other non-exclusive values.
// Option sets are awesome, because you can easily pass them
// both using dot syntax and array literal syntax, like when
// using the UIView animation API:
UIView.animate(withDuration: 0.3,
delay: 0,
options: .allowUserInteraction,
animations: animations)
UIView.animate(withDuration: 0.3,
delay: 0,
options: [.allowUserInteraction, .layoutSubviews],
animations: animations)
// The cool thing is that you can easily define your own option
// sets as well, by defining a struct that has an Int rawValue,
// that will be used as a bit mask.
extension Cache {
struct Options: OptionSet {
static let saveToDisk = Options(rawValue: 1)
static let clearOnMemoryWarning = Options(rawValue: 1 << 1)
static let clearDaily = Options(rawValue: 1 << 2)
let rawValue: Int
}
}
// We can now use Cache.Options just like UIViewAnimationOptions:
Cache(options: .saveToDisk)
Cache(options: [.saveToDisk, .clearDaily])
🙌 Using the where
clause when designing protocol-oriented APIs in Swift can let your implementations (or others' if it's open source) have a lot more freedom, especially when it comes to collections.
See "Using generic type constraints in Swift 4" for more info.
public protocol PathFinderMap {
associatedtype Node
// Using the 'where' clause for associated types, we can
// ensure that a type meets certain requirements (in this
// case that it's a sequence with Node elements).
associatedtype NodeSequence: Sequence where NodeSequence.Element == Node
// Instead of using a concrete type (like [Node]) here, we
// give implementors of this protocol more freedom while
// still meeting our requirements. For example, one
// implementation might use Set<Node>.
func neighbors(of node: Node) -> NodeSequence
}
👨🍳 Combine first class functions in Swift with the fact that Dictionary elements are (Key, Value) tuples and you can build yourself some pretty awesome functional chains when iterating over a Dictionary.
func makeActor(at coordinate: Coordinate, for building: Building) -> Actor {
let actor = Actor()
actor.position = coordinate.point
actor.animation = building.animation
return actor
}
func render(_ buildings: [Coordinate : Building]) {
buildings.map(makeActor).forEach(add)
}
😎 In Swift, you can call any instance method as a static function and it will return a closure representing that method. This is how running tests using SPM on Linux works.
More about this topic in my blog post "First class functions in Swift".
// This produces a '() -> Void' closure which is a reference to the
// given view's 'removeFromSuperview' method.
let closure = UIView.removeFromSuperview(view)
// We can now call it just like we would any other closure, and it
// will run 'view.removeFromSuperview()'
closure()
// This is how running tests using the Swift Package Manager on Linux
// works, you return your test functions as closures:
extension UserManagerTests {
static var allTests = [
("testLoggingIn", testLoggingIn),
("testLoggingOut", testLoggingOut),
("testUserPermissions", testUserPermissions)
]
}
👏 One really nice benefit of dropping suffixes from method names (and just using verbs, when possible) is that it becomes super easy to support both single and multiple arguments, and it works really well semantically.
extension UIView {
func add(_ subviews: UIView...) {
subviews.forEach(addSubview)
}
}
view.add(button)
view.add(label)
// By dropping the "Subview" suffix from the method name, both
// single and multiple arguments work really well semantically.
view.add(button, label)
👽 Using the AnyObject
(or class
) constraint on protocols is not only useful when defining delegates (or other weak references), but also when you always want instances to be mutable without copying.
// By constraining a protocol with 'AnyObject' it can only be adopted
// by classes, which means all instances will always be mutable, and
// that it's the original instance (not a copy) that will be mutated.
protocol DataContainer: AnyObject {
var data: Data? { get set }
}
class UserSettingsManager {
private var settings: Settings
private let dataContainer: DataContainer
// Since DataContainer is a protocol, we an easily mock it in
// tests if we use dependency injection
init(settings: Settings, dataContainer: DataContainer) {
self.settings = settings
self.dataContainer = dataContainer
}
func saveSettings() throws {
let data = try settings.serialize()
// We can now assign properties on an instance of our protocol
// because the compiler knows it's always going to be a class
dataContainer.data = data
}
}
🍣 Even if you define a custom raw value for a string-based enum in Swift, the full case name will be used in string interpolation.
Super useful when using separate raw values for JSON, while still wanting to use the full case name in other contexts.
extension Building {
// This enum has custom raw values that are used when decoding
// a value, for example from JSON.
enum Kind: String {
case castle = "C"
case town = "T"
case barracks = "B"
case goldMine = "G"
case camp = "CA"
case blacksmith = "BL"
}
var animation: Animation {
return Animation(
// When used in string interpolation, the full case name is still used.
// For 'castle' this will be 'buildings/castle'.
name: "buildings/\(kind)",
frameCount: frameCount,
frameDuration: frameDuration
)
}
}
👨🔬 Continuing to experiment with expressive ways of comparing a value with a list of candidates in Swift. Adding an extension on Equatable is probably my favorite approach so far.
extension Equatable {
func isAny(of candidates: Self...) -> Bool {
return candidates.contains(self)
}
}
let isHorizontal = direction.isAny(of: .left, .right)
See tip 35 for my previous experiment.
📐 A really interesting side-effect of a UIView
's bounds
being its rect within its own coordinate system is that transforms don't affect it at all. That's why it's usually a better fit than frame
when doing layout calculations of subviews.
let view = UIView()
view.frame.size = CGSize(width: 100, height: 100)
view.transform = CGAffineTransform(scaleX: 2, y: 2)
print(view.frame) // (-50.0, -50.0, 200.0, 200.0)
print(view.bounds) // (0.0, 0.0, 100.0, 100.0)
👏 It's awesome that many UIKit APIs with completion handlers and other optional parameters import into Swift with default arguments (even though they are written in Objective-C). Getting rid of all those nil arguments is so nice!
// BEFORE: All parameters are specified, just like in Objective-C
viewController.present(modalViewController, animated: true, completion: nil)
modalViewController.dismiss(animated: true, completion: nil)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
options: [],
animations: animations,
completion: nil)
// AFTER: Since many UIKit APIs with completion handlers and other
// optional parameters import into Swift with default arguments,
// we can make our calls shorter
viewController.present(modalViewController, animated: true)
modalViewController.dismiss(animated: true)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
animations: animations)
✂️ Avoiding Massive View Controllers is all about finding the right levels of abstraction and splitting things up.
My personal rule of thumb is that as soon as I have 3 methods or properties that have the same prefix, I break them out into their own type.
// BEFORE
class LoginViewController: UIViewController {
private lazy var signUpLabel = UILabel()
private lazy var signUpImageView = UIImageView()
private lazy var signUpButton = UIButton()
}
// AFTER
class LoginViewController: UIViewController {
private lazy var signUpView = SignUpView()
}
class SignUpView: UIView {
private lazy var label = UILabel()
private lazy var imageView = UIImageView()
private lazy var button = UIButton()
}
❤️ I love the fact that optionals are enums in Swift - it makes it so easy to extend them with convenience APIs for certain types. Especially useful when doing things like data validation on optional values.
func validateTextFields() -> Bool {
guard !usernameTextField.text.isNilOrEmpty else {
return false
}
...
return true
}
// Since all optionals are actual enum values in Swift, we can easily
// extend them for certain types, to add our own convenience APIs
extension Optional where Wrapped == String {
var isNilOrEmpty: Bool {
switch self {
case let string?:
return string.isEmpty
case nil:
return true
}
}
}
// Since strings are now Collections in Swift 4, you can even
// add this property to all optional collections:
extension Optional where Wrapped: Collection {
var isNilOrEmpty: Bool {
switch self {
case let collection?:
return collection.isEmpty
case nil:
return true
}
}
}
🗺 Using the where
keyword can be a super nice way to quickly apply a filter in a for
-loop in Swift. You can of course use map
, filter
and forEach
, or guard
, but for simple loops I think this is very expressive and nice.
func archiveMarkedPosts() {
for post in posts where post.isMarked {
archive(post)
}
}
func healAllies() {
for player in players where player.isAllied(to: currentPlayer) {
player.heal()
}
}
👻 Variable shadowing can be super useful in Swift, especially when you want to create a local copy of a parameter value in order to use it as state within a closure.
init(repeatMode: RepeatMode, closure: @escaping () -> UpdateOutcome) {
// Shadow the argument with a local, mutable copy
var repeatMode = repeatMode
self.closure = {
// With shadowing, there's no risk of accidentially
// referring to the immutable version
switch repeatMode {
case .forever:
break
case .times(let count):
guard count > 0 else {
return .finished
}
// We can now capture the mutable version and use
// it for state in a closure
repeatMode = .times(count - 1)
}
return closure()
}
}
✒️ Dot syntax is one of my favorite features of Swift. What's really cool is that it's not only for enums, any static method or property can be used with dot syntax - even initializers! Perfect for convenience APIs and default parameters.
public enum RepeatMode {
case times(Int)
case forever
}
public extension RepeatMode {
static var never: RepeatMode {
return .times(0)
}
static var once: RepeatMode {
return .times(1)
}
}
view.perform(animation, repeated: .once)
// To make default parameters more compact, you can even use init with dot syntax
class ImageLoader {
init(cache: Cache = .init(), decoder: ImageDecoder = .init()) {
...
}
}
🚀 One really cool aspect of Swift having first class functions is that you can pass any function (or even initializer) as a closure, and even call it with a tuple containing its parameters!
// This function lets us treat any "normal" function or method as
// a closure and run it with a tuple that contains its parameters
func call<Input, Output>(_ function: (Input) -> Output, with input: Input) -> Output {
return function(input)
}
class ViewFactory {
func makeHeaderView() -> HeaderView {
// We can now pass an initializer as a closure, and a tuple
// containing its parameters
return call(HeaderView.init, with: loadTextStyles())
}
private func loadTextStyles() -> (font: UIFont, color: UIColor) {
return (theme.font, theme.textColor)
}
}
class HeaderView {
init(font: UIFont, textColor: UIColor) {
...
}
}
💉 If you've been struggling to test code that uses static APIs, here's a technique you can use to enable static dependency injection without having to modify any call sites:
// Before: Almost impossible to test due to the use of singletons
class Analytics {
static func log(_ event: Event) {
Database.shared.save(event)
let dictionary = event.serialize()
NetworkManager.shared.post(dictionary, to: eventURL)
}
}
// After: Much easier to test, since we can inject mocks as arguments
class Analytics {
static func log(_ event: Event,
database: Database = .shared,
networkManager: NetworkManager = .shared) {
database.save(event)
let dictionary = event.serialize()
networkManager.post(dictionary, to: eventURL)
}
}
🎉 In Swift 4, type inference works for lazy properties and you don't need to explicitly refer to self
!
// Swift 3
class PurchaseView: UIView {
private lazy var buyButton: UIButton = self.makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
// Swift 4
class PurchaseView: UIView {
private lazy var buyButton = makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
😎 You can turn any Swift Error
into an NSError
, which is super useful when pattern matching with a code 👍. Also, switching on optionals is pretty cool!
let task = urlSession.dataTask(with: url) { data, _, error in
switch error {
case .some(let error as NSError) where error.code == NSURLErrorNotConnectedToInternet:
presenter.showOfflineView()
case .some(let error):
presenter.showGenericErrorView()
case .none:
presenter.renderContent(from: data)
}
}
task.resume()
Also make sure to check out Kostas Kremizas' tip about how you can pattern match directly against a member of URLError
.
🖥 Here's an easy way to make iOS model code that uses UIImage
macOS compatible - like me and Gui Rambo discussed on the Swift by Sundell Podcast.
// Either put this in a separate file that you only include in your macOS target or wrap the code in #if os(macOS) / #endif
import Cocoa
// Step 1: Typealias UIImage to NSImage
typealias UIImage = NSImage
// Step 2: You might want to add these APIs that UIImage has but NSImage doesn't.
extension NSImage {
var cgImage: CGImage? {
var proposedRect = CGRect(origin: .zero, size: size)
return cgImage(forProposedRect: &proposedRect,
context: nil,
hints: nil)
}
convenience init?(named name: String) {
self.init(named: Name(name))
}
}
// Step 3: Profit - you can now make your model code that uses UIImage cross-platform!
struct User {
let name: String
let profileImage: UIImage
}
🤖 You can easily define a protocol-oriented API that can only be mutated internally, by using an internal protocol that extends a public one.
// Declare a public protocol that acts as your immutable API
public protocol ModelHolder {
associatedtype Model
var model: Model { get }
}
// Declare an extended, internal protocol that provides a mutable API
internal protocol MutableModelHolder: ModelHolder {
var model: Model { get set }
}
// You can now implement the requirements using 'public internal(set)'
public class UserHolder: MutableModelHolder {
public internal(set) var model: User
internal init(model: User) {
self.model = model
}
}
🎛 You can switch on a set using array literals as cases in Swift! Can be really useful to avoid many if
/else if
statements.
class RoadTile: Tile {
var connectedDirections = Set<Direction>()
func render() {
switch connectedDirections {
case [.up, .down]:
image = UIImage(named: "road-vertical")
case [.left, .right]:
image = UIImage(named: "road-horizontal")
default:
image = UIImage(named: "road")
}
}
}
🌍 When caching localized content in an app, it's a good idea to add the current locale to all keys, to prevent bugs when switching languages.
func cache(_ content: Content, forKey key: String) throws {
let data = try wrap(content) as Data
let key = localize(key: key)
try storage.store(data, forKey: key)
}
func loadCachedContent(forKey key: String) -> Content? {
let key = localize(key: key)
let data = storage.loadData(forKey: key)
return data.flatMap { try? unbox(data: $0) }
}
private func localize(key: String) -> String {
return key + "-" + Bundle.main.preferredLocalizations[0]
}
🚳 Here's an easy way to setup a test to avoid accidental retain cycles with object relationships (like weak delegates & observers) in Swift:
func testDelegateNotRetained() {
// Assign the delegate (weak) and also retain it using a local var
var delegate: Delegate? = DelegateMock()
controller.delegate = delegate
XCTAssertNotNil(controller.delegate)
// Release the local var, which should also release the weak reference
delegate = nil
XCTAssertNil(controller.delegate)
}
👨🔬 Playing around with an expressive way to check if a value matches any of a list of candidates in Swift:
// Instead of multiple conditions like this:
if string == "One" || string == "Two" || string == "Three" {
}
// You can now do:
if string == any(of: "One", "Two", "Three") {
}
You can find a gist with the implementation here.
👪 APIs in a Swift extension automatically inherit its access control level, making it a neat way to organize public, internal & private APIs.
public extension Animation {
init(textureNamed textureName: String) {
frames = [Texture(name: textureName)]
}
init(texturesNamed textureNames: [String], frameDuration: TimeInterval = 1) {
frames = textureNames.map(Texture.init)
self.frameDuration = frameDuration
}
init(image: Image) {
frames = [Texture(image: image)]
}
}
internal extension Animation {
func loadFrameImages() -> [Image] {
return frames.map { $0.loadImageIfNeeded() }
}
}
🗺 Using map
you can transform an optional value into an optional Result
type by simply passing in the enum case.
enum Result<Value> {
case value(Value)
case error(Error)
}
class Promise<Value> {
private var result: Result<Value>?
init(value: Value? = nil) {
result = value.map(Result.value)
}
}
👌 It's so nice that you can assign directly to self
in struct
initializers in Swift. Very useful when adding conformance to protocols.
extension Bool: AnswerConvertible {
public init(input: String) throws {
switch input.lowercased() {
case "y", "yes", "👍":
self = true
default:
self = false
}
}
}
☎️ Defining Swift closures as inline functions enables you to recursively call them, which is super useful in things like custom sequences.
class Database {
func records(matching query: Query) -> AnySequence<Record> {
var recordIterator = loadRecords().makeIterator()
func iterate() -> Record? {
guard let nextRecord = recordIterator.next() else {
return nil
}
guard nextRecord.matches(query) else {
// Since the closure is an inline function, it can be recursively called,
// in this case in order to advance to the next item.
return iterate()
}
return nextRecord
}
// AnySequence/AnyIterator are part of the standard library and provide an easy way
// to define custom sequences using closures.
return AnySequence { AnyIterator(iterate) }
}
}
Rob Napier points out that using the above might cause crashes if used on a large databaset, since Swift has no guaranteed Tail Call Optimization (TCO).
Slava Pestov also points out that another benefit of inline functions vs closures is that they can have their own generic parameter list.
🏖 Using lazy properties in Swift, you can pass self
to required Objective-C dependencies without having to use force-unwrapped optionals.
class DataLoader: NSObject {
lazy var urlSession: URLSession = self.makeURLSession()
private func makeURLSession() -> URLSession {
return URLSession(configuration: .default, delegate: self, delegateQueue: .main)
}
}
class Renderer {
lazy var displayLink: CADisplayLink = self.makeDisplayLink()
private func makeDisplayLink() -> CADisplayLink {
return CADisplayLink(target: self, selector: #selector(screenDidRefresh))
}
}
👓 If you have a property in Swift that needs to be weak
or lazy
, you can still make it readonly by using private(set)
.
class Node {
private(set) weak var parent: Node?
private(set) lazy var children = [Node]()
func add(child: Node) {
children.append(child)
child.parent = self
}
}
🌏 Tired of using URL(string: "url")!
for static URLs? Make URL
conform to ExpressibleByStringLiteral
and you can now simply use "url"
instead.
extension URL: ExpressibleByStringLiteral {
// By using 'StaticString' we disable string interpolation, for safety
public init(stringLiteral value: StaticString) {
self = URL(string: "\(value)").require(hint: "Invalid URL string literal: \(value)")
}
}
// We can now define URLs using static string literals 🎉
let url: URL = "https://www.swiftbysundell.com"
let task = URLSession.shared.dataTask(with: "https://www.swiftbysundell.com")
// In Swift 3 or earlier, you also have to implement 2 additional initializers
extension URL {
public init(extendedGraphemeClusterLiteral value: StaticString) {
self.init(stringLiteral: value)
}
public init(unicodeScalarLiteral value: StaticString) {
self.init(stringLiteral: value)
}
}
To find the extension that adds the require()
method on Optional
that I use above, check out Require.
✚ I'm always careful with operator overloading, but for manipulating things like sizes, points & frames I find them super useful.
extension CGSize {
static func *(lhs: CGSize, rhs: CGFloat) -> CGSize {
return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
}
}
button.frame.size = image.size * 2
If you like the above idea, check out CGOperators, which contains math operator overloads for all Core Graphics' vector types.
🔗 You can use closure types in generic constraints in Swift. Enables nice APIs for handling sequences of closures.
extension Sequence where Element == () -> Void {
func callAll() {
forEach { $0() }
}
}
extension Sequence where Element == () -> String {
func joinedResults(separator: String) -> String {
return map { $0() }.joined(separator: separator)
}
}
callbacks.callAll()
let names = nameProviders.joinedResults(separator: ", ")
(If you're using Swift 3, you have to change Element
to Iterator.Element
)
🎉 Using associated enum values is a super nice way to encapsulate mutually exclusive state info (and avoiding state-specific optionals).
// BEFORE: Lots of state-specific, optional properties
class Player {
var isWaitingForMatchMaking: Bool
var invitingUser: User?
var numberOfLives: Int
var playerDefeatedBy: Player?
var roundDefeatedIn: Int?
}
// AFTER: All state-specific information is encapsulated in enum cases
class Player {
enum State {
case waitingForMatchMaking
case waitingForInviteResponse(from: User)
case active(numberOfLives: Int)
case defeated(by: Player, roundNumber: Int)
}
var state: State
}
👍 I really like using enums for all async result types, even boolean ones. Self-documenting, and makes the call site a lot nicer to read too!
protocol PushNotificationService {
// Before
func enablePushNotifications(completionHandler: @escaping (Bool) -> Void)
// After
func enablePushNotifications(completionHandler: @escaping (PushNotificationStatus) -> Void)
}
enum PushNotificationStatus {
case enabled
case disabled
}
service.enablePushNotifications { status in
if status == .enabled {
enableNotificationsButton.removeFromSuperview()
}
}
🏃 Want to work on your async code in a Swift Playground? Just set needsIndefiniteExecution
to true to keep it running:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let greeting = "Hello after 3 seconds"
print(greeting)
}
To stop the playground from executing, simply call PlaygroundPage.current.finishExecution()
.
💦 Avoid memory leaks when accidentially refering to self
in closures by overriding it locally with a weak reference:
Swift >= 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let self = self else {
return
}
self.cache(result)
...
Swift < 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let `self` = self else {
return
}
self.cache(result)
...
Note that the reason the above currently works is because of a compiler bug (which I hope gets turned into a properly supported feature soon).
🕓 Using dispatch work items you can easily cancel a delayed asynchronous GCD task if you no longer need it:
let workItem = DispatchWorkItem {
// Your async code goes in here
}
// Execute the work item after 1 second
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem)
// You can cancel the work item if you no longer need it
workItem.cancel()
➕ While working on a new Swift developer tool (to be open sourced soon 😉), I came up with a pretty neat way of organizing its sequence of operations, by combining their functions into a closure:
internal func +<A, B, C>(lhs: @escaping (A) throws -> B,
rhs: @escaping (B) throws -> C) -> (A) throws -> C {
return { try rhs(lhs($0)) }
}
public func run() throws {
try (determineTarget + build + analyze + output)()
}
If you're familiar with the functional programming world, you might know the above technique as the pipe operator (thanks to Alexey Demedreckiy for pointing this out!)
🗺 Using map()
and flatMap()
on optionals you can chain multiple operations without having to use lengthy if lets
or guards
:
// BEFORE
guard let string = argument(at: 1) else {
return
}
guard let url = URL(string: string) else {
return
}
handle(url)
// AFTER
argument(at: 1).flatMap(URL.init).map(handle)
🚀 Using self-executing closures is a great way to encapsulate lazy property initialization:
class StoreViewController: UIViewController {
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let view = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
view.delegate = self
view.dataSource = self
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
}
}
⚡️ You can speed up your Swift package tests using the --parallel
flag. For Marathon, the tests execute 3 times faster that way!
swift test --parallel
🛠 Struggling with mocking UserDefaults
in a test? The good news is: you don't need mocking - just create a real instance:
class LoginTests: XCTestCase {
private var userDefaults: UserDefaults!
private var manager: LoginManager!
override func setUp() {
super.setup()
userDefaults = UserDefaults(suiteName: #file)
userDefaults.removePersistentDomain(forName: #file)
manager = LoginManager(userDefaults: userDefaults)
}
}
👍 Using variadic parameters in Swift, you can create some really nice APIs that take a list of objects without having to use an array:
extension Canvas {
func add(_ shapes: Shape...) {
shapes.forEach(add)
}
}
let circle = Circle(center: CGPoint(x: 5, y: 5), radius: 5)
let lineA = Line(start: .zero, end: CGPoint(x: 10, y: 10))
let lineB = Line(start: CGPoint(x: 0, y: 10), end: CGPoint(x: 10, y: 0))
let canvas = Canvas()
canvas.add(circle, lineA, lineB)
canvas.render()
😮 Just like you can refer to a Swift function as a closure, you can do the same thing with enum cases with associated values:
enum UnboxPath {
case key(String)
case keyPath(String)
}
struct UserSchema {
static let name = key("name")
static let age = key("age")
static let posts = key("posts")
private static let key = UnboxPath.key
}
📈 The ===
operator lets you check if two objects are the same instance. Very useful when verifying that an array contains an instance in a test:
protocol InstanceEquatable: class, Equatable {}
extension InstanceEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs === rhs
}
}
extension Enemy: InstanceEquatable {}
func testDestroyingEnemy() {
player.attack(enemy)
XCTAssertTrue(player.destroyedEnemies.contains(enemy))
}
😎 Cool thing about Swift initializers: you can call them using dot syntax and pass them as closures! Perfect for mocking dates in tests.
class Logger {
private let storage: LogStorage
private let dateProvider: () -> Date
init(storage: LogStorage = .init(), dateProvider: @escaping () -> Date = Date.init) {
self.storage = storage
self.dateProvider = dateProvider
}
func log(event: Event) {
storage.store(event: event, date: dateProvider())
}
}
📱 Most of my UI testing logic is now categories on XCUIApplication
. Makes the test cases really easy to read:
func testLoggingInAndOut() {
XCTAssertFalse(app.userIsLoggedIn)
app.launch()
app.login()
XCTAssertTrue(app.userIsLoggedIn)
app.logout()
XCTAssertFalse(app.userIsLoggedIn)
}
func testDisplayingCategories() {
XCTAssertFalse(app.isDisplayingCategories)
app.launch()
app.login()
app.goToCategories()
XCTAssertTrue(app.isDisplayingCategories)
}
🙂 It’s a good idea to avoid “default” cases when switching on Swift enums - it’ll “force you” to update your logic when a new case is added:
enum State {
case loggedIn
case loggedOut
case onboarding
}
func handle(_ state: State) {
switch state {
case .loggedIn:
showMainUI()
case .loggedOut:
showLoginUI()
// Compiler error: Switch must be exhaustive
}
}
💂 It's really cool that you can use Swift's 'guard' statement to exit out of pretty much any scope, not only return from functions:
// You can use the 'guard' statement to...
for string in strings {
// ...continue an iteration
guard shouldProcess(string) else {
continue
}
// ...or break it
guard !shouldBreak(for: string) else {
break
}
// ...or return
guard !shouldReturn(for: string) else {
return
}
// ..or throw an error
guard string.isValid else {
throw StringError.invalid(string)
}
// ...or exit the program
guard !shouldExit(for: string) else {
exit(1)
}
}
❤️ Love how you can pass functions & operators as closures in Swift. For example, it makes the syntax for sorting arrays really nice!
let array = [3, 9, 1, 4, 6, 2]
let sorted = array.sorted(by: <)
🗝 Here's a neat little trick I use to get UserDefault key consistency in Swift (#function expands to the property name in getters/setters). Just remember to write a good suite of tests that'll guard you against bugs when changing property names.
extension UserDefaults {
var onboardingCompleted: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
}
📛 Want to use a name already taken by the standard library for a nested type? No problem - just use Swift.
to disambiguate:
extension Command {
enum Error: Swift.Error {
case missing
case invalid(String)
}
}
📦 Playing around with using Wrap to implement Equatable
for any type, primarily for testing:
protocol AutoEquatable: Equatable {}
extension AutoEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
let lhsData = try! wrap(lhs) as Data
let rhsData = try! wrap(rhs) as Data
return lhsData == rhsData
}
}
📏 One thing that I find really useful in Swift is to use typealiases to reduce the length of method signatures in generic types:
public class PathFinder<Object: PathFinderObject> {
public typealias Map = Object.Map
public typealias Node = Map.Node
public typealias Path = PathFinderPath<Object>
public static func possiblePaths(for object: Object, at rootNode: Node, on map: Map) -> Path.Sequence {
return .init(object: object, rootNode: rootNode, map: map)
}
}
📖 You can reference either the external or internal parameter label when writing Swift docs - and they get parsed the same:
// EITHER:
class Foo {
/**
* - parameter string: A string
*/
func bar(with string: String) {}
}
// OR:
class Foo {
/**
* - parameter with: A string
*/
func bar(with string: String) {}
}
👍 Finding more and more uses for auto closures in Swift. Can enable some pretty nice APIs:
extension Dictionary {
mutating func value(for key: Key, orAdd valueClosure: @autoclosure () -> Value) -> Value {
if let value = self[key] {
return value
}
let value = valueClosure()
self[key] = value
return value
}
}
🚀 I’ve started to become a really big fan of nested types in Swift. Love the additional namespacing it gives you!
public struct Map {
public struct Model {
public let size: Size
public let theme: Theme
public var terrain: [Position : Terrain.Model]
public var units: [Position : Unit.Model]
public var buildings: [Position : Building.Model]
}
public enum Direction {
case up
case right
case down
case left
}
public struct Position {
public var x: Int
public var y: Int
}
public enum Size: String {
case small = "S"
case medium = "M"
case large = "L"
case extraLarge = "XL"
}
}
Author: JohnSundell
Source Code: https://github.com/JohnSundell/SwiftTips
License: MIT license
1659283860
ActiveInteraction manages application-specific business logic. It's an implementation of service objects designed to blend seamlessly into Rails.
ActiveInteraction gives you a place to put your business logic. It also helps you write safer code by validating that your inputs conform to your expectations. If ActiveModel deals with your nouns, then ActiveInteraction handles your verbs.
Add it to your Gemfile:
gem 'active_interaction', '~> 5.1'
Or install it manually:
$ gem install active_interaction --version '~> 5.1'
This project uses Semantic Versioning. Check out GitHub releases for a detailed list of changes.
To define an interaction, create a subclass of ActiveInteraction::Base
. Then you need to do two things:
Define your inputs. Use class filter methods to define what you expect your inputs to look like. For instance, if you need a boolean flag for pepperoni, use boolean :pepperoni
. Check out the filters section for all the available options.
Define your business logic. Do this by implementing the #execute
method. Each input you defined will be available as the type you specified. If any of the inputs are invalid, #execute
won't be run. Filters are responsible for checking your inputs. Check out the validations section if you need more than that.
That covers the basics. Let's put it all together into a simple example that squares a number.
require 'active_interaction'
class Square < ActiveInteraction::Base
float :x
def execute
x**2
end
end
Call .run
on your interaction to execute it. You must pass a single hash to .run
. It will return an instance of your interaction. By convention, we call this an outcome. You can use the #valid?
method to ask the outcome if it's valid. If it's invalid, take a look at its errors with #errors
. In either case, the value returned from #execute
will be stored in #result
.
outcome = Square.run(x: 'two point one')
outcome.valid?
# => nil
outcome.errors.messages
# => {:x=>["is not a valid float"]}
outcome = Square.run(x: 2.1)
outcome.valid?
# => true
outcome.result
# => 4.41
You can also use .run!
to execute interactions. It's like .run
but more dangerous. It doesn't return an outcome. If the outcome would be invalid, it will instead raise an error. But if the outcome would be valid, it simply returns the result.
Square.run!(x: 'two point one')
# ActiveInteraction::InvalidInteractionError: X is not a valid float
Square.run!(x: 2.1)
# => 4.41
ActiveInteraction checks your inputs. Often you'll want more than that. For instance, you may want an input to be a string with at least one non-whitespace character. Instead of writing your own validation for that, you can use validations from ActiveModel.
These validations aren't provided by ActiveInteraction. They're from ActiveModel. You can also use any custom validations you wrote yourself in your interactions.
class SayHello < ActiveInteraction::Base
string :name
validates :name,
presence: true
def execute
"Hello, #{name}!"
end
end
When you run this interaction, two things will happen. First ActiveInteraction will check your inputs. Then ActiveModel will validate them. If both of those are happy, it will be executed.
SayHello.run!(name: nil)
# ActiveInteraction::InvalidInteractionError: Name is required
SayHello.run!(name: '')
# ActiveInteraction::InvalidInteractionError: Name can't be blank
SayHello.run!(name: 'Taylor')
# => "Hello, Taylor!"
You can define filters inside an interaction using the appropriate class method. Each method has the same signature:
Some symbolic names. These are the attributes to create.
An optional hash of options. Each filter supports at least these two options:
default
is the fallback value to use if nil
is given. To make a filter optional, set default: nil
.
desc
is a human-readable description of the input. This can be useful for generating documentation. For more information about this, read the descriptions section.
An optional block of sub-filters. Only array and hash filters support this. Other filters will ignore blocks when given to them.
Let's take a look at an example filter. It defines three inputs: x
, y
, and z
. Those inputs are optional and they all share the same description ("an example filter").
array :x, :y, :z,
default: nil,
desc: 'an example filter' do
# Some filters support sub-filters here.
end
In general, filters accept values of the type they correspond to, plus a few alternatives that can be reasonably coerced. Typically the coercions come from Rails, so "1"
can be interpreted as the boolean value true
, the string "1"
, or the number 1
.
In addition to accepting arrays, array inputs will convert ActiveRecord::Relation
s into arrays.
class ArrayInteraction < ActiveInteraction::Base
array :toppings
def execute
toppings.size
end
end
ArrayInteraction.run!(toppings: 'everything')
# ActiveInteraction::InvalidInteractionError: Toppings is not a valid array
ArrayInteraction.run!(toppings: [:cheese, 'pepperoni'])
# => 2
Use a block to constrain the types of elements an array can contain. Note that you can only have one filter inside an array block, and it must not have a name.
array :birthdays do
date
end
For interface
, object
, and record
filters, the name of the array filter will be singularized and used to determine the type of value passed. In the example below, the objects passed would need to be of type Cow
.
array :cows do
object
end
You can override this by passing the necessary information to the inner filter.
array :managers do
object class: People
end
Errors that occur will be indexed based on the Rails configuration setting index_nested_attribute_errors
. You can also manually override this setting with the :index_errors
option. In this state is is possible to get multiple errors from a single filter.
class ArrayInteraction < ActiveInteraction::Base
array :favorite_numbers, index_errors: true do
integer
end
def execute
favorite_numbers
end
end
ArrayInteraction.run(favorite_numbers: [8, 'bazillion']).errors.details
=> {:"favorite_numbers[1]"=>[{:error=>:invalid_type, :type=>"array"}]}
With :index_errors
set to false
the error would have been:
{:favorite_numbers=>[{:error=>:invalid_type, :type=>"array"}]}
Boolean filters convert the strings "1"
, "true"
, and "on"
(case-insensitive) into true
. They also convert "0"
, "false"
, and "off"
into false
. Blank strings will be treated as nil
.
class BooleanInteraction < ActiveInteraction::Base
boolean :kool_aid
def execute
'Oh yeah!' if kool_aid
end
end
BooleanInteraction.run!(kool_aid: 1)
# ActiveInteraction::InvalidInteractionError: Kool aid is not a valid boolean
BooleanInteraction.run!(kool_aid: true)
# => "Oh yeah!"
File filters also accept TempFile
s and anything that responds to #rewind
. That means that you can pass the params
from uploading files via forms in Rails.
class FileInteraction < ActiveInteraction::Base
file :readme
def execute
readme.size
end
end
FileInteraction.run!(readme: 'README.md')
# ActiveInteraction::InvalidInteractionError: Readme is not a valid file
FileInteraction.run!(readme: File.open('README.md'))
# => 21563
Hash filters accept hashes. The expected value types are given by passing a block and nesting other filters. You can have any number of filters inside a hash, including other hashes.
class HashInteraction < ActiveInteraction::Base
hash :preferences do
boolean :newsletter
boolean :sweepstakes
end
def execute
puts 'Thanks for joining the newsletter!' if preferences[:newsletter]
puts 'Good luck in the sweepstakes!' if preferences[:sweepstakes]
end
end
HashInteraction.run!(preferences: 'yes, no')
# ActiveInteraction::InvalidInteractionError: Preferences is not a valid hash
HashInteraction.run!(preferences: { newsletter: true, 'sweepstakes' => false })
# Thanks for joining the newsletter!
# => nil
Setting default hash values can be tricky. The default value has to be either nil
or {}
. Use nil
to make the hash optional. Use {}
if you want to set some defaults for values inside the hash.
hash :optional,
default: nil
# => {:optional=>nil}
hash :with_defaults,
default: {} do
boolean :likes_cookies,
default: true
end
# => {:with_defaults=>{:likes_cookies=>true}}
By default, hashes remove any keys that aren't given as nested filters. To allow all hash keys, set strip: false
. In general we don't recommend doing this, but it's sometimes necessary.
hash :stuff,
strip: false
String filters define inputs that only accept strings.
class StringInteraction < ActiveInteraction::Base
string :name
def execute
"Hello, #{name}!"
end
end
StringInteraction.run!(name: 0xDEADBEEF)
# ActiveInteraction::InvalidInteractionError: Name is not a valid string
StringInteraction.run!(name: 'Taylor')
# => "Hello, Taylor!"
String filter strips leading and trailing whitespace by default. To disable it, set the strip
option to false
.
string :comment,
strip: false
Symbol filters define inputs that accept symbols. Strings will be converted into symbols.
class SymbolInteraction < ActiveInteraction::Base
symbol :method
def execute
method.to_proc
end
end
SymbolInteraction.run!(method: -> {})
# ActiveInteraction::InvalidInteractionError: Method is not a valid symbol
SymbolInteraction.run!(method: :object_id)
# => #<Proc:0x007fdc9ba94118>
Filters that work with dates and times behave similarly. By default, they all convert strings into their expected data types using .parse
. Blank strings will be treated as nil
. If you give the format
option, they will instead convert strings using .strptime
. Note that formats won't work with DateTime
and Time
filters if a time zone is set.
Date
class DateInteraction < ActiveInteraction::Base
date :birthday
def execute
birthday + (18 * 365)
end
end
DateInteraction.run!(birthday: 'yesterday')
# ActiveInteraction::InvalidInteractionError: Birthday is not a valid date
DateInteraction.run!(birthday: Date.new(1989, 9, 1))
# => #<Date: 2007-08-28 ((2454341j,0s,0n),+0s,2299161j)>
date :birthday,
format: '%Y-%m-%d'
DateTime
class DateTimeInteraction < ActiveInteraction::Base
date_time :now
def execute
now.iso8601
end
end
DateTimeInteraction.run!(now: 'now')
# ActiveInteraction::InvalidInteractionError: Now is not a valid date time
DateTimeInteraction.run!(now: DateTime.now)
# => "2015-03-11T11:04:40-05:00"
date_time :start,
format: '%Y-%m-%dT%H:%M:%S'
Time
In addition to converting strings with .parse
(or .strptime
), time filters convert numbers with .at
.
class TimeInteraction < ActiveInteraction::Base
time :epoch
def execute
Time.now - epoch
end
end
TimeInteraction.run!(epoch: 'a long, long time ago')
# ActiveInteraction::InvalidInteractionError: Epoch is not a valid time
TimeInteraction.run!(epoch: Time.new(1970))
# => 1426068362.5136619
time :start,
format: '%Y-%m-%dT%H:%M:%S'
All numeric filters accept numeric input. They will also convert strings using the appropriate method from Kernel
(like .Float
). Blank strings will be treated as nil
.
Decimal
class DecimalInteraction < ActiveInteraction::Base
decimal :price
def execute
price * 1.0825
end
end
DecimalInteraction.run!(price: 'one ninety-nine')
# ActiveInteraction::InvalidInteractionError: Price is not a valid decimal
DecimalInteraction.run!(price: BigDecimal(1.99, 2))
# => #<BigDecimal:7fe792a42028,'0.2165E1',18(45)>
To specify the number of significant digits, use the digits
option.
decimal :dollars,
digits: 2
Float
class FloatInteraction < ActiveInteraction::Base
float :x
def execute
x**2
end
end
FloatInteraction.run!(x: 'two point one')
# ActiveInteraction::InvalidInteractionError: X is not a valid float
FloatInteraction.run!(x: 2.1)
# => 4.41
Integer
class IntegerInteraction < ActiveInteraction::Base
integer :limit
def execute
limit.downto(0).to_a
end
end
IntegerInteraction.run!(limit: 'ten')
# ActiveInteraction::InvalidInteractionError: Limit is not a valid integer
IntegerInteraction.run!(limit: 10)
# => [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
When a String
is passed into an integer
input, the value will be coerced. A default base of 10
is used though it may be overridden with the base
option. If a base of 0
is provided, the coercion will respect radix indicators present in the string.
class IntegerInteraction < ActiveInteraction::Base
integer :limit1
integer :limit2, base: 8
integer :limit3, base: 0
def execute
[limit1, limit2, limit3]
end
end
IntegerInteraction.run!(limit1: 71, limit2: 71, limit3: 71)
# => [71, 71, 71]
IntegerInteraction.run!(limit1: "071", limit2: "071", limit3: "0x71")
# => [71, 57, 113]
IntegerInteraction.run!(limit1: "08", limit2: "08", limit3: "08")
ActiveInteraction::InvalidInteractionError: Limit2 is not a valid integer, Limit3 is not a valid integer
Interface filters allow you to specify an interface that the passed value must meet in order to pass. The name of the interface is used to look for a constant inside the ancestor listing for the passed value. This allows for a variety of checks depending on what's passed. Class instances are checked for an included module or an inherited ancestor class. Classes are checked for an extended module or an inherited ancestor class. Modules are checked for an extended module.
class InterfaceInteraction < ActiveInteraction::Base
interface :exception
def execute
exception
end
end
InterfaceInteraction.run!(exception: Exception)
# ActiveInteraction::InvalidInteractionError: Exception is not a valid interface
InterfaceInteraction.run!(exception: NameError) # a subclass of Exception
# => NameError
You can use :from
to specify a class or module. This would be the equivalent of what's above.
class InterfaceInteraction < ActiveInteraction::Base
interface :error,
from: Exception
def execute
error
end
end
You can also create an anonymous interface on the fly by passing the methods
option.
class InterfaceInteraction < ActiveInteraction::Base
interface :serializer,
methods: %i[dump load]
def execute
input = '{ "is_json" : true }'
object = serializer.load(input)
output = serializer.dump(object)
output
end
end
require 'json'
InterfaceInteraction.run!(serializer: Object.new)
# ActiveInteraction::InvalidInteractionError: Serializer is not a valid interface
InterfaceInteraction.run!(serializer: JSON)
# => "{\"is_json\":true}"
Object filters allow you to require an instance of a particular class or one of its subclasses.
class Cow
def moo
'Moo!'
end
end
class ObjectInteraction < ActiveInteraction::Base
object :cow
def execute
cow.moo
end
end
ObjectInteraction.run!(cow: Object.new)
# ActiveInteraction::InvalidInteractionError: Cow is not a valid object
ObjectInteraction.run!(cow: Cow.new)
# => "Moo!"
The class name is automatically determined by the filter name. If your filter name is different than your class name, use the class
option. It can be either the class, a string, or a symbol.
object :dolly1,
class: Sheep
object :dolly2,
class: 'Sheep'
object :dolly3,
class: :Sheep
If you have value objects or you would like to build one object from another, you can use the converter
option. It is only called if the value provided is not an instance of the class or one of its subclasses. The converter
option accepts a symbol that specifies a class method on the object class or a proc. Both will be passed the value and any errors thrown inside the converter will cause the value to be considered invalid. Any returned value that is not the correct class will also be treated as invalid. Any default
that is not an instance of the class or subclass and is not nil
will also be converted.
class ObjectInteraction < ActiveInteraction::Base
object :ip_address,
class: IPAddr,
converter: :new
def execute
ip_address
end
end
ObjectInteraction.run!(ip_address: '192.168.1.1')
# #<IPAddr: IPv4:192.168.1.1/255.255.255.255>
ObjectInteraction.run!(ip_address: 1)
# ActiveInteraction::InvalidInteractionError: Ip address is not a valid object
Record filters allow you to require an instance of a particular class (or one of its subclasses) or a value that can be used to locate an instance of the object. If the value does not match, it will call find
on the class of the record. This is particularly useful when working with ActiveRecord objects. Like an object filter, the class is derived from the name passed but can be specified with the class
option. Any default
that is not an instance of the class or subclass and is not nil
will also be found. Blank strings passed in will be treated as nil
.
class RecordInteraction < ActiveInteraction::Base
record :encoding
def execute
encoding
end
end
> RecordInteraction.run!(encoding: Encoding::US_ASCII)
=> #<Encoding:US-ASCII>
> RecordInteraction.run!(encoding: 'ascii')
=> #<Encoding:US-ASCII>
A different method can be specified by providing a symbol to the finder
option.
ActiveInteraction plays nicely with Rails. You can use interactions to handle your business logic instead of models or controllers. To see how it all works, let's take a look at a complete example of a controller with the typical resourceful actions.
We recommend putting your interactions in app/interactions
. It's also very helpful to group them by model. That way you can look in app/interactions/accounts
for all the ways you can interact with accounts.
- app/
- controllers/
- accounts_controller.rb
- interactions/
- accounts/
- create_account.rb
- destroy_account.rb
- find_account.rb
- list_accounts.rb
- update_account.rb
- models/
- account.rb
- views/
- account/
- edit.html.erb
- index.html.erb
- new.html.erb
- show.html.erb
# GET /accounts
def index
@accounts = ListAccounts.run!
end
Since we're not passing any inputs to ListAccounts
, it makes sense to use .run!
instead of .run
. If it failed, that would mean we probably messed up writing the interaction.
class ListAccounts < ActiveInteraction::Base
def execute
Account.not_deleted.order(last_name: :asc, first_name: :asc)
end
end
Up next is the show action. For this one we'll define a helper method to handle raising the correct errors. We have to do this because calling .run!
would raise an ActiveInteraction::InvalidInteractionError
instead of an ActiveRecord::RecordNotFound
. That means Rails would render a 500 instead of a 404.
# GET /accounts/:id
def show
@account = find_account!
end
private
def find_account!
outcome = FindAccount.run(params)
if outcome.valid?
outcome.result
else
fail ActiveRecord::RecordNotFound, outcome.errors.full_messages.to_sentence
end
end
This probably looks a little different than you're used to. Rails commonly handles this with a before_filter
that sets the @account
instance variable. Why is all this interaction code better? Two reasons: One, you can reuse the FindAccount
interaction in other places, like your API controller or a Resque task. And two, if you want to change how accounts are found, you only have to change one place.
Inside the interaction, we could use #find
instead of #find_by_id
. That way we wouldn't need the #find_account!
helper method in the controller because the error would bubble all the way up. However, you should try to avoid raising errors from interactions. If you do, you'll have to deal with raised exceptions as well as the validity of the outcome.
class FindAccount < ActiveInteraction::Base
integer :id
def execute
account = Account.not_deleted.find_by_id(id)
if account
account
else
errors.add(:id, 'does not exist')
end
end
end
Note that it's perfectly fine to add errors during execution. Not all errors have to come from checking or validation.
The new action will be a little different than the ones we've looked at so far. Instead of calling .run
or .run!
, it's going to initialize a new interaction. This is possible because interactions behave like ActiveModels.
# GET /accounts/new
def new
@account = CreateAccount.new
end
Since interactions behave like ActiveModels, we can use ActiveModel validations with them. We'll use validations here to make sure that the first and last names are not blank. The validations section goes into more detail about this.
class CreateAccount < ActiveInteraction::Base
string :first_name, :last_name
validates :first_name, :last_name,
presence: true
def to_model
Account.new
end
def execute
account = Account.new(inputs)
unless account.save
errors.merge!(account.errors)
end
account
end
end
We used a couple of advanced features here. The #to_model
method helps determine the correct form to use in the view. Check out the section on forms for more about that. Inside #execute
, we merge errors. This is a convenient way to move errors from one object to another. Read more about it in the errors section.
The create action has a lot in common with the new action. Both of them use the CreateAccount
interaction. And if creating the account fails, this action falls back to rendering the new action.
# POST /accounts
def create
outcome = CreateAccount.run(params.fetch(:account, {}))
if outcome.valid?
redirect_to(outcome.result)
else
@account = outcome
render(:new)
end
end
Note that we have to pass a hash to .run
. Passing nil
is an error.
Since we're using an interaction, we don't need strong parameters. The interaction will ignore any inputs that weren't defined by filters. So you can forget about params.require
and params.permit
because interactions handle that for you.
The destroy action will reuse the #find_account!
helper method we wrote earlier.
# DELETE /accounts/:id
def destroy
DestroyAccount.run!(account: find_account!)
redirect_to(accounts_url)
end
In this simple example, the destroy interaction doesn't do much. It's not clear that you gain anything by putting it in an interaction. But in the future, when you need to do more than account.destroy
, you'll only have to update one spot.
class DestroyAccount < ActiveInteraction::Base
object :account
def execute
account.destroy
end
end
Just like the destroy action, editing uses the #find_account!
helper. Then it creates a new interaction instance to use as a form object.
# GET /accounts/:id/edit
def edit
account = find_account!
@account = UpdateAccount.new(
account: account,
first_name: account.first_name,
last_name: account.last_name)
end
The interaction that updates accounts is more complicated than the others. It requires an account to update, but the other inputs are optional. If they're missing, it'll ignore those attributes. If they're present, it'll update them.
class UpdateAccount < ActiveInteraction::Base
object :account
string :first_name, :last_name,
default: nil
validates :first_name,
presence: true,
unless: -> { first_name.nil? }
validates :last_name,
presence: true,
unless: -> { last_name.nil? }
def execute
account.first_name = first_name if first_name.present?
account.last_name = last_name if last_name.present?
unless account.save
errors.merge!(account.errors)
end
account
end
end
Hopefully you've gotten the hang of this by now. We'll use #find_account!
to get the account. Then we'll build up the inputs for UpdateAccount
. Then we'll run the interaction and either redirect to the updated account or back to the edit page.
# PUT /accounts/:id
def update
inputs = { account: find_account! }.reverse_merge(params[:account])
outcome = UpdateAccount.run(inputs)
if outcome.valid?
redirect_to(outcome.result)
else
@account = outcome
render(:edit)
end
end
ActiveSupport::Callbacks provides a powerful framework for defining callbacks. ActiveInteraction uses that framework to allow hooking into various parts of an interaction's lifecycle.
class Increment < ActiveInteraction::Base
set_callback :filter, :before, -> { puts 'before filter' }
integer :x
set_callback :validate, :after, -> { puts 'after validate' }
validates :x,
numericality: { greater_than_or_equal_to: 0 }
set_callback :execute, :around, lambda { |_interaction, block|
puts '>>>'
block.call
puts '<<<'
}
def execute
puts 'executing'
x + 1
end
end
Increment.run!(x: 1)
# before filter
# after validate
# >>>
# executing
# <<<
# => 2
In order, the available callbacks are filter
, validate
, and execute
. You can set before
, after
, or around
on any of them.
You can run interactions from within other interactions with #compose
. If the interaction is successful, it'll return the result (just like if you had called it with .run!
). If something went wrong, execution will halt immediately and the errors will be moved onto the caller.
class Add < ActiveInteraction::Base
integer :x, :y
def execute
x + y
end
end
class AddThree < ActiveInteraction::Base
integer :x
def execute
compose(Add, x: x, y: 3)
end
end
AddThree.run!(x: 5)
# => 8
To bring in filters from another interaction, use .import_filters
. Combined with inputs
, delegating to another interaction is a piece of cake.
class AddAndDouble < ActiveInteraction::Base
import_filters Add
def execute
compose(Add, inputs) * 2
end
end
Note that errors in composed interactions have a few tricky cases. See the errors section for more information about them.
The default value for an input can take on many different forms. Setting the default to nil
makes the input optional. Setting it to some value makes that the default value for that input. Setting it to a lambda will lazily set the default value for that input. That means the value will be computed when the interaction is run, as opposed to when it is defined.
Lambda defaults are evaluated in the context of the interaction, so you can use the values of other inputs in them.
# This input is optional.
time :a, default: nil
# This input defaults to `Time.at(123)`.
time :b, default: Time.at(123)
# This input lazily defaults to `Time.now`.
time :c, default: -> { Time.now }
# This input defaults to the value of `c` plus 10 seconds.
time :d, default: -> { c + 10 }
Use the desc
option to provide human-readable descriptions of filters. You should prefer these to comments because they can be used to generate documentation. The interaction class has a .filters
method that returns a hash of filters. Each filter has a #desc
method that returns the description.
class Descriptive < ActiveInteraction::Base
string :first_name,
desc: 'your first name'
string :last_name,
desc: 'your last name'
end
Descriptive.filters.each do |name, filter|
puts "#{name}: #{filter.desc}"
end
# first_name: your first name
# last_name: your last name
ActiveInteraction provides detailed errors for easier introspection and testing of errors. Detailed errors improve on regular errors by adding a symbol that represents the type of error that has occurred. Let's look at an example where an item is purchased using a credit card.
class BuyItem < ActiveInteraction::Base
object :credit_card, :item
hash :options do
boolean :gift_wrapped
end
def execute
order = credit_card.purchase(item)
notify(credit_card.account)
order
end
private def notify(account)
# ...
end
end
Having missing or invalid inputs causes the interaction to fail and return errors.
outcome = BuyItem.run(item: 'Thing', options: { gift_wrapped: 'yes' })
outcome.errors.messages
# => {:credit_card=>["is required"], :item=>["is not a valid object"], :"options.gift_wrapped"=>["is not a valid boolean"]}
Determining the type of error based on the string is difficult if not impossible. Calling #details
instead of #messages
on errors
gives you the same list of errors with a testable label representing the error.
outcome.errors.details
# => {:credit_card=>[{:error=>:missing}], :item=>[{:error=>:invalid_type, :type=>"object"}], :"options.gift_wrapped"=>[{:error=>:invalid_type, :type=>"boolean"}]}
Detailed errors can also be manually added during the execute call by passing a symbol to #add
instead of a string.
def execute
errors.add(:monster, :no_passage)
end
ActiveInteraction also supports merging errors. This is useful if you want to delegate validation to some other object. For example, if you have an interaction that updates a record, you might want that record to validate itself. By using the #merge!
helper on errors
, you can do exactly that.
class UpdateThing < ActiveInteraction::Base
object :thing
def execute
unless thing.save
errors.merge!(thing.errors)
end
thing
end
end
When a composed interaction fails, its errors are merged onto the caller. This generally produces good error messages, but there are a few cases to look out for.
class Inner < ActiveInteraction::Base
boolean :x, :y
end
class Outer < ActiveInteraction::Base
string :x
boolean :z, default: nil
def execute
compose(Inner, x: x, y: z)
end
end
outcome = Outer.run(x: 'yes')
outcome.errors.details
# => { :x => [{ :error => :invalid_type, :type => "boolean" }],
# :base => [{ :error => "Y is required" }] }
outcome.errors.full_messages.join(' and ')
# => "X is not a valid boolean and Y is required"
Since both interactions have an input called x
, the inner error for that input is moved to the x
error on the outer interaction. This results in a misleading error that claims the input x
is not a valid boolean even though it's a string on the outer interaction.
Since only the inner interaction has an input called y
, the inner error for that input is moved to the base
error on the outer interaction. This results in a confusing error that claims the input y
is required even though it's not present on the outer interaction.
The outcome returned by .run
can be used in forms as though it were an ActiveModel object. You can also create a form object by calling .new
on the interaction.
Given an application with an Account
model we'll create a new Account
using the CreateAccount
interaction.
# GET /accounts/new
def new
@account = CreateAccount.new
end
# POST /accounts
def create
outcome = CreateAccount.run(params.fetch(:account, {}))
if outcome.valid?
redirect_to(outcome.result)
else
@account = outcome
render(:new)
end
end
The form used to create a new Account
has slightly more information on the form_for
call than you might expect.
<%= form_for @account, as: :account, url: accounts_path do |f| %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<%= f.submit 'Create' %>
<% end %>
This is necessary because we want the form to act like it is creating a new Account
. Defining to_model
on the CreateAccount
interaction tells the form to treat our interaction like an Account
.
class CreateAccount < ActiveInteraction::Base
# ...
def to_model
Account.new
end
end
Now our form_for
call knows how to generate the correct URL and param name (i.e. params[:account]
).
# app/views/accounts/new.html.erb
<%= form_for @account do |f| %>
<%# ... %>
<% end %>
If you have an interaction that updates an Account
, you can define to_model
to return the object you're updating.
class UpdateAccount < ActiveInteraction::Base
# ...
object :account
def to_model
account
end
end
ActiveInteraction also supports formtastic and simple_form. The filters used to define the inputs on your interaction will relay type information to these gems. As a result, form fields will automatically use the appropriate input type.
It can be convenient to apply the same options to a bunch of inputs. One common use case is making many inputs optional. Instead of setting default: nil
on each one of them, you can use with_options
to reduce duplication.
with_options default: nil do
date :birthday
string :name
boolean :wants_cake
end
Optional inputs can be defined by using the :default
option as described in the filters section. Within the interaction, provided and default values are merged to create inputs
. There are times where it is useful to know whether a value was passed to run
or the result of a filter default. In particular, it is useful when nil
is an acceptable value. For example, you may optionally track your users' birthdays. You can use the inputs.given?
predicate to see if an input was even passed to run
. With inputs.given?
you can also check the input of a hash or array filter by passing a series of keys or indexes to check.
class UpdateUser < ActiveInteraction::Base
object :user
date :birthday,
default: nil
def execute
user.birthday = birthday if inputs.given?(:birthday)
errors.merge!(user.errors) unless user.save
user
end
end
Now you have a few options. If you don't want to update their birthday, leave it out of the hash. If you want to remove their birthday, set birthday: nil
. And if you want to update it, pass in the new value as usual.
user = User.find(...)
# Don't update their birthday.
UpdateUser.run!(user: user)
# Remove their birthday.
UpdateUser.run!(user: user, birthday: nil)
# Update their birthday.
UpdateUser.run!(user: user, birthday: Date.new(2000, 1, 2))
ActiveInteraction is i18n aware out of the box! All you have to do is add translations to your project. In Rails, these typically go into config/locales
. For example, let's say that for some reason you want to print everything out backwards. Simply add translations for ActiveInteraction to your hsilgne
locale.
# config/locales/hsilgne.yml
hsilgne:
active_interaction:
types:
array: yarra
boolean: naeloob
date: etad
date_time: emit etad
decimal: lamiced
file: elif
float: taolf
hash: hsah
integer: regetni
interface: ecafretni
object: tcejbo
string: gnirts
symbol: lobmys
time: emit
errors:
messages:
invalid: dilavni si
invalid_type: '%{type} dilav a ton si'
missing: deriuqer si
Then set your locale and run interactions like normal.
class I18nInteraction < ActiveInteraction::Base
string :name
end
I18nInteraction.run(name: false).errors.messages[:name]
# => ["is not a valid string"]
I18n.locale = :hsilgne
I18nInteraction.run(name: false).errors.messages[:name]
# => ["gnirts dilav a ton si"]
Everything else works like an activerecord
entry. For example, to rename an attribute you can use attributes
.
Here we'll rename the num
attribute on an interaction named product
:
en:
active_interaction:
attributes:
product:
num: 'Number'
ActiveInteraction is brought to you by Aaron Lasseigne. Along with Aaron, Taylor Fausak helped create and maintain ActiveInteraction but has since moved on.
If you want to contribute to ActiveInteraction, please read our contribution guidelines. A complete list of contributors is available on GitHub.
ActiveInteraction is licensed under the MIT License.
Author: AaronLasseigne
Source code: https://github.com/AaronLasseigne/active_interaction
License: MIT license
1659511140
:warning: | This gem is now in [passive maintenance mode][passive]. [(more)][passive] |
Making HTML emails comfortable for the Ruby rockstars
Roadie tries to make sending HTML emails a little less painful by inlining stylesheets and rewriting relative URLs for you inside your emails.
Email clients have bad support for stylesheets, and some of them blocks stylesheets from downloading. The easiest way to handle this is to work with inline styles (style="..."
), but that is error prone and hard to work with as you cannot use classes and/or reuse styling over your HTML.
This gem makes this easier by automatically inlining stylesheets into the document. You give Roadie your CSS, or let it find it by itself from the <link>
and <style>
tags in the markup, and it will go through all of the selectors assigning the styles to the matching elements. Careful attention has been put into selectors being applied in the correct order, so it should behave just like in the browser.
"Dynamic" selectors (:hover
, :visited
, :focus
, etc.), or selectors not understood by Nokogiri will be inlined into a single <style>
element for those email clients that support it. This changes specificity a great deal for these rules, so it might not work 100% out of the box. (See more about this below)
Roadie also rewrites all relative URLs in the email to an absolute counterpart, making images you insert and those referenced in your stylesheets work. No more headaches about how to write the stylesheets while still having them work with emails from your acceptance environments. You can disable this on specific elements using a data-roadie-ignore
marker.
!important
styles.style
attribute of tags.:hover
, @media { ... }
and friends around in a separate <style>
element.href
s and img
src
s absolute.data-roadie-ignore
markers before finishing the HTML.Add this gem to your Gemfile as recommended by Rubygems and run bundle install
.
gem 'roadie', '~> 4.0'
Your document instance can be configured with several options:
url_options
- Dictates how absolute URLs should be built.keep_uninlinable_css
- Set to false to skip CSS that cannot be inlined.merge_media_queries
- Set to false to not group media queries. Some users might prefer to not group rules within media queries because it will result in rules getting reordered. e.g.@media(max-width: 600px) { .col-6 { display: block; } }
@media(max-width: 400px) { .col-12 { display: inline-block; } }
@media(max-width: 600px) { .col-12 { display: block; } }
@media(max-width: 600px) { .col-6 { display: block; } .col-12 { display: block; } }
@media(max-width: 400px) { .col-12 { display: inline-block; } }
asset_providers
- A list of asset providers that are invoked when CSS files are referenced. See below.external_asset_providers
- A list of asset providers that are invoked when absolute CSS URLs are referenced. See below.before_transformation
- A callback run before transformation starts.after_transformation
- A callback run after transformation is completed.In order to make URLs absolute you need to first configure the URL options of the document.
html = '... <a href="/about-us">Read more!</a> ...'
document = Roadie::Document.new html
document.url_options = {host: "myapp.com", protocol: "https"}
document.transform
# => "... <a href=\"https://myapp.com/about-us\">Read more!</a> ..."
The following URLs will be rewritten for you:
a[href]
(HTML)img[src]
(HTML)url()
(CSS)You can disable individual elements by adding an data-roadie-ignore
marker on them. CSS will still be inlined on those elements, but URLs will not be rewritten.
<a href="|UNSUBSCRIBE_URL|" data-roadie-ignore>Unsubscribe</a>
By default, style
and link
elements in the email document's head
are processed along with the stylesheets and removed from the head
.
You can set a special data-roadie-ignore
attribute on style
and link
tags that you want to ignore (the attribute will be removed, however). This is the place to put things like :hover
selectors that you want to have for email clients allowing them.
Style and link elements with media="print"
are also ignored.
<head>
<link rel="stylesheet" type="text/css" href="/assets/emails/rock.css"> <!-- Will be inlined with normal providers -->
<link rel="stylesheet" type="text/css" href="http://www.metal.org/metal.css"> <!-- Will be inlined with external providers, *IF* specified; otherwise ignored. -->
<link rel="stylesheet" type="text/css" href="/assets/jazz.css" media="print"> <!-- Will NOT be inlined; print style -->
<link rel="stylesheet" type="text/css" href="/ambient.css" data-roadie-ignore> <!-- Will NOT be inlined; ignored -->
<style></style> <!-- Will be inlined -->
<style data-roadie-ignore></style> <!-- Will NOT be inlined; ignored -->
</head>
Roadie will use the given asset providers to look for the actual CSS that is referenced. If you don't change the default, it will use the Roadie::FilesystemProvider
which looks for stylesheets on the filesystem, relative to the current working directory.
Example:
# /home/user/foo/stylesheets/primary.css
body { color: green; }
# /home/user/foo/script.rb
html = <<-HTML
<html>
<head>
<link rel="stylesheet" type="text/css" href="/stylesheets/primary.css">
</head>
<body>
</body>
</html>
HTML
Dir.pwd # => "/home/user/foo"
document = Roadie::Document.new html
document.transform # =>
# <!DOCTYPE html>
# <html>
# <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>
# <body style="color:green;"></body>
# </html>
If a referenced stylesheet cannot be found, the #transform
method will raise an Roadie::CssNotFound
error. If you instead want to ignore missing stylesheets, you can use the NullProvider
.
You can write your own providers if you need very specific behavior for your app, or you can use the built-in providers. Providers come in two groups: normal and external. Normal providers handle paths without host information (/style/foo.css
) while external providers handle URLs with host information (//example.com/foo.css
, localhost:3001/bar.css
, and so on).
The default configuration is to not have any external providers configured, which will cause those referenced stylesheets to be ignored. Adding one or more providers for external assets causes all of them to be searched and inlined, so if you only want this to happen to specific stylesheets you need to add ignore markers to every other styleshheet (see above).
Included providers:
FilesystemProvider
– Looks for files on the filesystem, relative to the given directory unless otherwise specified.ProviderList
– Wraps a list of other providers and searches them in order. The asset_providers
setting is an instance of this. It behaves a lot like an array, so you can push, pop, shift and unshift to it.NullProvider
– Does not actually provide anything, it always finds empty stylesheets. Use this in tests or if you want to ignore stylesheets that cannot be found by your other providers (or if you want to force the other providers to never run).NetHttpProvider
– Downloads stylesheets using Net::HTTP
. Can be given a whitelist of hosts to download from.CachedProvider
– Wraps another provider (or ProviderList
) and caches responses inside the provided cache store.PathRewriterProvider
– Rewrites the passed path and then passes it on to another provider (or ProviderList
).If you want to search several locations on the filesystem, you can declare that:
document.asset_providers = [
Roadie::FilesystemProvider.new(App.root.join("resources", "stylesheets")),
Roadie::FilesystemProvider.new(App.root.join("system", "uploads", "stylesheets")),
]
NullProvider
If you want to ignore stylesheets that cannot be found instead of crashing, push the NullProvider
to the end:
# Don't crash on missing assets
document.asset_providers << Roadie::NullProvider.new
# Don't download assets in tests
document.external_asset_providers.unshift Roadie::NullProvider.new
Note: This will cause the referenced stylesheet to be removed from the source code, so email client will never see it either.
NetHttpProvider
The NetHttpProvider
will download the URLs that is is given using Ruby's standard Net::HTTP
library.
You can give it a whitelist of hosts that downloads are allowed from:
document.external_asset_providers << Roadie::NetHttpProvider.new(
whitelist: ["myapp.com", "assets.myapp.com", "cdn.cdnnetwork.co.jp"],
)
document.external_asset_providers << Roadie::NetHttpProvider.new # Allows every host
CachedProvider
You might want to cache providers from working several times. If you are sending several emails quickly from the same process, this might also save a lot of time on parsing the stylesheets if you use in-memory storage such as a hash.
You can wrap any other kind of providers with it, even a ProviderList
:
document.external_asset_providers = Roadie::CachedProvider.new(document.external_asset_providers, my_cache)
If you don't pass a cache backend, it will use a normal Hash
. The cache store must follow this protocol:
my_cache["key"] = some_stylesheet_instance # => #<Roadie::Stylesheet instance>
my_cache["key"] # => #<Roadie::Stylesheet instance>
my_cache["missing"] # => nil
Warning: The default Hash
store will never be cleared, so make sure you don't allow the number of unique asset paths to grow too large in a single run. This is especially important if you run Roadie in a daemon that accepts arbritary documents, and/or if you use hash digests in your filenames. Making a new instance of CachedProvider
will use a new Hash
instance.
You can implement your own custom cache store by implementing the []
and []=
methods.
class MyRoadieMemcacheStore
def initialize(memcache)
@memcache = memcache
end
def [](path)
css = memcache.read("assets/#{path}/css")
if css
name = memcache.read("assets/#{path}/name") || "cached #{path}"
Roadie::Stylesheet.new(name, css)
end
end
def []=(path, stylesheet)
memcache.write("assets/#{path}/css", stylesheet.to_s)
memcache.write("assets/#{path}/name", stylesheet.name)
stylesheet # You need to return the set Stylesheet
end
end
document.external_asset_providers = Roadie::CachedProvider.new(
document.external_asset_providers,
MyRoadieMemcacheStore.new(MemcacheClient.instance)
)
If you are using Rspec, you can test your implementation by using the shared examples for the "roadie cache store" role:
require "roadie/rspec"
describe MyRoadieMemcacheStore do
let(:memcache_client) { MemcacheClient.instance }
subject { MyRoadieMemcacheStore.new(memcache_client) }
it_behaves_like "roadie cache store" do
before { memcache_client.clear }
end
end
PathRewriterProvider
With this provider, you can rewrite the paths that are searched in order to more easily support another provider. Examples could include rewriting absolute URLs into something that can be found on the filesystem, or to access internal hosts instead of external ones.
filesystem = Roadie::FilesystemProvider.new("assets")
document.asset_providers << Roadie::PathRewriterProvider.new(filesystem) do |path|
path.sub('stylesheets', 'css').downcase
end
document.external_asset_providers = Roadie::PathRewriterProvider.new(filesystem) do |url|
if url =~ /myapp\.com/
URI.parse(url).path.sub(%r{^/assets}, '')
else
url
end
end
You can also wrap a list, for example to implement external_asset_providers
by composing the normal asset_providers
:
document.external_asset_providers =
Roadie::PathRewriterProvider.new(document.asset_providers) do |url|
URI.parse(url).path
end
Writing your own provider is also easy. You need to provide:
#find_stylesheet(name)
, returning either a Roadie::Stylesheet
or nil
.#find_stylesheet!(name)
, returning either a Roadie::Stylesheet
or raising Roadie::CssNotFound
.class UserAssetsProvider
def initialize(user_collection)
@user_collection = user_collection
end
def find_stylesheet(name)
if name =~ %r{^/users/(\d+)\.css$}
user = @user_collection.find_user($1)
Roadie::Stylesheet.new("user #{user.id} stylesheet", user.stylesheet)
end
end
def find_stylesheet!(name)
find_stylesheet(name) or
raise Roadie::CssNotFound.new(
css_name: name, message: "does not match a user stylesheet", provider: self
)
end
# Instead of implementing #find_stylesheet!, you could also:
# include Roadie::AssetProvider
# That will give you a default implementation without any error message. If
# you have multiple error cases, it's recommended that you implement
# #find_stylesheet! without #find_stylesheet and raise with an explanatory
# error message.
end
# Try to look for a user stylesheet first, then fall back to normal filesystem lookup.
document.asset_providers = [
UserAssetsProvider.new(app),
Roadie::FilesystemProvider.new('./stylesheets'),
]
You can test for compliance by using the built-in RSpec examples:
require 'spec_helper'
require 'roadie/rspec'
describe MyOwnProvider do
# Will use the default `subject` (MyOwnProvider.new)
it_behaves_like "roadie asset provider", valid_name: "found.css", invalid_name: "does_not_exist.css"
# Extra setup just for these tests:
it_behaves_like "roadie asset provider", valid_name: "found.css", invalid_name: "does_not_exist.css" do
subject { MyOwnProvider.new(...) }
before { stub_dependencies }
end
end
Some CSS is impossible to inline properly. :hover
and ::after
comes to mind. Roadie tries its best to keep these around by injecting them inside a new <style>
element in the <head>
(or at the beginning of the partial if transforming a partial document).
The problem here is that Roadie cannot possible adjust the specificity for you, so they will not apply the same way as they did before the styles were inlined.
Another caveat is that a lot of email clients does not support this (which is the entire point of inlining in the first place), so don't put anything important in here. Always handle the case of these selectors not being part of the email.
Inlined styles will have much higher specificity than styles in a <style>
. Here's an example:
<style>p:hover { color: blue; }</style>
<p style="color: green;">Hello world</p>
When hovering over this <p>
, the color will not change as the color: green
rule takes precedence. You can get it to work by adding !important
to the :hover
rule.
It would be foolish to try to automatically inject !important
on every rule automatically, so this is a manual process.
If you'd rather skip this and have the styles not possible to inline disappear, you can turn off this feature by setting the keep_uninlinable_css
option to false.
document.keep_uninlinable_css = false
Callbacks allow you to do custom work on documents before they are transformed. The Nokogiri document tree is passed to the callable along with the Roadie::Document
instance:
class TrackNewsletterLinks
def call(dom, document)
dom.css("a").each { |link| fix_link(link) }
end
def fix_link(link)
divider = (link['href'] =~ /?/ ? '&' : '?')
link['href'] = link['href'] + divider + 'source=newsletter'
end
end
document.before_transformation = ->(dom, document) {
logger.debug "Inlining document with title #{dom.at_css('head > title').try(:text)}"
}
document.after_transformation = TrackNewsletterLinks.new
You can configure the underlying HTML/XML engine to output XHTML or HTML (which is the default). One usecase for this is that {
tokens usually gets escaped to {
, which would be a problem if you then pass the resulting HTML on to some other templating engine that uses those tokens (like Handlebars or Mustache).
document.mode = :xhtml
This will also affect the emitted <!DOCTYPE>
if transforming a full document. Partial documents does not have a <!DOCTYPE>
.
Tested with Github CI using:
Let me know if you want any other runtime supported officially.
This project follows Semantic Versioning and has been since version 1.0.0.
Roadie uses Nokogiri to parse and regenerate the HTML of your email, which means that some unintentional changes might show up.
One example would be that Nokogiri might remove your
s in some cases.
Another example is Nokogiri's lack of HTML5 support, so certain new element might have spaces removed. I recommend you don't use HTML5 in emails anyway because of bad email client support (that includes web mail!).
Roadie uses Nokogiri to parse the HTML of your email, so any C-like problems like segfaults are likely in that end. The best way to fix this is to first upgrade libxml2 on your system and then reinstall Nokogiri. Instructions on how to do this on most platforms, see Nokogiri's official install guide.
@keyframes
?The CSS Parser used in Roadie does not handle keyframes. I don't think any email clients do either, but if you want to keep on trying you can add them manually to a <style>
element (or a separate referenced stylesheet) and tell Roadie not to touch them.
@media
queries are reordered, how can I fix this?Different @media
query blocks with the same conditions are merged by default, which will change the order in some cases. You can disable this by setting merge_media_queries
to false
. (See Install & Usage section above).
<body>
elements that are added?It sounds like you want to transform a partial document. Maybe you are building partials or template fragments to later place in other documents. Use Document#transform_partial
instead of Document#transform
in order to treat the HTML as a partial document.
If you add the data-roadie-ignore
attribute on an element, URL rewriting will not be performed on that element. This could be really useful for you if you intend to send the email through some other rendering pipeline that replaces some placeholders/variables.
<a href="/about-us">About us</a>
<a href="|UNSUBSCRIBE_URL|" data-roadie-ignore>Unsubscribe</a>
Note that this will not skip CSS inlining on the element; it will still get the correct styles applied.
If the URL is invalid on purpose, see Can I skip URL rewriting on a specific element? above. Otherwise, you can try to parse it yourself using Ruby's URI
class and see if you can figure it out.
require "uri"
URI.parse("https://example.com/best image.jpg") # raises
URI.parse("https://example.com/best%20image.jpg") # Works!
bundle install
rake
Roadie is set up with the assumption that all CSS and HTML passing through it is under your control. It is not recommended to run arbritary HTML with the default settings.
Care has been given to try to secure all file system accesses, but it is never guaranteed that someone cannot access something they should not be able to access.
In order to secure Roadie against file system access, only use your own asset providers that you yourself can secure against your particular environment.
If you have found any security vulnerability, please email me at magnus.bergmark+security@gmail.com
to disclose it. For very sensitive issues, please use my public GPG key. You can also encrypt your message with my public key and open an issue if you do not want to email me directly. Thank you.
This gem was previously tied to Rails. It is now framework-agnostic and supports any type of HTML documents. If you want to use it with Rails, check out roadie-rails.
Major contributors to Roadie:
You can see all contributors on GitHub.
(The MIT License)
Copyright (c) 2009-2022 Magnus Bergmark, Jim Neath / Purify, and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Author: Mange
Source code: https://github.com/Mange/roadie
License: MIT license
1669099573
In this article, we will know what is face recognition and how is different from face detection. We will go briefly over the theory of face recognition and then jump on to the coding section. At the end of this article, you will be able to make a face recognition program for recognizing faces in images as well as on a live webcam feed.
In computer vision, one essential problem we are trying to figure out is to automatically detect objects in an image without human intervention. Face detection can be thought of as such a problem where we detect human faces in an image. There may be slight differences in the faces of humans but overall, it is safe to say that there are certain features that are associated with all the human faces. There are various face detection algorithms but Viola-Jones Algorithm is one of the oldest methods that is also used today and we will use the same later in the article. You can go through the Viola-Jones Algorithm after completing this article as I’ll link it at the end of this article.
Face detection is usually the first step towards many face-related technologies, such as face recognition or verification. However, face detection can have very useful applications. The most successful application of face detection would probably be photo taking. When you take a photo of your friends, the face detection algorithm built into your digital camera detects where the faces are and adjusts the focus accordingly.
For a tutorial on Real-Time Face detection
Now that we are successful in making such algorithms that can detect faces, can we also recognise whose faces are they?
Face recognition is a method of identifying or verifying the identity of an individual using their face. There are various algorithms that can do face recognition but their accuracy might vary. Here I am going to describe how we do face recognition using deep learning.
So now let us understand how we recognise faces using deep learning. We make use of face embedding in which each face is converted into a vector and this technique is called deep metric learning. Let me further divide this process into three simple steps for easy understanding:
Face Detection: The very first task we perform is detecting faces in the image or video stream. Now that we know the exact location/coordinates of face, we extract this face for further processing ahead.
Feature Extraction: Now that we have cropped the face out of the image, we extract features from it. Here we are going to use face embeddings to extract the features out of the face. A neural network takes an image of the person’s face as input and outputs a vector which represents the most important features of a face. In machine learning, this vector is called embedding and thus we call this vector as face embedding. Now how does this help in recognizing faces of different persons?
While training the neural network, the network learns to output similar vectors for faces that look similar. For example, if I have multiple images of faces within different timespan, of course, some of the features of my face might change but not up to much extent. So in this case the vectors associated with the faces are similar or in short, they are very close in the vector space. Take a look at the below diagram for a rough idea:
Now after training the network, the network learns to output vectors that are closer to each other(similar) for faces of the same person(looking similar). The above vectors now transform into:
We are not going to train such a network here as it takes a significant amount of data and computation power to train such networks. We will use a pre-trained network trained by Davis King on a dataset of ~3 million images. The network outputs a vector of 128 numbers which represent the most important features of a face.
Now that we know how this network works, let us see how we use this network on our own data. We pass all the images in our data to this pre-trained network to get the respective embeddings and save these embeddings in a file for the next step.
Comparing faces: Now that we have face embeddings for every face in our data saved in a file, the next step is to recognise a new t image that is not in our data. So the first step is to compute the face embedding for the image using the same network we used above and then compare this embedding with the rest of the embeddings we have. We recognise the face if the generated embedding is closer or similar to any other embedding as shown below:
So we passed two images, one of the images is of Vladimir Putin and other of George W. Bush. In our example above, we did not save the embeddings for Putin but we saved the embeddings of Bush. Thus when we compared the two new embeddings with the existing ones, the vector for Bush is closer to the other face embeddings of Bush whereas the face embeddings of Putin are not closer to any other embedding and thus the program cannot recognise him.
In the field of Artificial Intelligence, Computer Vision is one of the most interesting and Challenging tasks. Computer Vision acts like a bridge between Computer Software and visualizations around us. It allows computer software to understand and learn about the visualizations in the surroundings. For Example: Based on the color, shape and size determining the fruit. This task can be very easy for the human brain however in the Computer Vision pipeline, first we gather the data, then we perform the data processing activities and then we train and teach the model to understand how to distinguish between the fruits based on size, shape and color of fruit.
Currently, various packages are present to perform machine learning, deep learning and computer vision tasks. By far, computer vision is the best module for such complex activities. OpenCV is an open-source library. It is supported by various programming languages such as R, Python. It runs on most of the platforms such as Windows, Linux and MacOS.
To know more about how face recognition works on opencv, check out the free course on face recognition in opencv.
Advantages of OpenCV:
Installation:
Here we will be focusing on installing OpenCV for python only. We can install OpenCV using pip or conda(for anaconda environment).
Using pip, the installation process of openCV can be done by using the following command in the command prompt.
pip install opencv-python
If you are using anaconda environment, either you can execute the above code in anaconda prompt or you can execute the following code in anaconda prompt.
conda install -c conda-forge opencv
In this section, we shall implement face recognition using OpenCV and Python. First, let us see the libraries we will need and how to install them:
OpenCV is an image and video processing library and is used for image and video analysis, like facial detection, license plate reading, photo editing, advanced robotic vision, optical character recognition, and a whole lot more.
The dlib library, maintained by Davis King, contains our implementation of “deep metric learning” which is used to construct our face embeddings used for the actual recognition process.
The face_recognition library, created by Adam Geitgey, wraps around dlib’s facial recognition functionality, and this library is super easy to work with and we will be using this in our code. Remember to install dlib library first before you install face_recognition.
To install OpenCV, type in command prompt
pip install opencv-python |
I have tried various ways to install dlib on Windows but the easiest of all of them is via Anaconda. First, install Anaconda (here is a guide to install it) and then use this command in your command prompt:
conda install -c conda-forge dlib |
Next to install face_recognition, type in command prompt
pip install face_recognition |
Now that we have all the dependencies installed, let us start coding. We will have to create three files, one will take our dataset and extract face embedding for each face using dlib. Next, we will save these embedding in a file.
In the next file we will compare the faces with the existing the recognise faces in images and next we will do the same but recognise faces in live webcam feed
First, you need to get a dataset or even create one of you own. Just make sure to arrange all images in folders with each folder containing images of just one person.
Next, save the dataset in a folder the same as you are going to make the file. Now here is the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Now that we have stored the embedding in a file named “face_enc”, we can use them to recognise faces in images or live video stream.
Here is the script to recognise faces on a live webcam feed:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
|
https://www.youtube.com/watch?v=fLnGdkZxRkg
Although in the example above we have used haar cascade to detect faces, you can also use face_recognition.face_locations to detect a face as we did in the previous script
The script for detecting and recognising faces in images is almost similar to what you saw above. Try it yourself and if you can’t take a look at the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
|
Output:
InputOutput
This brings us to the end of this article where we learned about face recognition.
You can also upskill with Great Learning’s PGP Artificial Intelligence and Machine Learning Course. The course offers mentorship from industry leaders, and you will also have the opportunity to work on real-time industry-relevant projects.
Original article source at: https://www.mygreatlearning.com