2017年10月31日火曜日

Selectorの書き方

Timerとかで処理先のメソッドを指定するSelectorだけど、書式が変わったし、引数を渡したい場合などがよくわかってないのでまとめ。

基本書式

override func viewDidLoad() {
    super.viewDidLoad()

    let t = Timer.init(timeInterval: 0.5,
                       target: self,
                       selector: #selector(ViewController.hoge(_:)),
                       userInfo: userInfo,
                       repeats: false)
    t.fire()

}

@objc func hoge(_ sender:Timer) -> Void {
    print("タイマー実行")
}

上のコードは0.5秒後にhoge()を一度だけ実行する。
selectorで指定するメソッドは、Swift4から頭に@objcを付けてやらなければいけない。
これはどうやらObjective-C由来の仕様だからで、今までは推定して実行してくれてたけど、もうやってくれないんだそうな。ケチだな。

selfがViewControllerの場合、Selectorは以下のとおり。

hoge()に引数がある場合

⭕️ #selector(ViewController.hoge(_:))
⭕️ #selector(ViewController.hoge)
⭕️ #selector(hoge(_:))
⭕️ #selector(hoge)

普通に後ろに()を付けたり、以前の書き方のように" "で囲むのはダメ。
❌ #selector(ViewController.hoge())
❌ #selector("ViewController.hoge(_:)")
❌ #selector("ViewController.hoge")

hoge()に引数がない場合

⭕️ #selector(ViewController.hoge)
⭕️ #selector(hoge)

 #selector(hoge(_:))
 #selector(hoge())

メソッドの引数は使えない?

引数を渡したくて、普通に書いてやっても、エラーになってしまう。
❌ selector: #selector(hoge(str: "文字"))

@objc func hoge(str:String) -> Void {
    print("タイマー実行 \(str)")
}

それでも引数を渡すのだ!

引数の渡し方は以下のようにuserInfo経由のものとなる。
複数の引数を渡したいなら、配列や辞書に入れて渡すのかな?
hogeの方ではsender.userInfoで取り出せる。

let userInfo = "引数がわり"
let t = Timer.init(timeInterval: 0.5,
                   target: self,
                   selector: #selector(hoge(_:)),
                   userInfo: userInfo,
                   repeats: false)


@objc func hoge(_ sender:Timer) -> Void {
    let str = sender.userInfo
    print("タイマー実行 \(String(describing: str))")
}

senderのかわりに別の名前でもいい。型名は送り手側のクラス名になるのだな。
@objc func hoge(_ s:Timer) -> Void {
    let str = s.userInfo
    print("タイマー実行 \(String(describing: str))")
}

めんどくさいね

書き方がちょくちょく変わるし、引数の渡し方も特殊だし、総じて面倒臭い。
もっと簡単な書き方にしやがれと思うのだが。

2017年10月29日日曜日

Objective-CからSwiftへの移行でNSKeyedunarchiverの不具合

Objective-Cで書いてたアプリをSwift4に書き直してる。
カスタムクラスをファイルに読み書きしているので、NSCodingを使ってNSKeyedUnarchiverを使っているんだけど、Objective-Cそのままのやり方でSwiftに書き直したら、カスタムクラスの
dataArray = NSKeyedUnarchiver.unarchiveObject(with:d) as! [incidentDatas]
のところでSIGABRTになって止まってしまい、その先のrequired init?(coder aDecoder: NSCoder) {〜
が実行されなかった。

エラーメッセージは以下。
2017-10-29 23:53:04.817840+0900 SkyReporter[6679:5156962] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (incidentDatas) for key (NS.objects); the class may be defined in source code or a library that is not linked'

調べると、Objective-CでNSObjectを継承しているクラスを継承してないSwiftのクラスに持ってくるとダメだとかなんとか出てきたけど、よくわからない。

結局はこちらのページ(ObjCからSwiftに書きなおす際にNSCodingがうまく動かない(解決済))のとおり、
@objc (incidentDatas)
class incidentDatas:NSObject, NSCoding {〜
というように、カスタムクラス名の前に@objcの一文付け加えてやったところ、
required init?(coder aDecoder: NSCoder) {〜
のところを実行してくれたよ。

その先で引っかかってるけど、とりあえず覚書として。

2017年10月24日火曜日

MacのTime Machineボリュームのフォーマット

Time Machineボリュームは、Mac OS拡張(ジャーナリング)じゃなきゃダメ?

Time Machineのボリュームが手狭になったので、新しい外付けHDを買ってきた。
デフォルトでWindows用フォーマットがされてるドライブなので、High Sierraから導入されたAPFSに変更した。
(APFSに変更する際にも面倒なトラブルがあったのだで、最後に書く)

Time Machineのバックアップ内容は最近は単純コピーで移せるというのでドラッグ&ドロップしたのだが、「ボリュームのフォーマットが正しくないためバックアップを作成できません。」と表示されてコピーができない。
フォーマット形式で「大文字/小文字を区別する」にしているとダメらしいが、そんな設定にはしていない。

試しにTime Machineの環境設定から新しいボリュームを選択したところ、「互換性のないファイルシステムだから、ディスク消去せんとあかんよ」とのメッセージ。
どうせ新品なので消去してみたら、フォーマットが「Mac OS拡張(ジャーナリング)」に変更された。どうやらTime MachineはAPFSに対応していないようだ。
この状態でようやくTime Machineのデータをコピーすることができた。

APFS以外が出てこない
ちなみに一度APFSにすると、フォーマットの選択肢にAPFS以外が出てこなくなるので、Time Machine環境設定から変更するしかないみたい。めんどくさいし、おっかないね😩

ディスクユーティリティでHDが扱えなくなった

最初にも書いたとおり、買ってきたIO DATA製の3TB HDはデフォルトでWindowsのフォーマットがされていた。そのままでもMacからアクセスできるんだけど、やっぱりちゃんとMac用のフォーマットに直して使いたい。新しいAPFSはコピーとかが速いって聞くし。

ディスクユーティリティからボリュームを選び、フォーマットを「APFS」にして消去してみたのだが、「消去プロセスを完了できませんでした。続けるには、“完了”をクリックします。」と出てアンマウントしてしまい、再マウントもできなくなってしまった。

調べたところ似たようなケースがあるようで、ターミナルを使ってアンマウント、フォーマットをするしかないようだ。
以下のサイトを参考にさせてもらい、無事にAPFSにすることができた。感謝である。
詳しい手順はリンク先を見て欲しいが、ターミナルから以下の操作を行う。
  1. Macにつながってるボリュームの一覧を表示し、問題のボリューム名を確認。
  2. 問題のボリュームをアンマウント。
  3. 問題のボリュームをHFS+でフォーマット。
  4. 無事マウント!
その後は普通にディスクユーティリティから操作できる。
いくらOSが新しくなっても、こんなめんどくさいことしなきゃいけない局面があるのはいただけないね。

2017年10月20日金曜日

構造体をファイルやUserDefaultsに保存したい

ファイルやUserDefaultsには構造体を直接保存することができない。
そのため、構造体で使われてるメモリ領域を一度バイナリーデータにして(シリアライズ)、それを保存することで実現する。
(構造体の要素を辞書に変換して保存するやり方もあるんだけど、要素一個一個変換しなきゃいけなさそうなのでやったことはない)

構造体 -> Data

たとえばCGRectをDataにしてみる。

 let data = Data(bytes: &self.rect, count: MemoryLayout<CGRect>.size)

bytes:はUnsafeRawPointer。そこに変換したい変数のアドレスを渡す。
count:はその変数が使用してるメモリの大きさ。CGRectが使用するメモリの大きさはMemoryLayout<CGRect>.sizeで求められる。

要は、{アドレス}から始まる{大きさ}分のメモリをDataに直してるわけだ。

Data -> 構造体

逆にDataをCGRectに戻す。

 data.getBytes(&self.rect, length: MemoryLayout<CGRect>.size)

これも同様な引数を渡す。
dataを{アドレス}から始まる{大きさ}分のメモリに書き込んでるような感じ。
実際は書き込むというよりdataのポインタとself.rectのポインタを同じにしてんのかな? よくわからんですが。
注意なのは、このdataはSwiftのData型じゃなく、Objective-Cで使われてたNSDataということ。Dataで同じようなメソッドがあればいいんだけど、Swift4でどう書いたらいいかわからなかったので、暫定的にNSDataで書いてる。
中身がほぼ同じなんで値の扱いは困らないみたいからいいんだけど。

おそらくは
data.copyBytes(to: &self.rect, count: MemoryLayout<CGRect>.size)
のメソッドを書くんじゃないかと思うんだけど、to:に書くUnsafeRawPointerがUInt8とかで、CGRectのアドレスをどう変換したらUInt8にできるのかわかんない。
まあいずれまたわかったら追記する。

2017年9月30日土曜日

シミュレータだとメールが送れない

iOS8、Xcode6当時からずっと直しやがらないバグ。

メールを送信させるために
MFMailComposeViewController *mailPicker = [[MFMailComposeViewController alloc] init];
を書くわけだけど、これがiPhoneシミュレータ上だとnilのままになってしまい、
[self presentViewController:mailPicker animated:YES completion:nil];
で送信用ビューを表示しようとしても落ちる。
iPhone4だと一瞬表示されるけど引っ込んじゃってエラーを吐く。

高い給料と年会費取ってんだからさっさと直せや、ゴルァ!
Appleゥァア゛!
と強く思うけど、実機だと問題ないのでしょうがないけど我慢する。

2017年9月25日月曜日

Xcode 9でArchive時に変なチェックが

Xcode9でアプリを審査提出すべくArchiveしていたら、これまでと手続きが少し変わり、ValidateおよびUploadの段階でこのような同じメッセージが出たので覚書。

App Store distribution options

Include bitcode for iOS content
Allows the App Store to build your app to take advantage of hardware, software or compiler changes.
iOSコンテンツのビットコードを含める
App Storeがあなたのアプリケーションを構築して、ハードウェア、ソフトウェア、またはコンパイラの変更を利用できるようにします。
Upload your app's symbols to receive symbolicated reports from Apple
Crash logs and other diagnostic information from your customers will be symbolicated and viewable within Xcode.
Appleのシンボル化されたレポートを受け取るために、あなたのアプリのシンボルをアップロードしてください。
お客様のクラッシュログやその他の診断情報は、Xcode内で象徴的に表示されます。
最初のチェックのビットコードとは、アプリをコンパイルする時に、最終的に近藤マシン語臣に変換する前段階で、中間コードのようなものに変換するんだそうな。それのことのようで、含めておくといいことあるなんて読んだ。
なんのこっちゃよくわからんけどね。
最初bitcoinって書いてあるのかと思って、驚いた(笑)

次のチェックはおそらくクラッシュ情報なんかをアップルに送信していいかどうかだろう。今までは勝手に送信されてたんじゃないかって思うけど、コンプライアンス上明示し、選択できるようにしたんじゃないかと。

両方ともチェックされたままでいいと思う。

Re-sign "App name"

"App name" needs to be re-signed for App Store distribution.
Select one of the following signing options to continue.
あなたのAppは、App Storeの配布のために再署名する必要があります。
続行するには、次の署名オプションのいずれかを選択します。
App Storeで配布するための署名を自動で管理するか手動でするかの選択。これも上の自動のままでいいだろう。

Distoribution completed with warnings

その後のいつもの手順でUploadは終わったみたいなんだけど、また変なメッセージが出た。
iTunes Storeのオペレーションに失敗したと。
1024x1024のPNGフォーマットのマーケティングアイコンが含まれてないからApp Reviewかベータ App Reviewに提出できないと。
プロジェクトのImages.xcassetsのアイコンのところに1024ptのアイコンを登録する欄が増えとる…orz
他にもiPhoneとiPadでそれぞれ1x〜3xの20ptのアイコンの欄が…。めんどくせえ。
ScreenShot同様に、アイコンもでっかいの一個アップロードして、小さいのはそれを縮小して対応するようにできないのかね? 21世紀なのに…グチグチ…。

アイコン追加してアップロードし直して無事審査提出できたわい。
そのかわりiOS11やiPhone Xへの対応なんかが超テキトーだから、リジェクトされるかもね?(笑)
そんなもんにいちいち対応してられるか! 毎度毎度仕様を変えやがって!

2017年9月23日土曜日

Web関係のViewはどんどん新しく

Webを表示するためのViewは、もともとUIWebViewだったけど、いつの間にかこれdeprecate(使用不可)になってるのな。

かわりに、いつ登場したんだか知らんけど、WKWebView(WebKit View)がオススメになってる。
さらに、SFSafariViewControllerってのもあるようで。

SFSafariViewControllerはXcodeでSafariServices.frameworkをLinkeすると使える。
Xcode9現在、まだStoryboardからは直接扱えないみたいだけど、Safariとほぼ同等の機能が使えるようだ。
デフォルトでタブブラウジングができないものの、cookieとかのデータが共有できるんだと。

カスタマイズして使いたい人はWKWebView、できるだけカスタマイズせずに楽に使いたい人はSFSafariViewみたいな住み分けかしら? 最終的に一本化されるかもしれないけど。