Swift4 [SE-160 Limiting @objc inference] 概要

[SE-160 Limiting @objc inference](https://github.com/apple/swift-evolution/blob/master/proposals/0160-objc-inference.md)の概要

## TL;DR
- Objective-Cランタイムから呼ぶ必要があるものは明示的に`@objc`の付与が必要になった(今まではコンパイラが勝手につけていた)
- `@objc`をクラス全体に適用する`@objcMembers`が追加になった
- いらない内部コードが減ってバイナリサイズが6-8%減る
- マイグレーションとかはXcodeのマイグレータでほぼOK

## 前提
Swift3では`@objc`推論が多く行われていた
(`@objc`とはSwiftのコードをObjective-Cランタイムから呼べるようにするためのもの)
以下の場合に暗黙的に`@objc`推論が行われている

- Objective-Cのクラスを継承している
- dynamicキーワードを付与している変数やメソッド
- etc.

## 提案された理由
- `@objc`推論される規則がわかりにくい
- Objective-Cのセレクターの衝突が起こりがち

```swift
class MyNumber : NSObject {
  init(_ int: Int) { }
  init(_ double: Double) { } // error: initializer 'init' with Objective-C selector 'init:'
      // conflicts with previous declaration with the same Objective-C selector
}
```

- [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/)に沿って書いても、Objective-Cから呼び出す時に[Objective-C Coding Guidelines for Cocoa](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html)に反する名前に変化されがち
- 特にinitializerの中では第一引数名を含めないといけないので`@objc`を合わせて書かなくてはいけなくなる

```swift
class MyNumber : NSObject {
  @objc(initWithInteger:) init(_ int: Int) { }
  @objc(initWithDouble:) init(_ double: Double) { }
}
```
- SwiftコンパイラはObjective-C Calling ConventionからSwift Calling Conventionを関連づけるthunkメソッドをObjective-Cメタデータ内に記録するためバイナリサイズが大きくなってしまう
 - Objective-C entry pointごとにコストがかかる
 - thunkメソッドにより6-8%のバイナリサイズが大きくなる(未使用のものも含まれている)
   Apple's Music Appでは5.7%サイズが減った

## 提案
プログラムの整合性上、必要な部分を除き`@objc`推論をしないようにする

その上で広範囲で`@objc`をon/offする時に定型記述を減らすためにclassレベル、extensionレベルのアノテーションを追加する

### 引き続き推論されるパターン
- `@objc`属性がついているものをオーバーライドする場合
 - `Super.foo()`に対するObjective-C callersがオーバーライドした`Sub.foo()`を適切に呼び出すため

```swift
class Super {
    @objc func foo() { }
}

class Sub : Super {
    /* inferred @objc */
    override func foo() { }
}
```

- `@objc`属性がついているprotocol要件を満たす時
 - `MyDelegate.bar()`がObjective-C message sendを介して呼ばれるため

```swift
@objc protocol MyDelegate {
    func bar()
}

class MyClass : MyDelegate {
    /* inferred @objc */
    func bar() { }
}
```

- `@IBAction`,`@IBOutlet`属性がついている時
 - Interface BuilderとのインタラクションがObjective-Cランタイムで行われるため
- `@NSManaged`属性がついている時
 - Core DataとのインタラクションがObjective-Cランタイムで行われるため

### 追加で推論されるパターン
- `@GKInspectable`属性がついている時
 - GameplayKitとのインタラクションがObjective-Cランタイムで行われるため
- `@IBInspectable`属性がついている時
 - Interface BuilderとのインタラクションがObjective-Cランタイムで行われるため

### 推論されなくなるパターン
- dynamicキーワードが付与されているもの
 - dynamicは動的ディスパッチをするためのもの(Objective-Cランタイムからよびだされる)なのでSwift3の時は`@objc`が推論されていた

```swift
class MyClass {
  dynamic func foo() { }       // error: 'dynamic' method must be '@objc'
  @objc dynamic func bar() { } // okay
}
```

- NSObject由来のもの
 - これがこの提案で唯一の大きな変更
 - これにより多くのメソッドをObjective-Cから呼べなくなる(これによりバイナリサイズが小さくなる)

```swift
class MyClass : NSObject {
  func foo() { } // not exposed to Objective-C in Swift 4
}
```

Swift3時代はObjective-Cで表現できるもののみ`@objc`推論されていた

```swift
extension MyClass {
  func bar(param: ObjCClass) { } // Swift3では推論された。この提案では推論されない
  func baz(param: SwiftStruct) { } // StructはObjective-Cでは表現できないので推論されない
}
```

### クラス全体で`@objc`推論を有効にさせる(`@objcMembers`)
XCTest,Realmのようないくつかのライブラリは依然としてObjective-Cランタイムに依存する
そのためクラス、そのExtension、サブクラス、およびそれらすべてのExtensionに対して今まで通りの`@objc`推論を一括で行うため
`@objcMembers`属性を新しく追加する。

```swift
@objcMembers
class MyClass : NSObject {
  func foo() { }             // implicitly @objc

  func bar() -> (Int, Int)   // not @objc, because tuple returns
      // aren't representable in Objective-C
}

extension MyClass {
  func baz() { }   // implicitly @objc
}

class MySubClass : MyClass {
  func wibble() { }   // implicitly @objc
}

extension MySubClass {
  func wobble() { }   // implicitly @objc
}

```

また以下のように`swift_objc_members`を付与することで
インポートされたObjective-Cクラスを`@objcMembers`としてSwiftにインポートすることができる

```swift
__attribute__((swift_objc_members))
@interface XCTestCase : XCTest
/* ... */
@end
```
↓

```swift
@objcMembers
class XCTestCase : XCTest { /* ... */ }
```
### Extensionでの`@objc`のon/off
Extensionに`@objc` or `@nonobjc`属性をつけることで
そのExtension内の`@objc`or`@nonobjc`属性を個別で持ってないメンバーに対して一律適用できる

```swift
class SwiftClass { }

@objc extension SwiftClass {
  func foo() { }            // implicitly @objc
  func bar() -> (Int, Int)  // error: tuple type (Int, Int) not
      // expressible in @objc. add @nonobjc or move this method to fix the issue
}

@objcMembers
class MyClass : NSObject {
  func wibble() { }    // implicitly @objc
}

@nonobjc extension MyClass {
  func wobble() { }    // not @objc, despite @objcMembers
}
```

### Swift4へのマイグレーション
Step1:
Swift4モードで、`@objc`推論されている警告の対処をする

Step2:
環境変数`SWIFT_DEBUG_IMPLICIT_OBJC_ENTRYPOINT`の値を1〜3にしてアプリをテストする

1. ObjectiveCのエントリポイントへの呼び出しを記録  
 ex.`***Swift runtime: entrypoint -[MyApp.MyClass foo] generated by implicit @objc inference is deprecated and will be removed in Swift 4`
1. 1 + バックトレースを発行
1. 2 + クラッシュ

Step3:
この時点でSwift4へマイグレーション可能になる
Swift4でビルドすると、廃止予定のルールに基づき`@objc`推論されたObjective-Cエントリポイントが削除される

※実際はXcodeのマイグレーターがよしなにやってくれるので、自分のプロジェクトではここら辺は何も考える必要がなかった