• Community update

    • DocC, Swift.org 웹사이트 등이 오픈소스화
    • 기존 워크 그룹(Swift on Server, Diversity in Swift)에 이어 Swift Website, C++ interoperability 워크 그룹이 시작됨
    • Swift를 더 많이 쓸 수 있도록, 리눅스 지원 강화(RPM Package - Amazon Linux 2, CentOS 7)
  • Swift가 비록 앱 만드는 데 주로 쓰이지만 비전은 고수준 스크립트부터 로우레벨까지 커버하는 것이다. 그래서 올해 몇가지 큰 변화가 물밑에서 진행되었다.

    • 유니코드 라이브러리를 외부 의존에서 자체 구현으로 변경 → 더 빠르고 작은 바이너리.
      • 바이너리가 작으면 Event-Driven Server등에서 쓰기 좋다.
      • 또 제한된 환경(ex. Secure Enclave)에서도 쓸 수 있게 된다.
  • Package

    • TOFU(Trust On First Use)지원
      • 패키지가 처음으로 다운로드 될 때 fingerprint가 기록된다.
      • 이후 패키지가 다운로드 될 때 이 fingerprint를 확인하여 변경되면 에러를 보고한다.
    • Command plugin
      • 확장성있고 안전한 빌드 툴을 제공하기 위한 방법

      • 사용 예시: 문서 만들기, 소스코드 포매팅, 테스트 리포트 만들기

      • 기존에는 shell script를 쓰던 영역을 swift로 대체 가능

      • 코드 예시

        • 커맨드라인 플러그인

          • 작성하고 나면 swift package 커맨드 라인에서 호출 가능
          @main struct MyPlugin: CommandPlugin {
          
              func performCommand(context: PluginContext, arguments: [String]) throws {
                  let process = try Process.run(doccExec, arguments: doccArgs)
                  process.waitUntilExit()
              }
          }
          
        • 빌드 툴 플러그인

          • ex. source code generation, resource processing
          • 샌드박스 안에서 실행되며, 패키지 안의 파일을 바꿀 수 있는 권한을 가짐
          import PackagePlugin
          
          @main struct MyCoolPlugin: BuildToolPlugin {
              func createBuildCommands(context: TargetBuildContext) throws -> [Command] {
          
                  let generatedSources = context.pluginWorkDirectory.appending("GeneratedSources")
          
                  return [
                      .buildCommand(
                          displayName: "Running MyTool",
                          executable: try context.tool(named: "mycooltool").path,
                          arguments: ["create"],
                          outputFilesDirectory: generatedSources)
                  ]
              }
          }
          
      • 플러그인은 Package안의 Plugin 폴더 안에 들어있게 된다.

        • Swift Executable로 취급된다.
    • Module Aliasing
      • 다른 패키지에 같은 이름의 모듈이 있을 때를 대비해 다른 이름을 제공할 수 있다.

        let package = Package(
                name: "MyStunningApp",
                dependencies: [
                    .package(url: "https://.../swift-metrics.git"),
                    .package(url: "https://.../swift-log.git")
                ],
                products: [
                    .executable(name: "MyStunningApp", targets: ["MyStunningApp"])
                ],
                targets: [
                    .executableTarget(
                        name: "MyStunningApp",
                        dependencies: [
                            .product(name: "Logging", 
                                     package: "swift-log"),
                            .product(name: "Metrics", 
                                     package: "swift-metrics",
                                     moduleAliases: ["Logging": "MetricsLogging"]),
          ])])
        
      • 쓰는 쪽에서는 선택해서 사용 가능

        // MyStunningApp
        
        import Logging           // from swift-log
        import MetricsLogging    // from swift-metrics
        
        let swiftLogger = Logging.Logger()
        
        let metricsLogger = MetricsLogging.Logger()
        
  • 성능 개선

    • Swift-Driver를 Xcode build system에서 바로 사용할 수 있게 됨

    • 타입 체킹 시간 감소 - 제네릭 쪽 핵심 로직을 재작성

      • 기존에는 연관된 프로토콜이 많을수록 시간 및 공간 복잡도가 지수 스케일로 증가

      • 예시로 든 코드

        • 기존 구현에서는 타입 체크에 17초
        • 새로운 구현에서는 1초 미만(760ms 정도)
        public protocol NonEmptyProtocol: Collection
            where Element == C.Element, 
                Index == C.Index {
            associatedtype C: Collection
        }
        
        public protocol MultiPoint {
            associatedtype C: CoordinateSystem
            typealias P = Self.C.P
        
            associatedtype X: NonEmptyProtocol 
                where X.C: NonEmptyProtocol, 
                    X.Element == Self.P
        }
        
        public protocol CoordinateSystem {
            associatedtype P: Point where Self.P.C == Self
            associatedtype S: Size where Self.S.C == Self
            associatedtype L: Line where Self.L.C == Self
            associatedtype B: BoundingBox where Self.B.C == Self
        }
        
        public protocol Line: MultiPoint {}
        
        public protocol Size {
            associatedtype C: CoordinateSystem where Self.C.S == Self
        }
        
        public protocol BoundingBox {
            associatedtype C: CoordinateSystem
            typealias P = Self.C.P
            typealias S = Self.C.S
        }
        
        public protocol Point {
            associatedtype C: CoordinateSystem where Self.C.P == Self
        }
        
    • 런타임 개선

      • 앱 시작할 때 프로토콜 채택 여부를 매번 검사해야 하는데, 이게 프로토콜이 많아질수록 오래 걸린다.
      • 이제 이 결과가 캐싱된다. 앱에 따라서 최대 절반까지도 런치 타임이 감소된다.
  • Concurrency updates

    • back deploy 지원(iOS 13까지)
      • 더 뒤 OS를 지원하기 위해서는 앱 번들 자체가 Swift concurrency 런타임을 카피해야 한다. → 옛날 ABI 안정화 이전 시절에 Swift가 했던 것과 동일한 일.
    • data race avoidance
      • swift는 자체적인 메모리 안정성 검사를 이미 하고 있었다.

        var numbers = [3, 2, 1]
        
        numbers.removeAll(where: { number in
            number == numbers.count // error: Overlapping accesses to "numbers, but modification requires exclusive access;
        }) 
        
      • 멀티 스레드에서도 동일하게 검사하게 되었다. → 이런게 필요하면 actor를 써야 한다.

        var numbers = [3, 2, 1]
        
        Task { numbers.append(0) } // Mutation of captured var 'numbers' in concurrently-executing code
        
        numbers.removeLast()
        
      • 이는 Swift 6로의 발전 과정 중 하나다.(Memory Safety to Thread Safety)

        • 위에서 말한 것은 Robust concurrency Model에 해당한다.
        • 추가적으로 Opt-in Safety Check도 들어간다. → 빌드 세팅에서 설정할 수 있다.(strict Concurrency Checking)
    • Distributed actors
      • actor가 현재 시스템이 아닌 다른 시스템에 존재하는 것.

      • 분산 시스템을 훨씬 간편하게 만든다.

        distributed actor Player {
           
            var ai: PlayerBotAI?
            var gameState: GameState
            
            distributed func makeMove() -> GameMove {
                return ai.decideNextMove(given: &gameState)
            }
        }
        
        // 호출부분
        func endOfRound(players: [Player]) async throws {
            // Have each of the players make their move
            for player in players {
                let move = try await player.makeMove()
            }
        }
        
        • 서버쪽에서도 쓸 수 있게 distibuted actor기능만 따로 패키지로 배포한다.
          • SwiftNIO를 이용한 네트워킹 레이어와 클러스터 상태를 관리하기 위한 SWIM 프로토콜 구현체를 포함한다.
    • Async algorithms
      • AsyncSequence에 관련된 알고리즘 모음 패키지
      • 별도 패키지로 제공하기 때문에 플랫폼과 OS에 관계 없이 쓸 수 있다.
    • Concurrency 성능 개선
      • actor에 우선순위 부여 가능
      • 우선순위가 낮은 작업이 우선순위가 높은 작업을 막지 못하도록 함(Priority-inversion avoidance)
    • Concurrency를 시각화하는 instrument 추가
  • Expressive Swift

    • 언어는 도구지만, 언어를 가지고 만들려는 것은 언어의 영향을 많이 받게 된다. → 망치를 들면 모든 게 못으로 보인다.

      • 그래서 Swift의 표현력을 높여서 더 다양한 상황을 표현할 수 있게 되어야 한다.
    • if let shorthand

      if let workingDirectoryMailmapURL {
        
          mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\\n")
      
      }
      
      guard let workingDirectoryMailmapURL else { return }
      
      mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\\n")
      
    • swift의 기능이 작은 변화에도 제대로 동작하지 않는 경우들이 있었다.

      • closure가 조금만 복잡해져도 반환 타입 추론이 안되는 경우 → 이번 버전부터는 좀 더 복잡한 경우에도 동작한다.
        • do-catch
        • if-else
        • 단순 print 호출 추가
      • C 스타일 코드를 다룰때
        • 포인터 간의 자동변환을 절대로 허용하지 않는다.(typed → typed, typed→ raw, raw → typed 모두)

        • 그래서 C코드를 Swift에서 쓰는게 까다롭다.

          • Obj-C는 이게 되서 C 코드랑 섞어 쓰는게 쉬웠다.
          • Swift에서도 비슷하게 C++ interoperability 그룹을 만든 것도 Obj-C와 C의 관계처럼 만들고 싶어서다.
          // Mismatches that are harmless in C…
          int mailmap_get_size(mailmap_t *map);
          void mailmap_truncate(mailmap_t *map, unsigned *sizeInOut);
          
          void remove_duplicates(mailmap_t *map) {
              int size = mailmap_get_size(map);
              size -= move_duplicates_to_end(map);
              mailmap_truncate(map, &size);
          }
          
          // …cause problems in Swift.
          func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) {
              var size = mailmap_get_size(map)
              size -= moveDuplicatesToEnd(map)
              mailmap_truncate(map, &size) // error!: UnsafeMutablePointer<Int32> -> UnsafeMutablePointer<UInt32)
          }
          
        • 원래는 이렇게 써야 한다.

          func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) {
              var size = mailmap_get_size(map)
              size -= moveDuplicatesToEnd(map)
              withUnsafeMutablePointer(to: &size) { signedSizePtr in
                  signedSizePtr.withMemoryRebound(to: UInt32.self, capacity: 1) { unsignedSizePtr in
                      mailmap_truncate(map, unsignedSizePtr)
                  }
              }
          }
          
        • 그래서 C에서 import된 코드에 대해서는 별도 룰을 적용해서, C에서 가능한 포인터 변환에 대해서는 허용하도록 바꿨다.

    • String Processing

      • 문자열 처리를 선언적으로 하기 위해 regex를 자체적으로 도입
      • Swift로 자체 구현된 엔진 사용
      • 이를 쓰기 위해서는 Swift Regex가 내장된 OS(iOS 16)을 써야 한다.
    • Generic code clarity