あたも技術ブログ

セットジャパンコーポレーションの社員が運営しています。

【iOS】Alamofire + Himotokiでいい感じのAPIクライアントを作る

どうも、社内開発でiOSを担当してる朝倉です。

現在、夏期休業中ですが、そういえばブログ更新しないといけないなと思い出したので書きます。

Alamofire + HimotokiでAPIクライアントを作るということで、n番煎じだしもっといい方法がネット上にはゴロゴロある気がするのですが、 現状、(僕が)いい感じだと思っている実装を書いてみたいと思います。

導入

CocoaPodsでAlamofireとHimotokiを導入します。

「そんなの知ってるよ」とかCarthage派の人やSPM派の人は読み飛ばしてください。

ターミナルでプロジェクトディレクトリへ移動し、

pod init

Podfileが作成されるので以下の2行を追加します。

pod 'Alamofire', '~> 4.4'
pod 'Himotoki', '~> 3.0'

このようになればOKです。

target 'プロジェクト名' do
  use_frameworks!
  pod 'Alamofire', '~> 4.4'
  pod 'Himotoki', '~> 3.0'
end

Podfileを保存し、

pod install

これで、AlamofireとHimotokiがプロジェクトに追加されたので、プロジェクト名.xcworkspaceファイルを開きます。

APIクライアントを作る

今回は、GitHubAPIを使用します。 まずは共通の通信部分を作成します。

import Alamofire
import Himotoki

enum Result<T> {
    case Success(T)
    case Error(Error)
}

protocol APIRequest {
    var baseeUrl: String { get }
    var headerTemplate: [String : String] { get }
    
    associatedtype Response
    var path: String { get }
    var method: HTTPMethod { get }
    
    var parameters: [String : Any]? { get set }
    var headers: [String : String]? { get set }
    
    func response(from object: Any) throws -> Self.Response
    
}

extension APIRequest {
    var baseeUrl: String {
        return "https://api.github.com"
    }
    var headerTemplate: [String : String] {
        return ["X-Requested-With" : "XMLHttpRequest"]
    }
    var parameters: Any? {
        return nil
    }
    var headers: [String : String]? {
        return nil
    }
    
    func request(finished: @escaping (Result<Self.Response>)->Void) {
        let encoding = JSONEncoding.default
        var header = headerTemplate as Dictionary<String, String>
        headerTemplate.forEach() {
            header[$0.0] = $0.1
        }
        
        let encodedURLString = (baseeUrl + path).addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
        
        Alamofire.request(encodedURLString!, method: method, parameters: parameters, encoding: encoding, headers: header).responseJSON { response in
            switch response.result {
            case .success(let value):
                print(value)
                do {
                let res = try self.response(from: value)
                    finished(Result.Success(res))
                } catch {
                    finished(Result.Error(error))
                }
            case .failure(let error):
                finished(Result<Self.Response>.Error(error))
            }
        }
    }
}

続いて、Decodableプロトコルを適応したモデルとAPIRequestプロトコルを適応したリクエストオブジェクトを作成していきます。

ユーザのリポジトリを取得する

import Himotoki
import Alamofire

struct Repository: Decodable {
    let fullName: String
    let ownerAvatarUrl: String
    let language: String?
    let url: String
    let htmlUrl: String
    
    static func decode(_ e: Extractor) throws -> Repository {
        return try Repository(
            fullName: e <| "full_name",
            ownerAvatarUrl: e <| ["owner", "avatar_url"],
            language: e <|? "language",
            url: e <| "url",
            htmlUrl: e <| "html_url"
        )
    }
}

struct  UserRepository: APIRequest {
    var headers: [String : String]? = nil
    var parameters: [String : Any]? = nil
    var userName: String
    
    var path: String {
        return "/users/\(self.userName)/repos"
    }
    var method: HTTPMethod {
        return .get
    }
    typealias Response = [Repository]
    
    init(userName: String) {
        self.userName = userName
    }
    
    func response(from object: Any) throws -> Response {
        return try decodeArray(object)
    }
}

今後、エンドポイントが増えた場合にはDecodableプロトコルを適応したレスポンスモデルとAPIRequestプロトコルを適応したリクエストオブジェクトを増やしていけばいいだけなのでお手軽です。

使い方

UserRepository(userName: "asashin227").request() { result in
    switch result {
    case .Success(let responce):
        print(responce)
    case .Error(let error):
        print(error.localizedDescription)
    }
}

ソース

とりあえずGitHubにあげたので参考にご利用ください。

github.com

メールってどうなってるのか考える

前回「名前解決」という不思議なワードを攻略したので
インターネット空間をどんな目印で動いてるか分かったかなと思います。

 今回はインターネットの利用の双璧「メール」について少し書いてみます。
インターネットの中というのは、それまで使われていた現実世界の概念をネットワークの世界に持ち込んだものが多いので、てっとり早く理解するには現実世界と比較するのが一番です。

 

さて、「メール」。読んで字のごとし「手紙」です。
日本語は便利なもので、「メール」「手紙」も、もとの意味は同じなのに表現を変えるだけで、いまや「メール」といえば「電子メール」を指すのが一般的になりました。

 「メール送っといて」といわれて、手紙書いて切手貼って郵便ポストへ・・・という人はかなりレアですよね。
だいたいはPCやスマホでメールを打って送信すると、しばらくすると相手のスマホなんかに届く。この間がどうなっているのか見てみようかと思います。

 

あなたがメールを送ると、メールサーバに送られます。これはポストに手紙を出すのに相当しますが、ここは簡単に想像できるとおもいます。
どこのメールサーバに送るかといえば、自分の契約しているプロバイダーとか、会社なら自社のメールサーバに送ることになり、これはメールソフトにあらかじめ初期設定とかで設定してあります。自分専用の窓口がある郵便局のようなイメージです。

 

その後、メールを受取ったサーバは、宛先アドレスから相手のメールサーバを探します。ここで、前回の名前解決が使われます。
相手のメールサーバがわかると、そのサーバへメールの転送が行われます。ちょうど、郵便局間で手紙が運ばれるのと同じですね。


・・・でもこれでは、サーバには到着しますが、スマホには来ないですよね?
郵便局に届いても自宅に届かないと手紙は読めません。

 

ここで登場するのが「プッシュ通信」という、いわば配達員と同じ動きをする機能です
サーバに到着したメールは、「サーバー側から」(ここがミソ)送信先の携帯端末などを探して、そこへメールを送り込みます。これは携帯端末とメールアドレスが1対1なのでできる芸当。主に携帯キャリア(d社とかa社とかs社とか・・・)などのメールで使われます。最近はWebブラウザでも同様の動きができるようになってます。

 

でも、携帯キャリアのメールアドレスじゃなくても、スマホにメール、きますよね?
これは実はメールが「来る」わけではなく「届く」といったほうが適当です。
「端末から」定期的だったり手動だったりでサーバーに「メールを取りに」いってます。メールがあれば、サーバからデータを引っ張ってくるので「プル通信」といわれます。この動きは、郵便局の私書箱へ届いている手紙を取りに行く、のと同じ働きになります。 

 

これ以外にもメール関係のキーワードとしては・・
・端末にメールをもってきて読むPOP3や、メールはサーバーに置いたまま、内容を読むIMAPなどという方式の違い。パソコンやプロバイダーのメールはPOP3、携帯など移動体のメールはIMAPがよく使われます
・セキュリティ対策、特にスパム送信対策のPOP before SMTPとか、SMTP Auth」という送信認証。実はもともとメール送信というのは認証なしで送れました。ですが、これではスパム送り放題ですので実装された方式です
・ほかにもセキュリティ対策で「サブミッションポート」だったり「暗号化」「フィルタリング」MIME」「HTMLメール」・・・

などなど、メールは昔からある、比較的単純な通信ですが、なかなか奥が深いです。

 

なお。
説明の都合上、かなり端折ったり、デフォルメしていますので
もっとメールの仕組みを知りたい人はSetJapanの門をたたいてみましょう。

 

【C#】Excelの取り扱いにClosedXMLを使用する

 今回はC#Excelを使用したい場合を考えたいと思います。2008年辺りまではCOMコンポーネントを使用してMicrosoft Excel 12.0 Object Libraryを参照し、COMオブジェクトの解放地獄でかなり苦しんでおられたのではないでしょうか。
 Office2007以降の場合、xlsx、xlsmファイルとなり内部的にはXML形式となりました。これによってCOMオブジェクトうを使用せずにExcelファイルを扱えるライブラリが色々公開されています。
 その中でClosedXMLをご紹介したいと思います。
 
1. Excelを扱うプロジェクトを作成します。
2. Visual Studioを起動し、「ツール」→「NuGet パッケージマネージャー」→「ソリューションの NuGet パッケージの管理」を選択します。
f:id:atamo_dev:20170719212412j:plain
3.「NuGet パッケージ管理ウィンドウ」で左のオンラインを選択します。
4. 右上オンラインの検索欄に「OpenXML」と入力し、検索を実行します。
f:id:atamo_dev:20170719212439j:plain
5. 表示された「DocumentFormat.OpenXml」を選択すると「インストール」ボタンが表示されていますので、「インストール」ボタンを押下し、指示に従ってインストールを行います。
6. 右上オンラインの検索欄に「ClosedXML」と入力し、検索を実行します。
f:id:atamo_dev:20170719212451j:plain
7. 表示された「ClosedXML」を選択すると「インストール」ボタンが表示されていますので、「インストール」ボタンを押下し、指示に従ってインストールを行います。

 これで前準備は完了しました。

github.comOpenXML SDK
github.comClosedXML


 以下Excelを扱ったサンプルクラスです。

using ClosedXML.Excel;
using System;
using System.IO;

namespace ExcelSample
{
  /// <summary>
  /// Excelの読み込みとワークシート、セルへのアクセスを行うクラス
  /// </summary>
  public class ExcelLoader : IDisposable
  {
    #region メンバ変数
    /// <summary>
    /// Excelのワークブック(2013以降のExcelを指定すること)
    /// </summary>
    private XLWorkbook _Workbook = null;
    #endregion

    #region プロパティ
    /// <summary>
    /// 全てのワークシートを取得します。
    /// </summary>
    public IXLWorksheets Worksheets
    {
      get
      {
        if (this._Workbook != null)
        {
          return this._Workbook.Worksheets;
        }
        else
        {
          return null;
        }
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// 新しいクラスのインスタンスを作成します。
    /// </summary>
    public ExcelLoader()
    {
    }

    /// <summary>
    /// 新しいクラスのインスタンスを作成します。
    /// </summary>
    /// <param name="path">Excelファイルのパス</param>
    public ExcelLoader(string path)
    {
      if (!string.IsNullOrEmpty(path) && File.Exists(path))
      {
        // ファイルパスが設定されているかつ、そのパスのファイルが存在する場合、ファイルを開く
        this._Workbook = new XLWorkbook(path);
      }
    }
    #endregion

    #region 公開メソッド
    /// <summary>
    /// 解放処理
    /// </summary>
    public void Dispose()
    {
      CloseExcel();
    }

    /// <summary>
    /// 引数で指定されたExcelファイルを開きます。
    /// </summary>
    /// <param name="path">Excelファイルのパス</param>
    /// <returns>true:オープン成功、false:オープン失敗</returns>
    public bool OpenExcel(string path)
    {
      CloseExcel();

      if (!string.IsNullOrEmpty(path) && File.Exists(path))
      {
        // ファイルパスが設定されているかつ、そのパスのファイルが存在する場合、ファイルを開く
        this._Workbook = new XLWorkbook(path);
        return true;
      }
      else
      {
        return false;
      }
    }

    /// <summary>
    /// 指定したインデックスのワークシートの指定セルを読み込みます。
    /// </summary>
    /// <param name="row">行番号</param>
    /// <param name="col">列番号</param>
    /// <param name="index">ワークシートのインデックス番号</param>
    /// <returns>true:オープン成功、false:オープン失敗</returns>
    public string ReadCell(int row, int col, int index = 1)
    {
      if (this._Workbook == null)
      {
        return string.Empty;
      }

      try
      {
        var worksheet = this._Workbook.Worksheet(index);
        var cell = worksheet.Cell(row, col);
        return cell.Value as String;
      }
      catch
      {
        // 指定されたワークシートが存在しなかった場合、空値を返却する
        return string.Empty;
      }
    }

    /// <summary>
    /// 指定したワークシートの指定セルを読み込みます。
    /// </summary>
    /// <param name="row">行番号</param>
    /// <param name="col">列番号</param>
    /// <param name="worksheet">ワークシートのインデックス番号</param>
    /// <returns>true:オープン成功、false:オープン失敗</returns>
    public string ReadCell(int row, int col, IXLWorksheet worksheet)
    {
      if (worksheet == null)
      {
        return string.Empty;
      }

      try
      {
        var cell = worksheet.Cell(row, col);
        return cell.Value as String;
      }
      catch
      {
        // 指定されたワークシートのセルが存在しなかった場合、空値を返却する
        return string.Empty;
      }
    }

    /// <summary>
    /// 現在オープンされているExcelファイルをクローズします。
    /// </summary>
    public void CloseExcel()
    {
      if (this._Workbook != null)
      {
        // 既にExcelが開かれている場合、破棄する
        this._Workbook.Dispose();
        this._Workbook = null;
      }
    }
    #endregion
  }
}

 クラスインスタンス生成時にExcelファイルのパスを指定し、Excelファイルからセルの読み込みを行う簡単なメソッドを実装したクラスです。
 COMオブジェクトの時代では速度面で遅く厳しいものがありましたが、ClosedXMLを使用した場合、速度もかなり速く利用を検討すべきだと思います。

イラストレーターで保存するとき「不明なエラーが発生しました」と出る!!保存できない人集合!!

イラストレーターで保存するとき「不明なエラーが発生しました」と出る!!保存できないじゃないか!!

という人向けの内容です!
私も出て困ったんですよねー、保存できないんですから。

 

そこで解決するために!最初はネットの情報にあったものを試してみました!

作業環境:Windows10、illustratorCC2017、メモリ8GBのデスクトップPC

 

イラレの環境ファイルを削除→PCを再起動
環境ファイルの場所は…
【users→ユーザー名→AppDate→Roaming→AdobeAdobe Illustrator 21 Settings→
ja_jp→x64→Adobe Illustrator Cloud Prefs】
Adobe Illustrator Cloud Prefsを削除する

別のサイトさんだと
【users→ユーザー名→AppDate→Roaming→AdobeAdobe Illustrator Settings→
ja_jp→x64→Adobe Illustrator Prefs】
Adobe Illustrator Prefsを削除する

でしたが、ファイルが見当たらなかったのでよく似たものを消してみました。
しかし直らないイラレが壊れることはありません)


・ファイルを作り直してコピーペーストする
直らなかった!

 

・バージョンを下げる
保存のバージョンを「illustrator CC→illustrator CS6」に変える
出なくなった!

 

・レイヤーのロックを外す
直る時と直らないときがある


(´・ω・`)え?分からないじゃん……

 

レイヤーの不具合じゃないかという話だけしか分かりませんでした。
が!作業の邪魔!なんとかしたい!って思ったので色々試しました。

とりあえず色々試したり調べたりして、回避できるかもしれない方法を見つけました。


多角形ツール+変形をして、その図形があるレイヤーをロック、または非表示にすると発生しました。
※あくまで自分の場合です

f:id:atamo_dev:20170714175640p:plain


多角形を作るだけならセーフでした。
でも、多角形作成後にその多角形の頂点や線をにゅーんと動かすとエラーが発生します

三角をよく多角形で作ってフキダシなんかを作る時に使っていました。
(次からは長方形ツールで作ろ……)

ただし、パスファインダーで合成してしまう、どこかの辺を消すなら問題ないです!
(その後変形しても問題なし)


あともう一つ試してみたのが、メモリが関係あるか
自分がいつも使っているPCは8GBです。

もう少しメモリの大きいPCでやったところ、エラーは発生しませんでした
もしかするとモリーの容量なのかも知れません。
実際のところ、原因って何なんだろう(´・ω・`)ウーン

 

多くのillustrator仲間の参考になると嬉しいです!

CocoaPodsでライブラリを公開する

プロジェクトを作成

まずはライブラリを作るためにプロジェクトを作成します。

XcodeでCreate new projectしてもいいのですが、CocoaPodsがテンプレートを公開しており、ターミナルから作成できるので今回はターミナルから作成していきます。

プロジェクト作成

以下のコマンドをターミナルで実行するとカレントディレクトリにプロジェクトが作成されます。

途中、幾つかの質問があるので、回答していきます。

pod lib create ライブラリ名

質問1 使用する言語はどちらですか?

What language do you want to use?? [ ObjC / Swift ]
> ObjC or Swift で回答

質問2 デモアプリを同梱しますか?

Would you like to include a demo application with your library? [ Yes / No ]
> Yes or No で回答

質問3 テストフレームワークを使いますか?

Which testing frameworks will you use? [ Quick / None ]
> Quick or None(使わない) で回答

質問4 Viewのテストを行いますか?

Would you like to do view based testing? [ Yes / No ]
> Yes or No で回答

成功するとXcodeが起動し、作成したプロジェクトが開きます。

ライブラリの作成

開いたプロジェクト内ソースツリーの[Pods]->[Development Pods]->[ライブラリ名]->[ライブラリ名]->[Classes]ReplaceMeというファイルがあるので、削除して自作ライブラリのファイルを配置します。

GitHubで公開

ライブラリのGitHubページを作成し、リポジトリのURLをコピーしておきます。

https://github.com/ユーザ名/ライブラリ名.git

プロジェクトファイル内のライブラリ名.podspecを編集します。

s.source: コピーしたリポジトリのURL

s.summary: 短い説明

s.description: 長い説明

#
# Be sure to run `pod lib lint ライブラリ名.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|
  s.name             = 'ライブラリ名'
  s.version          = '0.1.0'
  s.summary          = 'A short description of ライブラリ名.'

# This description is used to generate tags and improve search results.
#   * Think: What does it do? Why did you write it? What is the focus?
#   * Try to keep it short, snappy and to the point.
#   * Write the description between the DESC delimiters below.
#   * Finally, don't worry about the indent, CocoaPods strips it!

  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC

  s.homepage         = 'https://github.com/ユーザ名/ライブラリ名'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'ユーザ名' => 'メールアドレス' }
  s.source           = { :git => 'https://github.com/ユーザ名/ライブラリ名.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '8.0'

  s.source_files = 'ライブラリ名/Classes/**/*'
  
  # s.resource_bundles = {
  #   'ライブラリ名' => ['ライブラリ名/Assets/*.png']
  # }

  # s.public_header_files = 'Pod/Classes/**/*.h'
  # s.frameworks = 'UIKit', 'MapKit'
  # s.dependency 'AFNetworking', '~> 2.3'
end

編集後、GitHubリポジトリにPushします。

Xcode上からSource Controlブランチ名Configure ライブラリ名を開き、RemoteからGitHubリポジトリを追加します。

Source ControlCommitを選択し、左下のPush to remoteへチェックを入れたらCommit Filesを押下します。

バージョンの指定

CocoaPods上でのバージョンはGitのタグに対応するので、ターミナルから公開時のバージョンをタグで付けます

git tag 1.0.0

リモートに反映します

git push 1.0.0

確認

podspecに誤りがないか確認します。

ターミナルでプロジェクトのディレクトリへ移動し、以下のコマンドを実行します。

pod lib lint

エラー場表示される場合は修正します。

(僕は説明が短すぎると言うエラーが出ました

CocoaPods trunkへ登録する

ターミナルでコマンドを実行します

pod trunk register 公開するメール '公開する名前' --description='説明'

入力したメールアドレスへ確認メールが来るので記載されているリンクを押下します。

ターミナル上で以下のコマンドを実行して名前が表示されていれば登録が完了しています。

pod trunk me

CocoaPods trunkへ登録します。

以下のコマンドを実行

pod trunk push ライブラリ名.podspec

pod try ライブラリ名 を実行してサンプルプロジェクトが開けば登録できています。

商用フリー 和柄パターン素材

 

f:id:atamo_dev:20170701161927p:plain

個人的に作成した和柄模様の素材です。

 

素材ファイルは下記URLに配置してあります。

https://drive.google.com/drive/folders/0B9dZ314mf8WoVGxla3hXeEVBSGc?usp=sharing

再配布以外でしたら個人用途に限りご自由にお使いください。

報告も、配布元の表示も不要です。

 

各素材とも下記2種類ずつあるので用途別にお使いいただければと思います。

Illustratorスウォッチ登録用aiファイル

Photoshopのパターン定義用にpngファイル

 

By SetJapanCorporation,Inc. Designer : すんすん。

【NW】名前解決って、なに?

「名前解決」・・・不思議な言葉ですよね。
この言葉ですぐわかる人は業界の人だけでしょう
ここでいう「名前」はインターネットURL、「解決」はIPアドレス検索、でしょうか
言い換えれば「インターネットURLからサーバのIPアドレスをさがす」イメージです。

皆さんがWebサイトなど見るときよくあるパターンは・・・
インターネットURLがLINEで来たり、QRコード読んだりして、
ブラウザでWebサイト見たりします
でも、見たいWebサイトが世界のどこにあるかは知りません。

Webサイトを公開するひとは、作ったら、どこかのWebサーバに格納します。
そして、このサーバはIPアドレスを持っています。

しかし、あなたWebサーバの間にはインターネットっていう空間がある・・・
ここをどうやってくっつけているのか、かなりざっくりと見てみます。

 

では、わかりやすくRPGにたとえてみましょう。
http://www.setjapan.co.jp」のWebサイトを見るというのは・・・
「setjapan.co.jp城にいるwww王http語を使って話したい」
という感じのクエストになります。

とりあえず、あなたは一番手近にいるDNSサーバってローカルな賢者に聞きます。
パソコンやらスマホやらに設定されているけど、大体は管理者が入れてるから、ほぼユーザは知らなくてよい話。

 

 あなたsetjapan.co.jp城www王話がしたいんすけど?
ローカル賢者.jpか。それなら賢者JPRSに聞くのじゃ。」

さすがはローカル賢者、なんも知りませんが、親切な彼は次の賢者を教えてくれます。
ちなみに”.jp”は、japanの意味で、JPRSが管理しているドメイン(TLD)です。

 

 あなたsetjapan.co.jp城ってどこすか?www王っています?
賢者JPRSsetjapan.co.jp城はこの場所にある。そこのNSというものに聞け」
どうやら、たらいまわしは賢者の得意技のようです。

場所は(なんとなく)わかったので、そこいってNSって人に聞きましょう。
このNSがsetjapan.co.jp内のIPアドレス(とか、諸々)の管理してます。

 

あなた「ここsetjapan.co.jp城っすか?www王っています?」
従者NSwww王はこの場所にいる。」と、王のIPアドレスを教えてくれます

やっと、王様に会えたので、http語で話しかけてみます。
あなた「王様、あのニュースどうなってんすか?」
www王「ほれ。こんな感じじゃ。うひゃひゃひゃw」
さすがwww王、名前通り草不可避です。

とまぁ、皆さんがWebを楽しんでる裏では
毎度こんな冒険が繰り広げられてるわけです。

 

でも、そうそううまくいかないこともあり
途中でどの賢者に聞いたらいいかわからなくなったり(ドメインサーバ不明)
王の場所分かったけどいなかったり(サーバダウン)
そもそも最初の賢者がいない(DNSサーバ未設定)
などをひっくるめて、「名前解決ができない」などと言ったりします。

なお。
説明の都合上、かなり端折ったり、デフォルメしていますので
もっとDNSサーバのことを知りたい人はSetJapanの門をたたいてみましょう。