How to provide default values for parameters How to handle errors in functions
  • July 29, 2025

1. Providing Default Values for Function Parameters (Deep Dive)

1.1 Syntax and Ordering

  • Declaration
    You assign a default right in the parameter list:

    func foo(a: Int = 1, b: String = "hi") { … }
    
  • Ordering rules
    • All parameters with defaults can be mixed with non-defaulted parameters—but any non-defaulted parameter must come before any call site that omits it.
    • In practice, it’s clearest to group required parameters first, then defaulted ones:
      func connect(to host: String, port: Int = 80, useSSL: Bool = false) { … }
      

1.2 How It Works at the Call Site

  • When you omit an argument, the compiler rewrites your call to insert the default expression.
    connect(to: "example.com")  
    // is treated as:
    connect(to: "example.com", port: 80, useSSL: false)
    
  • Because defaults are compile-time constants or expressions, you can’t change them at runtime—clients never see a “nil” placeholder.

1.3 Default Values as Expressions

Defaults aren’t limited to literals; any expression that’s valid in that scope is allowed:

let globalTimeout = 30

func fetchData(endpoint: String,
               timeout: TimeInterval = TimeInterval(globalTimeout),
               cache: Bool = true)
{
    // …
}

or even calling other functions:

func now() -> Date { Date() }

func log(message: String, at timestamp: Date = now()) {
    print("[\(timestamp)]", message)
}

1.4 Interplay with Parameter Labels

Swift distinguishes between the external label (used by callers) and the internal name (used in the function body):

func move(from start: Point, to end: Point = .zero) { … }

// Callers write:
move(from: p1)            // second uses default .zero
move(from: p1, to: p2)

You can even omit the external label (_) while still providing a default:

func increment(_ value: Int = 1) -> Int {
    return base + value
}
increment()   // adds 1
increment(5)  // adds 5

1.5 Variadic Parameters with Defaults

You can combine variadics and defaults—though variadics always subsume any values provided:

func sum(_ values: Int..., initial: Int = 0) -> Int {
    return values.reduce(initial, +)
}

sum()             // returns 0
sum(1, 2, 3)      // returns 6
sum(4, 5, 6, initial: 10)  // returns 25

2. Handling Errors in Functions (Deep Dive)

Swift’s rich error‐handling model gives you fine-grained control over failure paths, cleanup, and propagation.

2.1 Defining Error Types

  • enums conforming to Error are the standard way:
    enum DataError: Error {
      case notFound(path: String)
      case invalidFormat(reason: String)
      case permissionDenied
    }
    
  • You can attach associated values to carry context.

2.2 Writing Throwing Functions

  • Mark with throws and use throw anywhere inside:
    func loadJSON(from path: String) throws -> [String: Any] {
      guard FileManager.default.fileExists(atPath: path) else {
        throw DataError.notFound(path: path)
      }
      // … attempt to read & parse …
      guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
        throw DataError.invalidFormat(reason: "Unreadable data")
      }
      // parse JSON…
      return jsonObject
    }
    
  • rethrows:
    Use when your function only throws if one of its function-typed parameters throws:

    func perform<T>(_ work: () throws -> T) rethrows -> T {
      // If `work` throws, this function will too.
      return try work()
    }
    

2.3 Propagating Errors

  • Within a throwing function, prefix calls to other throwing functions with try. Omitting try is a compile-error.
  • You can mix try? or try! to convert or force-unwrap if needed, but most of the time you’ll use plain try to let errors bubble up.

2.4 Catching Errors: do-catch

do {
  let json = try loadJSON(from: "config.json")
  print("Loaded config:", json)
} catch DataError.notFound(let path) {
  print("File not found at \(path)")
} catch DataError.invalidFormat(let reason) {
  print("Bad format:", reason)
} catch {
  print("Unexpected error:", error)
}
  • Pattern matching in catch lets you extract associated values.
  • A final catch without a pattern catches any error.

2.5 Cleanup with defer

If you need guaranteed cleanup—regardless of success or failure—use defer:

func processFile(_ path: String) throws {
  let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
  defer {
    handle.closeFile()
    print("File closed")
  }

  // If any throw happens below, the defer block still runs
  let data = try handle.readToEnd() ?? Data()
  // …
}
  • You can have multiple defer blocks; they execute in reverse order of appearance.

2.6 Shorthand Forms: try? and try!

  • try?
    Wraps the call in an optional, returning nil on error:

    let maybeJSON = try? loadJSON(from: "config.json")
    // maybeJSON is [String: Any]?; error is swallowed
    
  • try!
    Force‐unwrap: if an error is thrown, your program crashes:

    let guaranteedJSON = try! loadJSON(from: "default.json")
    // Only use if you're 100% sure it can't fail
    

2.7 Bridging to NSError (Objective-C Interop)

When calling Swift throwing functions from Obj-C (or Cocoa APIs expecting NSError**):

@objc func objcLoadJSON(from path: String, error: NSErrorPointer) -> [String: Any]? {
  do {
    return try loadJSON(from: path)
  } catch let err as NSError {
    error?.pointee = err
    return nil
  }
}

2.8 Rethrowing with Context

You can catch an error, wrap or annotate it, then rethrow:

func enhancedLoad(path: String) throws -> [String: Any] {
  do {
    return try loadJSON(from: path)
  } catch {
    throw DataError.invalidFormat(reason: "Failed at path \(path): \(error)")
  }
}

This lets you add context while preserving the error-handling flow.


 

YOU MIGHT ALSO LIKE...
How to take action when a property changes

1. Taking Action When a Property Changes: Property Observers Swift lets you observe and respond to changes in a property’s ...

How to create your own structs? How to compute property values dynamically?

1. Creating Your Own Structs In Swift, a struct is a value type that you define with the struct keyword. ...

How to use trailing closures and shorthand syntax?

1. Trailing Closure Syntax When the last parameter to a function is a closure, you can write that closure after ...

How to create and use closures?

1. What Is a Closure (and Why Swift Loves Them) A closure in Swift is a self-contained block of functionality ...

exyte

Concentric Onboarding iOS library for a walkthrough or onboarding flow with tap actions written with SwiftUI         Usage Create View's ...