How to Use Attributes of Swift

The attributes of Swift is a powerful tool that helps us to design more reliable, concise, and flexible code.
In addition of @discardableResult, @escaping, @autoclosure and so on that we usually use, Swift has many another attributes that we can use to write some ingenious usages.

Firstly, I list all attributes of Swift below

Name Modifier Scope
available declaration of types, properties, or methods
discardableResult declaration of methods
dynamicCallable declaration of types
dynamicMemberLookUp declaration of types
frozen declaration of structures, or enumerations
BKInspectable
inlinable function, computed property, subscript, initializer, or deinitializer
main declaration of structures, classes, or enumerations
nonobjc method, property, initializer
NSApplicationMain same as main
NSCopying stored variable properties of classes
NSManaged
objc
objcMembers
propertyWrapper
resultBuilder
requires_stored_property_inits declaration of classes
testable
UIApplicationMain same as main
usableFromInline same as inlinable
warn_unqualified_access
IBAction
IBSegueAction
IBOutlet
IBDesignable
IBInspectable
autoclosure
convention
escaping
unknown
_marker Used to mark a marker-protocol.
globalActor Used to custom a global actor.

available

  1. The availability limitation for platforms and versions.
1
@available(iOS 11.0, macOS 11.0, *)
  1. The availability limitation for Swift version.
1
@available(swift 5.0)
  1. Indicates the type or function can’t be used in current context.
1
2
3
4
5
@available(
*,
unavailable,
renamed: "renamedFunctionName",
message: "This method was already obsoleted.")
  1. Warns developers that the type or function will be deprecated.
1
2
3
4
5
6
7
@available(
macOS,
introduced: 8.0,
deprecated: 9.0,
obsoleted: 11.0,
renamed: "function4",
message: "rename the function3 to function4.")

discardableResult

1
2
3
4
@discardableResult
func requestUserInfo() -> Cancelable { ... }

requestUserInfo() // No warning.

dynamicCallable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@dynamicCallable
struct Storage {
func dynamicallyCall(withArguments keys: [Int]) -> [Int] { .. }
func dynamicallyCall(withArguments keys: [String]) -> [String] { ... }
func dynamicallyCall(withArguments keys: [Double]) { ... }

func dynamicallyCall(withKeywordArguments pairs: KeyValuePairs<String, Int>) { ... }
func dynamicallyCall(withKeywordArguments pairs: [String: String]) -> [String] { ... }
}

let storage = Storage()

let intArr = storage(1, 2, 3)
let stringArr = storage("123", "234")

storage(name: 100, age: 200, weight: 300)
  1. The struct, class, enumeration or protocol marked with @dynamicCallable must have either the function dynamicallyCall(withArguments:) or func dynamicallyCall(withKeywordArguments:).
  2. The arguments of the function dynamicallyCall(withArguments:) is conforming to ExpressibleByArrayLiteral.
  3. The keyword arguments of the function dynamicallyCall(withKeywordArguments:) is conforming to ExpressibleByDictionaryLiteral and its Key must be ExpressibleByStringLiteral conformance.

Except dynamicCallable, Swift provides another way to make a value can be called directly. The way is to write a method callAsFunction for your type.

1
2
3
4
5
6
7
8
9
struct Storage {
func callAsFunction() {...}

func callAsFunction(key: String) {...}
}

let storage = Storage()
storage()
storage(key: "name")

Mind you, if you implement @dynamicCallable and callAsFunction at the same time, the callAsFunction will override the functionality of @dynamicCallable(will be ignored).

dynamicMemberLookup

dynamicMemberLookup attribute requires the struct, class, enum, or protocol to have a subscript(dynamicMember:) method that accepts either ExpressibleByStringLiteral or a key path.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@dynamicMemberLookup
struct Storage {
var database: [String: Any] = [:]
subscript(dynamicMember member: String) -> Any? {
get {
database[member]
}
set {
database[member] = newValue
}
}
}

var storage = Storage()
storage.age = 18
print(storage.age) // 18

The argument member of the method must be ExpressibleByStringLiteral conformance, usually be String.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@dynamicMemberLookup
struct ValueWrapper<Wrapped> {
let wrappedValue: Wrapped

subscript<T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T {
get {
wrappedValue[keyPath: keyPath]
}
}
}

struct Person {
let name: String
var age: Int
}


let me = Person(name: "csl", age: 18)
let wrapper = ValueWrapper(wrappedValue: me)

print(wrapper.csl) // csl
print(wrapper.age) // 18
print(wrapper.weight) // Error: value of type `Person` has no member weight.

Using key path (supports KeyPath, WriteableKeyPath, or ReferenceWritableKeyPath) as the dynamicMember’s type can support compile-time type checking.

frozen

Restricts modifiability of structures or enumerations in future versions to boost performance.

In library evolution mode, apply this attribute to structures or enumerations makes them cannot add, remove, or reorder enumeration cases or stored properties in future versions.

Frozen types, the types of the stored properties of frozen structures, and the associated values of frozen enumeration cases must be public or marked with the usableFromInline attribute.

Detail in documents

GKInspectable

Apply this attribute to expose a custom GameplayKit component property to the SpriteKit editor UI. Applying this attribute also implies the objc attribute

inlinable & usableFromInline

Applying these two attributes to a function, method, computed property, subscript, initializer or deinitializer will expose their detailed implementation when being called as APIs. The caller will copy the implementation code directly to the calling context.

inlinable is used for public targets. usableFromInline is use for internal targets.
These modifiers can’t decorate private or fileprivate targets.

main, NSApplication, and UIApplication

Apply this attribute to a structure, class, or enumeration declaration to indicate that it contains the top-level entry point for program flow.

The type marked as @main must have a type method main without any arguments or return values.

1
2
3
4
5
6
@main
struct MyApp {
static func main() {
// Initializes your application.
}
}

The UIApplication is used for iOS projects and the NSApplication is used for macOS projects.
If you want to make an executable, you must provides at least one top-level entry point, as discussed in Top-Level Code

nonobjc

Apply this attribute to a method, property, or initializer declaration to suppress an implicit objc attribute. The nonobjc attribute tells the compiler to make the declaration unavailable in Objective-C language code.

NSCopying

Applying this attribute to a stored variable property of a class causes the property’s setter to be synthesized with a copy of the property’s value-returned by the copyWithZone(_:) method-instead of the value of the property itself.

The type of the property must conform to NSCopying.

NSManaged

Used in Core Data.

objc

Apply this attribute to any declaration that can be represented in Objective-C code-for examples

  1. nonnested classes
  2. protocols
  3. nongeneric enumerations(constrained to integer raw-value types)
  4. properties and methods(including getters and setters) of classes
  5. protocols and optional members fo a protocol
  6. initializers
  7. subscripts(including getters and setters)

Applying this attribute to an extension has the same effect as applying it to every member of that extension that isn’t explicitly marked with nonobjc attribute.

Applying the attribute with one argument to the declarations listed above can rename them for Objective-C.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@objc class Storage: NSObject {
var database: [String: Any] = [:]

@objc subscript(dynamicMember member: String) -> Any? {
@objc(query:)
get {
database[member]
}
@objc(saveValue:key:)
set {
database[member] = newValue
}
}
}

objcMembers

Apply the attribute to a class declaration, to implicitly apply the objc to all Objective-C compatible members of the class, its extensions, its subclasses, and all of the extensions of its subclasses.

Recommend to use objc to replace of objcMembers to decrease the binary size of you product and boost performance.

propertyWrapper

Detail in Learning Swift Again

resultBuilder

Detail in Swift5.4 New Feature ResultBuilder

requires_stored_property_inits

Apply this attribute to a class declaration to require all its stored properties within the class to provide a default value as part of their definition.

testable

Used in import declaration. All internal entities within the module imported with @testable will be treated as public in unit test.
All classes and class members of internal or public will be treated as open in unit test.

warn_unqualified_access

Used in declarations of top-level functions, instance methods, or class or static methods.
Displaying a warning when you access a method marked with @warn_unqualified_access attribute without a preceding qualifier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func test() {
print("--------")
}

enum MyNameSpace {
@warn_unqualified_access
static func test() {
print("********")
}

static func run() {
test() // Warning: Use of 'test' treated as a reference to static method in enum 'MyNameSpace'
self.test() // OK.
MyNameSpace.test() // OK.
}
}

autoclosure

Used in argument declarations of methods. The commonest usage is to delay the passed value’s evolution when being called.

1
2
3
4
5
6
7
8
9
10
func calculateComplexValue() -> Int {
print("a time-consuming calculation.")
return 999
}

func execute(cache: Int?, value: @autoclosure () -> Int) {
let _ = cache ?? value()
}

execute(cache: 100, value: calculateComplexValue()) // Doesn't all the `calculateComplexValue` function.

convention

Apply this attribute to decorate the type of a block. Then we can pass these decorated blocks to the arguments of C-pointer function or Objective-C block type when calling a method.

  • @convention(c): Defines a block as a C-pointer function.
1
2
3
4
5
6
7
8
9
10
CGFloat myCFunction(CGFloat (callback)(CGFloat x, CGFloat y)) {
return callback(1.1, 2.2);
}

let swiftCallback : @convention(c) (CGFloat, CGFloat) -> CGFloat = {
(x, y) -> CGFloat in
return x + y
}
let result = myCFunction( swiftCallback )
print(result) // 3.3
  • @convention(oc): Defines a block as a Objective-C block.
1
2
3
4
5
6
7
8
9
let animationsBlock : @convention(block) () -> () = {
NSLog("start")
}
let completionBlock : @convention(block) (Bool) -> () = {
(Bool completion) in
NSLog("start")
}
UIView.animateWithDuration(2, animations: animationsBlock, completion: completionBlock)

  • @convention(swift): Defines a pure Swift block.

escaping

Applying this attribute to a block argument of a function or method indicates the block argument might be called outside of the function’s scope.

unknown

Apply this attribute to the default case of a enumeration switching. If the switching doesn’t match all cases of the enumerations, the compiler will throw an warning message(just a pure default case will match all unmatched cases without any reminder. if the unfrozen enumeration adds some new cases, we might miss processing these cases).