ObjecTips

Swift & Objective-C で iOS とか macOS とか

アプリの譲渡 App Transfer の手順

App Transfer の概要

App Transfer は App Store でのアプリの譲渡機能で、レビューを維持したまま他の Developer アカウントへアプリを移管する事が出来る。ユーザはアプリを別途ダウンロードする必要はなく同一のアプリとしてアップデートを行う事が出来る。
現在は日本語ドキュメントが用意されているので詳しくはそちらを参照のこと
App の譲渡の概要
https://help.apple.com/app-store-connect/#/deved688524f

アプリの引き継ぎ、アップデートが可能とは言え、すべてが完璧に引き継がれるわけではない。
iCloudを利用しているとアプリを譲渡出来ない、Keychain共有を使用していない場合 Keychain に保存済みの情報にはアクセス出来なくなるなどいくつかの制約もある。
上記の「App の譲渡の概要」と合わせて以下も参照のこと
App の譲渡の条件
https://help.apple.com/app-store-connect/#/devaf27784ff

手順

*1

譲渡側

App Store Connect (iTunes Connect) でアプリを選択して「App 情報>追加情報>App の譲渡」を選択

f:id:Koze:20180725095837p:plain

App の譲渡の承諾画面が表示される

f:id:Koze:20180725100307p:plain f:id:Koze:20180725100314p:plain

TestFlight ベータ版テスト 譲渡する App からすべてのビルドおよびテスターを削除し、テスト情報の各フィールドのデータを消去する必要があります。

上記項目が譲渡の条件を満たしていないと表示される。

TestFlight のビルドを見ると TestFlight 配信済みのアプリが確認出来る。

f:id:Koze:20180725101425p:plain

これを全て削除

f:id:Koze:20180725101605p:plain

App Store Connect (iTunes Connect) ユーザと、すべてのテスター(外部テスター)を確認。

f:id:Koze:20180725101934p:plain f:id:Koze:20180725101942p:plain

これらも全て削除

f:id:Koze:20180725102103p:plain

もう一度「App の譲渡」画面を表示

f:id:Koze:20180725102357p:plain

TestFlight ベータ版テスト 譲渡する App からすべてのビルドおよびテスターを削除し、テスト情報の各フィールドのデータを消去する必要があります。

まだ上記の条件を満たしていないと表示される。
「TestFlight>APP 情報>テスト情報」を表示する。

f:id:Koze:20180725102802p:plain f:id:Koze:20180725102809p:plain

上記の記入欄を全て削除する。

f:id:Koze:20180725103256p:plain

再度「App の譲渡」画面を表示

f:id:Koze:20180725103421p:plain f:id:Koze:20180725103429p:plain

「続ける」を選択

f:id:Koze:20180725103550p:plain

受取側の Agent の Apple ID と Team ID を入力して「続ける」を選択すると同意画面が表示される。

f:id:Koze:20180725103854p:plain f:id:Koze:20180725103902p:plain

これで譲渡リクエスト完了

f:id:Koze:20180726180850p:plain

契約画面の Contract In Process からリクエストの取り消しも可能

f:id:Koze:20180725104311p:plain

受取側

Apple から Agent 宛にメールが届く

f:id:Koze:20180725104825p:plain

App Store Connect にログインすると譲渡リクエストに関するメッセージが表示される。

f:id:Koze:20180725104945p:plain

契約画面を表示すると Contract In Process に譲渡リクエストが表示される。

f:id:Koze:20180725105325p:plain

Review を選択すると App Transfer についての画面が表示される。

f:id:Koze:20180725114242p:plain

そのまま次の画面に進もうとしてもエラーで弾かれるので、適宜必要な情報を入力する。

f:id:Koze:20180725114655p:plain

完了すると譲渡リクエストが Contract In Effects に移る。

f:id:Koze:20180725110055p:plain

3時間ぐらいで処理が完了するとメールが届く。

f:id:Koze:20180725111224p:plain

これにて完了。

*1:スクリーンショットが iTunes Connect と App Store Connect と混在しているのは App Transfer を行なった時期が2018年3月で、記事を書いたのが App Store Connect 発表後の2018年7月のため。ご了承を。

NSUserDefaults に時・分のみ記録する(日付は不要なケース)

日付は不要で時・分のみを NSUserDefaults に保存したい場合、パッと以下の様な方法が思い付く

  • 時・分を2つに分けて2つの NSNumber で保存する
  • 時・分を分換算して1つの NSNumber で保存する
  • NSDate で保存して時・分のみを利用する

他には以下も考えられる

  • 時・分を設定した NSDateComponents を Archive して NSData にして保存する

Apple はどう実装しているのか実例を探してみる。


「システム環境設定>省エネルギー>ディスプレイをオフにするまで」の時間を1時間5分に設定
System Preferences>Energy Saver>Turn display off after

f:id:Koze:20180706001624p:plain

defaults read /Library/Preferences/com.apple.PowerManagement.plist
{
    "AC Power" =     {
        "Automatic Restart On Power Loss" = 0;
        DarkWakeBackgroundTasks = 1;
        "Disk Sleep Timer" = 10;
        "Display Sleep Timer" = 65;
        "Display Sleep Uses Dim" = 1;
        GPUSwitch = 2;
        "System Sleep Timer" = 65;
        "Wake On LAN" = 1;
    };
    SystemPowerSettings =     {
        "Update DarkWakeBG Setting" = 1;
    };
}

分に換算


「システム環境設定>省エネルギー>スケジュール...」で「起動またはスリープ解除」を毎日1:23「スリープ」を毎日4:56に設定
System Preferences>Energy Saver>Schedule...>Start up or wake, sleep

f:id:Koze:20180706001629p:plain

defaults read /Library/Preferences/SystemConfiguration/com.apple.AutoWake.plist
{
    RepeatingPowerOff =     {
        eventtype = sleep;
        time = 296;
        weekdays = 127;
    };
    RepeatingPowerOn =     {
        eventtype = wakepoweron;
        time = 83;
        weekdays = 127;
    };
}

分に換算


「システム環境設定>通知>おやすみモード>おやすみモードをオンにする設定」で開始を23:45、終了を1:23に設定
System Preferences>Notifications>Do Not Disturb>Turn on Do Not Disturb

f:id:Koze:20180706002503p:plain

sudo defaults read ~/Library/Preferences/ByHost/com.apple.notificationcenterui.*

(なぜか sudo しないと読めない、且つ設定変更直後3秒間ぐらいも読めない)

{
    dndEnd = 83;
    dndMirroring = 0;
    dndStart = 1425;
    doNotDisturb = 0;
}

分に換算


「システム環境設定>ディスプレイ>Night Shift>スケジュール」をカスタムにして開始を23:45、終了を1:23に設定
System Preferences>Displays>Night Shift>Schedule

f:id:Koze:20180706013113p:plain

sudo defaults read /private/var/root/Library/Preferences/com.apple.CoreBrightness.plist

又は

sudo defaults read com.apple.CoreBrightness.plist

抜粋

        CBBlueReductionStatus =         {
            AutoBlueReductionEnabled = 1;
            BlueLightReductionAlgoOverride = 4;
            BlueLightReductionAlgoOverrideTimestamp = "2018-07-05 16:51:14 +0000";
            BlueLightReductionDisableScheduleAlertCounter = 3;
            BlueLightReductionSchedule =             {
                DayStartHour = 1;
                DayStartMinute = 23;
                NightStartHour = 23;
                NightStartMinute = 45;
            };
            BlueReductionEnabled = 1;
            BlueReductionMode = 2;
            BlueReductionSunScheduleAllowed = 1;
            Version = 1;
        };

時・分を分ける

まとめ

NSUserDefaultssetObject:forKey: をすると plist のルートに値が保存されるけど、Apple の実装としては(全体的に保存される設定が複雑な事もあってか、保存パラメータから簡潔にモデルのインスタンスを作るためか)ルートには値を保存せずに辞書でラップして分換算で時・分を保存するのがメジャーなのかなという感じ。
分換算するか時・分を分けて保存するかはどちらでも良い様な気がするけど、とりあえず NSDateNSDateComponents で時・分のみを管理するなんてのは冗長で、管理の面からも human-readble で分かりやすい形で保存するのがベターだと思った。

メモ

plist の内容表示は以下

defaults read foo.plist
plutil -p foo.plist
/usr/libexec/PlistBuddy -c print foo.plist

設定ファイルの捜索場所は以下のあたり
対象の plist ファイルが色々なところに散らばっていて探すのは結構大変だった、、

~/Library/Preferences/
~/Library/Preferences/ByHost/
/Library/Preferences/
/private/var/root/Library/Preferences/

Objective-C と Swift の App Store 配布時のファイルサイズの違い

概要

元々 Objective-C のみで作られたアプリでライブラリに Firebase と自前の Objective-C Framework を複数含む。
これに Swift クラスを3つ(3ファイル)追加しアーカイブしたものを iTunes Connect にアップロードして「App Store ファイルのサイズ」から推定 App Store ファイルサイズを表示。

結果

表の左は Xcode 9.3 でビルドして 2018年3月17日 14:18 にアップロード
表の右は Xcode 9.4 でビルドして 2018年6月1日 1:48 にアップロード
(バージョンが揃ってないため正確なベンチにはならないけどざっと比較確認したかったので、、)

デバイスの種類 ダウンロードサイズ インストールサイズ デバイスの種類 ダウンロードサイズ インストールサイズ
Universal 3.69MB 5.17MB Universal 6.05MB 11.8MB
iPod Touch Sixth Generation 3.63MB 5.02MB iPod Touch Sixth Generation 5.95MB 11.6MB
iPhone SE 3.63MB 5.02MB iPhone SE 5.95MB 11.6MB
iPhone 5S 3.63MB 5.02MB iPhone 5S 5.95MB 11.6MB
iPhone 6 3.63MB 5.02MB iPhone 6 5.95MB 11.6MB
iPhone 6 Plus 3.65MB 5.03MB iPhone 6 Plus 5.96MB 11.6MB
iPhone 6s 3.63MB 5.02MB iPhone 6s 5.95MB 11.6MB
iPhone 6s Plus 3.65MB 5.03MB iPhone 6s Plus 5.96MB 11.6MB
iPhone 7 3.63MB 5.02MB iPhone 7 5.95MB 11.6MB
iPhone 7 Plus 3.65MB 5.03MB iPhone 7 Plus 5.96MB 11.6MB
iPhone 8 3.63MB 5.02MB iPhone 8 Plus 5.96MB 11.6MB
iPhone 8 Plus 3.65MB 5.03MB iPhone 8 5.95MB 11.6MB
iPhone X 3.65MB 5.03MB iPhone X 5.96MB 11.6MB
iPad (5th generation) 3.63MB 5.02MB iPad (5th generation) 5.95MB 11.6MB
iPad Wi-Fi + Cellular (5th generation) 3.63MB 5.02MB iPad Wi-Fi + Cellular (5th generation) 5.95MB 11.6MB
iPad (6th generation) 5.95MB 11.6MB
iPad Wi-Fi + Cellular (6th generation) 5.95MB 11.6MB
iPad Air Wifi 3.63MB 5.02MB iPad Air Wifi 5.95MB 11.6MB
iPad Air Wifi + Cell 3.63MB 5.02MB iPad Air Wifi + Cell 5.95MB 11.6MB
iPad Air 2 Wifi 3.63MB 5.02MB iPad Air 2 Wifi 5.95MB 11.6MB
iPad Air 2 Wifi + Cell 3.63MB 5.02MB iPad Air 2 Wifi + Cell 5.95MB 11.6MB
iPad Mini 2 Wifi 3.63MB 5.02MB iPad Mini 2 Wifi 5.95MB 11.6MB
iPad Mini 2 Wifi + Cell 3.63MB 5.02MB iPad Mini 2 Wifi + Cell 5.95MB 11.6MB
iPad Mini 3 Wifi 3.63MB 5.02MB iPad Mini 3 Wifi 5.95MB 11.6MB
iPad Mini 3 Wifi + Cell 3.63MB 5.02MB iPad Mini 3 Wifi + Cell 5.95MB 11.6MB
iPad mini 4 WiFi 3.63MB 5.02MB iPad mini 4 WiFi 5.95MB 11.6MB
iPad mini 4 WiFi + Cellular 3.63MB 5.02MB iPad mini 4 WiFi + Cellular 5.95MB 11.6MB
iPad Pro WiFi 3.63MB 5.02MB iPad Pro WiFi 5.95MB 11.6MB
iPad Pro WiFi + Cellular 3.63MB 5.02MB iPad Pro WiFi + Cellular 5.95MB 11.6MB
9.7-inch iPad Pro 3.63MB 5.02MB 9.7-inch iPad Pro 5.95MB 11.6MB
9.7-inch iPad Pro Cellular 3.63MB 5.02MB 9.7-inch iPad Pro Cellular 5.95MB 11.6MB
10.5-inch iPad Pro 3.63MB 5.02MB 10.5-inch iPad Pro 5.95MB 11.6MB
10.5-inch iPad Pro Wi-Fi + Cellular 3.63MB 5.02MB 10.5-inch iPad Pro Wi-Fi + Cellular 5.95MB 11.6MB
12.9-inch iPad Pro (2nd generation) 3.63MB 5.02MB 12.9-inch iPad Pro (2nd generation) 5.95MB 11.6MB
12.9-inch iPad Pro Wi-Fi + Cellular (2nd generation) 3.63MB 5.02MB 12.9-inch iPad Pro Wi-Fi + Cellular (2nd generation) 5.95MB 11.6MB

まとめ

ダウンロードベースで2.3MB程度、インストールベースで6.5MB程度が増加した。
もし Swift ソースに複数の Swift バージョンが含まれている場合はランタイムを内包するためもうちょっと容量が増えるのかも知れない。(未確認)
2つのビルドの期間に新しい iPad が発表されているのでデバイスのリストに差異が出ている。
アップロード時にサーバ側で評価された値がそのまま表示される様だ。

等幅フォントのリスト iOS monospace font

iOS で使える等幅フォントどれだっけな?てのをたまに知りたくなる。
検索しても引っかからないなーと思ったら gist に放り込んだまま(4年も前に!)でブログに書いてなかった模様。てことで記事化しておく。

iOS UIKit, macOS AppKit, どちらでも利用可能な Core Text と3通りの実装を書いている。

List of monospace font

パラメータを変えれば等幅フォントの一覧の他にも縦書き対応フォントの一覧を取得したり応用できる。

DebugLog を応用した DebugBlock

DebugLog

良く見かける技で、デバッグビルド時のみ出力されるログを以下の様に定義出来る。

#if DEBUG
#define DebugLog(...) NSLog(__VA_ARGS__)
#else
#define DebugLog(...)
#endif

Before

#if DEBUG
    NSLog(@"debug message");
#endif

After

    DebugLog(@"debug message");
DebugBlock

応用して Block を扱ってみる。

#if DEBUG
#define DebugBlock(block) block()
#else
#define DebugBlock(block)
#endif

Before

#if DEBUG
    view.layer.borderColor = [UIColor redColor].CGColor;
    view.layer.borderWidth = 1;
#endif

After

    DebugBlock(^{
        view.layer.borderColor = [UIColor redColor].CGColor;
        view.layer.borderWidth = 1;
    });

これでデバッグビルド時のみ実行される実装をブロック内に記述する事が出来る。
上記ではデバッグビルド時のみビューの位置と大きさを確認出来る様に枠線を表示している。


koze.hatenablog.jp

一応上記の方法で retain count を調べてみたところ、デバッグビルド時のみ変数のキャプチャが行われリリースビルド時はキャプチャが行われなかった。
Xcode で記述する際にブロック構文のコード補完が行われないのが惜しいけど、まぁまぁ使えそう。

画像のExifデータの取得方法と取得データの比較 iOS

検証環境
  • Xcode 9.2, iOS 11.2 Simulator
  • iOS 11.1 iPhone X の背面カメラで撮影したものを AirDrop で Mac に送ってシミュレータ経由で iCloud Photo Library に取り込んだ写真を用いてデータを確認

Exifの取得

まず取得方法をざっくり
Exif等のデータをプロパティとして Core ImageImage I/O で取得可能

CoreImage
    CIImage *image = [CIImage imageWithContentsOfURL:URL];
    NSDictionary<NSString *,id> *properties = image.properties;
    NSLog(@"%@", properties);
Image I/O + CGImageSourceCopyPropertiesAtIndex
    CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef)URL, nil);
    NSDictionary *properties = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(src, 0, nil));
    NSLog(@"%@", properties);
    CFRelease(src);


Image I/O でメタデータとしても取得可能

Image I/O + CGImageSourceCopyMetadataAtIndex
    CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef)URL, nil);
    CGImageMetadataRef metadataRef = CGImageSourceCopyMetadataAtIndex(src, 0, nil);
    NSArray *tags = CFBridgingRelease(CGImageMetadataCopyTags(metadataRef));
    for (id tag in tags) {
        CGImageMetadataTagRef tagRef = (__bridge CGImageMetadataTagRef)tag;
        CFShow(tagRef);
    }
    CFRelease(src);
    CFRelease(metadataRef);
結果1

CoreImageImage I/O + CGImageSourceCopyPropertiesAtIndex で取得出来る内容は同じ。
ただし後者の CGImageSourceCopyPropertiesAtIndex の場合は1つのファイルに複数の画像が含まれる画像形式に対応しているので、仮に複数画像が存在して且つ画像毎に取得出来るプロパティが異なっている場合 CIImage から取得出来るプロパティとどう違いがあるかは検証が必要。(おそらくindex 0の1枚目の画像のプロパティを返すんじゃないかと予想)

以下が取得出来るプロパティのサンプル

Exif data with iOS 11 iPhone X back camera

結果2

Image I/O + CGImageSourceCopyMetadataAtIndex で取得出来るものは他のものと内容や形式が異なっている。
ちなみに NSArray のままログ出力すると CFBasicHash あたりの表示が見辛くなってしまうのでサンプルではfor文で回して CFShow でタグを1つずつログ出力した。

以下が取得出来るメタデータのサンプル

Metadata with iOS 11 iPhone X back camera

取得データの比較

結果1と結果2を見比べると、ざっと見て結果2の方が数が少なそうに見える。比較して確認してみる。
メタデータのタグに対応するプロパティのキーやデータを探して、対応するものをCSVの別カラムに記載した。(MakerApple のキー2は512バイトのデータなので記載を省略)

Compare Exif and metadata tag.

補足

  • exifEX:PhotographicSensitivity

exifEX:PhotographicSensitivity はプロパティを見ると対応するものが無いように見える。
ググって見つけたCIPA(一般社団法人カメラ映像機器工業会)に掲載のPDFによると

http://www.cipa.jp/std/documents/e/DC-010-2012_E.pdf

PhotographicSensitivity は Exif 2.3以降の規格で、Exif 2.21 まで使用されていた ISOSpeedRatings を代替するものらしい。ISOSpeedRatings は deprecated との事。
テストに利用した写真の ExifVersion を見てみると 0221 とあるので、この写真では PhotographicSensitivity は使用されておらず ISOSpeedRatings に値が入っていて、タグ取得のAPIではマッピングによりこれを参照しているという事らしい。


  • iio:hasXMP

iio:hasXMP は正確な仕様は不明。
iio は ImageIO の prefix でApple独自のメタデータタグだと思われる。(ImageIOBase.h を見ると IIO_HAS_IOSURFACEIIO_BRIDGED_TYPE といった定義が見られる。)
名前の通りxmp のタグが存在しているかどうかを示すものだと推測。


  • xmp:CreateDate
  • xmp:ModifyDate
  • photoshop:DateCreated

先のCIPAのPDFには以下のように掲載されていたので、xmp:CreateDatexmp:ModifyDate はそれをそのまま適用。

DateTimeDigitized = xmp:CreateDate
DateTimeOriginal = exif:DateTimeOriginal
DateTime = xmp:ModifyDate

またメタデータタグには exif:DateTimeOriginal が存在しておらず、またプロパティには photoshop:DateCreated のタグに当てはまるものは無かった。
このタグの扱いをどうしたものかと思っていると CGImageMetadata.h のコメントに以下の記載を発見

  • Metadata Working Group guidance is factored into the mapping of CGImageProperties to
  • XMP compatible CGImageMetadataTags.
  • For example, kCGImagePropertyExifDateTimeOriginal will get the value of the
  • corresponding XMP tag, which is photoshop:DateCreated.

という事で、マッピングされているらしい。
よって DateTimeOriginal = photoshop:DateCreated として適用。

結果より

メタデータタグ CGImageMetadataRef を使うと exif:Flash のビットや exif:ExifVersion の配列を利用しやすい形式に変換してくれたり、マッピングにより Exif のバージョンの違いを吸収をしてくれるという利点がある。
ただし ColorModelProfileName だったりプライベート仕様の MakerApple の情報も余す事なく表示したい場合はプロパティを使用する必要がある。
用途に合わせてうまく併用するのが良いかも知れない。

システムと同じ並びに日本語の文字列をソートする

NSString の比較メソッド一覧

- (NSComparisonResult)compare:(NSString *)string;
- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask;
- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToCompare;
- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToCompare locale:(nullable id)locale;

- (NSComparisonResult)caseInsensitiveCompare:(NSString *)string;
- (NSComparisonResult)localizedCompare:(NSString *)string;
- (NSComparisonResult)localizedCaseInsensitiveCompare:(NSString *)string;

- (NSComparisonResult)localizedStandardCompare:(NSString *)string API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

例として iOS の読み上げ言語の設定画面(設定>アクセシビリティ>スピーチ>声)

f:id:Koze:20180119121548j:plain

テストコード

String sort similar to iOS.

カタカナの部分に関してはいずれも差異は無いが、漢字のソート結果が違っている。
localizedCompare:localizedStandardCompare: は漢字の部分もOSと並びが合っている。
localizedStandardCompare: のドキュメントを見ると

This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate.

Finder-like との事なので iOS, macOS のシステムの表示に合わせるには localizedStandardCompare: を使うのが良さそう。