Skip to the content.

Swift Concurrency와 Objective-C 혼용 팁

Swift Concurrency와 Objective-C를 혼용할 때 팁을 적는 글이에요.

기본 지식은 Concurrency Interoperability with Objective-C를 참고하시면 좋을 것 같아요. 이 글에 대해서는 다루지 않아요.

Task - Internal 함수들

Swift Conrrency의 Task에서는 Internal 함수를 제공하고 있어요.

https://github.com/apple/swift/blob/01086bc8b5d327ae44e94279a2f4ae4503af859c/stdlib/public/Concurrency/Task.swift#L922

Public은 아니지만 @_silgen_name으로 정의되어 있기 때문에 dlsym으로 손쉽게 호출할 수 있어요.

여기서 쓸만한 예제를 정리할게요. 생각날 때마다 꾸준히 업데이트 예정

isCancelled

Swift에서는 Task가 취소되었는지를 확인하는 Task.isCancelled API가 존재하지만 애플이 C에서는 API를 제공하고 있지 않습니다.

이를 C에서 구현하기 위해서 isCancelled가 무슨 원리인지를 보면

https://github.com/apple/swift/blob/a04d273f9b4cc70891dd957df92bc521c6ecc307/stdlib/public/Concurrency/TaskCancellation.swift#L80

이런 원리이기에 Internal 함수에서는

이렇게 하면 될 것 같네요.

테스트를 위해 아래처럼 AsyncObject를 정의하고

#pragma mark - AsyncObject.h

#import <Foundation/Foundation.h>

NS_HEADER_AUDIT_BEGIN(nullability, sendability)

@interface AsyncObject : NSObject
- (void)performWithCompletionHandler:(void (^)(void))completionHandler;
@end

NS_HEADER_AUDIT_END(nullability, sendability)
#pragma mark - AsyncObject.mm

#import "AsyncObject.h"
#import <dlfcn.h>

@implementation AsyncObject

- (void)performWithCompletionHandler:(void (^)(void))completionHandler {
    void *handle = dlopen("/usr/lib/swift/libswift_Concurrency.dylib", RTLD_NOW);
    auto _getCurrentAsyncTask = reinterpret_cast<void *(*)(void)>(dlsym(handle, "swift_task_getCurrent"));
    void *currentTask = _getCurrentAsyncTask();
    
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
        auto _taskIsCancelled = reinterpret_cast<bool (*)(void *)>(dlsym(handle, "swift_task_isCancelled"));
        bool isCancelled = _taskIsCancelled(currentTask);
        
        if (isCancelled) {
            NSLog(@"Cancelled!");
        }
        
        completionHandler();
    });
}

@end

Swift 코드에서는 아래처럼 실행하면

let task: Task<Void, Never> = .init {
    try? await Task.sleep(for: .seconds(1))
    let object: AsyncObject = .init()
    await object.perform()
}

task.cancel()

Cancelled라는 로그가 잘 찍히는 것을 확인할 수 있네요.

이렇게 하면 C에서도 취소 핸들링이 가능하네요.

TODO: Swift ABI의 이해도가 높아지면 Swift API 직접 호출해보는 방법도 있을듯? https://github.com/apple/swift/blob/a04d273f9b4cc70891dd957df92bc521c6ecc307/utils/api_checker/FrameworkABIBaseline/_Concurrency/ABI/macos.json

Macro