ObjecTips

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

Core Data Code Generation の Objective-C と Swift の違い

環境 Xcode 10.0

Core Data Code Generation

Code Generation は Xcode が Core Data のモデルクラスの基本実装を自動で行ってくれる機能で <ProductName>.build/Debug-iphonesimulator/<ProductName>.build/DerivedSources/CoreDataGenerated/<FileName> の中にファイルが自動で生成される。

FileName は .xcdatamodeld のファイル名

Objective-C + Code Generation

Objective-C の場合以下の様な4ファイルが生成される。
(Model は .xcdatamodeld で設定したエンティティ名、もしくはクラス指定をしていればクラス名が入る)

Model+CoreDataClass.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

NS_ASSUME_NONNULL_BEGIN

@interface Model : NSManagedObject

@end

NS_ASSUME_NONNULL_END

#import "Model+CoreDataProperties.h"

Model+CoreDataClass.m

#import "Model+CoreDataClass.h"

@implementation Model

@end

Model+CoreDataProperties.h

#import "Model+CoreDataClass.h"


NS_ASSUME_NONNULL_BEGIN

@interface Model (CoreDataProperties)

+ (NSFetchRequest<Model *> *)fetchRequest;

@property (nullable, nonatomic, copy) NSDate *creationDate;
@property (nullable, nonatomic, copy) NSDate *modificationDate;

@end

NS_ASSUME_NONNULL_END

Model+CoreDataProperties.m

#import "Model+CoreDataProperties.h"

@implementation Model (CoreDataProperties)

+ (NSFetchRequest< Model *> *)fetchRequest {
    return [NSFetchRequest fetchRequestWithEntityName:@"Model"];
}

@dynamic creationDate;
@dynamic modificationDate;

@end
Swift + Code Generation

Swift の場合以下の2ファイルが生成される。
Model+CoreDataClass.swift

import Foundation
import CoreData

@objc(Model)
public class Model: NSManagedObject {

}

Model+CoreDataProperties.swift

import Foundation
import CoreData


extension Model {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Model> {
        return NSFetchRequest<Model>(entityName: "Model")
    }

    @NSManaged public var creationDate: Date?
    @NSManaged public var modificationDate: Date?

}

これらのソースが自動で生成され利用する事が出来る。

Objective-C + Code Generation 利用編

Objective-C で Code Generation した場合は追加で以下の2つのファイルも生成され、利用にはこれを import する必要がある。

FileName+CoreDataModel.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

#import "Model+CoreDataClass.h"

FileName+CoreDataModel.m

#import "FileName+CoreDataModel.h"

Objective-C で利用するにはクラスのヘッダか実装部に import する

#import "ProductName+CoreDataModel.h"

Swift から利用するには Objective-C to Swift の橋渡しとして ProductName-Bridging-Header.h に import する

#import "ProductName+CoreDataModel.h"

*1

Swift + Code Generation 利用編

Swift で Code Generation した場合は追加で以下のファイルが作成されるが、それぞれのクラスのスコープがデフォルトの internal になっているため Objective-C と比べてソースの中身は特に何も実装されていない。

FileName-CoreDataModel.swift

import Foundation
import CoreData

Swift で Code Generation して Swift で使う場合、プロジェクト上にはヘッダもクラス定義も存在しない(見えていない)がクラスを呼び出して使用する事が出来る状態になる。

Objective-C から利用するには Swift to Objective-C の橋渡しとして ヘッダか実装部に以下を import する

#import "ProductName-Swift.h"

*2

*1:Bridging-Header は正確にはビルド設定の Objective-C Bridging Header の値

*2:正確にはビルド設定の Objective-C Generated Interface Header Name の値でデフォルト値は $(SWIFT_MODULE_NAME)-Swift.h

アプリの譲渡 App Transfer の注意点1

App Transfer の手順については以下

koze.hatenablog.jp

譲渡作業で確認できた注意点

ダウンロード等のレポート情報が見られなくなる

アプリの譲渡を行うと譲渡側の App Store Connect からアプリ自体が削除されてしまい、これまでのダウンロード数などのレポート画面を表示する事が出来なくなる。
アプリの受取側のレポートには譲渡後の数字のみが反映され譲渡前の数字を表示する事(知る事)は出来ない。
アプリを譲渡したからといってこれまでのマーケットに関するデータまで全て受取側に提供する必要がないのは分かるが、譲渡側がこれまでのレポートにアクセス出来なくなるのはちょっと問題かも。Apple に要望を出してもいいかも知れない。

アプリの譲渡前後を合算した総ダウンロード数などの情報が必要になる事はままあると思うので、譲渡の際には譲渡側で譲渡直前の最新の各種レポートをCSVなりXLS形式でダウンロードしておく事をオススメする。
今回のケースでは総ダウンロード数、全体の月別ダウンロード数、地域別の月別ダウンロード数、地域を掘り下げた各国の月別ダウンロード数、バージョン毎のダウンロード数、月別アップデート数を事前に取得しておいた。

例えば3月に譲渡を行なった場合に譲渡側では3月の途中までのデータ、受取側では3月の途中からのデータを取得可能なので、集計レポートを作成する際に3月分については各レポートを自前で合算してやる必要がある。

1月 2月 3月 譲渡 3月 4月 5月
ダウンロード数 10 10 4 6 10 10

手動で合算 ↓

1月 2月 3月 4月 5月
ダウンロード数 10 10 10 10 10

もし月別ではなく日別でレポートを取得した場合にきっちりと譲渡前後の日付でレポートが分割されるのであれば集計もセルを並べるだけでいいので楽だったかも知れない。(もし譲渡日のレポートが譲渡側と受取側で分割してレポートが取れるのであれば譲渡日のレポートをやはり自前で集計する必要がある)

App Store Connect のレポートは時間帯によっては前日や2日前分までしか取得出来ないので、レポートのタイムラグと譲渡切り替えのタイミングの関係で1日分譲渡側と受取側のどちらもアクセス出来ない日が存在するのではという懸念もある。

いずれにしても、もし次の機会があれば日別のレポートをダウンロードしておこうと思う。

アプリの譲渡 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 で記述する際にブロック構文のコード補完が行われないのが惜しいけど、まぁまぁ使えそう。