[iOS][Swift] SocialAccountKit を使った iOS 11 以降の Twitter と Facebook API へのアクセス

# はじめに
iOS 11 になって Twitter や Facebook への投稿や API にアクセスするために利用していた Account.framework と Social.framework が使えなくなった。 Apple は、 Twitter と Facebook が提供している公式の SDK の利用をを推奨している。これらを使うのが正しい方法なのだろう。でも、その際の既存のソースコードの修正がとても面倒臭い。

そのため、iOS 10.3 までのソースコードを可能なかぎり少ない修正で、かつ Account.framework と Social.framework の代替ができる [SocialAccountKitSwift](https://github.com/magickworx/SocialAccountKitSwift) を作成した。 このフレームワークを使用すれば、従来とほぼ同じような感じで Twitter や Facebook の API にアクセスできる。 

# 何ができるの

以下のような iOS 10.3 までの Social.framework を使った Twitter に投稿するソースコードが、

```Swift:Social.framework
if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeTwitter) {
  if let slc = SLComposeViewController(forServiceType: SLServiceTypeTwitter) {
    slc.completionHandler = {
      (result: SLComposeViewControllerResult) -> Void in
      switch (result) {
        case .done:
          print("tweeted")
        case .cancelled:
          print("tweet cancel")
      }
    }
    present(slc, animated: true, completion: nil)
  }
}

```

次のように書き換えられる。 修正箇所が少なくって、とても楽チン。

```Swift:SocialAccountKitSwift.framework
let accountType = SAKAccountType(.twitter)
if SAKComposeViewController.isAvailable(for: accountType) {
  if let slc = SAKComposeViewController(for: accountType) {
    slc.completionHandler = {
      (result: SAKComposeViewControllerResult) -> Void in
      switch (result) {
        case .done(let json): // API の Response を JSON 形式で受け取れる
          print("tweeted")
        case .cancelled:
          print("tweet cancel")
        case .error(let error): // Error も受け取れる
          print(error.localizedDescription)
      }
    }
    present(slc, animated: true, completion: nil)
  } 
}

```

同様に、 Twitter の API にアクセスするコードは、

```Swift:Social.framework
let requestURL = URL(string: "https://api.twitter.com/1.1/statuses/home_timeline.json")!
if let request = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: .GET, url: requestURL, parameters: [:]) {
  request.account = self.account
  request.perform(handler: { (responseData, urlResponse, error) in
    if let jsonData = responseData {
      dump(jsonData)
    }
  })
}
```

以下のように書き換え可能である。

```Swift:SocialAccountKitSwift.framework
let requestURL = URL(string: "https://api.twitter.com/1.1/statuses/home_timeline.json")!
if let request = try? SAKRequest(forAccount: self.account, requestMethod: .GET, url: requestURL, parameters: [:]) {
  request.perform(handler: { (responseData, urlResponse, error) in
    if let jsonData = responseData {
      dump(jsonData)
    }
  })
}
```

ざっくり説明すると、 AC と SL 接頭辞で始まるコードが SAK 接頭辞に置き換わっただけ。ACAccount も SAKAccount になった。開発者が自分でアカウント管理の仕組みを実装しない限り、大きな手間はないと思う。


# SocialAccountKit の使い方

自作アプリに組み込むまでの手順を説明するよ。
ほとんどが OAuth 認証のための準備に費すんだけどね。

## SocialAccountKitSwift.framework の作成

1. [GitHub](https://github.com/magickworx/SocialAccountKitSwift) にアクセスして、ソースコードを clone するか zip 形式でダウンロードする。
2. Xcode で Project を開き、Target を SocialAccountKitSwiftFatBinary を選択し、 Build を実行する。
3. SocialAccountKitSwift.framework が作成されたフォルダが表示されるので、${HOME}/Library/Frameworks などにコピーする。

## API Key と API Secret の入手と設定

アカウント管理のために、Twitter と Facebook から API Key と API Secret を入手する必要がある。どちらか一方しか利用しないなら、必要なサービスの API Key と API Secret を取得すれば良い。

### Twitter を使う場合

1. [Twitter Application Management](https://apps.twitter.com/) ページにアクセスする。
2. **Create New App** を実行して、アプリケーションを登録する。
3. **Keys and Access Tokens** タブを選択し、 **Consumer Key** と **Consumer Secret** の文字列をコピーする。
4. Xcode Project 内の **Twitter.plist** の **CosumerKey** と **ConsumerSecret** にペーストする。

```Twitter.plist
<plist version="1.0">
<dict>
  <key>ConsumerKey</key>
  <string>YOUR CONSUMER KEY</string>
  <key>ConsumerSecret</key>
  <string>YOUR CONSUMER SECRET</string>
</dict>
</plist>
```

### Facebook を使う場合

1. [開発者向け Facebook](https://developers.facebook.com/) ページにアクセスする。
2. 右上の **マイアプリ** から **新しいアプリを追加** を選択し、アプリを登録する。
3. ダッシュボード画面の **アプリID** と **app secret** の文字列をコピーする。
4. Xcode Project 内の **Facebook.plist** の **AppID** と **AppSecret** にペーストする。


```Facebook.plist
<plist version="1.0">
<dict>
  <key>AppID</key>
  <string>YOUR APP ID</string>
  <key>AppSecret</key>
  <string>YOUR APP SECRET</string>
  <key>Permissions</key>
  <array>
    <string>public_profile</string>
    <string>email</string>
    <string>manage_pages</string>
    <string>publish_pages</string>
    <string>publish_actions</string>
    <string>user_posts</string>
    <string>user_friends</string>
  </array>
</dict>
</plist>
```

**Permissions** の値を調整したい場合は、[アクセス許可のリファレンス - Facebook >ログイン](https://developers.facebook.com/docs/facebook-login/permissions) を参照してね。
Permission の値は認証後に変更できないので気を付けよう。また、 Facebook の承認が必要な値もあるので注意しよう。

さらにもう一手間、 OAuth 認証の設定をするよ。

1. プロダクト画面で **Facebook ログイン** を追加する。
2. **クライアントOAuth設定** 画面を開く。
3. **クライアントOAuthログイン** と **埋め込みブラウザOAuthログイン** を **はい** に変更する。

Facebook の設定は、いろいろと複雑なので、先ずは OAuth 認証でアカウント作成ができるかどうかを確認してから、プログラムの開発を進めると良い。

## API Key と API Secret の確認

取得した API Key と API Secret を使って SocialAccountKitDemo を実行してアカウント登録してみよう。

1. Twitter.plist あるいは Facebook.plist に入手した API Key と API Secret を設定する。
2. Xcode の Target を SocialAccountKitDemo に変更して Run を実行する。
3. アプリが起動したなら、画面右上の **＋** ボタンをタップする。
4. アカウント管理画面に切り替わるので、右上の **Edit** ボタンをタップする。
5. **Add New Account** をタップする。
6. Twitter / Facebook の認証画面に切り替わるので、 ID とパスワードを入力する。
7. 認証が無事完了したら、アカウント管理画面に戻るので、右上の **Done** ボタンをタップする。
8. 左上の **×** ボタンをタップしてアカウント画面を閉じる。

上記一連の操作でエラーが起きなければ、取得した API Key と API Secret が正しく使えるのが確認できた。

それと、デモアプリの左上のボタンをタップすると、登録したアカウントの一覧が表示される。選択すれば、タイムラインの情報を画面に表示するよ。また、画面左下のボタンをタップすると、投稿画面が表示されるので、自分のタイムラインに文章を書き込めるよ。試してみてね。

## 自作アプリに組み込む

### Xcode での設定

**Embedded Binaries** に作成した SocialAccountKitSwift.framework を登録する。

### アカウント管理機能の実装（必須）

iOS 10.3 までは、 iOS の **設定** アプリ内で Twitter と Facebook のアカウント管理機能が提供されていた。 iOS 11 以降では、それらの機能が削除されている。そのため、アプリ毎にアカウントを管理する必要がある。

このアカウント管理の仕組みを提供するのが、 **SAKAccountViewController** クラスである。 そのため、SocialAccountKitSwift を利用するなら、必ずこのクラスを呼び出す仕組みを実装しなければならない。

たとえば、UIBarButtonItem から Twitter のアカウント管理画面を呼び出すには、以下のようなコードを実装する。

```Swift:SAKAccountViewController
func manageAction(_ sender: UIBarButtonItem) {
  let accountType = SAKAccountType(.twitter)
  let viewController = SAKAccountViewController(accountType: accountType)
  present(viewController, animated: true, completion: nil)
}
```

SAKAccountViewController クラスは、アカウントの登録と削除のみの機能を提供する。もし独自のアカウント管理画面を実装したいなら、 SocialAccountKit フォルダ内の AccountViewController.swift を参考にすると良い。

### アカウントの取得

登録したアカウント情報は次のようなコードで取得できる。 例は Twitter の場合。

```Swift
class MyClass
{
  let store = SAKAccountStore.shared
  var accounts = [SAKAccount]()

  func getAccounts() {
    let accountType = SAKAccountType(.twitter)
    store.requestAccessToAccounts(with: accountType, completion: {
      [unowned self] (granted: Bool, error: Error?) in
      guard granted, error == nil else { return }
      if let accounts = self.store.accounts(with: self.accountType) {
        self.accounts = accounts
      }
    })
  }
}
```

上記のコードでも分かるように、可能なかぎり従来の Account.framework からの修正を少なくなるようにしている。この場合は、ACAccountStore に対応する SAKAccountStore である。違いは、 SAKAccountStore は Singleton なだけ。
それと、requestAccessToAccounts() の第一引数が、SAKAccountType になった点かな。

### API へのアクセス

iOS 10.3 までは、Social.framework の SLRequest クラスを利用すれば、Twitter や Facebook の API にアクセスできた。同様に、SocialAccountKitSwift でも SAKRequest クラスとして機能を提供している。

以下のコードでは、Twitter のタイムライン上の情報を 20件取得する。 エラー処理は省略。

```Swift:SAKRequest
let account: SAKAccount = self.accounts.first!
let accountType = SAKAccountType(.twitter)
let requestURL = URL(string: "https://api.twitter.com/1.1/statuses/home_timeline.json")!
let parameters: [String:Any] = [
  "count" : 20
]
let request = try? SAKRequest(forAccount: account, requestMethod: .GET, url: requestURL, parameters: parameters)
request.perform(handler: {
  [unowned self] (data, response, error) in
  guard error == nil, let data = data else { return }
  if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {                                                  
    let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [Dictionary<String,Any>]
    dump(json)
  }
})
```

SLRequest() と SAKRequest() の違いは、第一引数に SAKAccount を取っている所である。また、例外を投げるのも異なる。この点に関しては、エラー処理の設計がイマイチなのが原因である。

これらについて注意すれば、既存のソースコードのちょっとした修正で、iOS 11 でも API へのアクセスが可能になる。

### タイムラインへの投稿

Twitter や Facebook のタイムラインへの投稿も、 SAKComposeViewController クラスを利用して可能である。これも、SLComposeViewController クラスとほぼ同じ使い方である。違いは、第一引数に、SAKAccountType を渡す点である。この点に気を付ければ、従来のソースコードの修正も簡単に実施できるだろう。

```Swift:SAKComposeViewController
func composeAction(_ sender: UIBarButtonItem) {
  let accountType = SAKAccountType(.twitter)
  if SAKComposeViewController.isAvailable(for: accountType) {
    let viewController = SAKComposeViewController(for: accountType)
    viewController.completionHandler = {
      [unowned self] (result: SAKComposeViewControllerResult) -> Void in
      switch result {
        case .cancelled:
          print("cancelled")
        case .done(let json): // XXX: 'json' may be 'nil'.
          dump(json)
        case .error(let error):
          print(error.localizedDescription)
      }
    }
    present(viewController, animated: true, completion: nil)
  }
}
```

# ダウンロード

[Xcode Project 一式](https://github.com/magickworx/SocialAccountKitSwift) は、 github から入手可能。
スクリーンショットもあるので、そちらも参考にしてね。

# おわりに

SocialAccountKitSwift は、 iOS 10.3 までの Account.framework と Social.framework を使ったソースコードをできるだけ少ない手間で iOS 11 以降でも動作させるためのフレームワークだ。手間を惜しまないのであれば、 Twitter と Facebook が提供している公式の SDK を利用して対応するのが正解なのだろう。 

私は、Account.framework と Social.framework を既存のアプリで結構使っているので、そのためだけに 公式 SDK を使って対応するのが面倒だと思った。なので、SocialAccountKitSwift を作ったのだ。 自作フレームワークを作るまでは大変だが、作った後の対応は段違いに楽だ。

それと、このフレームワークを使う利点は、困ったらソースコードを見て自分で改良できるところだろう。 OAuth 認証のコードも RFC 5849 を読んで実装したので、自作アプリで使う際の参考にもなるだろう。他のサービスへの対応も考慮した設計にしたつもりだが、どうだろうか。