The Pragmatic Ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

CIのサービスいろいろ

Hound CIとかCode Climateとか知らなかったので調べたらここにCI Serviceのリストがあった。

Continuous-Integration-services/continuous-integration-services-list.md at master · ligurio/Continuous-Integration-services · GitHub

Jenkinsでゴニョゴニョやればできるけど、その手間とかメンテコストを考えると外部サービスでやれるものはやりたい。

Xcodeは年に一度はバージョンアップ問題でβ使わなければいけなかったりがあるので、iOS開発では100%外部依存することはできなかったりするところが課題

build.gradleからAndroidのバージョンベタ書きを避ける

Androidでgradleを使うと、デフォルトではこんな感じにバージョン情報がベタ書きで生成されます。

apply plugin: 'android'

android {
    compileSdkVersion 17
    buildToolsVersion "21.1.2"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 17
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

build.gradle一つですむプロジェクトであれば、これでもよいかもですが、マルチプロジェクトにしている場合だと変更する場合にたくさん書き換えることとなりミスの元となります。

そこで以下のようにgradle.propertiesに切り分けておきます。

gradle.properties

ANDROID_BUILD_MIN_SDK_VERSION=9
ANDROID_BUILD_TARGET_SDK_VERSION=21
ANDROID_BUILD_TOOLS_VERSION=21.1.2
ANDROID_BUILD_SDK_VERSION=21

build.gradle

android {
    compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
    buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION

    defaultConfig {
        minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION)
        targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
    }

参考

https://github.com/facebook/facebook-android-sdk/blob/master/gradle.properties

https://github.com/facebook/facebook-android-sdk/blob/master/facebook/build.gradle

AndroidStudioでプロジェクトを開こうとするとCause: org/codehaus/groovy/runtime/typehandling/ShortTypeHandling

AndroidStudioでbuild.gradleを指定してプロジェクトを開こうとすると

Cause: org/codehaus/groovy/runtime/typehandling/ShortTypeHandling

というダイアログが表示されてプロジェクトが開かない・・・ という状態になりました。


gradle-wrapper.propertiesのgradleのバージョンが古い(1.x.xとか)とこうなるようで

gradle/wrapper/gradle-wrapper.propertiesを以下のように2.2.1にすると解決

distributionUrl=http\://services.gradle.org/distributions/gradle-2.2.1-all.zip

Android NDKでerror: 'to_string' is not a member of 'std'

androidC++のstd::to_stringを使うと以下のようなビルドエラーがでました。(環境:Android NDK r10d)

error: 'to_string' is not a member of 'std'

もちろんC++11の設定自体はできていて、他のstd::functionなどは問題なく使えます。

LOCAL_CFLAGS    := -std=c++11

NDKのlibstdc++ではまだto_stringをサポートしてない?ぽいので自前で用意するしかなさそうな

template <typename T>
std::string to_string(T value)
{
    std::ostringstream os ;
    os << value ;
    return os.str() ;
}

APP_STL を gnustl_staticではなく、stlport_staticにするとto_string使うことができましたが、C++ exception supportがない模様

The Developer's Guide | Android Developers

参考

Android ndk std::to_string support - Stack Overflow

Swiftで引数リスト(CVaListPointer)の渡し方

SwiftからObjective-Cメソッドを呼ぶ際に、引数が引数リスト(va_list)の場合、Swiftでは型が以下のようなCVaListPointerになります。

class func raise(_ name: String,
          format format: String,
      arguments argList: CVaListPointer)

結論からいきますと、getVaListという関数を使って、getVaListの引数に引数リストとして渡したいArrayを突っ込めばよいです。

NSException.raise("hoge exception", format:"exception raised %d", arguments:getVaList([1]))





以下脱線

CVaListPointerの定義を調べてみるとこのようになるほどわからん感じの構造体です。

/// The corresponding Swift type to `va_list` in imported C APIs.
struct CVaListPointer {
    init(_fromUnsafeMutablePointer from: UnsafeMutablePointer<Void>)
}

CVaListPointerを生成するためのメソッドが用意されていてこれを使う。

/// Returns a `CVaListPointer` built from `args` that's backed by
/// autoreleased storage.
///
/// .. Warning:: This function is best avoided in favor of
///    `withVaList`, but occasionally (i.e. in a `class` initializer) you
///    may find that the language rules don't allow you to use
///    `withVaList` as intended.
func getVaList(args: [CVarArgType]) -> CVaListPointer

CVarArgTypeはprotocol

protocol CVarArgType {

    /// Transform `self` into a series of machine words that can be
    /// appropriately interpreted by C varargs
    func encode() -> [Word]
}

ということからgetVaListの引数には、CVarArgTypeプロトコルを実装しているクラスのArrayということになります。

Intなどの型はCVarArgTypeをextensionで実装してるので使えます。

extension Int : CVarArgType {

    /// Transform `self` into a series of machine words that can be
    /// appropriately interpreted by C varargs
    func encode() -> [Word]
}

調べた感じだとStringは一見CVarArgTypeを実装していないように見えるんですが、ArrayにString入れても使えるのはなんでかな・・

xcodebuildでdevice向けのビルドするときのdestinationの指定方法

以前xcodebuildのパラメータの指定方法についてこちらに書きました。

Travis CIでObjective-C/Swiftのテストを実行する - Pragmatic ball boy

テストするときはシミュレータを指定するのでこれで問題ないのですが、 実機向けのstaticライブラリを用意したりするときには、destinationの指定方法が以下のように変わります。

ここで問題なのが、nameとidのところなんですが、ビルドマシンに実機を常に指しておけば指定できますが、たいていはそういう使い方しないと思います。

XcodeからビルドするときはiOS Deviceを指定すれば実機なくてもビルドだけはできるのでそれと同じことはxcodebuildでもできるはず。

xcodebuildのドキュメントを見るとgeneric/をつければ良いと書いてあります。

To build against a platform generically instead of a specific device, the destination specifier may be prefixed with the optional string "generic/", indicating that the platform should be targeted generically.

よって、以下のようにgeneric/platform=iOSと指定することでiOS Deviceを指定するのと同じようにビルドすることができます。

$ xcodebuild -scheme MyScheme -destination 'generic/platform=iOS' build

ちなみに実機とsimulator両方で使えるfat binaryを作りたい場合は、destination複数指定できるので以下のように、2つdestinationを使います。

$ xcodebuild -scheme MyScheme -destination 'generic/platform=iOS' -destination 'platform=iOS Simulator,name=iPhone 6,0S=8.0' build

fat binaryに含まれているアーキテクチャを確認するには以下のコマンドを使えばよいです。

$ xcrun lipo -info *.a

FragmentでUnfortunately, XXX has stoppedでクラッシュする

Android StudioのNew->Fragmentで追加したFragmentを使って表示しようとすると

"Unfortunately, XXX has stopped"

というポップアップが出てクラッシュするという問題が発生。 Logにも何も出ていない・・・

どこでクラッシュしてるのかさっぱりわからなかったので、仕方なく追加したFragmentにブレークポイントをはりまくって確認したところ、onAttachを最後にお亡くなりになっていた。

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

Fragmentを呼ぶActivity側をOnFragmentInteractionListener interfaceに対応させないとダメのようだ。

よって以下のようにActivityにimplements XXXFragment.OnFragmentInteractionListener を追加し、onFragmentInteractionをオーバーライドすればOK。

public class XXXActivity extends ActionBarActivity implements XXXFragment.OnFragmentInteractionListener {

/// 省略

@Override
    public void onFragmentInteraction(Uri uri) {

    }

よく見たら作成されたFragmentに"Activities that contain this fragment must implement the XXXFragment.OnFragmentInteractionListener" て書いてありましたね・・、よく読めってことか

/**
 * A simple {@link Fragment} subclass.
 * Activities that contain this fragment must implement the
 * {@link XXXFragment.OnFragmentInteractionListener} interface
 * to handle interaction events.
 * Use the {@link XXXFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class XXXFragment extends Fragment {

追記:

Fragmentを追加する際に、optionで"Include interface callbacks?"にデフォルトでチェックが入っているため、今回のようになったようでした。

f:id:yanamura:20150131231815p:plain

32bit/64bit混成バイナリをiOS5.1.1端末にインストールできない

CocoaTouch 64ビット移行ガイド より、iOS5.1.1以降で32bit/64bitの混成バイナリが利用可能と書いてあります。

しかし、ipaファイルを作ってiOS5.1.1にインストールしようとすると失敗してしまいます。

AppleのDeveloper forumに情報があったのすが、 https://devforums.apple.com/message/1071370#1071370

iOS5.1.1では64bitの入った混成バイナリのインストールが不可能で、古いデバイスに対応するためにどうやらApple側でAppStoreにサブミットされたバイナリをiOS5.1.1用に64bitバイナリを含めないように加工してるのでは、、みたいな憶測が書かれていました。

実際AppStoreからダウンロードしたらインストールできたみたいなことは書いてあったので混成バイナリであってもiOS5.1.1で動作はするっぽいですが、事前に確認できないのはちょっと気持ち悪いですね。。

なので、どうしてもiOS5.1.1で動作確認したい場合は、iOS5.1.1の評価用にだけ32bitだけでビルドするように変更しなければならないということになります。

Android Studio : Project Structure

なぜかちゃんと設定されなくて手動で設定しなおした。

Android Studioだと~/Library/Android/sdk以下にAndroid SDKがインストールされるですね

Android SDK location

~/Library/Android/sdk

JDK location

/System/Library/Frameworks/JavaVM.framework/Home

Genymotionのインストール

手元にAndroidデバイスがないけどちょっとエミュレーターで動かしたい場合に、通常のエミュレーターだと遅すぎて使い物にならないのでGenymotionを利用しています。

インストールした時の手順のメモです。

Genymotionのダウンロードとインストール

ここからダウンロード(要Login)

Genymotion

ダウンロードしたdmgファイルを開き、GenymotionとGenymotion ShellをApplicationフォルダにドラックアンドドロップ

VertualBoxのダウンロードとインストール

ここからダウンロード

Downloads – Oracle VM VirtualBox

ダウンロードしたdmgファイルを開き、VirtualBox.pkgをダブルクリックするとインストーラーが立ち上がるのでポチポチ押していけばインストールが完了します。

Genymotionの起動

ApplicationフォルダのGenymotionを開きます。

"You do not have any virtual device yet. Do you want to add a new one?" とダイアログが出るのでYesにします。

virtual deviceが何もない状態なので、下のピンクの枠をのSign inクリックしてログインします。

f:id:yanamura:20150104121435p:plain

ログインすると下のようにvirtual deviceが取得され利用できるようになります。

f:id:yanamura:20150104120806p:plain

あとは使いたいdeviceを選んで、nextを押していくとOK

そうするとvirtual deviceが作成されます

f:id:yanamura:20150104121027p:plain

上のスタートボタンを押すと起動します

一瞬で起動するのでデフォルトのエミュレータとの差は雲泥の差ですね

参考

ユーザーガイド Genymotion

SwiftでUnitTestするときに"Use of undeclared type"になる場合の対処法

テストを実行するとこういうエラーがでることがあると思います。

Use of undeclared type 'Todo'

does not have a member named 'todo'

import XCTest
import TodoApp

class TodoTests: XCTestCase {
    var todo: Todo!
    
    override func setUp() {
        super.setUp()
        
        todo = Todo()
    }

たまに解決方法として全部publicにするとかいうのを見かけるのですが、それは間違いです。もしそうだとするとpublicなクラスしかテストできないってことになるので普通に考えたら明らかにおかしいですよね。。

ここから解決方法です。

方法その1

Swift1.x系での対処方法です。Swift2.x系でも使えます。

問題は、Test Targetの設定にあります。 ProjectのUnitTest用のTargetを選択し、Build Phasesのタブを選択します。

f:id:yanamura:20141227145947p:plain

エラーがでる場合は、Compile Sourcesにテスト対象のクラスが追加されていないと思われます。

f:id:yanamura:20141227145949p:plain

そこでこのようにテスト対象のクラスを追加してやればエラーは消えるはずです。

クラスをプロジェクトに追加される際に、どのTargetに対して追加するか聞かれるダイアログがでると思いますが、その際にテストTargetも対象にいれるのを忘れないように気をつけるとこういったことは防げます。

方法その2

Swift2.xから使える方法です。

importの前に@testableをつければよいです。

import XCTest
@testable import TodoApp

RealmなどTestTargetでテスト対象のクラス一緒にコンパイルできない場合はこの方法でやるしかないです。

C++11に対応したプログラミング言語C++第四版

C++11に対応してるいい本ないかなと思っていたら2015年2月に発売の第四版の日本語訳版にはC++11も含まれているようですね。

ちょっとお値段がはりますがほしい一冊です

もう一冊気になってるのがEffective Modern C++で、こちらはまだ出たばかりで日本語訳版はないです。 ちょうど現在(2014/12/27)はオライリーでも50% off、Kindleでは更に安くなっており、日本語訳まで待てないかたには買いどきでは

Travis CIでObjective-C/Swiftのテストを実行する

[環境]Xcode 6.1

Swift,Objective-Cどちらの場合でもこの方法でいけるはずです。

Travis CIにドキュメントが用意されていますが、xctoolを使った例が示されているので無視したほうがよいです。xctoolはAppleが用意した標準のビルドツールではなく、Facebookがその置換えとして作ったツールなので、Xcodeがバージョンアップするたびに大抵ぶっ壊れるので使わないほうが無難です。Travis CIでは何を間違ったのかデフォルトでxctoolを使っています、自分らでXcodeの変化についていけるようにメンテしてるならいいんですが、そこは不明。。ここではxcodebuildを使った方法を記載します。

Building an Objective-C Project - Travis CI

xcodebuild

command lineでテストを実行するにはxcodebuildを使います。

xcodebuild(1) Mac OS X Developer Tools Manual Page

xcodebuildには最低限以下のようなオプションを設定する必要があります

  • -workspace
  • -scheme(必須)
  • -configuration
  • -destination(必須)
    • platform
      • iOS Simulator を指定
    • name
      • シミュレーター名。 xcrun simctl listで確認
    • OS
      • iOSバージョン。 8.1 など。これも xcrun simctl listで確認

そして、buildactionとして test を指定すれば、ビルド後テストが実行されます。

 xcodebuild -workspace MyWorkspace.xcworkspace -scheme MyScheme -configuration Debug -destination 'platform=iOS
              Simulator,name=iPhone 6,0S=8.0' -destination 'platform=iOS Simulator,name=iPad Air,0S=7.1' test

destination複数設定できるので、複数のシミュレータで実行したい場合などは複数指定すれば、何度もxcodebuild testを実行する必要はありません。

引っかかりやすいポイント

scheme

schemeがgit repositoryで管理されていない状態でxcodebuildを実行すると

xcodebuild: error: The project 'XXXX' does not contain a scheme named 'XXXX'.

とエラーが出てしまいます。 schemeの情報は通常だとxcuserdataいかに保存されるようですが、ユーザー毎の情報なので普通はrepositoryからは.gitigoreで排除しているはず。それではどうするかというとxcshareddataとしてrepositoryにいれます。 方法は、以下のように"Manage schemes..."を選択し、scheme一覧を表示し、"shared"のチェックボックスにチェックをいれてcloseするとxcshareddataが生成されているのでrepositoryにコミットすればよいはず。

f:id:yanamura:20141025210054p:plain

f:id:yanamura:20141025210105p:plain

destinationの name と OSの対応の調べ方

以下のコマンドを実行します

> xcrun simctl list

そうすると以下のようにOSのバージョンとそれに対応するシミュレーターのリストが出てくるのでこれを使えばよいです。

== Device Types ==
iPhone 4s (com.apple.CoreSimulator.SimDeviceType.iPhone-4s)
iPhone 5 (com.apple.CoreSimulator.SimDeviceType.iPhone-5)
iPhone 5s (com.apple.CoreSimulator.SimDeviceType.iPhone-5s)
iPhone 6 Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus)
iPhone 6 (com.apple.CoreSimulator.SimDeviceType.iPhone-6)
iPad 2 (com.apple.CoreSimulator.SimDeviceType.iPad-2)
iPad Retina (com.apple.CoreSimulator.SimDeviceType.iPad-Retina)
iPad Air (com.apple.CoreSimulator.SimDeviceType.iPad-Air)
Resizable iPhone (com.apple.CoreSimulator.SimDeviceType.Resizable-iPhone)
Resizable iPad (com.apple.CoreSimulator.SimDeviceType.Resizable-iPad)
== Runtimes ==
iOS 7.0 (7.0.3 - 11B507) (com.apple.CoreSimulator.SimRuntime.iOS-7-0)
iOS 7.1 (7.1 - 11D167) (com.apple.CoreSimulator.SimRuntime.iOS-7-1)
iOS 8.1 (8.1 - 12B411) (com.apple.CoreSimulator.SimRuntime.iOS-8-1)
== Devices ==
-- iOS 7.0 --
    iPhone 4s (0FF14361-F567-4B40-9FE4-79D572867676) (Shutdown)
    iPhone 5 (29226DD1-620D-4746-911E-CEC9A5B76A94) (Shutdown)
    iPhone 5s (CD01CF0C-5715-499D-A1F2-96E1E7D043CF) (Shutdown)
    iPad 2 (C884ACD1-D4D7-4391-9147-882B2C22B394) (Shutdown)
    iPad Retina (8C7D647C-7024-4E44-8555-4273A4264891) (Shutdown)
    iPad Air (33CDAD90-98F8-49FE-8EF9-51CE0F7B9043) (Shutdown)
-- iOS 7.1 --
    iPhone 4s (2FA61CBE-0077-44C2-A981-2B9B09A53C77) (Shutdown)
    iPhone 5 (9FE60E90-DB4A-4975-885A-087171F27099) (Shutdown)
    iPhone 5s (42A87F6B-BB69-499A-BB3A-FBBF4F8BDBCF) (Booted)
    iPad 2 (1A35A3AE-6F13-499F-BF8D-3AF0CF23096E) (Shutdown)
    iPad Retina (953DF503-1B98-4A74-8784-42680F1498C1) (Shutdown)
    iPad Air (9B94DE85-F421-4C2B-B570-D78D31034F16) (Shutdown)
-- iOS 8.1 --
    iPhone 4s (10AB167F-7C6F-4773-A9F0-4E3F66E2A4E4) (Shutdown)
    iPhone 5 (A12F3ACD-1DCE-4666-A1FA-443AA6AECC39) (Shutdown)
    iPhone 5s (7597741D-4654-4AC3-83A0-C82C81AFFFF7) (Shutdown)
    iPhone 6 Plus (EFE25990-D630-4D91-9A16-29479BAC6C44) (Shutdown)
    iPhone 6 (6A000F08-7D65-4894-B4AA-8A9A20CD57E3) (Shutdown)
    iPad 2 (CF8E7FC1-CC33-4F21-AA17-6DA5812DEA51) (Shutdown)
    iPad Retina (7AA79E2F-C397-46AC-B0F4-A291EB7A3B62) (Shutdown)
    iPad Air (09476634-4289-437E-B2EB-E4BDB4BFD6DA) (Shutdown)
    Resizable iPhone (131ED5C5-7E49-46CE-A72F-9FB2283B5D70) (Shutdown)
    Resizable iPad (24C58956-E9EF-4295-80BC-676343C8FA1B) (Shutdown)

command lineでのテスト実行方法がわかったので、後はTravis CIでこれを実行できるようにします。

.travis.yaml

Travis CIでテストを実行するgitリポジトリのrootに.travis.yamlファイルを作成します。 SwiftObjective-Cの場合以下を記載。

language: objective-c
script: xcodebuild -workspace MyWorkspace.xcworkspace -scheme MyScheme -destination 'platform=iOS
              Simulator,name=iPhone 6,OS=8.1' test

script以下はなんでもよいのでxcodebuildを別のファイルに切り出して、そのファイルを実行するとかでもよいです。そのほうがtravis CI用途以外でも使えるので。

Appleのアプリの審査期間の目安

リリース時期が決まっているときなどはこれを見てどれくらい前に審査に出せば間に合いそうかチェックするのに役立ちます

Average App Store Review Times

審査期間はiOS8以降ものすごく長くなっていて最近はだいたい10~11日。in reviewに入るまでがやたらと長く、一旦in reviewになるとわりとさくっと終わる感じがします