20190927のSwiftに関する記事は17件です。

SwiftでCの構造体をキャストしてUNIXドメインソケットを使う方法

SwiftでもCのシステムコールを呼ぶことができるのでうまく使えば、UNIXドメインソケットを使ったSwiftとCのライブラリ間のスレッド通信を行うことができる。

Cでの操作方法

C言語でUNIXドメインソケットを使う場合にはsockaddr_un構造体をsockaddrにキャストしてbindやconnectに渡す必要がある。

int sock = socket(AF_UNIX, SOCK_STREAM, 0);

struct sockaddr_un addr;

strcpy(addr.sun_path, "/tmp/hogehoge");

addr.sun_family = AF_UNIX;

bind(sock, (struct sockaddr *)&addr, sizeof(addr));

Swiftでの操作方法

Swiftで同様にキャストを行なう必要があるが、let sockAddrUn = sockAddr as sockaddr_unのようなキャストはできない。
Swiftの場合は、

sockaddr_un構造体 → UnsafeRawPointer化 → assumingMemoryBoundを使ってsockaddr構造体に変換

のようなステップを踏む必要がある。

具体的には、次のように書く

let sock = socket(AF_UNIX, SOCK_STREAM, 0)

let sockPath = "/tmp/hogehoge"

var sockAddrUn = sockaddr_un()
sockAddrUn.sun_family = sa_family_t(AF_UNIX)
sockPath.withCString { path in
    Darwin.memcpy(&(sockAddrUn.sun_path), path, Int(strlen(path)))
}

let rawPtr = UnsafeRawPointer(&sockAddrUn)
let sockAddrPtr = rawPtr.assumingMemoryBound(to: sockaddr.self)

Darwin.bind(sock, sockAddrPtr, socklen_t(MemoryLayout<sockaddr_un>.stride))

こんな書き方は絶対ダメ

因みに最初に構造体のキャスト方法がわからず変数を二つ用意してmemcpyをするという荒技を行なっていた。

let sock = socket(AF_UNIX, SOCK_STREAM, 0)

let sockPath = "/tmp/hogehoge"

var sockAddrUn = sockaddr_un()
sockAddrUn.sun_family = sa_family_t(AF_UNIX)
sockPath.withCString { path in
    Darwin.memcpy(&(sockAddrUn.sun_path), path, Int(strlen(path)))
}

var sockAddr = sockaddr()
Darwin.memcpy(&sockAddr, &sockAddrUn, MemoryLayout<sockaddr_un>.stride)

Darwin.bind(sock, &sockAddr, socklen_t(MemoryLayout<sockaddr_un>.stride))

sockaddr_unの分だけコピーしてbindにsockaddr_unの長さを渡すのでビルドは通るし、動きはするがReleaseビルドで最適化設定がされるとsockaddrからはみ出た分のデータが消えてしまい、bindがEAFNOSUPPORTエラーを返すようになる。

A構造体 → UnsafeRawPointer → assumingMemoryBoundでB構造体 でキャストするようにしましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSのLocalDB系をまとめてみた

概要

オブジェクトの永続化するためのローカルDB系についてメリデメなどを諸々調べてみた!
https://jmty.kibe.la/@misato_naruse/5483 こっちも書いたので合わせて

比較対象

  • UserDefaults
  • SQLite
    • CoreData
    • SQLiteマッパーライブラリ
  • Realm

▼CoreDataとSQLiteのマッパーライブラリとの違い

 CoreDataはSQLiteのマッパーとはちょっと違う

前提

  • ジモティーではUserDefaultsを使用
  • ↑使用用途:ユーザデータや検索情報の保存

▼UserDefaults

概要

  • 2008年:iOS 2.0+
  • アプリやユーザのデフォルト状態や動作方法等の保存を主目的とされる

特徴

  • key=valueでデータを保存する
    • KVO形式のため、オブジェクトを直接呼び出さずにデータの保存と取得ができる
  • plistにXML形式で保存される
  • スレッドセーフ
  • アプリ起動時にメモリに読み込まれ、以降キャッシュを利用するため処理が高速

他との違い

  • データの絞り込みができない
    • SQLiteのWHEREやRealmのfilterなどのように使用できない
  • 大規模なものには向いてない
    • key=valueのためカラム等の概念とは異なるため、 ユーザ1、ユーザ2..ユーザ100と増えていくデータの保存には上に理由も合わせて向いていない
  • 保存できる型が決まっている
    • NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary
    • それ以外を保存したい場合はNSDataに変換する

基本の書き方

// 保存
UserDefaults.standard.set("保存したい値", forKey: "data")
// 取得
let date = UserDefaults.standard.integer(forKey: "data")

▼SQLite

CoreData

概要

  • 2010年:iOS 3.0+
  • データの構造が複雑で、アプリからユーザーの入力した情報を保存したり、任意の条件で入力した情報の検索結果を返したい場合などにUserDefaultsではなくこちらを使用する

特徴

  • swiftライクでコードが書けながらもSQLiteの機能を使うことができる
    swift
    let fetchRequest = NSFetchRequest(entityName: "RegistData")
    let registData = NSPredicate(format: "id = %d", 1)
    fetchRequest.registData = registData
    do {
    let users = try appDelegate.managedObjectContext.executeFetchRequest(fetchRequest) as! [User]
    print(users) // データの絞り込み
    }

  • .xcdatamodeldとして中身を可視化でき、追加もコードを書かずにできる

  • ↑でエンティティを追加すると自動的に以下のようなモデルクラスが作成される

import CoreData

class RegistData: NSManagedObject {
    @NSManaged var id: NSNumber?
    @NSManaged var registData: String?
}
  • メモリ節約機能(フォールト)が備わっている
    • プロパティにアクセスした際に初めてデータを読み込む
    • 節約にはなるが、逆にデータがうまく更新されないというデメリットもある
  • 使いやすくするMagicalRecordという外部ライブラリがある

マイグレーションやり方

軽微な変更

  • LightWeightMigration
    ```swift
    lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "coreDataTest")
    let description = NSPersistentStoreDescription()
    description.shouldMigrateStoreAutomatically = true
    description.shouldInferMappingModelAutomatically = true
    container.persistentStoreDescriptions = [description]

    container.loadPersistentStores(completionHandler: { _, error in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
    

    }()
    ```

  • 既存スキーマを変更したい場合:新規プロパティ追加だけであれば↑を書けば以降の処理は自動で行ってくれる

複雑な変更

  • →ManualMigration
  • 上のコードを記述+マッピングマイグレーション(新しくスキーマを作成する)を行う
    • 新しく追加したEntityにSubclassとして既存Modelを紐づける

他との違い

⇆UserDefaults

  • スレッドセーフでない
    • CoreDataのデータオブジェクトを管理するクラス(ManageObjectContext)
    • そのため各スレッドでManageObjectContextを作成する必要がある #### ⇆SQLite
  • 自身が自身を管理して永続性を保証するオブジェクトを作り出す
    • 上で書いたモデルクラスのこと。EntityとModelが紐づいていることが保証されている #### ⇆Realm
  • どっちもマイグレーションは基本自動でやってくれるが、CoreDataはModelと紐づいているので既存データの

基本の書き方

/// AppDelegate.swift
func applicationWillTerminate(_ application: UIApplication) {
    self.save()
}

func save() {
    let context = persistentContainer.viewContext
    if context.hasChanges {
        do {
            try context.save()
        } // 略
    }
}

/// VC
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var registData: RegistData?
var registDataArray: [RegistData] = []

//  保存
@IBAction func tapRegisterButton(_ sender: UIButton) {
    self.registerData = RegisterData(context: self.context)
    self.registerData?.registerData = "保存したい値" 
    self.registerDataArray.append(self.registerData!)
    (UIApplication.shared.delegate as! AppDelegate).save()
}

// 取得
do {
    self.registDataArray = try self.context.fetch(RegistData.fetchRequest())
} // 略

SQLiteマッパーライブラリ

概要

  • iOS標準搭載されているDBMSライブラリ
  • 基本的にFMDB,SwiftData,GRDB.swiftなどのラッパーライブラリを合わせて使用する
  • ↑これらのライブラリを使わずに直接sqlite3.hをインポートして記述することも可能

■FMDB

  • podsではなくローカルインポート
    • +Bridging-Header.h経由で使用する必要がある
  • 結構SQLite寄りのコード
  • マイグレーションに関しての記事が全然見当たらなかったけど、別途専用のライブラリを使うとかは必要...? ```swift /// Bridging-Header.h #import "FMDatabase.h" を追加

/// Repository
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
var dirPath = paths.first! as String
var path = dirPath.stringByAppendingPathComponent("xxx.db")
let db = FMDatabase(path: path)

// 取得
var resultSet = db.executeQuery("SELECT * FROM notes", withParameterDictionary: nil)
while resultSet.next() {
var data = resultSet.stringForColumn("data")
}

// 保存
var now = NSDate()
var updateSQL =
"UPDATE notes SET name = :name,price = :price,updated_at = :updated_at"

var params = ["data": "保存したい値"]
self.db.executeUpdate(updateSQL, withParameterDictionary: params)
```

■SwiftData

  • FMDBの次に主流ぽかったけど、swift3以降に対応してないみたい

■GRDB.swift

  • 2018年にでた新しめのライブラリ
  • cocoaPods
  • マイグレーション機能を搭載
  • KVO形式で保存取得できる
  • RxGRDBってのもあるみたい ```swift /// podsfile use_frameworks! pod 'GRDB.swift'

/// Repository
let inMemoryQueue = DatabaseQueue()
let queue = DatabaseQueue(path: "path")
let pool = DatabasePool(path: "path")
let queue = DatabaseQueue()
do {
try queue.inDatabase{ (db) in
try db.create(table: User.databaseTableName) { (t) in
t.column("id", .integer).primaryKey(onConflict: .ignore, autoincrement: true)
t.column("name", .text).notNull()
t.column("age", .integer).notNull()
t.column("score", .integer).notNull()
t.column("created_at", .date).notNull()
}
// 取得
try User.fetchOne(db, key: data)
}
}

// Rxで記述
User.all().rx
.fetchAll(in: queue)
.subscribe(onNext: { (users) in
// ここで処理
})

// ageが20のユーザーのみに絞る
User.filter(User.Columns.age == 20)
.rx.fetchAll(in: queue)
.subscribe(onNext: { (users) in
// ここで処理
})

/// SQLiteっぽく書くのもOKらしい
do{
try GlobalData.dbQueue.inDatabase { db in
let rows = try Row.fetchCursor(db, "SELECT * FROM hogehoge_table")

while let row = try rows.next() {
if row.value(named: "hogehoge_field1") != nil {
let hogehoge_int = row.value(named: "hogehoge_field1") as Int
let hogehoge_string = row.value(named: "hogehoge_field2") as String
}
}
}
}
```

▼Realm

概要

  • 2015年〜
  • SQLiteを使わない独自のDBライブラリ(NOSBD(NotOnlySQL))

特徴

  • 処理速度が速い
  • ドキュメントが手厚い
  • Android版もあるクロスプラットフォーム

マイグレーションやり方

let config = Realm.Configuration(schemaVersion: 1) // 新しいスキーマバージョンを指定する
let realm = try! Realm(configuration: config)

// プロパティの新規追加だけであれば以下の処理は不要
Realm.Configuration.defaultConfiguration = Realm.Configuration(
  schemaVersion: 1,
  migrationBlock: { migration, oldSchemaVersion in
    // 初めてマイグレーションを実行するのでoldSchemaVersionは0です
    if (oldSchemaVersion < 1) {
      // 名前の変更は`enumerateObjects(ofType: _:)`の外側で実行する必要があります。
      migration.renameProperty(onType: Person.className(), from: "yearsSinceBirth", to: "age")
    }
  })
  • Modelの中身が新規プロパティ追加だけであれば以降の処理は自動で行ってくれる
    • 旧データを自動で移行する
  • もしAとBを足したCを新規プロパティ追加したい場合など既存データを変更したい場合はmigrationBlockを実行する

基本の書き方

  • Pods/Carthegeで導入 ```swift // 保存 /// AppDelegate.swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Reaml暗号化 let key = "xxxxxxxxxxx" let configuration = Realm.Configuration(encryptionKey: key.data(using: .utf8, allowLossyConversion: false)) Realm.Configuration.defaultConfiguration = configuration }

/// Model
import RealmSwift
class RegisterData: Object {
dynamic var id = UUID().uuidString
dynamic var registData: String?
override class func primaryKey() -> String? {
return "id"
}
}

/// Repository
import RealmSwift
// 保存
func save() {
let registData: RegistData?
do {
let realm = try Realm(configuration: Realm.Configuration.defaultConfiguration)
try realm.write {
registData.date = "保存したい値"
}
}
}
// 取得
func get() {
let registData: RegistData?
do {
let realm = try Realm(configuration: Realm.Configuration.defaultConfiguration).object(self).first
try realm.write {
registData.date = realm.data
}
}
}
```

比較

SQLiteとRealm

  • →大量のデータを扱うにはRealm、少量ならばSQLiteの方が早い

参考

CoreDataとRealm

  • どっちもスレッドセーフじゃないため各スレッドでオブジェクトを生成する必要がある

なるせの考察

  • UserDefaults:今くらいの目的であれば必要十分かも
  • CoreData:swiftyにSQLite使えるのはそさそ。スキーマの管理も可視化できるのは個人的に好き
    • ただ、あんま理解してないけどスレッド周りが怖そうだし、導入自体のハードル高そう
  • FMDB(SQLite):古い文献しかなくて辛かったし、マイグレーションあたりが未知数
  • GRDB.swift(SQLite):良さそう。CoreDataよりも簡単そう
  • Realm:大量のデータを運用するわけじゃないから、あんま意味なさそうだ。書くのは楽そう

→結論:UserDefaultsでいいのかも..(´・ω・`)

最後に

難しいよう.....(´;ω;`)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

よちよちSwiftエンジニアのコードレビュー備忘録

これはSwift歴4ヶ月の自分が、ベテランiOSエンジニアの方にがっつりコードレビューしていただいた際の備忘録です。今後、良くないコード書かないために、また自分がレビューする際に適切に指摘できるようになるために、内容をまとめることにしました。

コーディングルールは、組織によって違いがあると思います。これから紹介するレビューでのルールが絶対というわけではありません。この内容を参考にしていただき、より良いコーディングルール、レビューの行ってくれる方々が少しでもいてくれれば幸いです。

アクセス修飾子を適切に設定する

Swiftではアクセス修飾子(public、internal、private、fileprivate、open)を省略した場合、internalを同じ意味になります。internalは「同一モジュール内であればアクセス可能」です。internalだとアクセス範囲が広すぎる場合があるので、必要に応じて適切に設定します。

以下、例です。

UIViewControllerのUIのOutletにprivateをつける

class StartViewController: UIViewController {
    /// タイトルのラベル
    @IBOutlet private weak var titleLabel: UILabel!
    /// スタートボタン
    @IBOutlet private weak var startButton: UIButton!
}

titleLabel、startButtonそれぞれに「private」が設定されています。privateがない場合、ViewControllerの外からtitleLabel、startButtonの値が書き換えることが可能になっています。そういった要件が必要でない場合は「private」などの修飾子を設定し、値を書き換えられないようにします。

継承してほしくないclassにはfinalをつける

final修飾子をclassに設定すると、設定されたclassは継承が不可になります。継承を前提としないclassにfinalを付けないと、意図しないところで継承が行われることで、結果コードの可読性、保守の難易度が不必要に上がります。そういった事態を防ぐため、継承しないclassにはfinal修飾子を設定します。

// NextViewControllerは継承不可
final class NextViewController: UIViewController {
}

不要なコメントアウトされたコードは削除する

実装を進めていく中で、不必要になったコードをコメントアウトすることがあるかと思います。しかしコメントアウトされたコードが残っていると、時間が経つうちに、そのコードがアプリケーションにとって本当に必要なのか、コードを見ても判断できなくなっていきます。結果コードの可読性が落ち、保守が難しいプログラムになってしまいますので、不要なコメントアウトされたコードは積極的に削除します。

変数宣言の際、letとvarでは基本letを使う

letで宣言された変数は、一度値を設定すると後から書き換えることができない変数です。

let a = 1
a = 2 // error

反対にvarで宣言された変数は、後でも値を書き換えることができます。 

var b = 2
b = 10 // ok!

値の再代入の必要がないにもかかわらずvarを使ってしまうと、コードを読む際「この変数は値が変わる可能性があるから、値の変化を追いかける必要がある」と認識させてしまう可能性があり、不必要にコードの可読性が下がります。変数宣言の際には、基本的にはletを使うようにします。

デコード処理で「!」は使わない

jsonのデコード処理の際、値のキャストが必要になったことがあるのですが、キャストに失敗したときにアプリが落ちるような実装を行ってました。

extension Coordinate {
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // とりあえずDoubleで変換できない値がAPIから来た場合はクラッシュしてもらう
        x = Double(try container.decode(String.self, forKey: .x))!
        y = Double(try container.decode(String.self, forKey: .y))!
    }

}

しかしデコード処理のエラー処理の適切な書き方をレビューで教えて頂きましたので、今後はこの書き方で実装を行います。

extension Coordinate {
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        // キャストに失敗した場合はDecodingError.typeMismatchの例外を投げる
        // エラーのハンドリングが丁寧でわかりやすい
        let rawX = try container.decode(String.self, forKey: .x)
        guard let _x = Double(rawX) else {
            throw DecodingError.typeMismatch(
                String.self,
                DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Can not convert to Double type."))
        }
        x = _x

        let rawY = try container.decode(String.self, forKey: .y)
        guard let _y = Double(rawY) else {
            throw DecodingError.typeMismatch(
                String.self,
                DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Can not convert to Double type."))
        }
        y = _y
    }
}

Binderを積極的に利用する

Binderを利用しない場合ViewModelからデータを取得しUIに渡すコードは、一例ですが以下のような書き方になります。複雑になればなるほど、viewDidLoadが肥大化してしまい、可読性が落ちます。

override func viewDidLoad() {
    super.viewDidLoad()

    let output = viewModel.transform()

    // 処理が長い...
    output.response.drive(onNext: { [unowned self] res in
        self.dateLabel.text = res.date
        self.dayOfTheWeekLabel.text = res.dayOfTheWeek
        self.hourLabel.text = res.hour
        self.salaryLabel.text = res.salary
    }).disposed(by: disposeBag)

}

Binderを利用することで、viewDidLoadのコードの見通しが良くなり、可読性が上がります。

override func viewDidLoad() {
    super.viewDidLoad()

    let output = viewModel.transform()

    // bindingの記述がシンプルに
    output.response.drive(binding)
            .disposed(by: disposeBag)

}

// binderの処理を別関数で実装
private var binding: Binder<Response> {
    return Binder(self, binding: { (vc, res) in
        vc.dateLabel.text = res.date
        vc.dayOfTheWeekLabel.text = res.dayOfTheWeek
        vc.hourLabel.text = res.hour
        vc.salaryLabel.text = res.salary
    })
}

さいごに

エンジニア4年目にして初めてコードレビューしていただける機会を頂けたのですが、本当に勉強になりましたし、レビューして頂いたエンジニアの方には感謝しかありません。この場をお借りして御礼申し上げます。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

URLSession系の優良記事まとめ

JSON

非エンジニアに贈る「具体例でさらっと学ぶJSON」 | DevelopersIO
オブジェクト, 配列, キー, らへんがごっちゃな方はこの記事から
[Swift3] JSON文字列の作成 | DevelopersIO
Swift3でJSONパースを行う - Qiita

URLSession

Swiftで遊ぼう! - 482 - URLSessionの基礎 - Swiftで遊ぼう! on Hatena
→サンプルなどを用いた説明
【Swift】URLSessionまとめ - Qiita
→関数などのまとめ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xcode11で古いプロジェクトテンプレートを利用する

動機

先日iOS13がリリースされましたが、アプリを新規に始める場合に、iOS11〜1OS13あたりをターゲットに設定することは、まだまだあるかと思います。

開発では、新しいXcode11をメインに使用したいのですが、Single View Appテンプレートでプロジェクトを新規作成した場合、そのままターゲット設定をiOS13未満にするとScenes関連のclassが使用できないためコンパイルエラーがでます。
Scenesとは、iOS13で登場したひとつのアプリプロセスに対してマルチUI(マルチウインドウ)をサポートするための仕組みのようで、現状では主にiPadでの用途があるようです。これによってSceneDelegate.swiftと関連設定が、デフォルトのプロジェクトに含まれているためです。

ターゲットiOSバージョンによってはXcode11で、Scenes関連の設定が含まれないXcode10バージョンのSingle App Viewテンプレートもまだまだ使いたい時があるなあ、と思ったのでXcode10からコピーしてXcode11からも使えるようにしておけば便利かもと思った次第です。

というわけで、当記事ではXcodeのカスタムテンプレート追加手順を、表題の件に絞ってまとめておきます。

そもそもそういう頻度が少ない場合

そんなにプロジェクト新規作成の頻度が少ない場合、後述するテンプレートのコピーをしなくても、Xcode10&11併用環境であればiOS12〜以下をターゲットする場合、普通にXcode10.3でプロジェクトを新規作成して開始すれば大丈夫です。そうでない場合は、新規作成のためだけに古いXcodeを保持しておかなければならない、というデメリットがあります。(マイグレーションが必要な時などに役には立つのですが)

または手作業でXcode11で作成したプロジェクトのSceneDelegate.swiftなどを消すことも可能。しかし何度も行う場合には省略したいとも思います。
- Xcode 11 - Opt out of UISceneDelegate/SwiftUI on iOS 13
- How to remove SceneDelegate code from iOS 13 project?

テンプレートのコピー手順

では、Xcode10から古いタイプのSingle View Appのプロジェクトテンプレートをコピーしてきて独自のテンプレートとして、Xcode11で使えるようにします。

作業後の完成イメージのキャプチャですが、作業後にはプロジェクト作成ウィザードに画像のような形で選択出来るようになります。

スクリーンショット 2019-09-27 17.23.21.png

カスタムテンプレート用のフォルダを作成

Xcode独自テンプレートは以下のパス配下に追加する決まりになってます。

# 独自Projectテンプレートのパス
~/Library/Developer/Xcode/Templates/Project\ Template
# 独自Fileテンプレートのパス
~/Library/Developer/Xcode/Templates/File\ Template

テンプレートを置くフォルダを作成。テンプレートのカテゴリ表示はフォルダ名が反映されるのでMy Templatesという名前のフォルダを作成することにします。

$ mkdir -p ~/Library/Developer/Xcode/Templates/Project\ Templates/My\ Templates

Xcode10.3からのテンプレート複製

フォルダの準備ができましたので、次は中身です。プロジェクト新規作成ウィザードに表示されるテンプレートはXcode.appパッケージを開いた中に入ってますので、そいつをコピーします。Xcode10.3がない場合はダウンロードしてくる必要があります。Xcode.appを右クリックからパッケージの内容を表示で辿っていくか、以下のコマンドを実行して開きます。

# パスの中の`Xcode_10.3.app`部分は適宜変更してください。
open /Applications/Xcode_10.3.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project\ Templates/iOS/Application/

Finderで操作するか、以下のコマンドを実行しXcode_10.3.appのパッケージの中からSingle View App.xctemplateをカスタムテンプレート用フォルダへとコピーします。

cp -r /Applications/Xcode_10.3.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project\ Templates/iOS/Application/Single\ View\ App.xctemplate ~/Library/Developer/Xcode/Templates/Project\ Templates/My\ Templates/Single\ View\ App.xctemplate

追記(2019/09/29):
[重要] テンプレートのIdentifierがXcode11のオリジナルと重複していると、ウィザードにオリジナルの方のSingle View Appアイコンが表示されなくなってしまっていたので、
コピーした方のSingle View App.xctemplateの中のTemplateInfo.plistファイルを開いて(Xcodeが起動するはず)Identifier部分を何か好きな文字列に編集を行います。(↓画像の例ではオリジナルの文字列に_Xcode10.3をを付加しました。)

スクリーンショット 2019-09-29 17.06.52.png

作業は以上で完了です。

確認

Xcode11を開いて、プロジェクト新規作成のウィザードを起動します。

  • File -> New -> Project...を選択か、または、ショートカットキーでCmd + Shift + Nです。

Xcode10からカスタムテンプレートとしてコピーしたSingle View AppMy Templates項目に表示されており、選択するとScenes関連なしのプロジェクトを開始出来るようになっているはずです。

まとめ

Xcodeでは、最新のAPIに合わせたテンプレートへ変わることは珍しくないように思いますので、過渡期期間では今後も役に立つかもしれません。

参考?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BrightFuturesのレスポンス結果を表示するのはonSuccessのスコープ内でやろう

BrightFuturesを使用した非同期処理の場合、
APIから取得した結果を表示するためにUILable等に設定するのは、onSuccessのスコープ内でやること。
そうしないと非同期処理のため、値がまだ帰ってきていない内に描画処理が走り、想定通りに反映されない。

普段JavaScriptで書いてるときは自然としてそうだが失念してた。
Async/Awaitほしい、、

class SomeViewController: AutoLayoutViewController {

  private var someObj:SomeObject = SomeObject()
  private var someLabel: UILabel

   override func viewDidLoad() {

   [BrightFuturesを使った非同期処理]
     .onSuccess { success in
       someObj = success

       // ここが正解
       self.someLabel.text = success.text
     }

   // ここでやると反映されない
   // self.someLabel.text = success.text

  }

}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react-native Swift共同開発メンバー募集!!

共同開発メンバー募集
性的嗜好によってマッチングするマッチングアプリ『sheep』開発中!!
1:現在創業間もない会社でアプリを開発しています。
2:我々はこの領域で世界一を目指しています!
3:マッチングアプリを一緒に開発してくれるエンジニアの方を募集しています
4:現在、資金調達中です。(https://yay2019.work/)

アプリ開発したい方、
ゼロイチで開発したい方
グローバルカンパニーを一緒に作りたい方
まずはランチなど一緒に行きませんか?
一緒にグローバルカンパニーを作りましょう!!
サービスの概要は下記からご覧ください。

https://drive.google.com/file/d/1_lz-XKQBn6tWrav2fKGLztuZa1-qptm5/view?usp=sharing![sheep.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309610/39640d66-a52a-a6fb-d2a7-8e566ed5e9ca.jpeg)
sheep.jpg

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactorのtransform内でPublishSubjectをマップした挙動をテストした際にハマったこと

transform(mutation:)内の処理

以下のようにeventをマップしたReactor内のtransformの挙動をテストする際につまづいたポイントを書き残しておきます。

import ReactorKit
import RxSwift

final class HogeReactor: Reactor {
    ...
    func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
        return .merge(
            mutation,
            provider.accountService.event.flatMap { event -> Observable<Mutation> in
                switch event {
                case let .updateProfile(user):
                    return .just(.setUser(user))
                }
            }
        )
    }
    ...
}

eventの実装

ちなみにprovider.accountService.eventは以下のようにPublishSubjectで実装されています。

enum AccountEvent {
    case updateProfile(user: User)
}

protocol AccountServiceType {
    var event: PublishSubject<AccountEvent> { get }
    ...
}

final class AccountService: AccountServiceType {
    let event = PublishSubject<AccountEvent>()
    ...
}

テスト失敗

これをテストする際に単純にstreamを流しただけだとテストがうまくいきませんでした。

@testable import Hoge
import Nimble
import Quick

class HogeReactorSpec: QuickSpec {
    ...
    describe("state.user") {
        context("when receives updateProfile event", closure: {
            it("is updated user", closure: {
                let updatedUser = TestData.testUser()
                stubServiceProvider.accountService.event.onNext(.updateProfile(user: updatedUser))

                // Failed:stateにupdatedUserがセットされず
                expect(hogeReactor.currentState.user) == updatedUser  
            })
        })
    }
}

一度もtransform(mutation:)が呼ばれてないため、eventのマップがそもそもできていないのが原因ぽいです。

テスト通過

transform(mutation:)を呼ぶために適当にactionを呼んでやるとうまくいきました。

@testable import Hoge
import Nimble
import Quick

class HogeReactorSpec: QuickSpec {
    ...
    describe("state.user") {
        context("when receives updateProfile event", closure: {
            it("is updated user", closure: {
                // NOTE: Call any action to set up transformed mutation
                hogeReactor.action.onNext(.load)

                let updatedUser = TestData.testUser()
                stubServiceProvider.accountService.event.onNext(.updateProfile(user: updatedUser))

                // Pass:stateにupdatedUserがセットされる
                expect(hogeReactor.currentState.user) == updatedUser
            })
        })
    }
}

本当にこれが正しいのかわかりませんが、とりあえず動いたので良しとしてます。
知見ある方いましたらコメントで教えていただけると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xcode11で作成したプロジェクトからSceneに関する機能をオミットする

Xcode11とUISceneとUIWindowにまつわるトラブルシュートに追われている。
個別のケースに分解してお届け。

背景

Xcode10で作成したプロジェクトをXcode11で開いた場合と、Xcode11で作成したプロジェクトで UIWindowの振る舞いが異なった。この違いはプロジェクトがScene Based Lifecycleに対応しているかどうかに左右されることが分かった。

対応

簡単に言うと以下を実施すればよい。

  • AppDelegatewindowを生やす
  • Info.plistからSceneに関する項目を取り除く
  • AppDelegateからSceneに関する項目を取り除く

これにより、iOS13で動かしてもAppDelegateを基本としたアプリのように動作する。(View Hierarchyを見るとUISceneは健在なので、互換性を保っているだけっぽい)

AppDelegatewindowを生やす

Sceneに関連する物事をオミットしたあと、AppDelegateにwindowプロパティが無いとブラックアウトしてしまうので、生やす。

// AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
}
// AppDelegate.h
@interface AppDelegate: UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;

@end

Info.plistからSceneに関する項目を取り除く

Info.plist から [Application Scene Manifest] の項目を取り除く。

ここまでの対応だけだと、iOS13でアプリを実行した場合に表示すべきStoryboardの情報が見つけられずブラックアウトする。

AppDelegateからSceneに関する項目を取り除く

AppDelegateから、Scene Based Lifecycleに関するメソッドを取り除くことで、オミットが達成される。

具体的には AppDelegate application:configurationForConnectionSceneSession:を取り除けば良い。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Xcode11]カスタムトランジションが遷移しなくなった時の対処法

久々の投稿です。パッと見ではヒットしなかった話題なのですが、ハマる人もいる気がするので軽めに書きます。

何が起こったか

UIViewControllerTransitioningDelegateを利用してトランジションアニメーションを導入していたアプリをXcode11でビルドしたところアプリが落ちないのに遷移が正常に動作せず、アプリが操作不可になる現象が発生した

前提知識

iOS13からモーダルのデフォルトの挙動がハーフモーダルのような形に変化したのはよく知られていることだと思います。
これにあわせてsegueの遷移には
image.png
このように今までのTransitionの設定に加えPresentationという項目が増えているのがわかります。ここを設定することで今までのようなフルスクリーンのモーダルに変更したりすることができます。

解決法

この増えた設定項目が肝でした。カスタムトランジションの場合、このPresentationとともに増えたコンテキストの概念が迷子になるらしくそれが影響して遷移がうまく完了しないということのようでした。

ですのでシンプルにこのようなコードを入れてあげると解決します。

let viewController = NextViewController.instatiate()
viewController.modalPresentationStyle = .currentContext
present(viewController, animated: true, completion: nil)

このように明示的に modalPresentationStyle を設定すれば解決しました。
ちなみに僕の場合はcurrentContextもしくはfullScreenで動くようになりましたが、これはトランジションの種類にもよるかと思います。

ぜひ同じ症状にあった人は様々な項目を試してみてください。(本当は意味的にどれを選ぶべきとかありそうですが一旦...笑)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ios開発、addTargetメソッドを調べてみた

addTargetの引数

target アクションメソッドが呼ばれるオブジェクトを指定。
action 呼ばれるメソッドを記述
controlEvents UIControlEventsの定数を記述

controlEventsに入るUIControlEventsには以下がある

touchDown コントロール内でのタッチダウン
touchDownRepeat 複数回のタッチダウンイベント
touchDragInside コントロール内で指がドラッグされた
touchDragOutside コントロール外で指がドラッグされた
touchDragEnter コントロール内へ指がドラッグされた
touchDragExit コントロール内から外へドラッグされた
touchUpInside コントロール内でタッチアップされた
touchUpOutside コントロール外でタッチアップされた
touchCancel 不明
valueChanged 値の変化
primaryActionTriggered タッチアップと同じ?
editingDidBegin TextFieldでの編集開始
editingChanged TextField内の文字変化や入力
editingDidEnd 画面外タップでのTextFieldの編集終了
editingDidEndOnExit リターンキーによるTextFieldの編集終了
allTouchEvents すべてのタッチイベント
allEditingEvents TextFieldに関するイベント
applicationReserved 予備だと思う
systemReserved 予備だと思う
allEvents 全てのイベント

参考

https://pg-happy.jp/swift-addtarget.html#toc_id_1

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クロージャとは

クロージャとは

クロージャとは、文の中に直接埋め込むことのできる命令の塊のこと!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xcode11で作成したプロジェクトのDeployment Targetを下げるとブラックアウトする問題の対処法

Xcode11とUISceneとUIWindowにまつわるトラブルシュートに追われている。
個別のケースに分解してお届け。

現象

Xcode11で作成したプロジェクトをiOS13未満の環境で実行するとブラックアウトしてしまう。

再現手順

  1. Xcode11にて、新規プロジェクトの作成を行う。
    • test off
    • SwiftUI off
  2. Project設定を開き、Deployment Targetを iOS12 にする。
    • iOS13未満ならなんでもいい
  3. iOS シミュレーターのうち、iOS12のもので実行する。
    • iOS13未満ならなんでもいい

原因

コンソールに「AppDelegateUIWindow *window が必要だ」と説明されている。

2019-09-27 07:34:57.175062+0900 sample-xcode11[52812:454025] [Application] The app delegate must implement the window property if it wants to use a main storyboard file.

iOS13を対象としたアプリケーションでは、root windowは SceneDelegateが所有しているので、AppDelegateには必要ないというAppleのおせっかいである。

対応

  • AppDelegatewindowを生やす。
// AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
}
// AppDelegate.h
@interface AppDelegate: UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;

@end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】Sign In with Apple 実装してみた。

Sign in with Appleの必須化

2019/6/3に更新されたレビューガイドラインに以下の記述がありました。

Sign in with Apple will be available for beta testing this summer. It will be required as an option for users in apps that support third-party sign-in when it is commercially available later this year

※ 公式:https://developer.apple.com/news/?id=06032019j

どうやらログイン連携にサードパーティを用いているアプリには、Sign in with Apple の実装が必須になるよう…。
これは早めに試してみようということで早速実装してみました!!

環境

  • Xcode11 以上
  • 対応端末iOS13 以上

今回は自社の HiNative を使ってお試し実装してみました。
(ちゃっかり宣伝: WEBApple Store

実装

ステップはたったの3つ!結論からいうと超簡単でした。

  1. Xcodeの設定
  2. Providerを生成してリクエスト
  3. Delegateでその後の処理

1. Xcodeの設定

TARGET > Signing & CapabilitiesSign in with Apple を追加します。
スクリーンショット 2019-09-27 0.41.57.png スクリーンショット 2019-09-27 0.42.15.png

これでXcodeの設定は完了です。

1.2. (ステップには記載なし) 適当にボタン作ってね

ボタンとActionを用意してください〜。
ボタンのレイアウトは公式から指定があるので、こちらの公式を参考に!

2. Providerを生成してリクエスト

ViewController.swift
import AuthenticationServices

@objc
func authorizationAppleID() {
  if #available(iOS 13.0, *) {
    let appleIDProvider = ASAuthorizationAppleIDProvider()
    let request = appleIDProvider.createRequest()
    request.requestedScopes = [.fullName, .email]

    let authorizationController = ASAuthorizationController(authorizationRequests: [request])
    authorizationController.delegate = self
    authorizationController.performRequests()
  }
}

if #available(iOS 13.0, *) なので、ボタンの表示非表示も気をつけないといけないですね。

3. Delegateでその後の処理

ViewController.swift
extension ViewController: ASAuthorizationControllerDelegate {
    @available(iOS 13.0, *)
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            // 取得できる値
            let userIdentifier = appleIDCredential.user
            let fullName = appleIDCredential.fullName
            let email = appleIDCredential.email
        }
    }

    @available(iOS 13.0, *)
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // エラー処理
    }
}
補足

ASAuthorizationAppleIDCredential は 2 で生成した ASAuthorizationAppleIDProvider のリクエストが成功した際に呼ばれます。
今回はこの実装のみですが、他にも iCloud Keychainのクレデンシャル情報 を用いた実装もあるようです。
(詳しく知りたい方は下記の参考記事を見てみてください!)

動かすとこんな画面が出てきます

スクリーンショット 2019-09-27 1.20.10.png スクリーンショット 2019-09-27 1.20.42.png スクリーンショット 2019-09-27 1.21.14.png

FaceIDでログインできるので本当に楽…!!

参考にさせて頂いた記事

【iOS】対応必須かも?Sign In with Appleまとめ(第一報)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIでWeb APIから取得した結果を表示する

SwiftUIのViewを更新するためにはデータに応じて、適切な状態の宣言をする必要があります。

ここでは、Web APIと通信し結果を表示するという、よくあるアプリをSwiftUIで実装することで、その方法を紹介します。
必要な実装は以下の2つです。

  1. ObservableObject に準拠するクラスを作成し変更の通知を発行
  2. SwiftUIのViewで通知を監視

それでは、GitHubのフォローしているユーザー一覧を表示するアプリを例にし、実装していきます。

ObservableObjectに準拠するクラスを作成し変更の通知を発行

通信を行い取得したユーザーを保持するクラスを作成し、ObservableObject に準拠させます。(準拠させると言ってもクラスであれば特に何かを実装する必要は無いです)
そして、保持したユーザーの変更を通知するためには、 @Published 属性をプロパティに付与します。

import Foundation
import Combine

class FollowingUserStore: ObservableObject {
    @Published var users: [User] = []

    init() {
        load()
    }

    func load() {
        let url = URL(string: "https://api.github.com/users/maoyama/following")!
        URLSession.shared.dataTask(with: url) { data, response, error in
            DispatchQueue.main.async {
                self.users = try! JSONDecoder().decode([User].self, from: data!)
            }
        }.resume()
    }
}

struct User: Decodable, Identifiable {
    var id: Int
    var login: String
}

たったこれだけの実装で、通信を行いユーザーを取得できた際の通知を発行することが可能になります。

SwiftUIのViewで通知を監視

今回のような、あるViewだけで使用するデータの通知を監視すれば良い場合はProperty WrapperのObservedObject を使う方法が適切です。
ObservableObject をプロパティに保持し、@ObservedObject を付与します。

import SwiftUI

struct ContentView: View {
    @ObservedObject var store = FollowingUserStore()

    var body: some View {
        List(store.users) { (user) in
            UserRow(user: user)
        }
    }
}

struct UserRow: View {
    var user: User

    var body: some View {
        Text(user.login)
    }
}

これで FollowingUserStore.users の変更を監視できます。

まとめ

これだけの実装でWeb APIから取得した結果を表示することができました。

Sep-27-2019 00-01-57.gif

また、適切な宣言をするだけで、データの同期を手動ですることなく、SwiftUIとCombineが自動で行ってくれています。これにより、コードがとてもシンプルで読みやすくなることが期待できそうです。

参考資料

https://developer.apple.com/documentation/swiftui/state_and_data_flow
https://developer.apple.com/videos/play/wwdc2019/226/
https://developer.apple.com/documentation/combine/observableobject

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift入門】オプショナル整数型の「?」や「!」について、整理してみた

1. コードを接続した際に自動的に追加されたコードを一つずつ紐解く

ViewController.swift
// ---(略)---
@IBOutlet weak var label: UILabel!
// ---(略)---

1-1. @IBOutlet

@IBOutletは、変数labelがプロパティとしてストーリーボードと接続できることを表している。

1-2. weak

weakは、メモリ管理の設定。

1-3. var label

var labelは、varキーワードを用いて、変数labelを宣言している。

1-4. 「:」の後のUILabel

「:」の後のUILabelは、変数labelの型がUILabelクラスであることを表す。

1-5. !

キーワードは「オプショナル」。2. につづく。

2. オプショナル整数型(= nilを取り得る整数型)について

2-1. オプショナル整数型とは?

nilを変数に代入する場合は、下記のように型を明示することが必要。そして、型の後に「?」を付ける。

example
var age:Int? = nil

このInt?は、オプショナル整数型(= nilを取り得る整数型)と呼ばれる。

2-2. オプショナル整数型を使った計算

2-2-1. 問題点

// オプショナル整数型と整数型は型が異なるため、エラーが返る
var age:Int? = 25
print(age + 1)

↓↓↓

2-2-2. 解決策

変数名の後に「!」を書く。このように指定することで、オプショナル整数型の値が格納されている変数の中から、整数値のみを取り出すことができる。

// 「オプショナル整数型」の変数から整数を取り出す
var age:Int? = 25
print(age! + 1)

2-2-3. 注意点

変数ageにnilが代入されているときに「!」をつけると、アプリがクラッシュするので注意。

// nilが代入されている変数から値を取り出すと、アプリがクラッシュ
var age:Int? = nil
print(age! + 1)

★ つまり

オプショナル型の変数から値を取り出すときに「!」を付けることで、変数の中身がnilでないことを宣言していることになる。

3. オプショナル整数型のメリット:きちんとエラーが表示されるようにして、謎のクラッシュ防ぐ

3-1.NG例

Swiftのチェックは通るは、アプリがクラッシュする
var age:Int = nil
print(age + 1)

3-2. OK例

型が異なるため、Swiftのチェックできちんとエラーが表示される
var age:Int? = nil
print(age + 1)

4. 「!」を省略した書き方

4-1. 省略前

「!」を省略する前
var age:Int? = nil
print(age! + 1)

4-2. 省略後

「!」を省略したあと
var age:Int! = nil
print(age + 1)

=> 4-1.4-2. は、同じ働きをするが、4-2.の方がシンプルに書くことができる。

5. まとめ

ViewController.swift
// ---(略)---
@IBOutlet weak var label: UILabel!
// ---(略)---

つまり、下記のUILabel!は、labelプロパティから値を取り出すときに「!」を書くのを省略するための書き方。labelプロパティは、これ以降、プロパティのあとに「!」を付けることなく値を取り出すことができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Alamofire, MoyaのURLEncodingで、Boolが0,1になってしまうことへの対応

事象: Moyaを使ってAPIをリクエストしたが、Boolが0/1で送られていた

API系のライブラリとしてよく用いられるのが、 Moya というライブラリだと思います。

私自身も99%のプロジェクトでMoyaを使っています。

Moyaでリクエストパラメータを送る際に以下の形式で送っています。

requestParameters(parameters: [String : Any], encoding: Moya.ParameterEncoding)

しかしながら、この形式で送ると、Bool型が一度Any型に変換され、そして、最終的にNSNumberとしてリクエストされるようです。

詳しくは こちら

とはいえ、↑この人が言うように、BoolをAnyにすると、NSNumberになる的な事象は、playground上では確認できませんでした。。

対応: Bool.description で、Bool型をString型に変更して対応

Bool型→Any型: NSNumber (0/1)
Bool型→String型→Any型: Bool (true/false)

↑のようになるようです。

元々のコード

var parameters: [String: Any]? {
    return ["key" : boolParam]
}

↓↓↓↓↓↓↓↓↓

修正後のコード

var parameters: [String: Any]? {
    return ["key" : boolParam.description]
}

その他

パケットキャプチャで確認しましたが、こちら↓とても参考になりました。
通信系のデバッグには Charles が便利

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む