10 Underrated Swift Features You Probably Aren’t Using (But Should)
Swift is packed with cool tricks that often go unnoticed. While everyone obsesses over async/await and SwiftUI, there are plenty of low-key features that can make your life easier if only more people knew about them. So, let’s talk about those. 1. some Keyword for Type Opaqueness You’ve probably used Any to deal with unknown types. But have you used some? It lets you return an opaque type without exposing unnecessary details. Less clutter, more clarity. Example: func makeButton() -> some View { Button("Tap Me") { print("Button tapped!") } } This matters because without some, generics can turn into a tangled mess really fast. 2. @autoclosure for Cleaner Lazy Evaluations Instead of wrapping expressions inside {} for no reason, let @autoclosure handle it for you. Example: func logIfNeeded(_ message: @autoclosure () -> String) { #if DEBUG print(message()) #endif } This way, you can just pass "Some log message" without worrying about closures. 3. @discardableResult to Avoid Unused Result Warnings Sometimes, you call a function for its side effects and don’t care about the return value. But Swift does. Example: @discardableResult func performTask() -> Bool { return true } Now you won’t see warnings for ignoring the return value. Simple fix. 4. @inline(__always) for Performance Boost If you care about performance, this forces inlining, even when the compiler doesn’t think it’s necessary. Example: @inline(__always) func fastFunction() { print("This will always be inlined!") } It’s a small optimization, but for high-performance apps, small tweaks add up. 5. Conditional Conformance for Generics Swift allows types to conform to protocols only when certain conditions are met. Example: extension Array: Equatable where Element: Equatable {} Now, arrays of Equatable elements can be compared directly. Before this, you had to manually implement Equatable. Not fun. 6. withoutActuallyEscaping(_:) for Non-Escaping Closures Got a non-escaping closure but need to pass it somewhere that expects an escaping one? Swift has a trick for that. Example: func execute(_ closure: (T) -> Void, with value: T) { withoutActuallyEscaping(closure) { escapable in escapable(value) } } This keeps things type-safe without the usual @escaping gymnastics. 7. defer for Guaranteed Cleanup You’ve probably seen finally in other languages. Swift’s defer is even better—it always runs, no matter how the function exits. Example: func fetchData() { defer { print("Cleanup executed") } print("Fetching data...") } No matter what happens, cleanup is handled. No surprises. 8. ExpressibleBy*Literal for Custom Types Ever wanted to assign literals to your custom structs? You can. Example: struct Temperature: ExpressibleByFloatLiteral { var value: Double init(floatLiteral value: Double) { self.value = value } } let temp: Temperature = 36.6 // Works because of ExpressibleByFloatLiteral Now you don’t have to write Temperature(value: 36.6). Just use 36.6. Neat, right? 9. mutating func in Structs Structs are immutable by default. But what if you need to modify them? mutating lets you do it safely. Example: struct Counter { var count = 0 mutating func increment() { count += 1 } } Still keeps your code clean and predictable while allowing changes where necessary. 10. lazy Properties for Efficient Initialization Why initialize a heavy object if you might not even use it? lazy postpones initialization until it’s actually needed. Example: class ExpensiveObject { init() { print("Expensive Object Created") } } class MyClass { lazy var expensiveInstance = ExpensiveObject() } let obj = MyClass() // Nothing happens yet obj.expensiveInstance // Now the instance is created This avoids unnecessary resource allocation. Especially useful in UI-heavy apps. The best part is that most of them require just one extra keyword to start using.
Swift is packed with cool tricks that often go unnoticed. While everyone obsesses over async/await
and SwiftUI, there are plenty of low-key features that can make your life easier if only more people knew about them. So, let’s talk about those.
1. some
Keyword for Type Opaqueness
You’ve probably used Any
to deal with unknown types. But have you used some
? It lets you return an opaque type without exposing unnecessary details. Less clutter, more clarity.
Example:
func makeButton() -> some View {
Button("Tap Me") {
print("Button tapped!")
}
}
This matters because without some
, generics can turn into a tangled mess really fast.
2. @autoclosure
for Cleaner Lazy Evaluations
Instead of wrapping expressions inside {}
for no reason, let @autoclosure
handle it for you.
Example:
func logIfNeeded(_ message: @autoclosure () -> String) {
#if DEBUG
print(message())
#endif
}
This way, you can just pass "Some log message"
without worrying about closures.
3. @discardableResult
to Avoid Unused Result Warnings
Sometimes, you call a function for its side effects and don’t care about the return value. But Swift does.
Example:
@discardableResult
func performTask() -> Bool {
return true
}
Now you won’t see warnings for ignoring the return value. Simple fix.
4. @inline(__always)
for Performance Boost
If you care about performance, this forces inlining, even when the compiler doesn’t think it’s necessary.
Example:
@inline(__always)
func fastFunction() {
print("This will always be inlined!")
}
It’s a small optimization, but for high-performance apps, small tweaks add up.
5. Conditional Conformance for Generics
Swift allows types to conform to protocols only when certain conditions are met.
Example:
extension Array: Equatable where Element: Equatable {}
Now, arrays of Equatable
elements can be compared directly. Before this, you had to manually implement Equatable
. Not fun.
6. withoutActuallyEscaping(_:)
for Non-Escaping Closures
Got a non-escaping closure but need to pass it somewhere that expects an escaping one? Swift has a trick for that.
Example:
func execute<T>(_ closure: (T) -> Void, with value: T) {
withoutActuallyEscaping(closure) { escapable in
escapable(value)
}
}
This keeps things type-safe without the usual @escaping
gymnastics.
7. defer
for Guaranteed Cleanup
You’ve probably seen finally
in other languages. Swift’s defer
is even better—it always runs, no matter how the function exits.
Example:
func fetchData() {
defer { print("Cleanup executed") }
print("Fetching data...")
}
No matter what happens, cleanup is handled. No surprises.
8. ExpressibleBy*Literal
for Custom Types
Ever wanted to assign literals to your custom structs? You can.
Example:
struct Temperature: ExpressibleByFloatLiteral {
var value: Double
init(floatLiteral value: Double) {
self.value = value
}
}
let temp: Temperature = 36.6 // Works because of ExpressibleByFloatLiteral
Now you don’t have to write Temperature(value: 36.6)
. Just use 36.6
. Neat, right?
9. mutating func
in Structs
Structs are immutable by default. But what if you need to modify them? mutating
lets you do it safely.
Example:
struct Counter {
var count = 0
mutating func increment() {
count += 1
}
}
Still keeps your code clean and predictable while allowing changes where necessary.
10. lazy
Properties for Efficient Initialization
Why initialize a heavy object if you might not even use it? lazy
postpones initialization until it’s actually needed.
Example:
class ExpensiveObject {
init() { print("Expensive Object Created") }
}
class MyClass {
lazy var expensiveInstance = ExpensiveObject()
}
let obj = MyClass() // Nothing happens yet
obj.expensiveInstance // Now the instance is created
This avoids unnecessary resource allocation. Especially useful in UI-heavy apps.
The best part is that most of them require just one extra keyword to start using.