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

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

SafariとChromeでブックーマークを共有し、同じ見た目にしてアクセスを快適にする

SafariChromeの両方を使うけど、不満があった

Macを使っている人の多くは、SafariGoogle Chromeの2つのブラウザを使用しているんじゃないかと思っている。

自分の場合、普段使いはChromeで、Apple Developer等のAppleでログインが必要なサイトはSafariを使うようにわけている。ただ、Safari開いている途中、何か調べようとした際にわざわざChrome開くとは面倒なので、Safariをそのまま使うこともある。

Safari

  • Apple系のログインが必要なDeveloperサイトはたまにSafariじゃないと正常に動作しないことがある
  • タブ周りはちょっと扱いにくい
  • keychainがあり便利

Google Chrome

  • 動作が軽快
  • タブ周りの動きが扱いやすい
  • なんとなくセキュアなものは開きたくない
  • パスワード保存できるけど、なんかやだ

企業としての傾向でAppleは、個人情報は取るが個人を特定しにくくしたりプライバシーへの配慮を重視している一方、Googleは、取れる情報片っ端から取って内部のサービスに活用しまくりなので、ブラウザを使う時に分けてしまう。

2つのブラウザ使うのは、いいけど頻繁にアクセスするサイトはブックマークから開くことも多いので、どちらのブラウザでも同じようにアクセスできるのが迷いもなくて望ましいよねって思う。

Chrome's bookmark like Safari's

SafariChromeでブックマークや見た目を統一しようと思った時、Safariは自由度低いので自ずとChrome拡張機能Safariに寄せれないかと考えた。

目指す機能

  • Safariで新規タブを開いた時に表示されるブックマーク一覧の見た目を、Chromeで新規タブ開いた時に同様に表示されるように再現
    f:id:aryzae:20180714164447p:plain

  • Safariでブックマーク追加や編集した時、Chromeに自動的に反映される

  • Chromeでブックマーク追加や編集した時、Safariに自動的に反映される

この機能があれば、ブラウザ意識することなく、ブックマークが常に同じになるし、SafariならiCloudiOS端末にも反映される。ChromeならGoogle Accountに紐づいて、同様に別の端末のChromeでも同じ拡張機能、ブックマークが使えるから便利だろうと思ったので、Chrome拡張機能を調べて実現を目指した。

Chromeに入れる拡張機能

率直に言えば、目指す機能は全部達成できた。そのために必要な拡張機能は以下の2つ。

ルーターをAterm WG2600HP3に変えた (2018/07/18追記)

NECから新ルーター発売

昔からNECルーターは質が安定していたので、愛用していた。

今まで、Aterm WR9500Nという機種を使っていたのだけれど、これが2011年に発売されたモデルで今となっては古く、802.11acにも対応していないので、いい加減買い換えたいと思っていた。

そこに舞い込んだAterm WG2600HP3の発表!もうこれに買い換えるしかないっしょ!と発表から発売までの間うきうきしていた。

www.aterm.jp

7月5日(木)に無事発売されたので、即購入した。

f:id:aryzae:20180706110103j:plain

ビフォー

新しいルーターに買い換えることもあって、少なくともacに対応できたことで無線の速度を向上をできる期待があるし、あわよくば、有線の速度も最新の技術により向上を期待していた。

そこで試行回数少ないもののSpeedtestで5回ずつ計測(画像は順不同)を行ってみた。

ちなみに回線はNUROを使用している。

有線 (Windows 10)

1回目 2回目 3回目 4回目 5回目
f:id:aryzae:20180706110409p:plain:w150 f:id:aryzae:20180706110413p:plain:w150 f:id:aryzae:20180706110417p:plain:w150 f:id:aryzae:20180706110420p:plain:w150 f:id:aryzae:20180706110424p:plain:w150
ping平均(ms) ping最高値(ms) ping最低値(ms)
4.2 3 7
DL平均(Mbps) DL最高値(Mbps) DL最低値(Mbps) UP平均(Mbps) UP最高値(Mbps) UP最低値(Mbps)
592.432 662.16 498.50 394.362 527.59 255.98

無線 (iPhone X - 802.11n 5GHz)

1回目 2回目 3回目 4回目 5回目
f:id:aryzae:20180706110851p:plain:w150 f:id:aryzae:20180706110855p:plain:w150 f:id:aryzae:20180706110901p:plain:w150 f:id:aryzae:20180706110906p:plain:w150 f:id:aryzae:20180706110909p:plain:w150
ping平均(ms) ping最高値(ms) ping最低値(ms)
4.8 4 6
DL平均(Mbps) DL最高値(Mbps) DL最低値(Mbps) UP平均(Mbps) UP最高値(Mbps) UP最低値(Mbps)
187.19 197.66 167.25 221.396 242.58 187.20

アフター

ルーター入れ替え前と同様に5回ずつテストを行ったが、ルーターの設定に少し時間かかったこともあり、時間帯による差も少なからず影響が出たかもしれない下記結果になった。

有線 (Windows 10)

1回目 2回目 3回目 4回目 5回目
f:id:aryzae:20180706112342p:plain:w150 f:id:aryzae:20180706112346p:plain:w150 f:id:aryzae:20180706112354p:plain:w150 f:id:aryzae:20180706112358p:plain:w150 f:id:aryzae:20180706112401p:plain:w150
ping平均(ms) ping最高値(ms) ping最低値(ms)
4 3 5
DL平均(Mbps) DL最高値(Mbps) DL最低値(Mbps) UP平均(Mbps) UP最高値(Mbps) UP最低値(Mbps)
890.592 (150.328%🆙) 930.01 855.74 754.768 (191.390%🆙) 933.94 160.79

無線 (iPhone X - 802.11ac 5GHz)

1回目 2回目 3回目 4回目 5回目
f:id:aryzae:20180706112843p:plain:w150 f:id:aryzae:20180706112847p:plain:w150 f:id:aryzae:20180706112851p:plain:w150 f:id:aryzae:20180706112856p:plain:w150 f:id:aryzae:20180706112901p:plain:w150
ping平均(ms) ping最高値(ms) ping最低値(ms)
5.2 5 6
DL平均(Mbps) DL最高値(Mbps) DL最低値(Mbps) UP平均(Mbps) UP最高値(Mbps) UP最低値(Mbps)
454.182 (242.632%🆙) 465.31 426.36 352.202 (159.082%🆙) 493.96 147.18

まとめ

試行回数少なかったり、同一時間帯じゃなかったり、無線の規格が同一のもので比較していない等、正確性には欠けるところはあるものの、それでも使用者として速度が全体的に上がってルーター買い換えた意味はあったといえる結果になったので、満足している。

ルーター自体古くなることで速度低下とか起こるのかも詳しくしらないが、壊れていなくとも新しいルーターに数年毎に買い換える意味はあると思うので、速度がでない人は買い換えることも一考してみては?と思う。

2018/07/18追記

前回の時間に合わせて22時に再度数回有線で測ったところ、ざっくりとした平均で400Mbps弱しか出なかったので、やはりアフターで速かったの時間帯の差によるものだった様子。

OSSのようなFramework ProjectをPlaygroundで動作確認する方法

前説

OSS内にPlaygroundが含まれている場合、そのPlaygroundを弄って動作を確認することは容易です。しかし、Playgroundは、含まれていないけどPlayground上で動作を軽く確認したい!

軽く確認するためにTest Project作ってPodsやCarthageでOSS引っ張ってくるのはしんどいということ…ありませんか?

もしくは、オリジナルの神Frameworkを作ったから、Playground上で動作確認したい…そういうことありませんか?

そういった軽く動きをPlayground上で確認する方法をご紹介します。

材料

  • Playgroundで動作確認したいFramework Project (例 Alamofireや神Framework等)
  • Xcode (Xcode 8系以降ならほぼ同じ手順で可能なはずです。)

今回は、下記環境を使って説明していきます。 * Almofire 4.7.2 * Xcode 9.3

1. Workspaceを作る

既にFramework Projectの方で*.xcworkspace(画像赤い点線) が用意されている場合、それが使えるので「2.」へ。

f:id:aryzae:20180523235302p:plain

WorkspaceがないProjectは、Workspaceを作ります

  1. Workspaceを作りたいFramework Projectを開く
  2. MenuからFile>Save As Workspace...を選択 f:id:aryzae:20180523235028p:plain
  3. Workspaceを保存する f:id:aryzae:20180523235031p:plain

2. Playgroundを作り、Workspaceに追加する

Playgroundを作り、Workspaceに追加してProject上で参照できるようにします。

  1. *.xcworkspaceXcodeで開く
  2. MenuからFile>New>Playground...を選択 f:id:aryzae:20180523235035p:plain
  3. Playgroundを保存する f:id:aryzae:20180523235039p:plain
  4. 開いているWorkspaceに対してMenuからFile>Add Files to "Project Name"...を選択 f:id:aryzae:20180523235043p:plain
  5. 「2.」「3.」で作成したPlaygroundを選択 f:id:aryzae:20180523235048p:plain

3. Framework Projectをbuildと動作確認

今回Playground上で実行させるコードは下記で、簡単に説明すると英語のwikipediaからJSONで情報を取ってくる内容になっています。

import Alamofire

print("=== start ===")
// url
var url = "https://en.wikipedia.org/w/api.php"

// parameters
var parameters: Parameters = [
    "action" : "query",
    "format" : "json",
    "titles" : "oryzae"
]

Alamofire.request(url, method: .get, parameters: parameters)
    .responseJSON { response in
        print(response)
}

print("=== end ===")

しかし、この時点ではまだ下記のようにerrorが出て、Framework Projectで実装されたコードをPlayground上で実行できません。

f:id:aryzae:20180524001045p:plain

Playground上でimportできるようにFrameworkを作成

  1. 任意のSimulatorを選択してbuild
    Generic iOS Deviceの場合、build errorになるので必ずSimulatorを選ぶこと
  2. buildによりFrameworkが作成され、Playgroundでimport&実行が可能に

f:id:aryzae:20180524001533p:plain

EX. Playgroundで非同期処理も実行

Playgroundでerrorが表示されていないので、Alamofireも実行されている!やったね✌️…と、よくみるとAlamofireのresponse内のprintがログに表示されていない。

実はPlaygroundは、上から順に同期処理を行い、最後の行に到達したら処理を終了するため、非同期処理は実行されない。

非同期処理も行わせるためには、下記の// 追加とあるコードをPlaygroundに追加する必要がある。

import Alamofire
import PlaygroundSupport // 追加

// 非同期処理を実行させる
PlaygroundPage.current.needsIndefiniteExecution = true // 追加

print("=== start ===")
// url
var url = "https://en.wikipedia.org/w/api.php"

// parameters
var parameters: Parameters = [
    "action" : "query",
    "format" : "json",
    "titles" : "oryzae"
]

Alamofire.request(url, method: .get, parameters: parameters)
    .responseJSON { response in
        print(response)
}

print("=== end ===")

PlaygroundPage.current.needsIndefiniteExecutionは、名前の通り非同期処理の実行が必要かどうかを持つPropertyである。default値はfalseなので意図的にtrueにしてやる必要がある。

これで、非同期処理含めFrameworkの処理をPlaygroundで実行出来たね。

f:id:aryzae:20180524001933p:plain

iTunes Music Library.xmlのタグ名と項目の値のメモ

iTunes Music Library.xml

  • iTunes Music Library.xmlとは
  • 未入力の項目は、keyが存在しない
  • bool値は<true/>のタグで表される
タグ 補足
Track ID integer 5944
Size integer 36089400
Total Time integer 278293
Disc Number integer 1 Discの分子
Disc Count integer 2 Discの分母
Track Number integer 1 曲数の分子
Track Count integer 12 曲数の分母
BPM integer 100 属性から入力できる項目
Date Modified date 2018-05-05T10:32:15Z
Date Added date 2018-05-05T10:30:07Z
Bit Rate integer 1009
Sample Rate integer 44100
Rating integer 60 ☆による評価 ☆1つで20
Album Rating integer 60 ☆による評価 ☆1つで20
Album Rating Computed bool true
Loved bool true ♡による評価 Dislikedの反対
Disliked bool true ♡による評価 Lovedの反対
Compilation bool true CompilationのCheckbox
Artwork Count integer 1 Artworkの枚数
Persistent ID string AE6D45CB4E7D50E6
Track Type string File
File Folder Count integer -1
Library Folder Count integer -1
Name string 曲名 属性から入力できる項目
Artist string アーティスト名 属性から入力できる項目
Album Artist string アルバムアーティスト 属性から入力できる項目
Composer string 作曲者名 属性から入力できる項目
Album string アルバム名 属性から入力できる項目
Grouping string グループ名 属性から入力できる項目
Genre string ジャンル名 属性から入力できる項目
Kind string Apple Losslessオーディオファイル
Comments string コメント 属性から入力できる項目
Sort Name string きょくめい 読みがな
Sort Album string あるばむめい 読みがな
Sort Artist string あーてぃすとめい 読みがな
Sort Album Artist string あるばむあーてぃすとめい 読みがな
Sort Composer string さっきょくしゃめい 読みがな
Location string file:///Users/aryzae/Desktop/
01%20test%20name.m4a
長いので改行している

Mac AppでWindowからfileやdirectoryを選択してpathを取得するやり方

Mac Appの開発へ乗り出し

iTunesで曲を管理しているが、不便に感じていることがあるため、その辺の利便性をなんとかしたいと思い、Mac Appの開発を気ままに進めることにした。

ひとまずやりたいこと

  • directoryを参照して、その配下にあるfile(subdirectory配下も含む)を列挙

コードと自分向けへの説明

下記のWindowを表示し、そこからdirectoryを選択させたい

f:id:aryzae:20180504152845p:plain

  • NSOpenPanelのbeginメソッドで目的のWindowは表示できる
  • NSOpenPanelはPropetyによって、file選択、複数選択等を切り替えられる
@IBAction func openPanel(_ sender: NSButton) {
    let panel = NSOpenPanel()
    // fileを選択可能にするかどうか。defaultは、true 
    panel.canChooseFiles = false
    // 複数選択可能にするかどうか。defaultは、false
    panel.allowsMultipleSelection = false
    // directoryを選択可能にするかどうか。defaultは、false
    panel.canChooseDirectories = true
    // 開始
    panel.begin { response in
        // OK以外に`cancel`, `abort`, `stop`, `continue`があるが、`OK`, `cancel`以外は異常系
        guard response == .OK else { return }
        // closure内でのみ選択したdirectoryのpathが取れる
        // また、panelの持つurlは、file protocol(file://)がついている 
        self.readItems(url: panel.url)
    }
}

NSOpenPanelで選択したdirectory配下のfileを列挙する

  • 先ほどのコードでdirectoryのpathまでは取得できるようになったので、ファイルを列挙する
  • FileManagerのenumeratorメソッドで列挙可能
  • enumeratorincludingPropertiesForKeysで、列挙するfileの各種propertyも取捨できるのかと思ったが、後続のresourceValuesの時に指定したら取れているのでObj-C時代は必要だったが、今は不要?
  • enumeratoroptionsで、隠しファイルやsubdirectory配下も含めるかを指定可能
  • enumeratorのclosure部分はerror handlerなので、必要に応じて書く
func readItems(url: URL?) {
    guard let url = url else { return }
    guard let directoryPath = URL(string: url.path) else { return }
    // includingPropertiesForKeysの意味あるのか?
    let enumerator = FileManager.default.enumerator(at: directoryPath,
                                                    includingPropertiesForKeys: [.isDirectoryKey],
                                                    options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants]) { (url, error) -> Bool in
        // closuerは、errorハンドルのみ
        print(error)
        // trueならerrorでも継続、falseなら停止
        return true
    }
    // 列挙
    for element in enumerator! {
        guard let url = element as? URL else { return }
        print(url)
        let resourceValue = try! url.resourceValues(forKeys: [.isDirectoryKey, .nameKey, .isHiddenKey])
        // forKeysで指定していないものは、nilになる
        print(resourceValue.isDirectory)
        print(resourceValue.name)
        print(resourceValue.isHidden)
        print(resourceValue.isPackage)
        print(resourceValue.localizedName)
    }
}