20210806のSwiftに関する記事は6件です。

UITableViewCellに作ったButtonに機能を追加

今回の内容 機能説明 Cellに表示したハート型のButtonを押すと、ButtonのBackgroundImageが変わります。 Buttonの初期状態はハートの内側が白いです。押すとハート全体が青い画像に変わります。 コードと簡単解説 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {} Cellの作成 cellHeartButtonをCellのUIButtonとして作成します。 cellHeartButton.tag = indexPath.rowは、Buttonが押された時にどのButtonが押されたか、判別するためにButtonにTagを設定しています。 cellHeartButton.addTarget(self, action: #selector(goodOrNoGood), for: .touchDown)は、Buttonが押された時の処理を追加します。 let cellContentsArray = ["1","2","3","4","5","6","7","8","9","10"] ~~~一部省略~~~ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let cellContentsLabel = cell.contentView.viewWithTag(1) as! UILabel let cellHeartButton = cell.contentView.viewWithTag(2) as! UIButton cellContentsLabel.text = cellContentsArray[indexPath.row] cellHeartButton.tag = indexPath.row cellHeartButton.addTarget(self, action: #selector(goodOrNoGood), for: .touchDown) return cell } #selector(goodOrNoGood) if sender.backgroundImage(for: .normal) == UIImage(systemName: "heart"){}では、Buttonの画像がUIImage(systemName: "heart")の時はUIImage(systemName: "heart.fill")に変化させます。 goodOrNoGood @objc func goodOrNoGood(sender:UIButton){ if sender.backgroundImage(for: .normal) == UIImage(systemName: "heart"){ sender.setBackgroundImage(UIImage(systemName: "heart.fill"), for: .normal) print(sender.tag) }else{ sender.setBackgroundImage(UIImage(systemName: "heart"), for: .normal) print(sender.tag) } } 終わり ご指摘、ご質問などありましたら、コメントまでお願い致します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

swiftのHelloworldにGoogle AdMovのテスト広告(バナー)をくっつけた

不労所得とかいうものが欲しかったので、手始めにiOSアプリに広告をつけてみようとしたらテスト広告だけでかなり手こずって一日費やしてしまったので覚書。 普通に働いた方が早い。 最終的に動いた手順 Google AdMovに登録する 最近はGoogle AdMovって呼ぶらしい。前はなんか名前が違った気がする。 コロコロ仕様も変わりそうなのでこんなこと覚えてる暇があったら普通に働いた方が早い。 登録はここから。 AdMovのダッシュボードから広告をつけたいアプリを登録する。 登録してないアプリでテスト以外の広告を流すと最悪垢バンされるらしい。 テスト広告だけなら関係なさそうなので割愛。 Xcodeでプロジェクトを作成 特に難しいことはないが、AppDelegate.swiftと(Game)ViewController.swiftが最初からあるテンプレだと楽かも。 ここから先はAdMovのスタートガイドをもとに進める。 CocoaPodsの導入 CocoaPodsを使ったことがない場合は入れる。 ターミナルで、 sudo gem install cocoapods インストールが終わったら、 pod setup Mobile Ads SDKのインポート プロジェクトのディレクトリに移動して、 pod init その後Podfileをエディタ等で開き、 Podfile (略) target 'アプリ名' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! pod 'Google-Mobile-Ads-SDK' <-この行を追加 # Pods for アプリ名 (略) コマンドラインから、 pod install --repo-update --repo-updateはなくてもいい気がするが公式で書いてあったから従った。 Info.plistの更新 よくわからなかったので書かれてる通りにやった。 CocoaPodsを使ったので以降は.xcodeprojファイルではなく.xcworkspaceファイルで作業する。 .xcodeprojファイルで作業するとかいう初歩的なミスをすると果てしなく時間を無駄にする。 ケアレスミスに対する救済措置がないので普通に働いた方が早い。 Info.plistを右クリックからOpen with External Editorで開いて編集。 Info.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> 以下に、 Info.plist <key>GADApplicationIdentifier</key> <string>ca-app-pub-3940256099942544~1458002511</string> <key>SKAdNetworkItems</key> <array> <dict> <key>SKAdNetworkIdentifier</key> <string>cstr6suwn9.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>4fzdc2evr5.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>2fnua5tdw4.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>ydx93a7ass.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>5a6flpkh64.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>p78axxw29g.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>v72qych5uu.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>c6k4g5qg8m.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>s39g8k73mm.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>3qy4746246.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>3sh42y64q3.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>f38h382jlk.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>hs6bdukanm.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>prcb7njmu6.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>v4nxqhlyqp.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>wzmmz9fp6w.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>yclnxrl5pm.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>t38b2kh725.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>7ug5zh24hu.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>9rd848q2bz.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>n6fk4nfna4.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>kbd757ywx3.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>9t245vhmpl.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>4468km3ulz.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>2u9pt9hc89.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>8s468mfl3y.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>av6w8kgt66.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>klf5c3l5u5.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>ppxm28t8ap.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>424m5254lk.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>uw77j35x4d.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>578prtvx9j.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>4dzt52r2t5.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>e5fvkxwrpn.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>8c4e2ghe7u.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>zq492l623r.skadnetwork</string> </dict> <dict> <key>SKAdNetworkIdentifier</key> <string>3qcr597p9d.skadnetwork</string> </dict> </array> この長ったらしいやつを追加。 Mobile Ads SDKの初期化 アプリの起動時に一度SDKを初期化する必要があるらしい。 AppDelegate.swiftを開いて、 AppDelegate.swift import UIKit import GoogleMobileAds//この行を追加 @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. GADMobileAds.sharedInstance().start(completionHandler: nil)//この行を追加 return true } (以下略) これで一回実行してみて通ったら広告の実装に進む。 テスト広告の実装 今回はバナー広告で試してみる。 ここからはバナー広告のスタートガイドに沿って進める。 スタートガイドにも書いてある通り、実際の広告でテストを行うと垢バンされることもあるらしいので、必ずテスト広告でテストすること。 テスト広告の専用ユニットIDはca-app-pub-3940256099942544/2934735716。 (Game)ViewController.swiftを開いて、 GameViewController.swift import UIKit import SpriteKit import GameplayKit import GoogleMobileAds //追加 class GameViewController: UIViewController { var bannerView: GADBannerView! //追加 override func viewDidLoad() { super.viewDidLoad() bannerView = GADBannerView(adSize: kGADAdSizeBanner) //追加 addBannerViewToView(bannerView) //追加 if let view = self.view as! SKView? { // Load the SKScene from 'GameScene.sks' if let scene = SKScene(fileNamed: "GameScene") { // Set the scale mode to scale to fit the window scene.scaleMode = .aspectFill // Present the scene view.presentScene(scene) } view.ignoresSiblingOrder = true view.showsFPS = true view.showsNodeCount = true } bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716" //追加 bannerView.rootViewController = self //追加 bannerView.load(GADRequest()) //追加 } //addBannerViewToView関数を追加 func addBannerViewToView(_ bannerView: GADBannerView) { bannerView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(bannerView) view.addConstraints( [NSLayoutConstraint(item: bannerView, attribute: .bottom, relatedBy: .equal, toItem: bottomLayoutGuide, attribute: .top, multiplier: 1, constant: 0), NSLayoutConstraint(item: bannerView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0) ]) } (以下略) 結果 テスト広告が表示された。 結論 やっと動いた。長い。 しかもどうせすぐ仕様が変わる。 何ならここをみるより公式を見た方がいい。 普通に働いた方が100倍早い。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Computed PropertiesとFunctionsの使い分け

Computed PropertiesとFunctionsの使い分け どっちがいいだろう?と迷ったことがあったので調べた。 ※Functions vs Computed property — What to use? の抜粋和訳。 Computed Propertyを使った方がいいケース 引数がいらない 読み込み、変換、トグル、printなどの処理をしない ごく単純な値をget/setする O(1) Functionを使った方がいいケース ランダムな値を返す 今日の日付 他のobjectやsingletonから取ってきた値を返す フォーマットした値を返す サーバーとやりとりする O(N)、あまり使わない O記法とは この記事がわかりやすそうなので読んでみる。 https://note.com/strictlyes/n/n82d0a3874256 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

同期処理と非同期処理について

はじめに 今回はたった2日でマスターできるiPhoneアプリ開発集中講座という書籍の中に、お菓子の虜Web APIを利用したお菓子検索アプリを作るセクションがあったので、練習として作成してみました。 その際に、同期処理と非同期処理というワードが出てきて少し混乱したので、初学者の自分がこれから勉強する方と自分に向けてまとめておきます。 同期処理 同期処理とはプログラムに書いた処理を順番に実行していく方法です。 A,B,Cという処理がプログラムに記述されているとすると、Aが処理を完了するまではB,Cは処理の開始をせずに待機していなければいけません。 同期処理のメリットとしては、どの処理が実行されるのかが明確であることかなと思います。 デメリットとしては、実行中の処理が完了しないと、次の処理に移ることが出来ないため、利便性が低くなってしまいます。 非同期処理 非同期処理は同期処理と対する概念で、ある処理が実行されている間に他の処理を止めないことを言います。 A,B,Cの閭里がプログラムに記述されているとすると、Aが処理を完了するのを待たずに、B,Cは処理を開始できるので、利便性が高くなります。 非同期処理のメリットとしては、実行流の閭里が完了していなくても次の処理に移ることが出来るので、ユーザーの操作を受付できることです。 別の処理は動いていますがユーザーは操作を継続できるので、アプリを快適に使用できることに繋がります。 デメリットとしては、処理が順番に実行されるわけではないため、全体で見るとどの処理が実行されているのかが把握しづらくなる可能性があります。 同期処理と非同期処理の違い 専門用語を理解しやすくするためには、身近にあるもので説明することが良いと思っています。 今回は2つの概念を飲食店のオペレーションの流れで説明していきます。 この記事の下に参考にした記事を載せておきますので、詳しくはそちらをご覧ください。 図で説明してくれているので、かなり分かりやすいと思います。 同期処理=Subway Subwayのオペレーションの流れとしては、お客さんは食べたいトッピングを店員さんに注文していきます。 注文を受けた店員さんはトッピングを1つずつにパンに載せていき、注文から会計まで店員さんがつきっきりで担当します。 注文をしたお客さんの対応が終わるまでは、次に並んでいるお客さんは待っていなければならないので、トッピングが多くなったり注文数が増えると待ち時間が長くなります。 ただ店員さんとしては並んでいる順番通りに対応していくので、お客さんの注文で混乱することがなく、商品の間違いが発生することもほとんどありません。 非同期処理=マクドナルド マクドナルドのオペレーションの流れとしては、お客さんが注文をすると店員さんは会計を先に済ませてから調理を始めます。 店員さんは調理をする人とレジで注文をするに担当が分かれていて、1人の店員さんが1人お客さんに最後までつきっきりで担当するとは限りません。 お客さんは注文した商品ができあがるまで、レジ横で受け取りを待ちます。 ただ、商品が出来上がった順に呼ばれるので、一番最初に注文したからといって一番最初に商品を受け取れるとは限りません。 なので、お客さんが待たされる時間がSubwayよりも短くなるというメリットがあります。 ただ店員さんとしては注文が一気にくるので新人さんであればどれから手を付けていけば良いのか混乱してしまい、商品の間違いが発生してしまうリスクがあります。 まとめ 一応分かりやすくするために、同期処理のメリットも書きましたが、アプリではユーザーが快適に使用できるようにするために非同期での処理を行っていくことが多いようです。 なので、開発者はデータの流れをしっかりと理解してコードを書いていかないと可読性が下がり、アプリの運用が大変になるということです。 ・同期処理はある処理の完了を待ってから次の処理を開始する ・非同期処理はある処理の完了を待たずに次の処理を開始する。 同期・非同期処理を理解する時にこの上の2つの処理の違いが大切なところなので、僕自身もこの違いを意識しながらアプリを作っていきたいと思います。 参考リンク、書籍 非同期処理の概念について解説 SwiftUI対応 たった2日でマスターできるiPhoneアプリ開発集中講座 Xcode 12/iOS 14対応
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhone,iPadのppi対応表

ppiとは ppi(ピーピーアイ)とは、pixels per inchの略で、ディスプレイやビットマップ画像における解像度を示す単位である。別名画素密度 (pixel density) とも呼ばれる。 ppi - Wikipedia あまりないとは思いますがレイアウトを物理サイズで何センチにしたいなどと言うとき機種によってppiを使用して計算します。 表 機種名 ppi           iPhone 1st 163 iPhone 3G 163 iPhone 3GS 163 iPhone 4 326 iPhone 4S 326 iPhone 5 326 iPhone 5C 326 iPhone 5S 326 iPhone SE 326 iPhone 6 326 iPhone 6 Plus 401 iPhone 6S 326 iPhone 6S Plus 401 iPhone 7 326 iPhone 7 Plus 401 iPhone 8 326 iPhone 8 Plus 401 iPhone SE 2nd 326 iPhone X 458 iPhone XS 458 iPhone XS Max 458 iPhone XR 326 iPhone 11 326 iPhone 11 Pro 458 iPhone 11 Pro Max 458 iPhone 12 401 iPhone 12 Pro 401 iPhone 12 Mini 476 iPhone 12 Pro Max 458 iPod Touch 163 iPod Touch 2nd 163 iPod Touch 3rd 163 iPod Touch 4th 326 iPod Touch 5th 326 iPad 132 iPad 3G 132 iPad 2nd 132 iPad 3rd 264 iPad 4th 264 iPad 5th 264 iPad 6th 264 iPad 7th 264 iPad 8th 264 iPad Mini 163 iPad Mini Retina 326 iPad Mini Retina CN 326 iPad Mini 3rd 326 iPad Mini 4th 326 iPad Mini 5th 326 iPad Air 264 iPad Air 2nd 264 iPad Air 3rd 264 iPad Air 4th 264 iPad Pro 9.7 inch 264 iPad Pro 10.5 inch 264 iPad Pro 11 inch 264 iPad Pro 12.9 inch 264 iPad Pro 2nd 11 inch 264 iPad Pro 2nd 12.9 inch 264 iPad Pro 3th 11 inch 264 iPad Pro 3th 12.9 inch 264 iPad Pro 4th 12.9 inch 264 iPad Pro 5th 12.9 inch 264 最後に 自分用にまとめてみましたが、間違いなどありましたらコメント/リクエストお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

resultBuilderを使ってswift製の内部DSLを作ろう

resultBuilderとは? resultBuilderとはSwift5.4で新たに追加されたアトリビュートの一つです。 この機能を一言で説明すると「内部DSLの生成補助ツール」だと思います。 (上記は個人的な理解です。正確な定義はproposalや公式のドキュメントを参照お願いします。) 内部DSLとは? DSLとはドメイン固有言語の略称であり、特定のドメイン領域に特化した言語のことを意味します。 DSLにも2種類あり、プログライミング言語の書き方を工夫して見かけ上の構文を自然言語に近づけたものを内部DSL、汎用プログラミング言語とは全く別の構文を持ったものを外部DSLと呼ぶそうです。 内部DSLの例 内部DSLの例としてRubyのRspecが挙げられます。 RspecはRubyという言語を拡張したフレームワークですが、テストの記述という機能に特化して自然言語のようにテストケースを記載できます。 外部DSLの例 一方外部DSLの例としてはSQLが挙げられます。 SQLはデータ操作に特化した言語ですが、RubyやJavaのような汎用プログラミング言語とは異なる独自の構文で記述する必要があります。 DSLについては以下の記事で詳しく解説して下さってます。是非見てみてください。 上記を踏まえると、swiftにおける内部DSLとは「swiftという言語を拡張し、特定のドメイン領域の実装を書きやすく読みやすくした言語」だと言えそうです。 例えばSwiftUIのViewのbody実装部分はまさにresultBuilderを利用した内部DSLと言えます。 Viewをポンポン置くだけでViewを構築できるのは不思議だなと思ったことはありませんか? あれの裏側ではresultBuilderが機能しています。 こんな風にresultBuilderを使うことで今までより簡単に内部DSLが作成できます。 それはつまり、これからのiOS開発では各プロジェクトのドメインに対してどんどん内部DSLが作成され、 複雑だったドメインの実装がどんどん読みやすく書きやすくなっていくのではないかと思ってます。 なので、僕はこのresultBuilderは近いうちにiOS開発において当たり前に使われる存在になってくると思ってます。 (WWDCのaync/awaitやactorの登場に影に隠れてあんまり目立ってない気がしますが、、、) なので今回は以下2点を分かる範囲で書いてみます。 resultBuilderを使うと何ができてどういう理由で内部DSLが作成しやすくなるのか resultBuilderを使用した内部DSL作成例 この記事を読んでresultBuilderを使って内部DSLを作ってみようという気になる方がいたら幸いです。 1.resultBuilderを使うと何ができて何が嬉しいのか シンタックスなしで配列を宣言できる。 まずはこちらの配列をご覧ください。 var numbers: [Int] {[ 1, 2, 3 ]} // [1,2,3] ただのIntの配列です。 resultBuilderを使うと上記の配列をこんな感じに置き換えられます。 @resultBuilder public struct NumbersBuilder { public static func buildBlock(_ components: Int...) -> [Int] { components } } @NumbersBuilder var numbersMadeViaBuilder: [Int] { 1 2 3 } // [1,2,3] SwiftUIのように要素を置くだけでIntの配列を生成できました。 配列を宣言していた時と比べ、resultBuilderを利用した場合は以下の変化がありますね。 1. 各要素の末尾にカンマを打つ必要がない。 2. 配列の最初と最後にArrayのシンタックス([])をつけなくてよい。 小さな変化ですが、カンマの追加や[]の区切りを修正するのも要素が増えてくると面倒になってきます。 resultBuilderで記載する場合はそれらの作業は不要になります。 配列を宣言する時の条件分岐をスッキリ書ける まだあります! resultBuilderを使うと配列を宣言する時の条件分岐をスッキリ書くことができます。 配列を条件によって分岐させたい場合があるとします。 shouldAppendFourがtrueの時に4を追加する場合を考えてみます。 // Pattern1 var numbers: [Int] { if shouldAppendFour { return [1, 2, 3, 4] } else { return [1, 2, 3] } } // Pattern1 var numbers: [Int] { var numnbers = [1, 2, 3] if shouldAppendFour { numnbers.append(4) } return numnbers } こんな感じでしょうか。上記の書き方の課題を整理してみましょう。 Pattern1: 追加したい要素以外も宣言する必要がある。 Pattern1の書き方では条件によっては4が欲しいだけなのに、それ以外の要素も毎回宣言する必要があります。 +の演算子で結合する場合も同様です。 これくらいの規模なら気になりませんが、要素の数が増えてきたり条件が複雑になると行数も増えて読み辛くなります。 Pattern2: appendのために配列をvarで宣言しないといけない。 4を追加するために配列を再代入可能なvarで宣言する必要があります。 上記の例では計算型プロパティの中での宣言なので意図せず代入される恐れはありませんが、再代入可能というだけで複雑度は少し上がります。 resultBuilderの場合 resultBuilderでは要素を並べている途中で条件分岐を記述できます。 ifを使用できるように先ほど作ったNumbersBuilderを修正します。 @resultBuilder public struct NumbersBuilder { public static func buildBlock(_ components: Int...) -> [Int] { components } public static func buildOptional(_ component: [Int]?) -> Int { component?.first ?? 0 } } buildOptional(_)の関数を用意してやることでelseなしのif文を許容できるようになります。 上記を踏まえて4を追加してみましょう。 @NumbersBuilder var numbersMadeViaBuilder: [Int] { 1 2 3 if shouldAppendFour { 4 } } これで条件ごとに追加したい要素以外も宣言したり、varで宣言してappendする必要がなくなりました! 見た目もかなりスッキリします。 resultBuilderには他にも様々な拡張用の関数が用意されています。 @resultBuilder public struct NumbersBuilder { public static func buildBlock(_ components: Int...) -> [Int] { components } // elseなしのifが使えるようになる public static func buildOptional(_ component: [Int]?) -> Int { component?.first ?? 0 } // if-elseが使えるようになる public static func buildEither(first component: [Int]) -> Int { component.first ?? 0 } // if-elseが使えるようになる public static func buildEither(second component: [Int]) -> Int { component.first ?? 0 } // for-inが使えるようになる public static func buildArray(_ components: [[Int]]) -> Int { components.flatMap { $0 } .reduce(0) { $0 + $1 } // for-inの中身全部足した合計を返す } // #if (ビルドターゲット指定での条件分岐)が使えるようになる public static func buildLimitedAvailability(_ component: [Int]) -> [Int] { component } } var sources: [Int] { [1, 2, 3, 4] } @NumbersBuilder var numbersMadeViaBuilder: [Int] { 1 2 3 shouldAppendFour ? 4 : 5 for source in sources { source * 5 } #if DEBUG 6 #endif } print(numbersMadeViaBuilder) // [1, 2, 3, 4, 50] 要素を変換したり、最終的なアウトプットを加工して返すこともできる。 さらにresultBuilderを使うと要素を変換したり、最終的なアウトプットを加工して返すこともできます。 要素を変換する buildExpression(_ expression:)を使うと受け取った要素を他の型に変換できます。 @resultBuilder public struct NumbersBuilder { public static func buildBlock(_ components: Int...) -> [Int] { components } // Doubleが来たらIntに変換する public static func buildExpression(_ expression: Double) -> Int { Int(expression) } } @NumbersBuilder var numbersMadeViaBuilder: [Int] { 1.5 2.3 7.9 } print(numbersMadeViaBuilder) // [1, 2, 7] この書き方ができることによって、型の異なる要素を同列に並べる表現が可能になります。 resultBuilderを使用しない場合、swift自身の型チェックにより異なる型を列挙するのは困難でした。 当たり前ですが、Intの配列で宣言するとIntしか扱うことはできません。 IntとDoubleが混在させる場合は、Genericsやprotocolによって抽象化した型を扱うか、別々に用意して同じ型に変換して扱う必要がありました。 上記のような型の整合性に対応するためにコードを書くのも冗長ですし、本来は要素を列挙して同列に扱いたいものが別々に宣言されると見通しが悪くなります。 var numbers: [Int] {[ 1, 2.0 // コンパイルエラー ]} resutlBuilderを使うことで型の変換をreusltBuilder側に委譲しつつ、特定の要素を同列に列挙していくことが可能です。 最終的なアウトプットを加工して返す buildFinalResult(_ component:)を使うと最終的なアウトプットの型を指定することができます。 @resultBuilder public struct NumbersBuilder { public static func buildBlock(_ components: Int...) -> [Int] { components } // Doubleが来たらIntに変換する public static func buildExpression(_ expression: Double) -> Int { Int(expression) } // 全部の合計をIntにして返す public static func buildFinalResult(_ component: [Int]) -> Int { component.reduce(0) { $0 + $1 } } } @NumbersBuilder var sum: Int { 1.5 2.3 7.9 } print(sum) // 10 1のまとめ ここまでの内容をまとめます。 reusltBuilderを使うとできること シンタックスなしで配列を記述できる。 配列の中で条件分岐やループなどの機能を使えるように拡張できる。 受け取った要素を変換して列挙できる。 列挙した要素を最終的に加工して返すことができる。 嬉しいポイント 要素ごとにカンマやカッコなどの細かいシンタックスの調整をしなくて良い。 条件分岐やループが使えるので、重複した要素を用意したり、varで宣言してappendしなくても良い。 異なる型を同列に列挙できる。 2.resultBuilderを使用した内部DSL作成例 Swift5.4で登場したresultBuilderですが、既に各方面で使われています。 とりあえずプロジェクトで使ってみたい場合 配列の生成をresultBuilderでやってみるのがおすすめです。 Builderとなるstruct側をGenericにしてあげると、一つの型ならどんな型の配列も対応できる配列生成用のBuilderができます。 @resultBuilder public struct ArrayBuilder<T> {  public static func buildBlock(_ components: T...) -> [T] { components } } // Tを変えれば何でもいける。 @ArrayBuilder<String> var strings: [String] { "あああ" "いいい" "ううう" } print(strings) // ["あああ", "いいい", "ううう"] @ArrayBuilder<URL> var urls: [URL] { URL(string: "https://www.google.com")! URL(string: "https://qiita.com")! } print(urls) // [https://www.google.com, https://qiita.com] 配列の生成はどんなプロジェクトでもどこかでやっていると思いますので、少し書き換えてみて効果を実感できます。 resultBuilderを使用した内部DSL作成例 以下のレポジトリがとても良いです。 色々と紹介されてますが、UIKit周りはConstraintの組み立てやStackViewへのsubviewの追加とかが使いやすそうです。 image.constraints { view in view.centerXAnchor == container.centerXAnchor view.topAnchor == container.topAnchor + 20 view.widthAnchor == container.widthAnchor -- 20 view.heightAnchor == view.widthAnchor * 0.6 } icon.constraints { view in view.centerAnchor == iconContainer.centerAnchor view.sizeAnchor == CGSize(width: 20, height: 20) } あと個人的に面白いと思ったのがCombineのPubliserをresultBuilderで構築できるようにした例です。 CombineでPublisherからPubliserに変換する時は途中flatMapを噛ませます。 その時にFailureのハンドリングやeraseToAnyPubliser()の呼び出しでネストもコード量も増えるのが気になってました。 上記のレポジトリではFinalResultでeraseToAnyPubliser()を返したり、ExpressionでsetFailureType(to: )を使ったりと、Publisherを連結する時の冗長性をresultBuilderで排除してます。 Combineを使うことが増えてきたので使ってみようと思います。 2.のまとめ resultBuilderをとりあえず試してみたい場合はGenericな配列の生成用Builderを試してみるのがおすすめ。 awesome-reuslt-buildersに色んなドメインでの内部DSLがまとめられてる。 PubliserBuilder良さそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む