iOSソフトウェアを開発するようになってネットワーク接続を必要になってくるのではないだろうか.例えばHTTP通信や非同期通信をしたいことがあるだろう.
iOSではデフォルトで許可されてるのはhttps通信
のみで,http通信は許可されていない.開発中はhttpと通信したいことも多いので,http通信の有効化を行う手順を紹介する.
前提条件
- XCode のインストール
- Swiftで記述します
iOSデバイスでネットワーク接続許可をする
XCodeでの設定
今回は私用のプロジェクトを使います
まずXCodeの自分のプロジェクトのボタンをクリックする.
次に自分のプロジェクトのターゲットをクリックする.
info
を選択し,Custom macOS Application Target Properties
の欄の+
ボタンを押し,App Transport Security Settings
を追加する.
App Transport Security Settings
の欄で+
ボタンを押し,Allow Arbitrary Loads
を追加する.
その後,Allow Arbitary Loads
のvalueをYES
にする.
これでネットワーク接続の許可は完成である.
SwiftでのHTTP通信部分の作成
アプリを開いたときのエントリーを書く.
import SwiftUI
@main
struct YourAppNameApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Userモデルを定義する
struct User: Codable {
let id: Int
let name: String
}
APIリクエストを行う.GETとPOSTのメソッドを含む.
import Foundation
class APIService {
func testGet(_ url: String, completion: @escaping (Result<Any, Error>) -> Void) {
let request = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
completion(.failure(error))
} else if let data = data {
do {
let jsonData = try JSONSerialization.jsonObject(with: data, options: [])
completion(.success(jsonData))
} catch {
completion(.failure(error))
}
}
}
task.resume()
}
func testPost(_ url: String, user: User, completion: @escaping (Result<Any, Error>) -> Void) {
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let encoder = JSONEncoder()
let jsonData = try? encoder.encode(user)
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
completion(.failure(error))
} else if let data = data {
do {
let jsonData = try JSONSerialization.jsonObject(with: data, options: [])
completion(.success(jsonData))
} catch {
completion(.failure(error))
}
}
}
task.resume()
}
}
Test GET
とTest POST
のボタンがあり,それぞれのリクエストの結果を表示する.
import SwiftUI
struct ContentView: View {
@State private var getResult = ""
@State private var postResult = ""
var body: some View {
VStack {
Button("Test GET") {
let apiService = APIService()
apiService.testGet("http://[getするためのエンドポイントを記載したURL]") { result in
switch result {
case .success(let data):
getResult = "GET Success: \(data)"
case .failure(let error):
getResult = "GET Error: \(error)"
}
}
}
Text(getResult)
Button("Test POST") {
// POSTするUserインスタンスを設定
let user = User(id: 1, name: "John")
let apiService = APIService()
apiService.testPost("http://[postするためのエンドポイントを記載したURL]", user: user) { result in
switch result {
case .success(let data):
postResult = "POST Success: \(data)"
case .failure(let error):
postResult = "POST Error: \(error)"
}
}
}
Text(postResult)
}
}
}
このようなJSONがPOSTで送られるはず
{
"id": 1,
"name": "John"
}
非同期通信をするための下準備
今回はボタンを押すと文字列を送信し,同意にデータを受信し続ける状態を作成する.
非同期通信をするためにCoCoaPodsというものをインストール
CocoaPodsとは
iOSアプリ開発におけるライブラリ管理ツール.CocoaPodsを使用することで,簡単にサードパーティのライブラリをプロジェクトに統合することができる.
CocoaPodsのセットアップ
sudo gem install cocoapods
CocoaPodsのセットアップする.
pod setup
これで,CocoaPodsのインストールと初期設定が完了した.
ここでXcodeでプロジェクトを閉じることを忘れない
その後,プロジェクトのルートディレクトリに移動し,以下のコマンドを実行する.
pod init
これでPodfileが作成された.
Podfileの中身を以下のように編集する.
platform :ios, '17.0'
use_frameworks!
target 'YourAppName' do
pod 'Socket.IO-Client-Swift', '~> 16.0.1'
end
YourAppName
の部分は、実際のプロジェクト名に置き換えること.
変更を保存し,Podfile を閉じる.
その後以下のコマンドを入力し,パッケージをインストールする.
pod install
これでプロジェクトのルートディレクトリに .xcworkspace
ファイルが生成される.以降は.xcworkspace
ファイルを開いてプロジェクトを開くようにしよう.
Swiftでの非同期通信部分の作成
アプリを開いたときのエントリーを書く.
import SwiftUI
@main
struct YourAppNameApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
アプリを開いた瞬間から、非同期通信をするのはよろしくないので,一旦タイトル画面を作成する.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DataView()) {
Text("データ送受信画面へ")
}
}
.navigationBarTitle("メイン画面", displayMode: .inline)
}
}
}
通信をする画面の定義と、その機能の実装.
import SwiftUI
struct DataView: View {
@ObservedObject var client: WebSocketClient
init() {
client = WebSocketClient()
client.setup(url: "ws://websocketのURL")
}
let options = [1, 2, 3, 4, 5]
@State private var selectedOption1 = 1
@State private var selectedOption2 = 1
var body: some View {
VStack {
HStack {
Picker("id1", selection: $selectedOption1) {
ForEach(options, id: \.self) { option in
Text("\(option)")
}
}
.pickerStyle(MenuPickerStyle())
Picker("id2", selection: $selectedOption2) {
ForEach(options, id: \.self) { option in
Text("\(option)")
}
}
.pickerStyle(MenuPickerStyle())
}
Button("スタート") {
let message = ["id1": selectedOption1, "id2": selectedOption2]
let jsonData = try? JSONEncoder().encode(message)
if let jsonString = String(data: jsonData!, encoding: .utf8) {
client.send(jsonString)
}
}
.disabled(!client.isConnected)
}
.onAppear {
client.connect()
}
.onDisappear {
client.disconnect()
}
}
}
このビューではid1,id2に1から5を選択して入れられるようにして,スタート
を押したら送信をする.
このようなJSONデータとなるはずだ,
{
"id1": 1,
"id2": 2
}
リクエスト行うためのデータ通信の定義
import Foundation
class WebSocketClient: NSObject, ObservableObject {
private var webSocketTask: URLSessionWebSocketTask?
@Published var messages: [String] = []
@Published var isConnected: Bool = false
func setup(url: String) {
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
webSocketTask = urlSession.webSocketTask(with: URL(string: url)!)
}
func connect() {
webSocketTask?.resume()
receive()
}
func disconnect() {
webSocketTask?.cancel(with: .goingAway, reason: nil)
}
func send(_ message: String) {
let msg = URLSessionWebSocketTask.Message.string(message)
webSocketTask?.send(msg) { error in
if let error = error {
print(error)
}
}
}
private func receive() {
webSocketTask?.receive { [weak self] result in
switch result {
case .success(let message):
switch message {
case .string(let text):
print("Received text message: \(text)")
DispatchQueue.main.async {
self?.messages.append(text)
}
case .data(let data):
print("Received binary message: \(data)")
@unknown default:
fatalError()
}
self?.receive()
case .failure(let error):
print("Failed to receive message: \(error)")
}
}
}
}
extension WebSocketClient: URLSessionWebSocketDelegate {
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
print("didOpenWithProtocol")
DispatchQueue.main.async {
self.isConnected = true
}
}
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
print("didCloseWith: closeCode: \(closeCode) reason: \(String(describing: reason))")
DispatchQueue.main.async {
self.isConnected = false
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("didCompleteWithError error: \(String(describing: error))")
DispatchQueue.main.async {
self.isConnected = false
}
}
}
これで非同期通信ができる
よく起こりうるエラー
Starscreamモジュールの最小デプロイメントターゲットがiOS 12.0であるのに対し,現在のプロジェクトのデプロイメントターゲットがiOS 1.0に設定されているというエラー.
SocketEngineがWebSocketDelegateプロトコルに準拠していないというエラー
Xcodeのサンドボックスによって引き起こされている.rsync.sambaプロセスがSocketIO.framework内の特定のファイルへの書き込みアクセスを試みているが,サンドボックスによってブロックされているというエラー