面白きことは良きことなり

拙く未熟なiOSエンジニアの備忘録と戯言

Swiftの文字列にて、Implicitly Unwrapped Optional型の文字列を扱う際に失敗した話

Implicitly Unwrapped Optional型と呼ばれる下記のような記述を行うことで、 この変数を使う時にUnwrapしてくれるというのは大体の人は知っていると思う。

let hoge: String!

しかし、Unwrapしない時もあり、そのパターンに嵌ったので今後同じことがないように記録

パターン1:文字列に組み込んだ時 “hoge\(fuga)moga”

下記の場合、Unwrapされず、文字列にOptionalとまで入ってしまう。 hogeに対して!をつけてやればUnwrapされ、欲しい文字列になるだろうが、 Implicitly Unwrapped Optional型なのに、わざわざ!をつけてやる意味がわからない。 f:id:aryzae:20170502214215p:plain

結果、良いか悪いわからないが、下記記述方法に落ち着いた。 また、let hoge: Stringと、String型で定義してやっても、前者の方法でもなんら問題ない f:id:aryzae:20170502214219p:plain

パターン2:ローカル変数に入れた後、メソッドへ渡した時

先にやりたいことを提示しておく。 下記みたく引数にString型を取るメソッドにString!型を渡し、Unwrapして使うようにしたい f:id:aryzae:20170502215911p:plain

しかし、一度別の変数に入れた後、その変数をメソッドに渡すとForceUnwrapしなさいと怒られエラーになる f:id:aryzae:20170502215906p:plain

なぜなのか、型を確認したのが、下記となる。 最初String!型だったのに、変数に入れた際、 String型でもString!型でもなく、String?型になっている f:id:aryzae:20170502220105p:plain

理由

  • Swift3からImplicitly Unwrapped Optionalは、Optional型に付属するようになった
  • 型評価が必要な時だけUnwrapされる
  • それ以外はOptionalとして保持されている www.natashatherobot.com

Frameworkに含まれるXIBやNIBファイルを使用しているのに表示されず"Unknown class HogeClass in Interface Builder file."と出る時の対処方法

3rd Party提供Frameworkに含まれるXIB、NIB

3rd Partyが提供しているFrameworkには、稀にXIBやNIBが含まれており、それを画面上に配置して使うことがある。 この時、Xcodeプロジェクトに取り込み、StoryboardでCustomClassとして適用して使うこともあるだろう。

動作確認すると・・・

配置もバッチリ!動作確認で動かしてみる・・・。 するとどうだろう画面には取り込んだはずのXIBやNIBの面影すらない。

ログを確認すると

Unknown class HogeClass in Interface Builder file.

と出ている。 取り込んだはずなのに、Unknownとかどういうこと?ってなること必死である。

解決方法

Build Setting>Linking>Other Linker Flags-ObjC を追加する

Frameworkで忘れがちな-ObjCこれを忘れてはいけない。 追加したら再度実行してみよう。

フリーのApple IDアカウントを使ってXcodeからアプリを実機転送できることで、App ID登録時にエラーを出してしまう可能性の問題

要約

  • Xcode7からApple IDアカウントがあれば、有料の開発者登録を行わずとも実機にアプリを転送できるようになった
  • その結果App IDを無作為に作成することができ、アカウントが違っても同じApp IDを作成することはできないので、何故かApp IDの登録でエラーになり、ナンデー?!となることも
  • フリーアカウントではApp IDの管理が一切できず、有料登録をするのも実際の開発をする人ぐらいなので、App ID取得するだけして放置される問題がある
  • 個人的には有料の開発者登録している人のみが実機にアプリを転送できる以前に戻って欲しい

問題の発生内容

1.→ フリーのApple IDアカウントでXcodeにサインイン後、適当なプロジェクトを作成

2.→ Bundle Identifierを任意のものに設定する

3.→ 実機を繋いでビルドする

4.→ 下記スクリーンショットのようにプロビジョニングが自動生成され、App IDも一意のものとして作成される

f:id:aryzae:20170422022542p:plain

5.→ 有料登録している開発者アカウントでApple Developerにサインインし、「2.」のBundle Identifierと同じ文字列をApp IDに登録を試みる

f:id:aryzae:20170422022540p:plain

6.→ 「There were errors in the data supplied. Please correct and re-submit.」「An App ID with Identifier ‘~’ is not available. Please enter a different string.」

とエラーが出て使えないから別のApp IDにしてねと言われる。

過去に自分が別アカウントとかで作って、忘れてる場合等にはこのエラー内容じゃ何がダメなのかわからないのも問題。

「既に使われています。」くらいのエラーは出して欲しいところ。

f:id:aryzae:20170422022541p:plain

対策

  • 安直な逆ドメインのApp IDとかは被りやすいため、なるべく複雑なものにする必要がある。
  • 個人のフリーアカウントで開発し、リリースは会社等の別アカウントで行う場合、誤ってフリーアカウントでApp IDを取ることもあるため、開発中は仮のBundle Identifierを設定したり、証明書とプロビジョニングをリリース予定アカウントから引っ張ってくる
  • ベストなのは、有料登録済みのリリース予定アカウントで、App IDを先に作成しておく

参考URL

dev.classmethod.jp

iOS(Swift)から3ステップでMastodonに画像付き投稿を行う

まえがき

  • 前回は、登録、ログイン、投稿の3ステップを記載
  • 前回の延長線上で画像付きの投稿もやってみよと思い立つ
  • 画像やmime/typeは固定で記述してるので適宜変更してください。
  • 今回もmastdn.jpを使用しています。適宜変更してください。
  • タイトルのことが出来る処理の流れを主軸に記述しているため、ForceUnwrap等よくない書き方は意図的にしています。(早めに直します・・・)

今回もQiitaに投稿済み (今後も技術記事は両方に投稿&Wメンテ予定。これに伴い、はてな記法からMarkdownに変更)

準備

extension Data {
    public mutating func append(_ string: String) {
        let data = Data(string.utf8)
        return self.append(data)
    }
}
// 今回はPNG固定でファイル名とmime/typeを使用
let filename = "hoge.png"
let mimetype = "image/png"

// Create body for media
func createBodyWith(parameters: [String: String]?, filePathKey: String?, imageData: Data, boundary: String) -> Data {
    var body = Data();

    if let parameters = parameters {
        for (key, value) in parameters {
            body.append("--\(boundary)\r\n")
            body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
            body.append("\(value)\r\n")
        }
    }

    body.append("--\(boundary)\r\n")
    body.append("Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n")
    body.append("Content-Type: \(mimetype)\r\n\r\n")
    body.append(imageData)
    body.append("\r\n")
    body.append("--\(boundary)--\r\n")

    return body
}

func generateBoundaryString() -> String {
    return "Boundary-\(NSUUID().uuidString)"
}

Step.1 前回の内容を使ってログイン

aryzae.hatenablog.com

上記の前記事を参考に、access_tokenを取得します。

Step.2 画像をアップロード

// 画像アップロード先URL
let uploadUrl = URL(string: "https://mastdn.jp/api/v1/media")!

// params生成
// access_token: Step.1で取得しているもの
let params: [String: String] = ["access_token": responseJson["access_token"] as! String]

// imageData生成
let imageData = UIImagePNGRepresentation(UIImage(named: filename)!)!

// boudary生成
let boundary = generateBoundaryString()

// request生成(前回作ったメソッドが使えないため、地道にValue追加)
var request = URLRequest(url: urlString!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpBody = createBodyWith(parameters: params, filePathKey: "file", imageData: datas, boundary: boundary)

// 画像アップロードPOST
let task = session.dataTask(with: request, completionHandler: { data, response, error in
    do {
        self.responseJson = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! Dictionary<String, AnyObject>
    } catch {
    }
})
task.resume()
// responseJsonの中身
{
    "id": 12356,
    "preview_url": "https://mastdn.jp/system/media_attachments/files/000/069/064/small/b804bb6a6c8db3d2.png?1492450358",
    "text_url": "https://mastdn.jp/media/hOgVBgaqylfQM-IolQw",
    "type": "image",
    "url": "https://mastdn.jp/system/media_attachments/files/000/069/064/original/b804bb6a6c8db3d2.png?1492450358"
}

Step.3 画像ID付きでTootの投稿

// 前回のToot投稿に画像アップロード時のidを付加するだけ
// TootURL
let tootUrl = URL(string: "https://mastdn.jp/api/v1/statuses")!

// access_token: Step.1で取得しているもの
// status: Tootの内容
// media_ids: Step.2で取得したid(最大4つまで)。値は配列に格納。
// visibility: 公開範囲。(省略可能)
let body: [String: String] = ["access_token": responseJson["access_token"] as! String, "status": "開発アプリからのテスト投稿", "visibility": "public", "media_ids": [responseJson["id"]] as AnyObject]

// Toot POST
do {
    try post(url: tootUrl, body: body) { data, response, error in
        // dataは返ってくるが、投稿できるまでの処理を書くだけなので省略
    }
} catch {
}

参考にしたURL

  1. Tootsuite documentation
  2. Image upload example with Swift and PHP

Kaspersky Internet Security(for Mac)は使い物にならない

導入理由

Macを使うようになったのもここ4〜5年の内で、今だメイン機Windowsだったりする。 PCゲームをすることもあって、自作PCも組むこともあり、必然的にWindowsがメインになるわけ。

そしてPCゲームする時セキュリティソフトは軽く、検知率が高いほうがいいってことで 8年くらいKaspersky Internet Securityを使って満足していた。 最近は1ライセンス5台まで使えるようになったこともあり、セキュリティを意識していなかったMacにも導入しておこうと思いインストールした。

使用数日の感想

  • Windowsで重さを感じないが、Macではとにかく重い
  • 保護無効にしても変わらず、App自体を終了すると軽くなるため、起動すらアウト
  • 起動中は、SourceTreeでpullやpush、fetchをするとインジケーターが出て5〜10分くらいかたまる
  • Xcodeのビルドも体感2倍以上遅くなる

CPUやメモリのリソースをとにかく持っていくらしく、Webブラウザしか使わない人にはいいかもしれない。

結論

Kaspersky Internet Security for Macは使うべきでない! 代わりのいいセキュリティソフトを誰か教えてください!