В UIKit есть несколько старых добрых API-интерфейсов на основе завершения, например вот этот:
func open(
_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:],
completionHandler completion: (@MainActor @Sendable (Bool) -> Void)? = nil
)
Но также, с появлением Swift Structured Concurrency, они добавили async
версии этих же методов:
func open(
_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:]
) async -> Bool
Об этом преобразовании существует полная документация, и в ней говорится, что привязки async/await для таких методов генерируются автоматически по фиксированному определенному алгоритму.
Но меня интересует следующее - как именно происходит преобразование?
Я предполагаю, что он использует один из двух возможных подходов:
Итак, какой из них они используют, как они это делают?
По сути, он просто использует продолжение, точно так же, как вы соединяете API-интерфейсы обработчика завершения с Swift Concurrency с помощью withXXXContinuation { ... }
. Сгенерированный SIL мало чем отличается.
Я написал два таких файла:
// foo.h
#import <Foundation/Foundation.h>
@interface SimpleClass : NSObject
- (void)presentWithCompletion:(void (^)(BOOL success))completion;
@end
// main.swift
let foo = SimpleClass()
print(await foo.present())
и скомпилирован в SIL с помощью swiftc -emit-sil -import-objc-header foo.h main.swift
.
Результат
sil_stage canonical
import Builtin
import Swift
import SwiftShims
@MainActor @_hasStorage @_hasInitialValue let foo: SimpleClass { get }
// foo
sil_global hidden [let] @$s4main3fooSo11SimpleClassCvp : $SimpleClass
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
// function_ref async_Main
%2 = function_ref @async_Main : $@convention(thin) @async () -> () // user: %8
%3 = integer_literal $Builtin.Int64, 2048 // user: %4
%4 = struct $Int (%3 : $Builtin.Int64) // user: %10
%5 = metatype $@thick ().Type // user: %6
%6 = init_existential_metatype %5 : $@thick ().Type, $@thick any Any.Type // user: %10
// function_ref thunk for @escaping @convention(thin) @async () -> ()
%7 = function_ref @$sIetH_yts5Error_pIegHrzo_TR : $@convention(thin) @async (@convention(thin) @async () -> ()) -> (@out (), @error any Error) // user: %8
%8 = partial_apply [callee_guaranteed] %7(%2) : $@convention(thin) @async (@convention(thin) @async () -> ()) -> (@out (), @error any Error) // user: %9
%9 = convert_function %8 : $@async @callee_guaranteed () -> (@out (), @error any Error) to $@async @callee_guaranteed @substituted <τ_0_0> () -> (@out τ_0_0, @error any Error) for <()> // user: %10
%10 = builtin "createAsyncTask"<()>(%4 : $Int, %6 : $@thick any Any.Type, %9 : $@async @callee_guaranteed @substituted <τ_0_0> () -> (@out τ_0_0, @error any Error) for <()>) : $(Builtin.NativeObject, Builtin.RawPointer) // user: %11
%11 = tuple_extract %10 : $(Builtin.NativeObject, Builtin.RawPointer), 0 // user: %13
// function_ref swift_job_run
%12 = function_ref @swift_job_run : $@convention(thin) (UnownedJob, UnownedSerialExecutor) -> () // user: %17
%13 = builtin "convertTaskToJob"(%11 : $Builtin.NativeObject) : $Builtin.Job // user: %14
%14 = struct $UnownedJob (%13 : $Builtin.Job) // user: %17
%15 = builtin "buildMainActorExecutorRef"() : $Builtin.Executor // user: %16
%16 = struct $UnownedSerialExecutor (%15 : $Builtin.Executor) // user: %17
%17 = apply %12(%14, %16) : $@convention(thin) (UnownedJob, UnownedSerialExecutor) -> ()
// function_ref swift_task_asyncMainDrainQueue
%18 = function_ref @swift_task_asyncMainDrainQueue : $@convention(thin) () -> Never // user: %19
%19 = apply %18() : $@convention(thin) () -> Never
unreachable // id: %20
} // end sil function 'main'
// async_Main
sil private @async_Main : $@convention(thin) @async () -> () {
bb0:
%0 = builtin "buildMainActorExecutorRef"() : $Builtin.Executor // user: %1
%1 = enum $Optional<Builtin.Executor>, #Optional.some!enumelt, %0 : $Builtin.Executor // user: %28
alloc_global @$s4main3fooSo11SimpleClassCvp // id: %2
%3 = global_addr @$s4main3fooSo11SimpleClassCvp : $*SimpleClass // users: %15, %7
%4 = metatype $@thick SimpleClass.Type // user: %6
// function_ref SimpleClass.__allocating_init()
%5 = function_ref @$sSo11SimpleClassCABycfC : $@convention(method) (@thick SimpleClass.Type) -> @owned SimpleClass // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick SimpleClass.Type) -> @owned SimpleClass // user: %7
store %6 to %3 : $*SimpleClass // id: %7
%8 = integer_literal $Builtin.Word, 1 // user: %10
// function_ref _allocateUninitializedArray<A>(_:)
%9 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %10
%10 = apply %9<Any>(%8) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %12, %11
%11 = tuple_extract %10 : $(Array<Any>, Builtin.RawPointer), 0 // user: %33
%12 = tuple_extract %10 : $(Array<Any>, Builtin.RawPointer), 1 // user: %13
%13 = pointer_to_address %12 : $Builtin.RawPointer to [strict] $*Any // user: %30
%14 = alloc_stack $Bool // users: %29, %34, %17
%15 = load %3 : $*SimpleClass // users: %16, %25
%16 = objc_method %15 : $SimpleClass, #SimpleClass.present!foreign : (SimpleClass) -> () async -> Bool, $@convention(objc_method) (Optional<@convention(block) (Bool) -> ()>, SimpleClass) -> () // user: %25
%17 = get_async_continuation_addr Bool, %14 : $*Bool // users: %27, %18
%18 = struct $UnsafeContinuation<Bool, Never> (%17 : $Builtin.RawUnsafeContinuation) // user: %21
%19 = alloc_stack $@block_storage UnsafeContinuation<Bool, Never> // users: %26, %23, %20
%20 = project_block_storage %19 : $*@block_storage UnsafeContinuation<Bool, Never> // user: %21
store %18 to %20 : $*UnsafeContinuation<Bool, Never> // id: %21
// function_ref @objc completion handler block implementation for @escaping @callee_unowned @convention(block) (@unowned Bool) -> () with result type Bool
%22 = function_ref @$sSbIeyBy_SbTz_ : $@convention(c) (@inout_aliasable @block_storage UnsafeContinuation<Bool, Never>, Bool) -> () // user: %23
%23 = init_block_storage_header %19 : $*@block_storage UnsafeContinuation<Bool, Never>, invoke %22 : $@convention(c) (@inout_aliasable @block_storage UnsafeContinuation<Bool, Never>, Bool) -> (), type $@convention(block) (Bool) -> () // user: %24
%24 = enum $Optional<@convention(block) (Bool) -> ()>, #Optional.some!enumelt, %23 : $@convention(block) (Bool) -> () // user: %25
%25 = apply %16(%24, %15) : $@convention(objc_method) (Optional<@convention(block) (Bool) -> ()>, SimpleClass) -> ()
dealloc_stack %19 : $*@block_storage UnsafeContinuation<Bool, Never> // id: %26
await_async_continuation %17 : $Builtin.RawUnsafeContinuation, resume bb1 // id: %27
bb1: // Preds: bb0
hop_to_executor %1 : $Optional<Builtin.Executor> // id: %28
%29 = load %14 : $*Bool // user: %31
%30 = init_existential_addr %13 : $*Any, $Bool // user: %31
store %29 to %30 : $*Bool // id: %31
// function_ref _finalizeUninitializedArray<A>(_:)
%32 = function_ref @$ss27_finalizeUninitializedArrayySayxGABnlF : $@convention(thin) <τ_0_0> (@owned Array<τ_0_0>) -> @owned Array<τ_0_0> // user: %33
%33 = apply %32<Any>(%11) : $@convention(thin) <τ_0_0> (@owned Array<τ_0_0>) -> @owned Array<τ_0_0> // users: %43, %40
dealloc_stack %14 : $*Bool // id: %34
// function_ref default argument 1 of print(_:separator:terminator:)
%35 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA0_ : $@convention(thin) () -> @owned String // user: %36
%36 = apply %35() : $@convention(thin) () -> @owned String // users: %42, %40
// function_ref default argument 2 of print(_:separator:terminator:)
%37 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA1_ : $@convention(thin) () -> @owned String // user: %38
%38 = apply %37() : $@convention(thin) () -> @owned String // users: %41, %40
// function_ref print(_:separator:terminator:)
%39 = function_ref @$ss5print_9separator10terminatoryypd_S2StF : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> () // user: %40
%40 = apply %39(%33, %36, %38) : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> ()
release_value %38 : $String // id: %41
release_value %36 : $String // id: %42
release_value %33 : $Array<Any> // id: %43
%44 = integer_literal $Builtin.Int32, 0 // user: %45
%45 = struct $Int32 (%44 : $Builtin.Int32) // user: %47
// function_ref exit
%46 = function_ref @exit : $@convention(c) (Int32) -> Never // user: %47
%47 = apply %46(%45) : $@convention(c) (Int32) -> Never
unreachable // id: %48
} // end sil function 'async_Main'
// thunk for @escaping @convention(thin) @async () -> ()
sil shared [transparent] [reabstraction_thunk] @$sIetH_yts5Error_pIegHrzo_TR : $@convention(thin) @async (@convention(thin) @async () -> ()) -> (@out (), @error any Error) {
// %1 // user: %2
bb0(%0 : $*(), %1 : $@convention(thin) @async () -> ()):
%2 = apply %1() : $@convention(thin) @async () -> ()
%3 = tuple () // user: %4
return %3 : $() // id: %4
} // end sil function '$sIetH_yts5Error_pIegHrzo_TR'
// swift_job_run
sil [available 12.0.0] @swift_job_run : $@convention(thin) (UnownedJob, UnownedSerialExecutor) -> ()
// swift_task_asyncMainDrainQueue
sil [available 12.0.0] @swift_task_asyncMainDrainQueue : $@convention(thin) () -> Never
// SimpleClass.__allocating_init()
sil shared @$sSo11SimpleClassCABycfC : $@convention(method) (@thick SimpleClass.Type) -> @owned SimpleClass {
// %0 "$metatype" // user: %1
bb0(%0 : $@thick SimpleClass.Type):
%1 = thick_to_objc_metatype %0 : $@thick SimpleClass.Type to $@objc_metatype SimpleClass.Type // user: %2
%2 = alloc_ref_dynamic [objc] %1 : $@objc_metatype SimpleClass.Type, $SimpleClass // user: %4
// function_ref @nonobjc SimpleClass.init()
%3 = function_ref @$sSo11SimpleClassCABycfcTO : $@convention(method) (@owned SimpleClass) -> @owned SimpleClass // user: %4
%4 = apply %3(%2) : $@convention(method) (@owned SimpleClass) -> @owned SimpleClass // user: %5
return %4 : $SimpleClass // id: %5
} // end sil function '$sSo11SimpleClassCABycfC'
// _allocateUninitializedArray<A>(_:)
sil [always_inline] [_semantics "array.uninitialized_intrinsic"] @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer)
// @objc completion handler block implementation for @escaping @callee_unowned @convention(block) (@unowned Bool) -> () with result type Bool
sil shared [transparent] [thunk] @$sSbIeyBy_SbTz_ : $@convention(c) (@inout_aliasable @block_storage UnsafeContinuation<Bool, Never>, Bool) -> () {
// %0 // user: %2
// %1 // user: %5
bb0(%0 : $*@block_storage UnsafeContinuation<Bool, Never>, %1 : $Bool):
%2 = project_block_storage %0 : $*@block_storage UnsafeContinuation<Bool, Never> // user: %3
%3 = load %2 : $*UnsafeContinuation<Bool, Never> // user: %7
%4 = alloc_stack $Bool // users: %5, %8, %7
store %1 to %4 : $*Bool // id: %5
// function_ref _resumeUnsafeContinuation<A>(_:_:)
%6 = function_ref @$ss25_resumeUnsafeContinuationyySccyxs5NeverOG_xntlF : $@convention(thin) <τ_0_0> (UnsafeContinuation<τ_0_0, Never>, @in τ_0_0) -> () // user: %7
%7 = apply %6<Bool>(%3, %4) : $@convention(thin) <τ_0_0> (UnsafeContinuation<τ_0_0, Never>, @in τ_0_0) -> ()
dealloc_stack %4 : $*Bool // id: %8
return undef : $() // id: %9
} // end sil function '$sSbIeyBy_SbTz_'
// _resumeUnsafeContinuation<A>(_:_:)
sil shared [available 12.0.0] @$ss25_resumeUnsafeContinuationyySccyxs5NeverOG_xntlF : $@convention(thin) <T> (UnsafeContinuation<T, Never>, @in T) -> () {
// %0 // user: %3
// %1 // user: %3
bb0(%0 : $UnsafeContinuation<T, Never>, %1 : $*T):
// function_ref UnsafeContinuation.resume<>(returning:)
%2 = function_ref @$sScc6resume9returningyxn_ts5NeverORs_rlF : $@convention(method) <τ_0_0, τ_0_1 where τ_0_1 == Never> (@in τ_0_0, UnsafeContinuation<τ_0_0, Never>) -> () // user: %3
%3 = apply %2<T, Never>(%1, %0) : $@convention(method) <τ_0_0, τ_0_1 where τ_0_1 == Never> (@in τ_0_0, UnsafeContinuation<τ_0_0, Never>) -> ()
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function '$ss25_resumeUnsafeContinuationyySccyxs5NeverOG_xntlF'
// _finalizeUninitializedArray<A>(_:)
sil shared [readnone] [_semantics "array.finalize_intrinsic"] @$ss27_finalizeUninitializedArrayySayxGABnlF : $@convention(thin) <Element> (@owned Array<Element>) -> @owned Array<Element> {
[%0: escape! v** => %r.v**, escape! v**.c*.v** => %r.v**.c*.v**]
// %0 // user: %2
bb0(%0 : $Array<Element>):
%1 = alloc_stack $Array<Element> // users: %6, %5, %4, %2
store %0 to %1 : $*Array<Element> // id: %2
// function_ref Array._endMutation()
%3 = function_ref @$sSa12_endMutationyyF : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> () // user: %4
%4 = apply %3<Element>(%1) : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> ()
%5 = load %1 : $*Array<Element> // user: %7
dealloc_stack %1 : $*Array<Element> // id: %6
return %5 : $Array<Element> // id: %7
} // end sil function '$ss27_finalizeUninitializedArrayySayxGABnlF'
// default argument 1 of print(_:separator:terminator:)
sil shared @$ss5print_9separator10terminatoryypd_S2StFfA0_ : $@convention(thin) () -> @owned String {
bb0:
%0 = string_literal utf8 " " // user: %5
%1 = integer_literal $Builtin.Word, 1 // user: %5
%2 = integer_literal $Builtin.Int1, -1 // user: %5
%3 = metatype $@thin String.Type // user: %5
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%4 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %5
%5 = apply %4(%0, %1, %2, %3) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %6
return %5 : $String // id: %6
} // end sil function '$ss5print_9separator10terminatoryypd_S2StFfA0_'
// default argument 2 of print(_:separator:terminator:)
sil shared @$ss5print_9separator10terminatoryypd_S2StFfA1_ : $@convention(thin) () -> @owned String {
bb0:
%0 = string_literal utf8 "\n" // user: %5
%1 = integer_literal $Builtin.Word, 1 // user: %5
%2 = integer_literal $Builtin.Int1, -1 // user: %5
%3 = metatype $@thin String.Type // user: %5
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%4 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %5
%5 = apply %4(%0, %1, %2, %3) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %6
return %5 : $String // id: %6
} // end sil function '$ss5print_9separator10terminatoryypd_S2StFfA1_'
// print(_:separator:terminator:)
sil @$ss5print_9separator10terminatoryypd_S2StF : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> ()
// exit
// clang name: exit
sil [clang exit] @exit : $@convention(c) (Int32) -> Never
// @nonobjc SimpleClass.init()
sil shared [thunk] @$sSo11SimpleClassCABycfcTO : $@convention(method) (@owned SimpleClass) -> @owned SimpleClass {
// %0 "self" // users: %2, %1
bb0(%0 : $SimpleClass):
%1 = objc_method %0 : $SimpleClass, #SimpleClass.init!initializer.foreign : (SimpleClass.Type) -> () -> SimpleClass, $@convention(objc_method) (@owned SimpleClass) -> @owned SimpleClass // user: %2
%2 = apply %1(%0) : $@convention(objc_method) (@owned SimpleClass) -> @owned SimpleClass // user: %3
return %2 : $SimpleClass // id: %3
} // end sil function '$sSo11SimpleClassCABycfcTO'
// Array._endMutation()
sil shared [_semantics "array.end_mutation"] @$sSa12_endMutationyyF : $@convention(method) <Element> (@inout Array<Element>) -> () {
[%0: noescape! **]
// %0 // users: %9, %1
bb0(%0 : $*Array<Element>):
%1 = struct_element_addr %0 : $*Array<Element>, #Array._buffer // user: %2
%2 = struct_element_addr %1 : $*_ArrayBuffer<Element>, #_ArrayBuffer._storage // user: %3
%3 = struct_element_addr %2 : $*_BridgeStorage<__ContiguousArrayStorageBase>, #_BridgeStorage.rawValue // user: %4
%4 = load %3 : $*Builtin.BridgeObject // user: %5
%5 = end_cow_mutation %4 : $Builtin.BridgeObject // user: %6
%6 = struct $_BridgeStorage<__ContiguousArrayStorageBase> (%5 : $Builtin.BridgeObject) // user: %7
%7 = struct $_ArrayBuffer<Element> (%6 : $_BridgeStorage<__ContiguousArrayStorageBase>) // user: %8
%8 = struct $Array<Element> (%7 : $_ArrayBuffer<Element>) // user: %9
store %8 to %0 : $*Array<Element> // id: %9
%10 = tuple () // user: %11
return %10 : $() // id: %11
} // end sil function '$sSa12_endMutationyyF'
// String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
sil [always_inline] [readonly] [_semantics "string.makeUTF8"] @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String
// UnsafeContinuation.resume<>(returning:)
sil shared [available 12.0.0] @$sScc6resume9returningyxn_ts5NeverORs_rlF : $@convention(method) <T, E where E == Never> (@in T, UnsafeContinuation<T, Never>) -> () {
// %0 // user: %3
// %1 // user: %2
bb0(%0 : $*T, %1 : $UnsafeContinuation<T, Never>):
%2 = struct_extract %1 : $UnsafeContinuation<T, Never>, #UnsafeContinuation.context // user: %3
%3 = builtin "resumeNonThrowingContinuationReturning"<T>(%2 : $Builtin.RawUnsafeContinuation, %0 : $*T) : $()
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function '$sScc6resume9returningyxn_ts5NeverORs_rlF'
// Mappings from '#fileID' to '#filePath':
// 'main/main.swift' => 'main.swift'
Вызов present
примерно преобразуется в
UnsafeContinuation
, обернув RawUnsafeContinuation
, который кажется просто необработанным указателем. Этот указатель создается инструкцией get_async_continuation_addr
.present
(см. раздел с пометкой «Реализация блока обработчика завершения @objc для...»)UnsafeContinuation.resume
await_async_continuation
)Обратите внимание, что именно это и делает withUnsafeContinuation
(источник).
Сравните вывод с компиляцией этого файла Swift:
// main.swift
class SimpleClass {
func present(completion: (Bool) -> Void) {}
}
let foo = SimpleClass()
print(await withUnsafeContinuation { continuation in
foo.present { continuation.resume(returning: $0) }
})
Код SIL примерно такой же. Есть просто явный вызов функции withUnsafeContinuation
, отражающий то, что вы сделали в коде Swift. Вы по-прежнему можете увидеть все шаги, которые я упомянул выше, потому что withUnsafeContinuation
отмечен @_alwaysEmitIntoClient
. Вы можете найти вызов resume
в разделе с пометкой «закрытие №1 в замыкании №1».