Xcode8でテストが実行できない Could not determine bundle identifier for TEST_HOST
Xcode8でxcodebuildでtestを走らせると以下のようなエラーがでてテストが実行できなくなりました
xcodebuild: error: Failed to build workspace XXX with scheme XXX. Reason: Could not determine bundle identifier for XXXTests's TEST_HOST:
どうもTEST_HOST、つまりテスト対象のビルドバイナリがないと怒られてるみたいで、 testの前にbuildを追加したら通りました。
$ xcodebuild -workspace XXX -scheme XXX -configuration Debug clean build test -destination 'platform=iOS Simulator,OS=10.0,name=iPhone 6'
ちなみに、xcodebuildじゃなくて、Xcodeでテスト実行する際も、ビルドする前にいきなりテストを実行すると同じエラーが発生します。 これについては、先にビルドしておけば解決します。
動画のフォトアルバムへの保存
動画のフォトアルバムへの保存
iOS8まで
import AssetsLibrary ... ALAssetsLibrary().writeVideoAtPathToSavedPhotosAlbum(filePathURL) { _ in // 完了後の処理 }
iOS9以降
import Photos ... PHPhotoLibrary.sharedPhotoLibrary().performChanges( { PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(filePathURL) }, completionHandler: { _ in // 完了後の処理 } )
Android Studioをインストールしてプロジェクトをビルドするときに躓いたところ
Androidの既存のプロジェクトをビルドしようとしてAndroid Studioのインストールからはじめると 毎回同じようなところで躓いている気がするのでメモ
エラーで Unsupported major.minor version 52.0 が出た時
原因
これはAndroidStudioが使っているJDKが古いと出る
対策
- まずはJDK8をインストールする
- AndroidStudioを起動して、File -> Other Settings -> Default Project Structure を選択
- JDK locationを変更してJDK8のPathに変更する
エラーで Error:failed to find Build Tools revision 23.0.0 rc3 が出た時
原因
これは対応するAndroid SDK Build Toolsがインストールされていないのが原因
対策
既存のプロジェクトでSwift2.3のままXcode8でビルドを通す
Xcode8でビルドするとデフォルトではSwift3でコンパイルされてしまいます。 とりあえずXcode8にあげるけどSwift2.3のままにしておいて、Swift3対応は後でという場合の対処方法です。
Build Settings
Build Settingsの"Use Legacy Swift Language Version"を"Yes"にする
Cocoapodsの変更
Podfileの一番最後に以下を追加
post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '2.3' end end end
ライブラリをXcode8&Swift2.3対応のブランチを指定するようにする
Swift2.3とSwift3ではバイナリ互換がないので、ライブラリも含めてSwift2.3でコンパイルする必要があります
例
pod 'Alamofire', :git => 'https://github.com/Alamofire/Alamofire', :branch => 'swift2.3'
pod 'Realm', :git => 'https://github.com/realm/realm-cocoa.git', :branch => 'master', :submodules => true pod 'RealmSwift', :git => 'https://github.com/realm/realm-cocoa.git', :branch => 'master', :submodules => true
AnsibleでUbuntu16.04使った時にでたエラー
Ansibleで使うUbuntuを16.04にしてみたところいくつかエラーが発生した
config.vm.box = "ubuntu/xenial64"
エラーその1
vagrant upしてTASK [setup]で止まる
TASK [setup] ******************************************************************* fatal: [default]: FAILED! => {"changed": false, "failed": true, "module_stderr": "", "module_stdout": "/bin/sh: 1: /usr/bin/python: not found\r\n", "msg": "MODULE FAILURE", "parsed": false}
原因はUbuntu 16.04ではデフォルトでPython3系が入っているためで、これを2系に変えないとAnsibleは動かない
ということでVagrantfileに以下を追加して対応
config.vm.provision "shell", inline: <<-SHELL sudo apt-get update sudo apt-get install -y python-simplejson SHELL
エラーその2
apt: upgrade=fullでエラーがでる
TASK [common : upgrade a server] *********************************************** fatal: [default]: FAILED! => {"changed": false, "failed": true, "msg": "Could not find aptitude. Please ensure it is installed."}
apt: upgradeの前に以下を追加
- name: install aptitude apt: pkg=aptitude
UITableViewCellのセパレーターを消す
セルのセパレーター消すやつ
override func awakeFromNib() { separatorInset = UIEdgeInsets(top: 0, left: bounds.width, bottom: 0, right: 0) }
RxSwiftでTableView その3
RxTableViewSectionedReloadDataSourceでは複数Sectionに対応できましたが、 Section単位でreloadData()がされてしまっていました。
今回はRxTableViewSectionedAnimatedDataSourceを使ってみます
RxTableViewSectionedAnimatedDataSource
ViewControllerはRxTableViewSectionedReloadDataSourceとほぼ一緒で、dataSourceの型がRxTableViewSectionedAnimatedDataSourceになっただけです。
ViewController
import RxSwift import RxCocoa import RxDataSources class MultiListViewController: UIViewController { @IBOutlet weak var tableView: UITableView! @IBOutlet weak var button1: UIButton! private let viewModel = MultiListViewModel() private let dataSource = RxTableViewSectionedAnimatedDataSource<MySectionModel>() private let disposeBag = DisposeBag() override func viewDidLoad() { dataSource.configureCell = { (_, tableView, indexPath, element) in let cell = tableView.dequeueReusableCellWithIdentifier("Cell1")! cell.textLabel?.text = "\(element) @ row \(indexPath.row)" return cell } viewModel.items.asObservable() .bindTo(tableView.rx_itemsWithDataSource(dataSource)) .addDisposableTo(disposeBag) button1.rx_tap .bindNext { [weak self] in self?.viewModel.change() } .addDisposableTo(disposeBag) } }
ViewModel
dataSourceとbindingさせる配列はAnimatableSectionModelType protocolに対応している必要があります。
更にAnimatableSectionModelTypeのitemはIdentificableType protocolに対応している必要があります。これはitemが変化したかどうか判定するために使われます。
struct MySectionModel { var header: String var items: [Item] } extension MySectionModel : AnimatableSectionModelType { typealias Item = String var identity: String { return header } init(original: MySectionModel, items: [Item]) { self = original self.items = items } } extension String: IdentifiableType { public typealias Identity = String public var identity : Identity { return self } } class MultiListViewModel { let items = Variable<[MySectionModel]>([]) var section1 = MySectionModel(header: "section1", items: ["a", "b", "c"]) var section2 = MySectionModel(header: "section2", items: ["d", "e", "f"]) init () { items.value.append(section1) items.value.append(section2) } func change() { items.value[0] = MySectionModel(header: "section1", items: ["a", "b", "c", "z"]) } }
変更があった場合、内部ではtableViewのperformBatchUpdatesが実行されるので変化のあったcellだけ変更がかかります。
アニメーションを変更したい場合はAnimationContigurationを変更してやればよいです。
dataSource.animationConfiguration = AnimationConfiguration(insertAnimation: .Right, reloadAnimation: .Right, deleteAnimation: .Right)
RxTableViewSectionedAnimatedDataSourceの使い所
- cellの挿入や削除などでアニメーションを入れたい場合
- 変化があった場合にSectionやTableView全体をreloadDataしたくない場合
ただ気になるところが、内部的な変化の判定するために、変更前後の配列の比較を行っているので効率的とはいえません。 利用する側にとっては何も考えずに配列ごと上書きしてしまえばよいのですが、すごい大きい配列になった場合にパフォーマンスとか大丈夫なのかなと懸念がありそうな気がします。
RxSwiftでTableView その2
rx_itemsWithCellIdentifierではSection1つしかダメでした。
複数Sectionを使う方法としていくつかあるっぽいのですが、 まずRXTableViewSectionedReloadDataSourceを使ってみます。
RXTableViewSectionedReloadDataSource
これはRxSwiftには含まれておらず別途RxDataSourcesを導入する必要があります。
ViewContorller
import RxSwift import RxCocoa import RxDataSources class MultiListViewController: UIViewController { @IBOutlet weak var tableView: UITableView! @IBOutlet weak var button1: UIButton! private let viewModel = MultiListViewModel() private let dataSource = RxTableViewSectionedReloadDataSource<MySectionModel>() private let disposeBag = DisposeBag() override func viewDidLoad() { dataSource.configureCell = { (_, tableView, indexPath, element) in let cell = tableView.dequeueReusableCellWithIdentifier("Cell1")! cell.textLabel?.text = "\(element) @ row \(indexPath.row)" return cell } viewModel.items.asObservable() .bindTo(tableView.rx_itemsWithDataSource(dataSource)) .addDisposableTo(disposeBag) button1.rx_tap .bindNext { [weak self] in self?.viewModel.change() } .addDisposableTo(disposeBag) } }
ViewModel
RXTableViewSectionedReloadDataSourceをbindせる配列はSectionModelType protocolに対応している必要があります。
import RxSwift import RxCocoa import RxDataSources struct MySectionModel { var rows: [String] init(rows: [String]) { self.rows = rows } } extension MySectionModel: SectionModelType { var items: [String] { return rows } init(original: MySectionModel, items: [String]) { self = original self.rows = items } } class MultiListViewModel { let items = Variable<[MySectionModel]>([]) private var section1 = MySectionModel(rows: ["a", "b", "c"]) private var section2 = MySectionModel(rows: ["A", "B", "C"]) init () { items.value.append(section1) items.value.append(section2) } func change() { items.value[0] = MySectionModel(rows: ["x", "y", "z"]) } }
RXTableViewSectionedReloadDataSourceはその名の通り、Section単位でreloadData()をして表示内容を更新します。 Sectionの中身を変えたいときは上のfunc change()のようにSection毎データを更新してやらないとreloadData()が発火しないようです。
RXTableViewSectionedReloadDataSourceの使い所
- 複数Sectionが扱いたい場合
- Sectionを更新するときはそのSectionまるごとデータを差し替えて支障がないとき
補足
ここではSectionModelTypeプロトコルに対応させたMySectionModelを自分で定義しましたが、 RxSwiftでSectionModelというジェネリクスのstructが用意されているのでそれを使っても実装できます
public struct SectionModel<Section, ItemType>
RxSwiftでTableView その1
RxSwiftでTableViewとdatasourceをbindingさせる方法はいくつかあるようなので1つずつ見ていきます。 今回はrx_itemsWithCellIdentifilerを使ってみます。
rx_itemsWithCellIdentifier
単にSectionが1つのリストをTableViewに表示するだけであればrx_itemsWithCellIdentifierを使えば非常にシンプルに実装できます。
ViewController
class ListViewController: UIViewController { @IBOutlet weak var tableView: UITableView! @IBOutlet weak var button: UIButton! private let viewModel = ListViewModel() private let disposeBag = DisposeBag() override func viewDidLoad() { viewModel.friends.asObservable() .bindTo(tableView.rx_itemsWithCellIdentifier("Cell")) { (row, element, cell) in cell.textLabel!.text = element } .addDisposableTo(disposeBag) tableView.rx_itemSelected .subscribeNext { [weak self] indexPath in self?.viewModel.selectAtIndex(indexPath.row) self?.tableView.deselectRowAtIndexPath(indexPath, animated: true) } .addDisposableTo(disposeBag) button.rx_tap .bindNext { self.viewModel.add() } .addDisposableTo(disposeBag) } }
ViewModel
final class ListViewModel { let friends = Variable<[String]>([]) init () { friends.value.append("1") friends.value.append("2") friends.value.append("3") } func selectAtIndex(index: Int) { print(friends.value[index]) } func add() { friends.value.append("10") } }
このように、単に表示するだけであればrx_itemsWithCellIdentifierとdatasouceをbindingするだけなのですごく楽です。
ただRxSwift内部のソースを見てみると、以下のようにdatasourceに変化があった場合reloadData()を使っているので全体に再描画が発生します。
/// RxTableViewReactiveArrayDataSource class RxTableViewReactiveArrayDataSource<Element> : _RxTableViewReactiveArrayDataSource , SectionedViewDataSourceType { typealias CellFactory = (UITableView, Int, Element) -> UITableViewCell ... func tableView(tableView: UITableView, observedElements: [Element]) { self.itemModels = observedElements tableView.reloadData() } }
rx_itemsWithCellIdentifierの使い所
以上のことから使い所を考えると
- Sectionが1つしかない
- datasourceに変化があったときに毎回reloadData()が走っても問題ないような場合
- cellの挿入にアニメーションが不要
といった感じかなと
SwiftLint
Swiftでコーディング規約に沿っているかチェックするツールにSwiftLintというのがあるのでつかってみました。
SwiftLintではコーディング規約としてGithubのSwift style guildeを使っています。
使い方
- $ brew install swiftlint
- XcodeでSwiftLintをかけるプロジェクトを開く
- ProjectSettingのBuildPhasesを開く
- 左上の+ボタンを選択して、New Run Script Phaseを選択
- Run Script内に以下を記載
if which swiftlint >/dev/null; then swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fi
こうするとビルドするとコーディング規約のチェックが走るようになります。
例外
ただし、このままだと全てのファイルが対象となってしまい、3rd Partyのライブラリなども含まれてしまいます。
ディレクトリを対象からはずす
以下のようにして対象外のディレクトリを指定します
- .swiftlint.yml というファイルをプロジェクトのトップディレクトリに作成します。
- .swiftlint.yml内には以下のようにexcluded: 以下に、対象外とするディレクトリを列挙すればよいです
excluded: - Pods
ルールを変更する
このルールは適用したくないとか、errorではなくwarningにしたい場合は、ルールを.swift.ymlに書きます
# 除外するルール disabled_rules: - line_length - trailing_whitespace - trailing_newline - function_parameter_count - force_try - type_name - function_body_length # ルールの変更 force_cast: warning variable_name: min_length: 2
ソースコードの行単位でルールを除外する
コメントで // swiftlint:disable:this <ルール名> で行単位で除外できます。
let cell = tableView.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as! Cell1 // swiftlint:disable:this force_cast
細かいところはSwiftLintのREADMEを読むとよいです。
RxSwiftでログイン画面のサンプル実装
ユーザー名とパスワードを規定の文字数以上入力するとログインボタンが有効になるViewControllerをRxSwiftを使って実装してみました。
SwiftBondしか使ったことなく初めてRxSwiftを使ってみた感想としては、RxSwiftのほうが似たようなものがたくさんあり学習コストは高いなと思いました。 Observableについて見てみると、RxSwiftのほうはReactiveXのObservableであるためonNext,onCompleted,onErrorがありますが、SwiftBondのほう単に変化を見るだけでいわばonNextしかないようなものなので、その辺で差がメリット・デメリットにあらわれているのかなと。
ViewController
import UIKit import RxSwift import RxCocoa class LoginViewController: UIViewController { @IBOutlet weak var inputTextField: UITextField! @IBOutlet weak var loginButton: UIButton! @IBOutlet weak var passwordTextField: UITextField! private let viewModel = LoginViewModel() private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() addBindings() } private func addBindings() { // username // UITextField -> ViewModel inputTextField.rx_text .bindTo(viewModel.userName) .addDisposableTo(disposeBag) // ViewModel -> UITextField viewModel.userName.asObservable() .observeOn(MainScheduler.instance) .bindTo(inputTextField.rx_text) .addDisposableTo(disposeBag) // password // UITextField -> ViewModel passwordTextField.rx_text .bindTo(viewModel.password) .addDisposableTo(disposeBag) // ViewModel -> UITextField viewModel.password.asObservable() .observeOn(MainScheduler.instance) .bindTo(passwordTextField.rx_text) .addDisposableTo(disposeBag) // UIButtonのタップイベント loginButton.rx_tap .subscribeNext { [weak self] in self?.viewModel.login() } .addDisposableTo(disposeBag) // ViewModel -> UIButtonのenabled viewModel.enableLoginButton .bindTo(loginButton.rx_enabled) .addDisposableTo(disposeBag) } }
ViewModel
import Foundation import RxSwift import RxCocoa class LoginViewModel { let userName = Variable("") let password = Variable("") let enableLoginButton: Observable<Bool> init() { // 3文字以上だったらボタンをenbaleにする enableLoginButton = Observable.combineLatest(userName.asObservable(), password.asObservable()) { $0.0.characters.count >= 3 && $0.1.characters.count >= 3 } } func login() { print("user=", userName.value) print("password=", password.value) userName.value = "" password.value = "" } }
VariableよりDriverとかのほうがよかったのかとか、VariableをsubscribeするならsubscribeNextじゃなくてsubscribeでもいいんじゃないかなとか色々怪しい気がするんですが、もうちょっと勉強したいと思います
iOSでSlackのWebSocket疎通まで
やること
iOSからSlackのWebSocketを使ってメッセージのやり取りをできる状態にする
事前準備
redirect urlを用意せずにモバイルアプリ単体でアクセストークンを取得するのは無理っぽいのでとりあえずテスト用のを使う
- テスト用のアクセストークンの取得
手順
- Web APIのrtm.startを使ってWebSocket Message ServerのURLを取得する(30秒しか有効期間がないので毎回取得が必要)
- 取得したURLを使ってWebSocketのコネクションをはる
超適当なサンプル
- 適当にXcodeでプロジェクトを作成
- Podfileに以下を記載(cocoapods 1.0以上)
target 'プロジェクト名' do use_frameworks! pod 'Alamofire', '~> 3.4' pod 'Starscream', '~> 1.1.3' end
- $ pod install
- ViewControllerを以下のように書く(accessTokenのところにaccessTokenを入れる)
import Alamofire import Starscream class ViewController: UIViewController, WebSocketDelegate { var socket: WebSocket? let accessToken = "xxx" override func viewDidLoad() { super.viewDidLoad() let URL = "https://slack.com/api/rtm.start?token=\(accessToken)" Alamofire.request(.POST, URL, encoding: .JSON) .responseJSON { [weak self] response in print(response.request) // original URL request print(response.response) // URL response print(response.data) // server data print(response.result) // result of response serialization if let JSON = response.result.value { print("JSON: \(JSON)") print("url:", JSON["url"]) if let urlString = JSON["url"] as? String { self?.socket = WebSocket(url: NSURL(string: urlString)!) self?.socket?.delegate = self self?.socket?.connect() } } } } // MARK: WebSocket Delegate func websocketDidConnect(socket: WebSocket) { print("did connect") } func websocketDidDisconnect(socket: WebSocket, error: NSError?) { } func websocketDidReceiveMessage(socket: WebSocket, text: String) { print("message:", text) } func websocketDidReceiveData(socket: WebSocket, data: NSData) { print("data:", data) } }
動かして以下が表示されればOK
did connect message: {"type":"hello"}
久しぶりにcocoapodsを使ってはまったところ
cocoapods 1.0.1
The dependency xxx
is not used in any concrete target.
Podfileの書き方がcocoapods 1.0系になって変わったらしく
Podfileを
$ pod init
で生成すると
# Uncomment this line to define a global platform for your project # platform :ios, '9.0' target 'SampleProj' do # Comment this line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for SampleProj pod 'Bond', '~> 4.0' target 'SampleProjTests' do inherit! :search_paths # Pods for testing end end
このようにプロジェクト名でテンプレが生成されるので、Pods for xxx以下にpod xxxを書けば良い
Unable to find a specification for 'xxxxx'
$ pod setup
してから
$ pod install
で解決
Crashlyticsにクラッシュレポートが送信されない
普通はFabricで手順通りにやってればクラッシュレポートは送信されるはずなんですが、なぜか送信されないってことがありました。
answersとかは動いているのでFabric自体はちゃんと取り込まれてるのになぜだ・・と思って調べたら
troubleshootingに書いてありました https://docs.fabric.io/ios/crashlytics/crashlytics.html#troubleshooting
Make sure our SDK line is after all other 3rd-party SDK lines that install an exception handler. (We need to be last one called in your appDidFinishLaunchingWithOptions method.)
他に例外ハンドラを使うようなライブラリ使ってる場合は、appDidFinishLaunchingWithOptionsの一番最後でFabric.with([Crashlytics()])呼べってことでした。
なぜかとういうと、クラッシュレポートを送信するライブラリはたぶんNSSetUncaughtExceptionHandlerを使って例外をキャッチしてクラッシュチェックをしているのですが、この関数は一つしかハンドラをセットできないので、複数クラッシュレポートを送るようなライブラリを使っていた場合、最後にセットしたものが動くってことだと思われます。 https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/#//apple_ref/c/func/NSSetUncaughtExceptionHandler
おまけ
どうしてもFabricを使いつつ他の例外ハンドラを動かしたい場合は、こんな感じでCrashlyticsのセットした例外ハンドラを保存しておいて、NSSetUncaughtExceptionHandlerで独自のコールバックをセットしてその中で、Crashlyticsのハンドラの実行と独自の処理をやればいいのではないかと思います(試してないけど
static var exceptionHandler: ((NSException) -> Void)? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { Fabric.with([Crashlytics()]) AppDelegate.exceptionHandler = NSGetUncaughtExceptionHandler(); let callback: @convention(c) (NSException) -> Void = { // do somithing AppDelegate.exceptionHandler?($0) } NSSetUncaughtExceptionHandler(callback) return true }
2015振り返り
運動
いつも3ヶ月くらいで飽きてましたが、今年は週1走ることを目標にやってみてだいたい達成できました。
ブログ
ここ2年くらいさぼてったので、今年は月1くらいで継続的に書くことはできました
2015-01-01から1年間の記事一覧 - Pragmatic ball boy
会社のエンジニアブログでも初めて投稿し、100近くはてぶ獲得できました
64bit環境におけるObjective-Cのポインタ | GREE Engineers' Blog
仕事
現職に入社以来ずっと同じ仕事ばっかりだったので、社内の新規事業開発でエンジニアを募集していたので挙手してジョインしてLIMIAというサービスをリリースしました(といっても僕はほぼ実装してないですが・・
まとめ
- 飽きっぽい人でも目標があると続くのでやっぱり目標大事ですよね
2016は個人でなにかリリースしたいとおもいます
良いお年を!