print(1+2)
@dynamicMemberLookup struct Employee { subscript(dynamicMember member: String) -> String { let properties = ["name": "Taylor Swift", "city": "Nashville"] return properties[member, default: ""] } subscript(dynamicMember member: String) -> Int { let properties = ["age": 26, "height": 178] return properties[member, default: 0] } } let e = Employee() let name: String = e.name // "Taylor Swift" let city: String = e.city // "Nashville" let age: Int = e.age // 26 let height: Int = e.height // 178 let city_wrongType: Int = e.city // 0 let height_wrongType: String = e.height // ""
@dynamicMemberLookup
自体はメッセージ指向のために入れたわけじゃないけど、そういう捉え方をしてみるとおもしろそうってことでしょうか?actor
↓ https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782#part-2-actors-eliminating-shared-mutable-state@IBOutlet
と @IBAction
をつなげてるときは同じ理由で、オブジェクト指向という概念を特定の言語の(俗に「オブジェクト指向」向けと称される)機能から外挿して学ぶのは難しいし、間違った「オブジェクト指向」を創造してしまう危険がある。少し遠回りに思えても、それぞれの考案者自らがその主張を綴った論文を読んで理解を試みるほうがいい。
class Group { ... } class User { ... } let group: Group = ... let owner: User = group.owner owner.age += 1 // 何が起こる?
Group
が内部に保持してるインスタンスをそのまま返してたら Group
の owner
の age
もインクリメントされますけどowner.age += 1
は group
に影響を与えません。 (edited)class User { let name: String let age: Int ... } var user = User(name: "Swift", age: 4) user = user(name: user.name, age: user.age + 1)
(edited)// ミュータブルクラス class User { var name: String var age: Int ... } let user = User(name: "Swift", age: 4) user.age += 1 // 楽
(edited)struct
でやると // struct struct User { var name: String var age: Int } var user = User(name: "Swift", age: 4) user.age += 1 // 楽
(edited)mutating func
とかおもしろいですよね。mutating func
と inout
が使いやすさのキーになってる気がしててview.frame.origin.x += 10;
とか。 (edited)view.frame.origin.x += 10;
はコンパイルエラー
(edited)CGRect frame; frame.origin.x += 10;
はOK 話外れました (edited)reduce(into:...)
も追加されたのは Swift 4 で https://github.com/apple/swift-evolution/blob/master/proposals/0171-reduce-with-inout.mdmutating func
とか inout
は機能としてはあったけど、reduce(into:...)
って inout
な引数を持つ高階関数(メソッド)ですけどlet idToUser = users.reduce(into: [:]) { idToUser, user in idToUser[user.id] = user }
(edited)inout
)って時代遅れの遺物的な扱いされてた(主観)けど、値型で見事に活用されてるのとかもおもしろいと思います。inout
は概念上は call by copy-restore です。 inout
で渡せますが当然参照できないので、 get で読み出されて set で書き戻されます。mutating func
であっても、コールバックで self
を書き換えることはできないですし、 mutating func foo() { DispatchQueue.main.async { self.bar += 1 // できない } }
async/await
で結構問題になる気がしててmutating func foo() async { await baz() self.bar += 1 // コンパイルエラー }
(edited)mutating func
なのに self
変更できないのとか、初見でとまどいそう。inout
だと自分同士でできない。 // ミュータブルクラス func sendMoney(from user1: User, to user2: User) { user1.money -= 100 user2.money += 100 } sendMoney(from: user1, to: user2) // OK sendMoney(from: user1, to: user1) // OK
// struct func sendMoney(from user1: inout User, to user2: inout User) { // これ自体は OK user1.money -= 100 user2.money += 100 } sendMoney(from: &user1, to: &user2) // OK sendMoney(from: &user1, to: &user1) // コンパイルエラー
var
と let
の間にもうちょいなんかほしいなーと思うことはあって、 struct User { let name: String var age: Int } do { let group = Group( id: 1, owner: User(name: "hoge", age: 16) ) // groupがletなのでこれはできない // group.owner.age += 1 } // というのはいいんだけど do { var group = Group( id: 1, owner: User(name: "hoge", age: 16) ) group.owner.age += 1 print(group) // Group(id: 999, owner: __lldb_expr_1.User(name: "ほげほげぴよぴよーー", age: 65536)) // 👆!!?? } // 実はこうなってました struct Group { let id: Int var owner: User { didSet { self = Group(id: 999, owner: User(name: "ほげほげぴよぴよーー", age: 65536)) } } }
sendMoney(from: &user1, to: &user1) // コンパイルエラー
は、 @rintaro さんの話を聞いてから見ると納得感はありますね。print("hello world")
(edited)print("Hello world")
print("hello, world")
print("hello world")
print("Hello World!")
print("hello world")
print("hello world")
print("Hello World")
print("hello")
swift print("hello")
print("Hello World")
テストprint("Hello World!")
print("Work from home")
print("Hello World!")
print("Hello, World!")
print("Hello!")
print("Hello, world!")
print("Hello world")
print("Hello World")
print("Hello World!")
print("Hello, World!")
print("ハローワールド")
print("Hello World")
print("Hello world")
Usage: @swiftbot [--version=SWIFT_VERSION] [--command={swift, swiftc}] [--options=SWIFTC_OPTIONS] ``` [Swift Code] ``` Examples: @swiftbot ``` print("Hello world!") ``` @swiftbot --version=4.0.3 ``` print("Hello world!") ``` @swiftbot --command=swiftc --options=-dump-parse ``` print("Hello world!") ``` @swiftbot --platform=mac ``` print("Hello world!") ``` Subcommands: @swiftbot versions: show available Swift toolchain versions @swiftbot contribute: show repository URLs @swiftbot help: show help
print("Hello World")
swift print("Hello World")
print("Hello world!")
Usage: @swiftbot [--version=SWIFT_VERSION] [--command={swift, swiftc}] [--options=SWIFTC_OPTIONS] ``` [Swift Code] ``` Examples: @swiftbot ``` print("Hello world!") ``` @swiftbot --version=4.0.3 ``` print("Hello world!") ``` @swiftbot --command=swiftc --options=-dump-parse ``` print("Hello world!") ``` @swiftbot --platform=mac ``` print("Hello world!") ``` Subcommands: @swiftbot versions: show available Swift toolchain versions @swiftbot contribute: show repository URLs @swiftbot help: show help
swift print("Hello world!")
print("hello world")
print("Hello")
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
print("Hello world")
(edited)Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
print("hello world")
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
print("test")
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
print("Hello!!")
print(1 + 2 + 3 + 4 + 5)
swift print(""" _人人人人人人人_ > 無限ループ <  ̄Y^Y^Y^Y^Y^Y^Y^ ̄ """)
_人人人人人人人_ > 無限ループ <  ̄Y^Y^Y^Y^Y^Y^Y^ ̄
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
print("Hello WOrld!")
print("hello")
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
fatalError()
Fatal error: file <stdin>, line 1 Current stack trace: 0 libswiftCore.so 0x00007ff43dc62490 swift_reportError + 50 1 libswiftCore.so 0x00007ff43dcd3b50 _swift_stdlib_reportFatalErrorInFile + 115 2 libswiftCore.so 0x00007ff43d9e954e <unavailable> + 1385806 3 libswiftCore.so 0x00007ff43d9e9157 <unavailable> + 1384791 4 libswiftCore.so 0x00007ff43d9e9782 <unavailable> + 1386370 5 libswiftCore.so 0x00007ff43d9e7a20 _assertionFailure(_:_:file:line:flags:) + 520 7 swift 0x00000000004f5d3e <unavailable> + 1006910 8 swift 0x00000000004fa5b2 <unavailable> + 1025458 9 swift 0x00000000004e7d6c <unavailable> + 949612 10 swift 0x00000000004dca99 <unavailable> + 903833 11 swift 0x00000000004d2abb <unavailable> + 862907 12 swift 0x00000000004cf72d <unavailable> + 849709 13 swift 0x0000000000459672 <unavailable> + 366194 14 libc.so.6 0x00007ff43fd0d740 __libc_start_main + 240 15 swift 0x00000000004591c9 <unavailable> + 365001 Stack dump: 0. Program arguments: /usr/bin/swift -frontend -interpret - -disable-objc-interop -I /Libraries/.build/x86_64-unknown-linux-gnu/debug -I /Libraries/.build/checkouts/SwiftBacktrace/Sources/CSwiftBacktrace/include -I /Libraries/.build/checkouts/SwiftBacktrace/Sources/Clibunwind/include -I /Libraries/.build/checkouts/swift-nio/Sources/CNIOHTTPParser/include -I /Libraries/.build/checkouts/swift-nio/Sources/CNIOSHA1/include -I /Libraries/.build/checkouts/swift-nio/Sources/CNIOAtomics/include -I /Libraries/.build/checkouts/swift-nio/Sources/CNIODarwin/include -I /Libraries/.build/checkouts/swift-nio/Sources/CNIOLinux/i
print("hello world")
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
print("hey!")
swift print("Hello World")
Usage: @swift52 [SWIFT_OPTIONS] ``` [Swift Code] ```
print("rm -rf /")
print("Hello WOrld!")
print(111)
hello world
hello world
<stdin>:1:6: error: consecutive statements on a line must be separated by ';' hello world ^ ; <stdin>:1:1: error: use of unresolved identifier 'hello' hello world ^~~~~ <stdin>:1:7: error: use of unresolved identifier 'world' hello world ^~~~~
print("はろー")
print("はろー")
swift-5.2-RELEASE
はろー
hello world
<stdin>:1:6: error: consecutive statements on a line must be separated by ';' hello world ^ ; <stdin>:1:1: error: use of unresolved identifier 'hello' hello world ^~~~~ <stdin>:1:7: error: use of unresolved identifier 'world' hello world ^~~~~
git clone git@github.com:refactoring-challenge/reversi-ios.git
途中から来られた方向け: ・今日は音声のみです。画面はありません ・チャンネルリストの下の方にある「音声チャット」に 🔊swift-zoomin というのがあります。そちらにお入りください ・このテキストチャンネルもコードを共有したり、補助的に使用します 今回のリポジトリはこちら。clone して実行してみてください https://github.com/koher/refactoring-challenge-reversi-ios 何かわからないことがあれば音声かこのチャットで遠慮なく聞いてください
public enum Disk { case dark case light }
// 3 列目・ 4 行目のセルの状態を取得 let disk: Disk? = boardView.diskAt(x: 3, y: 4) // 3 列目・ 4 行目のセルを黒のディスクが置かれている状態に変更 boardView.setDisk(.dark, atX: 3, y: 4, animated: false)
boardView.setDisk(.dark, atX: 3, y: 4, animated: true) { isFinished in // アニメーション完了時に呼ばれる }
extension ViewController: BoardViewDelegate { func boardView(_ boardView: BoardView, didSelectCellAtX x: Int, y: Int) { // x 列目・ y 列目のセルがタップされたときに呼ばれる } }
enum VerticalIndex: Int { case one = 0 case two case three case four case five case six case seven case eight }
// Immutable 最高! // index がはみ出さないことを、初期値を Index で保証しているから、force unwrap しても安心だね!
protocol Foo { associatedtype PublisherType: Publisher where Output == Bar var bar: PublisherType { get } }
BoardView.diskAt
のfunc丸ごと消してコンパイルエラーを起こすことから始めました ここで起こったコンパイルエラーを新しく作る盤面管理するやつ使ってコンパイルエラー直してやる予定BoardView
のAPIをprotocolに切り出して、ロジックを具体的な BoardView
から切り離してみたいと思ったけど、 placeDisk
がいやらしい var board: Board = Board(""" -------- x------- -o------ --ooo--- ---ox--- -----oox ---ooo-- --o-x--- """) do { try board.place(.dark, atX: 4, y: 5) XCTAssertEqual(board, Board(""" -------- x------- -x------ --xoo--- ---xx--- ----xxxx ---oxo-- --o-x--- """)) } catch let error { XCTFail("\(error)") }
width
や xRange
が static let
でなかったからなんですよね static let
でないと、インスタンス毎に違う場合がある(今回は8x8でない盤面が今後追加される計画がある)のではないかと勘繰ってしまうpublic enum State: Equatable { case beingPlayed(turn: Disk) case over(winner: Disk?) }
let board: Board = Board(width: 6, height: 4) XCTAssertEqual(board, Board(""" ------ --ox-- --xo-- ------ """))
public struct Board { // ... public init(width: Int, height: Int, disks: [Disk?]? = nil) { precondition(width >= 2, "`width` must be >= 2: \(width)") precondition(height >= 2, "`height` must be >= 2: \(height)") precondition(width.isMultiple(of: 2), "`width` must be an even number: \(width)") precondition(height.isMultiple(of: 2), "`height` must be an even number: \(height)")
enum Player { case human case computer }
struct Board { var discs: [Disc?] = [ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, .white, .black, nil, nil, nil, nil, nil, nil, .black, .white, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, ] }
var 🎲: Int { (1 ... 6).randomElement()! } print(🎲) print(🎲) print(🎲)
var 🎲: Int { (1 ... 6).randomElement()! } print(🎲) print(🎲) print(🎲)
enum Disk: CaseIterable
sides
はCaseIterableで良さそう override func viewDidLoad() { super.viewDidLoad() boardView.delegate = self messageDiskSize = messageDiskSizeConstraint.constant do { try gameManager.loadGame() } catch _ { gameManager.newGame() } updateUI() }
let directions = [ (x: -1, y: -1), (x: 0, y: -1), (x: 1, y: -1), (x: 1, y: 0), (x: 1, y: 1), (x: 0, y: 1), (x: -1, y: 0), (x: -1, y: 1), ] ... for direction in directions {
// MARK: Reversi logics
^ とにかくここをなくす、のが私は重要だとみたcountDisks
だけなくせた(lldb) e print(self.gameState.serialized) o11 xxxxxxxo -ooooxxo ooxoxoxx ooxxooxx oxxxxoxx xxxxoxxx xxoxxxxx xxxxxxxx
class func animate(withDuration duration: TimeInterval, animations: @escaping () -> Void, // ここ completion: ((Bool) -> Void)? = nil)
@escaping
じゃないといけないの?placeDisk
をどうにかしたら終わろうと思ってましたが、どうにかできたと思ったとたん、アニメーション中のままと認識されて次の手が指せなくなる不具合を産んでしまったので、今日はこの辺であきらめます。。。 nil
を返すところですか? < シンプル (edited)struct Line: Hashable { let start: Coordinate let end: Coordinate let direction: Direction let distance: Distance init?(start: Coordinate, direction: Direction, distance: Distance) { // NOTE: Be nil if the X is out of boards. let unsafeX: CoordinateX? switch direction { case .top, .bottom: unsafeX = start.x case .left, .topLeft, .bottomLeft: unsafeX = CoordinateX(rawValue: start.x.rawValue - distance.rawValue) case .right, .topRight, .bottomRight: unsafeX = CoordinateX(rawValue: start.x.rawValue + distance.rawValue) } // NOTE: Be nil if the Y is out of boards. let unsafeY: CoordinateY? switch direction { case .left, .right: unsafeY = start.y case .top, .topLeft, .topRight: unsafeY = CoordinateY(rawValue: start.y.rawValue - distance.rawValue) case .bottom, .bottomLeft, .bottomRight: unsafeY = CoordinateY(rawValue: start.y.rawValue + distance.rawValue) } switch (unsafeX, unsafeY) { case (.none, _), (_, .none): return nil case (.some(let x), .some(let y)): self.start = start self.end = Coordinate(x: x, y: y) self.direction = direction self.distance = distance } }
i
, j
を間違えるとかよくある話ですし、それを型で弾けるのおもしろいですね。Direction
に Distance
を渡して CoordinateInterval
的なのを返すメソッドを作って、 Coordinate
と CoordinateInterval
の間に +
(戻り値は Coordinate?
)を実装するとシンプルになりませんか?direction.interval(of: distance)
で CoordinateInterval
を作るのでそれを使う限りは dx = 1, dy = 2 みたいなのは作れないですが、 +
の戻り値が型として不正な値を許容するのはその通りですね。ただ、イメージとしてはその結果を Line
に使うんじゃなくて、前段のエラー判定をシンプルにできるかなと。init?(start: Coordinate, direction: Direction, distance: Distance) { guard let end = start + direction.interval(of: distance) else { return nil } self.start = start self.end = end self.direction = direction self.distance = distance }
switch
の二回の分岐が Direction.interval
の一回にまとめられそうだし、fileprivate extension
使ってコード分解したりしますね。
Line
を型にするのおもしろいですね。CoordinateDelta
を導入しても Line
の安全性は init?
での検査で保証できそうな気がするんですが、どういう問題があったのでしょうか?Coordinate.add(_: CoordinateDelta) -> Coordinate?
だとどうですか? (edited)struct DirectedDistance { var direction: Direction var distance: Distance }
Coordinate.add(_: DirectedDistance) -> Coordinate?
とか?Line
も作る気持ちです。Line
の init?
は読みやすくなるかなと思いました。 Line
のメンバを start
と directedDistance
に置き換えるかは悩みますね。Distance
の話とか、 CoordinateX
と CoordinateY
を分ける話とか、それらの間で演算をどう扱うかとか、 Line
のイニシャライザで検査することで不正な Line
を作れなくしてるとか、切り口がめちゃくちゃあって、それぞれで LT できそうだなと。序盤は少なく取った方が有利
このへんにつながるのかな。x
が角として、 o
の位置は場合によってなんとかなりますが、 -
の位置はかなりきついです。 o- xo
--oooo-- --ooo--- -ooooooo oooxxooo ooxooooo oooooooo --oooo-- --oooo--
x
が打って次の手で角取られないためには--oooo-- --ooox-- -oooxooo oooxxooo ooxooooo oooooooo --oooo-- --oooo--
か--oooo-- --ooo--- xooooooo oxoxxooo ooxooooo oooooooo --oooo-- --oooo--
だけど--oooo-- --ooox-- -oooxooo oooxxooo ooxooooo oooooooo --oooo-- --oooo--
を --ooooo- --oooo-- -ooooooo ooooxooo oooooooo oooooooo --oooo-- --oooo--
でしのいで黒が角をあげないといけなくなっても (edited)o
が角とったけど一番上の行を取り返されるパターンのことが言いたかったことです。 o-ooooo- -ooooo-- oxoooooo ........
x
が少なすぎてこのパターンにならなかった。x
が少ないとコントロールしやすそうですね。(i, j)
が白になった数をW_ij
として、一方で黒になった数をB_ij
とする。ただし置いた時点で 1
とする。 ゲーム終了時点で、マス(i, j)
は次のように分岐する 1. 黒である => B_ij = W_ij + 1
2. 白である => B_ij = W_ij - 1
(i, j)
だけでなくて隣接するところの条件を組み込んだりして、なんとかこの路線で攻められるか?これまでにパスがないと仮定して黒b枚、白w枚の局面では、黒はこれまでに b0 = floor((b + w) / 2) + 1 枚、白は w0 = floor((w + b) / 2) を置いている。このとき、白は黒よりも (w - w0) - (b - b0) 杯多くワインを飲んでいる
(edited)b
を黒を置いた数、w
を白を置いた数とするΣB_ij
をゲームが終わった時点で(おいた時を1として)黒をひっくり返した数ΣW_ij
をゲームが終わった時点で白をひっくり返した数ΣB_ij - b
、白 ΣW_ij - w
となる。そして、 1. b = w
のとき……これはさっきの議論で消していいのでOK 2. b ≠ w
のとき …… これをかんがえるか! という感じかな。b = w + k
みたいな感じにして、そのうえで(1)を k = 0
のベースケースしたうえで帰納法まわせないか? k < 0
のケースあるから無理か……?
ΣB_ij > ΣW_ij
は間違いない。b < w
のケース)が存在するか? (edited)盤上の石の総数 = b + w
になると理解してます。 (edited)b
、白の枚数を w
で表し、黒・白がこれまでに置いた枚数をそれぞれ pb
, pw
、黒・白がこれまでにひっくり返した枚数をそれぞれr fb
, fw
とする。黒と白のひっくり返した枚数の差を f = fb - fw
とする。このとき、 b = 2 + pb + fb - fw = 2 + pb + f w = 2 + pw + fw - fb = 2 + pw - f
が成り立つ。 (edited)pb = (b + w - 4 + 1) // 2 pw = (b + w - 4) // 2
が成り立つ(ただし、 //
は除算後に切り捨て)。 (edited)b + w
が奇数なので、 pb = (b + w - 4 + 1) / 2
となる。b1
, w1
などと表す。n
連続パスし、黒が 1 枚ずつひっくり返した後の局面の値を b2
, w2
などと表す。b2 = b1 + 2 * n w2 = w1 - n fb2 = fb1 + n fw2 = fw1 pb2 = pb1 + n pw2 = pw1
が成り立つ。 (edited)f2 = fb2 - fw2 < 0
かつ、 b2 > w2
となるような b1
, w1
, n
を見つけられれば、それが反例の候補となる。n = 10
は非現実的でも、 n = 2
か 3
くらいで見つけられる気がしてて、適当な数を入れて計算してみます。pb2
と pw2
の式が間違えてたので修正しました。let b1 = 17 let w1 = 24 let n = 3 let pb1 = (b1 + w1 - 4 + 1) / 2 let pw1 = (b1 + w1 - 4) / 2 let f1 = b1 - 2 - pb1 assert(f1 == -(w1 - 2 - pw1)) let pb2 = pb1 + n let pw2 = pw1 let b2 = b1 + 2 * n let w2 = w1 - n let f2 = f1 + n assert(f2 == b2 - 2 - pb2) assert(f2 == -(w2 - 2 - pw2)) print("BEFORE: b1 = \(b1), w1 = \(w1), f1 = \(f1)") print("AFTER: b2 = \(b2), w2 = \(w2), f2 = \(f2)") print("b2 - w2 = \(b2 - w2)")
BEFORE: b1 = 17, w1 = 24, f1 = -4 AFTER: b2 = 23, w2 = 21, f2 = -1 b2 - w2 = 2
b1 + w1
が奇数になるときは白の番なので。n = 2
でもいけました。 @swift-5.2.5
let b1 = 17 let w1 = 22 let n = 2 let pb1 = (b1 + w1 - 4 + 1) / 2 let pw1 = (b1 + w1 - 4) / 2 let f1 = b1 - 2 - pb1 assert(f1 == -(w1 - 2 - pw1)) let pb2 = pb1 + n let pw2 = pw1 let b2 = b1 + 2 * n let w2 = w1 - n let f2 = f1 + n assert(f2 == b2 - 2 - pb2) assert(f2 == -(w2 - 2 - pw2)) print("BEFORE: b1 = \(b1), w1 = \(w1), f1 = \(f1)") print("AFTER: b2 = \(b2), w2 = \(w2), f2 = \(f2)") print("b2 - w2 = \(b2 - w2)")
BEFORE: b1 = 17, w1 = 22, f1 = -3 AFTER: b2 = 21, w2 = 20, f2 = -1 b2 - w2 = 1
fb
, fw
)はわかりませんが、ひっくり返した枚数の差( f
)は一意に決まりますよね? (edited)b = 2 + pb + fb - fw = 2 + pb + f w = 2 + pw + fw - fb = 2 + pw - f
と pb = (b + w - 4 + 1) // 2 pw = (b + w - 4) // 2
から。 (edited)好きな方に足せる(任意のパスを許容) 好きな数だけ石を取れる
これです ️(けっきょく少なくとも1つは取らないといけないなど、違いがよくわかってない) (edited)b = 2 + pb + fb - fw = 2 + pb + f w = 2 + pw + fw - fb = 2 + pw - f
と、パスが発生していないとき、 pb = (b + w - 4 + 1) // 2 pw = (b + w - 4) // 2
です。--oo oxxo --x- ----
↑の状態で o
を置いて --oo oxoo --o- --o-
になったときって、 o
のプレイヤーが x
のワインを 2 杯飲むんですよね?pan: elapsed time 0 seconds 1: (GameModel) (state 1) [gameState = GMReady] 2: (GameModel) (state 2) [gameStateDidChange!GMReady] 3: (AnimatedGameModel) (state 1) [gameStateDidChange?gameState] 4: (GameModel) (state 15) [((gameState==GMReady))] 5: (BoardAnimationModel) (state 1) [boardAnimationStateDidChange!boardAnimationState] 6: (AnimatedGameModel) (state 2) [boardAnimationStateDidChange?boardAnimationState] 7: (AnimatedGameModel) (state 18) [IF] 7: (AnimatedGameModel) (state 8) [IF] 7: (AnimatedGameModel) (state 5) [animatedGameState = AGMReady] 7: (AnimatedGameModel) (state 9) [.(goto)] 7: (AnimatedGameModel) (state 17) [.(goto)] 8: (BoardAnimationModel) (state 2) [((boardAnimationState==BAMNotAnimating))] 9: (AnimatedGameModel) (state 20) [animatedGameStateDidChange!animatedGameState] 10: (AnyView) (state 1) [animatedGameStateDidChange?animatedGameState] 11: (AnyView) (state 2) [animatedGameCommandQueue!AGMPass] 12: (AnimatedGameModel) (state 68) [animatedGameCommandQueue?animatedGameCommand] 13: (AnimatedGameModel) (state 69) [((animatedGameState==AGMReady))] 14: (AnimatedGameModel) (state 70) [((animatedGameCommand==AGMPass))] 15: (AnimatedGameModel) (state 71) [gameCommandQueue!GMPass] 16: (GameModel) (state 16) [gameCommandQueue?gameCommand] 17: (AnimatedGameModel) (state 99) [((animatedGameCommand==AGMPass))] 18: (AnimatedGameModel) (state 100) [(1)] 19: (GameModel) (state 17) [((gameCommand==GMPass))] 20: (GameModel) (state 18) [gameState = GMReady] 21: (AnyView) (state 2) [animatedGameCommandQueue!AGMPass] 22: (AnimatedGameModel) (state 68) [animatedGameCommandQueue?animatedGameCommand] 23: (AnimatedGameModel) (state 69) [((animatedGameState==AGMReady))] 24: (AnimatedGameModel) (state 70) [((animatedGameCommand==AGMPass))] spin: trail ends after 24 steps
placeDisk(_:atX:y:animated:completion:)
って animated: true
しか使われてないですか?BoardView
の setDisk
は animated: false
で呼び出してる箇所はありますが。animated: false
にしてペースを早めてますw発表順: koher yaso_san sugigami takasek 休憩 koher Hiron marty-suzuki lovee 休憩 KishikawaKatsumi Kuniwak koher
サーバミュート
というやつでしょうかねミュート
は、自分がその人の声を無視するだけの機能です。 サーバーミュート
はその人の口を塞ぐ機能です。Interactor
は誰が保持しているんでしょうか?// Use Case class UseCase { var delegate: UseCaseDelegate? func foo() { delegate?.showX(42) } } protocol UseCaseDelegate: AnyObject { func showX(_ x: Int) } ////////////////// // Presenter class Presenter: UseCaseDelegate { func showX(_ x: Int) { print(x) } } let useCase = UseCase() useCase.delegate = Presenter() useCase.foo()
class Presenter: UseCaseDelegate { func showX(_ x: Int) { // 何もしない } }
↑これはプレゼンターでもなんでもなく、ただ単にxをとって無視してるだけのオブジェクトですねshowX
はあまりよくないかなと言う思いはあります、show
はなんとなくプレゼンテーション処理に見えるので、xDidChange
とかの方がよりDelegateメソッドとしてふさわしいと思います @nishitakuビュー: はえー、ビューを更新する大変や。リクエストしてモデルさんから帰ってくるデータがたくさん来るからな。いそがしいそがし モデル: … なあ、相談したいことあるんやけど。 ビュー: なんやモデルさん。なんかあった? モデル: せや、ワイ、ビューを直接変えたいねん。 ビュー:ファッ?それはなんでや モデル:お前の意図とは関係なく、データAが来るんや。仕様やと、ビューにそのデータAを表示せんといかんねん ビュー:ファッ、それってワイをメンバ変数としてモデルさんのところに定義せんといかんやん。密結合や!ワイがリクエストした時に表示すればええんちゃう? モデル:… やけど、仕様やからな。お前がリクエストしたのと全然関係ないねん。 ビュー:ファーッ。それきついわ。相互につっよい依存関係できてしまうわ。モデルさんに対して全部アクセス権を与えたくないねん。 モデル:うーん。どうすればいいやろ ビュー:。。。せや!ワイに処理を”移譲(デリゲート)”してくれ。デリゲートしてくれたら、ワイを直接もたんでええやん。 モデル:それはどういうことや ビュー:あくまで、ワイを「データAが来たときにデータAを投げる先」として捉えるようにデリゲートProtocolを作って定義やれば、ワイを強い実体として持たなくてええんや。わいはそのProtocolに準拠すればええ。それでワイがデータAだけを受け取ってワイが更新すればええんや モデル:それって変数として持つのとどう違うん? ビュー:まず、モデルさんがビューという強い実体を持つのはダメなんや。やからデリゲートを通じるようにするだけにして「データを投げるなにか」として抽象化できるんやで。 モデル:ということはあれか。わいはおまえをビューとして知らんでええやん。「ただデータを投げる先」やな ビュー:せや!これでビュー -> モデルの関係はあるけど、逆は「ビュー」としての依存ではなく、「何かデータAを投げる先」として扱えるんや
(edited)Topic: Swift Zoomin' #2 延長戦 Time: May 30, 2020 07:00 PM Osaka, Sapporo, Tokyo Join Zoom Meeting https://us02web.zoom.us/j/86719010066?pwd=eWlmdXAwWlAySk8zRGxKS1VHZUtPdz09 Meeting ID: 867 1901 0066 Password: 686227
Kuniwak 20:20 KishikawaKatsumi 20:55 休憩 21:25 lovee 21:35 takasek 22:05 koher 22:35 クロージング 23:05
self.updateMessageViews() self.updateCountLabels()
👉🏻 競技プログラミングって何? 👉🏻 Swift の特徴と競技プログラミング 👉🏻 Swift で競プロ( AtCoder )に挑戦しよう!
let numbers = readLine()! // 標準入力から 1 行読み込む .split(separator: " ") // 文字列をスペースで分割 .map { Int($0)! } // 各要素を Int に変換 let sum = numbers.reduce(0, +) print(sum) // 標準出力に sum を出力
swift main.swift < input.txt > output.txt diff output.txt answer.txt
という感じになりますlet a: Int = Int(readLine()!)! let bc: [Int] = readLine()!.split(separator: " ").map { Int($0)! } let b = bc[0] let c = bc[1] let s: String = readLine()! print(a + b + c, s)
async/await
2. Swift で AtCoder 過去問に挑戦( 2 問) "先取り! Swift 6 の async/await
" では Swift 6 の目玉機能 `async/a...// サーバーから JSON を取得し、 // User インスタンスをデコードし、 // それに加えて User が保持する thumbnailURL から // サムネイル画像をダウンロードする非同期関数 // fetchUserWithThumbnail を完成させて下さい。 import Foundation import FoundationNetworking struct User: Identifiable, Codable { typealias ID = Int let id: ID var name: String var thumbnailURL: URL } func download(from url: URL, completion: @escaping (Result<Data, Error>) -> Void) { do { let data: Data = try Data(contentsOf: url) completion(.success(data)) } catch { completion(.failure(error)) } } func fetchUserWithThumbnail(for id: User.ID, completion: @escaping (Result<(user: User, thumbnail: Data), Error>) -> Void) { let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! // 🚧 ここを実装する } fetchUserWithThumbnail(for: 123) { result in do { let (user, thumbnail) = try result.get() print(user.name, thumbnail.count) } catch { print(error) } }
// サーバーから JSON を取得し、 // User インスタンスをデコードし、 // それに加えて User が保持する thumbnailURL から // サムネイル画像をダウンロードする非同期関数 // fetchUserWithThumbnail を完成させて下さい。 import Foundation import FoundationNetworking struct User: Identifiable, Codable { typealias ID = Int let id: ID var name: String var thumbnailURL: URL } func download(from url: URL) async throws -> Data { let data: Data = try Data(contentsOf: url) return data } func fetchUserWithThumbnail(for id: User.ID) async throws -> (user: User, thumbnail: Data) { let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! // 🚧 ここを実装する } runAsyncAndBlock { do { let (user, thumbnail) = try await fetchUserWithThumbnail(for: 123) print(user.name, thumbnail.count) } catch { print(error) } }
// サーバーから JSON を取得し、 // User インスタンスをデコードする // 非同期関数 fetchUser を完成させて下さい。 // // サーバーから JSON を取得するには // download 関数を用います。 // download の実装は擬似的なものですが // 変更せずにそのまま利用して下さい。 // // なお、通信やデコードに起因するエラーは // 起こらないものとします。 import Foundation import FoundationNetworking struct User: Identifiable, Codable { typealias ID = Int let id: ID var name: String var thumbnailURL: URL } func download(from url: URL) async -> Data { let data: Data = try! Data(contentsOf: url) return data } func fetchUser(for id: User.ID) async -> User { let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! let data = await download(from: url) let user: User = try! JSONDecoder().decode(User.self, from: data) return user } runAsyncAndBlock { let user = await fetchUser(for: 123) print(user.name) }
import Foundation import FoundationNetworking struct User: Identifiable, Codable { typealias ID = Int let id: ID var name: String var thumbnailURL: URL } func download(from url: URL) async throws -> Data { let data: Data = try Data(contentsOf: url) return data } func fetchUserWithThumbnail(for id: User.ID) async throws -> (user: User, thumbnail: Data) { let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! let data = try await download(from: url) let decoder = JSONDecoder() let user = try decoder.decode(User.self, from: data) let thData = try await download(from: user.thumbnailURL) return (user: user, thumbnail: thData) } runAsyncAndBlock { do { let (user, thumbnail) = try await fetchUserWithThumbnail(for: 123) print(user.name, thumbnail.count) } catch { print(error) } }
koher 24121
// サーバーから User とその Article 最新 10 件の JSON を取得し、 // それらを返す非同期関数 fetchUserWithArticles を実装して下さい。 // ただし、 User と Article の JSON は並行して取得するものとし、 // User と Artcile の取得には fetchUser および // fetchArticles を用いるものとします。 import Foundation import FoundationNetworking struct User: Identifiable, Codable { typealias ID = Int let id: ID var name: String var thumbnailURL: URL } struct Article: Identifiable, Codable { typealias ID = Int let id: ID var title: String } func fetchUser(for id: User.ID) async throws -> User { let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! let data: Data = try Data(contentsOf: url) let user: User = try JSONDecoder().decode(User.self, from: data) return user } func fetchArticles(for userID: User.ID, limit: Int) async throws -> [Article] { let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/articles?userID=\(userID.description)")! let data: Data = try Data(contentsOf: url) let articles: [Article] = try JSONDecoder().decode([Article].self, from: data) return articles } func fetchUserWithArticles(for id: User.ID, limit: Int) async throws -> (user: User, articles: [Article]) { // 🚧 ここを実装する } runAsyncAndBlock { do { let (user, articles) = try await fetchUserWithArticles(for: 123, limit: 10) print(user, articles) } catch { print(error) } }
return
のとこ、こう書けんの?ってので書けてまだソワソワする async let user = try await fetchUser(for: id) async let articles = try await fetchArticles(for: id, limit: limit) return try (user: await user, articles: await articles)
return try await (user: user, articles: articles)
これが異常なソワソワ感だったreturn try await (fetchUser(for: id), fetchArticles(for: id, limit: 10))
これだとasync letとは異なる??// ViewController の reloadUserButton が押されたときに // fetchUser 関数を使ってサーバーから User を取得し、 // userNameLabel.text に取得したユーザーの name を設定するように // onReloadUserButtonPressed メソッドを完成させて下さい。 import Foundation import FoundationNetworking class UIViewController {} final class UIButton {} final class UILabel { var text: String? } struct User: Identifiable, Codable { typealias ID = Int let id: ID var name: String var thumbnailURL: URL } func fetchUser(for id: User.ID) async throws -> User { let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! let data: Data = try Data(contentsOf: url) let user: User = try JSONDecoder().decode(User.self, from: data) return user } final class ViewController: UIViewController { let reloadUserButton: UIButton = .init() let userNameLabel: UILabel = .init() let userID: User.ID = 123 // 🚧 このメソッドを完成させる func onReloadUserButtonPressed(_ sender: UIButton) { } } let viewController: ViewController = .init() viewController.onReloadUserButtonPressed(viewController.reloadUserButton)
(edited)@asyncHandler
、バグりそうって思ったけど func bar() { print(2) // foo1() DispatchQueue.main.async { print(4) // foo2() DispatchQueue.main.async { print(5) } } }
みたいに書いてたよくあるコードと同じなんですね (edited)@asyncHandler
付けないと 'async' in a function that does not support concurrency
ってエラーもちゃんと出る// ViewController の cancelReloadingUser ボタンを押すと // reloadUser ボタンで実行されている User のリロードを // キャンセルするように ViewController の実装を完成させて下さい。 import Foundation import FoundationNetworking class UIViewController {} final class UIButton {} final class UILabel { var text: String? } struct User: Identifiable, Codable { typealias ID = Int let id: ID var name: String var thumbnailURL: URL } func fetchUser(for id: User.ID) async throws -> User { let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! let data: Data = try Data(contentsOf: url) let user: User = try JSONDecoder().decode(User.self, from: data) return user } // 🚧 このクラスを完成させる final class ViewController: UIViewController { let reloadUserButton: UIButton = .init() let cancelReloadingUserButton: UIButton = .init() let userNameLabel: UILabel = .init() @asyncHandler func onReloadUserButtonPressed(_ sender: UIButton) { if let user = try? await fetchUser(for: 123) { userNameLabel.text = user.name } } func onCancelReloadingUserButtonPressed(_ sender: UIButton) { } } let viewController: ViewController = .init() viewController.onReloadUserButtonPressed(viewController.reloadUserButton) viewController.onCancelReloadingUserButtonPressed(viewController.cancelReloadingUserButton)
// Thread X await DispatchQueue.main.async() // Main Thread
// コールバックで結果を受け取る非同期関数 download を使って、 // async で結果を返す非同期関数 download を実装して下さい。 import Foundation import FoundationNetworking func download(from url: URL, completion: @escaping (Result<Data, Error>) -> Void) { do { let data: Data = try Data(contentsOf: url) completion(.success(data)) } catch { completion(.failure(error)) } } // 🚧 ここを実装する runAsyncAndBlock { do { let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=123")! let data: Data = try await download(from: url) print(String(data: data, encoding: .utf8)!) } catch { print(error) } }
Future
が await
できるようになったりしたら、withUnsafeThrowingContinuation
と役割被りそうextension RawRepresentable where Self: Encodable, RawValue == UUID { func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(self.rawValue.uuidString) } }
のような感じで拡張する必要がありますがmembers: [String] = 6 values { [0] = "koogawa" [1] = "log5" [2] = "kashihararara" [3] = "terry" [4] = "kagaffy" [5] = "ykws" }
$ swift Welcome to Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28). Type :help for assistance. 1> let members = ["log5", "terry", "ykws", "kagaffy", "koogawa", "kashihararara"].shuffled() members: [String] = 6 values { [0] = "koogawa" [1] = "log5" [2] = "kashihararara" [3] = "terry" [4] = "kagaffy" [5] = "ykws" }
koogawa ---+ A +---+ log5 ---+ +---+ kashihararara ---+ | terry ---+ +--- B +---+ | kagaffy ---+ +---+ ykws -------+
(edited)func downloadData(from url: URL) async throws -> Data { let (data, response) = try await URLSession.shared.data(from: url) if let response = response as? HTTPURLResponse { guard response.statusCode == 200 else { throw ResponseError(statusCode: response.statusCode) } } return data }
get()
メソッドなんて生えてるのか… (edited) override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // User の JSON の取得 Task { do { let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let userData: Data = try await downloadData(from: url) let user: User = try JSONDecoder().decode(User.self, from: userData) // View への反映 title = user.name nameLabel.text = user.name // アイコン画像の取得 let iconData: Data = try await downloadData(from: user.iconURL) // Data を UIImage に変換 guard let iconImage: UIImage = .init(data: iconData) else { // エラーハンドリング print("The icon image at \(user.iconURL) has an illegal format.") return } // View への反映 iconImageView.image = iconImage } catch { // エラーハンドリング print(error) } } }
do-catch
をネストせずに書く例です。 override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // User の JSON の取得 Task { let user: User do { let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let userData: Data = try await downloadData(from: url) user = try JSONDecoder().decode(User.self, from: userData) } catch { // エラーハンドリング print(error) return } // View への反映 title = user.name nameLabel.text = user.name let iconData: Data do { // アイコン画像の取得 iconData = try await downloadData(from: user.iconURL) } catch { // エラーハンドリング print(error) return } // Data を UIImage に変換 guard let iconImage: UIImage = .init(data: iconData) else { // エラーハンドリング print("The icon image at \(user.iconURL) has an illegal format.") return } // View への反映 iconImageView.image = iconImage } }
import Foundation final class BankAccount { var balance: Int = 0 var test: [Int:Int] = [:] func deposit(_ amount: Int) -> Int { balance += amount test[100] = 100 Thread.sleep(forTimeInterval: 1.0) return balance } } func bankAccountMain() { let account = BankAccount() Task { print(account.deposit(100)) } Task { print(account.deposit(100)) } }
import Foundation final class BankAccount { var balance = 0 func deposit(_ amount: Int) -> Int { Thread.sleep(forTimeInterval: 1.0) balance += amount return balance } } func bankAccountMain() { let bankAccount = BankAccount() Task { print(bankAccount.deposit(100)) } Task { print(bankAccount.deposit(200)) } }
import Foundation final class BankAccount { var balance = 0 func deposit(_ amount: Int) -> Int { balance += amount Thread.sleep(forTimeInterval: 1.0) return balance } } func bankAccountMain() { (1...100).forEach { _ in let bankAccount = BankAccount() Task { print(bankAccount.deposit(100)) } Task { print(bankAccount.deposit(100)) } } }
Task.detached
にしたら競合しました (at Intel) (edited)200
が 2 回出力された import Foundation final class BankAccount { var balance = 0 func deposit(_ amount: Int) -> Int { balance += amount Thread.sleep(forTimeInterval: 1.0) return balance } } func bankAccountMain() { let bankAccount = BankAccount() Task.detached { print(bankAccount.deposit(100)) } Task { print(bankAccount.deposit(100)) } }
func getInterest(with rate: Double) -> Int
100 200 300 300
import Foundation actor BankAccount { var balance = 0 func deposit(_ amount: Int) -> Int { balance += amount Thread.sleep(forTimeInterval: 1.0) return balance } func getInterest(with rate: Double) -> Int { let newBalance = Double(balance) * (1.0 + rate) return Int(newBalance) } } func bankAccountMain() { let bankAccount = BankAccount() Task.detached { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) } Task { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) } }
(edited)100 200 500 1250
import Foundation actor BankAccount { var balance = 0 func deposit(_ amount: Int) -> Int { balance += amount Thread.sleep(forTimeInterval: 1.0) return balance } func getInterest(with rate: Double) -> Int { let newBalance = Int(Double(balance) * (1.0 + rate)) let balance = deposit(newBalance) return balance } } func bankAccountMain() { let bankAccount = BankAccount() Task.detached { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) } Task { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) } }
100 200 300 450
import Foundation actor BankAccount { var balance = 0 func deposit(_ amount: Int) -> Int { balance += amount Thread.sleep(forTimeInterval: 1.0) return balance } func getInterest(with rate: Double) -> Int { let interest = Int(Double(balance) * rate) let balance = deposit(interest) return balance } } func bankAccountMain() { let bankAccount = BankAccount() Task.detached { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) } Task { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) } }
Task.detached { print(Thread.current) print(await bankAccount.deposit(100)) // 1 print(await bankAccount.getInterest(with: 0.5)) // 3 } Task { print(Thread.current) print(await bankAccount.deposit(100)) // 2 print(await bankAccount.getInterest(with: 0.5)) // 4 }
func getInterest(with rate: Double) -> Int { deposit(Int(Double(balance) * rate)) }
print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5))
struct AccountError: Error { }
throw NSError(domain: "おかねがたりません", code: -9)
<NSThread: 0x6000024e8680>{number = 6, name = (null)} <NSThread: 0x6000024d4240>{number = 7, name = (null)} 100 200 300 450 250 50
import Foundation struct AccountError: Error { } actor BankAccount { var balance = 0 func deposit(_ amount: Int) -> Int { balance += amount Thread.sleep(forTimeInterval: 1.0) return balance } func getInterest(with rate: Double) -> Int { deposit(Int(Double(balance) * rate)) } func withdraw(_ amount: Int) throws -> Int { let newBalance = balance - amount guard newBalance >= 0 else { throw AccountError() } return deposit(-amount) } func transfer(_ amount: Int, to account: BankAccount) async throws { let newBalance = balance - amount guard newBalance >= 0 else { throw AccountError() } _ = deposit(-amount) _ = await account.deposit(amount) } } func bankAccountMain() { let bankAccount = BankAccount() Task.detached { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) print(try await bankAccount.withdraw(200)) } Task { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) print(try await bankAccount.withdraw(200)) } }
(edited)import Foundation struct WithdrawError: Error { } actor BankAccount { var balance = 0 func deposit(_ amount: Int) -> Int { balance += amount Thread.sleep(forTimeInterval: 1.0) return balance } func getInterest(with rate: Double) -> Int { deposit(Int(Double(balance) * rate)) } func withdraw(_ amount: Int) throws -> Int { precondition(amount > 0) guard balance - amount >= 0 else { throw WithdrawError() } balance -= amount Thread.sleep(forTimeInterval: 1.0) return balance } func transfer(_ amount: Int, to account: BankAccount) async throws { _ = try withdraw(amount) _ = await account.deposit(amount) } } func bankAccountMain() { let bankAccount = BankAccount() Task.detached { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) print(try await bankAccount.withdraw(200)) } Task { print(Thread.current) print(await bankAccount.deposit(100)) print(await bankAccount.getInterest(with: 0.5)) print(try await bankAccount.withdraw(200)) } }
<NSThread: 0x600003d4e1c0>{number = 6, name = (null)} <NSThread: 0x600003d3ae40>{number = 4, name = (null)} A: 100 A: 200 A: 300 A: 450 A: 350 A: 250 A:150 B: 100 A:150 B: 100
import Foundation struct WithdrawError: Error { } actor BankAccount { var balance = 0 func deposit(_ amount: Int) -> Int { balance += amount Thread.sleep(forTimeInterval: 1.0) return balance } func getInterest(with rate: Double) -> Int { deposit(Int(Double(balance) * rate)) } func withdraw(_ amount: Int) throws -> Int { precondition(amount > 0) guard balance - amount >= 0 else { throw WithdrawError() } balance -= amount Thread.sleep(forTimeInterval: 1.0) return balance } func transfer(_ amount: Int, to account: BankAccount) async throws { _ = try withdraw(amount) _ = await account.deposit(amount) } } func bankAccountMain() { let accountA = BankAccount() let accountB = BankAccount() Task.detached { print(Thread.current) print("A: \(await accountA.deposit(100))") print("A: \(await accountA.getInterest(with: 0.5))") print("A: \(try await accountA.withdraw(100))") try await accountA.transfer(50, to: accountB) print("A:\(await accountA.balance) B: \(await accountB.balance)") } Task { print(Thread.current) print("A: \(await accountA.deposit(100))") print("A: \(await accountA.getInterest(with: 0.5))") print("A: \(try await accountA.withdraw(100))") try await accountA.transfer(50, to: accountB) print("A:\(await accountA.balance) B: \(await accountB.balance)") } }
8-2
ブランチを使います。 https://github.com/koher/swift-zoomin-8/tree/8-2 // View への反映 title = state.$user.name nameLabel.text = state.$user.name iconImageView.image = state.$user.iconImage
import UIKit import Combine final class UserViewController: UIViewController { private let state: UserViewState private let iconImageView: UIImageView = .init() private let nameLabel: UILabel = .init() private var cancellables: Set<AnyCancellable> = [] init(id: User.ID) { self.state = UserViewState(id: id) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() // レイアウト iconImageView.translatesAutoresizingMaskIntoConstraints = false iconImageView.layer.cornerRadius = 40 iconImageView.layer.borderWidth = 4 iconImageView.layer.borderColor = UIColor.systemGray3.cgColor iconImageView.clipsToBounds = true view.addSubview(iconImageView) nameLabel.translatesAutoresizingMaskIntoConstraints = false view.addSubview(nameLabel) NSLayoutConstraint.activate([ iconImageView.widthAnchor.constraint(equalToConstant: 80), iconImageView.heightAnchor.constraint(equalToConstant: 80), iconImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), iconImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), nameLabel.centerXAnchor.constraint(equalTo: iconImageView.centerXAnchor), nameLabel.topAnchor.constraint(equalTo: iconImageView.bottomAnchor, constant: 16), ]) // View への反映 state .$user .receive(on: DispatchQueue.main) .sink { [weak self] user in self?.nameLabel.text = user?.name } .store(in: &cancellables) state .$iconImage .receive(on: DispatchQueue.main) .sink { [weak self] iconImage in self?.iconImageView.image = iconImage } .store(in: &cancellables) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Task { await state.loadUser() } } }
(edited)import Combine import Foundation import class UIKit.UIImage final class UserViewState { let id: User.ID @Published private(set) var user: User? @Published private(set) var iconImage: UIImage? init(id: User.ID) { self.id = id } func loadUser() async { do { // User の JSON の取得 let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let data = try await downloadData(from: url) // JSON のデコード let user: User = try JSONDecoder().decode(User.self, from: data) // state への反映 self.user = user // アイコン画像の取得 let iconData = try await downloadData(from: user.iconURL) // Data を UIImage に変換 guard let iconImage: UIImage = .init(data: iconData) else { // エラーハンドリング print("The icon image at \(user.iconURL) has an illegal format.") return } // state への反映 self.iconImage = iconImage } catch { // エラーハンドリング print(error) } } }
extension Task where Failure == Never { ... @discardableResult @_alwaysEmitIntoClient public init( priority: TaskPriority? = nil, @_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping () async -> Success ) { ... } }
Actor-isolated property '$user' can not be referenced from the main actor
Task {}
1個の中に複数のawaitを詰め込むか、Task {}
を複数用意するかで迷ってしまうimport UIKit import Combine final class UserViewController: UIViewController { private let state: UserViewState private let iconImageView: UIImageView = .init() private let nameLabel: UILabel = .init() private var cancellables: Set<AnyCancellable> = [] init(id: User.ID) { self.state = UserViewState(id: id) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() // レイアウト iconImageView.translatesAutoresizingMaskIntoConstraints = false iconImageView.layer.cornerRadius = 40 iconImageView.layer.borderWidth = 4 iconImageView.layer.borderColor = UIColor.systemGray3.cgColor iconImageView.clipsToBounds = true view.addSubview(iconImageView) nameLabel.translatesAutoresizingMaskIntoConstraints = false view.addSubview(nameLabel) NSLayoutConstraint.activate([ iconImageView.widthAnchor.constraint(equalToConstant: 80), iconImageView.heightAnchor.constraint(equalToConstant: 80), iconImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), iconImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), nameLabel.centerXAnchor.constraint(equalTo: iconImageView.centerXAnchor), nameLabel.topAnchor.constraint(equalTo: iconImageView.bottomAnchor, constant: 16), ]) // View への反映 Task { await state .$user .receive(on: DispatchQueue.main) .sink { [weak self] user in self?.nameLabel.text = user?.name } .store(in: &cancellables) } Task { await state .$iconImage .receive(on: DispatchQueue.main) .sink { [weak self] iconImage in self?.iconImageView.image = iconImage } .store(in: &cancellables) } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Task { await state.loadUser() } } } extension Published.Publisher: @unchecked Sendable where Output: Sendable {}
(edited)import Combine import Foundation import class UIKit.UIImage actor UserViewState { let id: User.ID @Published private(set) var user: User? @Published private(set) var iconImage: UIImage? init(id: User.ID) { self.id = id } func loadUser() async { do { // User の JSON の取得 let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let data = try await downloadData(from: url) // JSON のデコード let user: User = try JSONDecoder().decode(User.self, from: data) // state への反映 self.user = user // アイコン画像の取得 let iconData = try await downloadData(from: user.iconURL) // Data を UIImage に変換 guard let iconImage: UIImage = .init(data: iconData) else { // エラーハンドリング print("The icon image at \(user.iconURL) has an illegal format.") return } // state への反映 self.iconImage = iconImage } catch { // エラーハンドリング print(error) } } }
extension Published.Publisher: @unchecked Sendable {}
が強引っぽく見えるのですが、これは製品コードでも問題ないのでしょうか?import UIKit import Combine final class UserViewController: UIViewController { private let state: UserViewState private let iconImageView: UIImageView = .init() private let nameLabel: UILabel = .init() private var cancellables: Set<AnyCancellable> = [] init(id: User.ID) { self.state = UserViewState(id: id) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() // レイアウト iconImageView.translatesAutoresizingMaskIntoConstraints = false iconImageView.layer.cornerRadius = 40 iconImageView.layer.borderWidth = 4 iconImageView.layer.borderColor = UIColor.systemGray3.cgColor iconImageView.clipsToBounds = true view.addSubview(iconImageView) nameLabel.translatesAutoresizingMaskIntoConstraints = false view.addSubview(nameLabel) NSLayoutConstraint.activate([ iconImageView.widthAnchor.constraint(equalToConstant: 80), iconImageView.heightAnchor.constraint(equalToConstant: 80), iconImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), iconImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), nameLabel.centerXAnchor.constraint(equalTo: iconImageView.centerXAnchor), nameLabel.topAnchor.constraint(equalTo: iconImageView.bottomAnchor, constant: 16), ]) // View への反映 do { let task = Task { [weak self] in guard let state = self?.state else { return } for await user in await state.$user.values { guard let self = self else { return } self.nameLabel.text = user?.name } } cancellables.insert(.init { task.cancel() }) } do { let task = Task { [weak self] in guard let state = self?.state else { return } for await iconImage in await state.$iconImage.values { guard let self = self else { return } self.iconImageView.image = iconImage } } cancellables.insert(.init { task.cancel() }) } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Task { await state.loadUser() } } } extension Published.Publisher: @unchecked Sendable where Output: Sendable {} extension UIImage: @unchecked Sendable {}
import Combine import Foundation import class UIKit.UIImage actor UserViewState { let id: User.ID @Published private(set) var user: User? @Published private(set) var iconImage: UIImage? init(id: User.ID) { self.id = id } func loadUser() async { do { // User の JSON の取得 let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let data = try await downloadData(from: url) // JSON のデコード let user: User = try JSONDecoder().decode(User.self, from: data) // state への反映 self.user = user // アイコン画像の取得 let iconData = try await downloadData(from: user.iconURL) // Data を UIImage に変換 guard let iconImage: UIImage = .init(data: iconData) else { // エラーハンドリング print("The icon image at \(user.iconURL) has an illegal format.") return } // state への反映 self.iconImage = iconImage } catch { // エラーハンドリング print(error) } } }
extension URL: @unchecked Sendable {}
extension URL: @unchecked Sendable {}
のような Sendable に準拠するだけのエクステンションは 1 つのファイルにまとめるのが一般的でしょうか? そもそもなんで Sendable に準拠していないんだろう…import UIKit import Combine @MainActor final class UserViewController: UIViewController { private let state: UserViewState private let iconImageView: UIImageView = .init() private let nameLabel: UILabel = .init() private var cancellables: Set<AnyCancellable> = [] init(id: User.ID) { self.state = UserViewState(id: id) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() // レイアウト iconImageView.translatesAutoresizingMaskIntoConstraints = false iconImageView.layer.cornerRadius = 40 iconImageView.layer.borderWidth = 4 iconImageView.layer.borderColor = UIColor.systemGray3.cgColor iconImageView.clipsToBounds = true view.addSubview(iconImageView) nameLabel.translatesAutoresizingMaskIntoConstraints = false view.addSubview(nameLabel) NSLayoutConstraint.activate([ iconImageView.widthAnchor.constraint(equalToConstant: 80), iconImageView.heightAnchor.constraint(equalToConstant: 80), iconImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), iconImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), nameLabel.centerXAnchor.constraint(equalTo: iconImageView.centerXAnchor), nameLabel.topAnchor.constraint(equalTo: iconImageView.bottomAnchor, constant: 16), ]) // View への反映 do { let task = Task { [weak self] in guard let state = self?.state else { return } for await user in state.$user.values { guard let self = self else { return } self.nameLabel.text = user?.name } } cancellables.insert(.init { task.cancel() }) } do { let task = Task { [weak self] in guard let state = self?.state else { return } for await iconImage in state.$iconImage.values { guard let self = self else { return } self.iconImageView.image = iconImage } } cancellables.insert(.init { task.cancel() }) } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Task { await state.loadUser() } } } extension Published.Publisher: @unchecked Sendable where Output: Sendable {} extension UIImage: @unchecked Sendable {}
import Combine import Foundation import class UIKit.UIImage @MainActor final class UserViewState { let id: User.ID @Published private(set) var user: User? @Published private(set) var iconImage: UIImage? init(id: User.ID) { self.id = id } func loadUser() async { do { // User の JSON の取得 let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let data = try await downloadData(from: url) // JSON のデコード let user: User = try JSONDecoder().decode(User.self, from: data) // state への反映 self.user = user // アイコン画像の取得 let iconData = try await downloadData(from: user.iconURL) // Data を UIImage に変換 guard let iconImage: UIImage = .init(data: iconData) else { // エラーハンドリング print("The icon image at \(user.iconURL) has an illegal format.") return } // state への反映 self.iconImage = iconImage } catch { // エラーハンドリング print(error) } } }
@MainActor protocol StateProtocol {} final class UserViewState: StateProtocol { ... }
2021-11-13 23:22:28.460902+0900 SwiftZoomin8[66695:7888964] [boringssl] boringssl_metrics_log_metric_block_invoke(144) Failed to log metrics Combine/Publisher+AsyncSequence.swift:65: Fatal error: Received an output without requesting demand 2021-11-13 23:22:28.483897+0900 SwiftZoomin8[66695:7888923] Combine/Publisher+AsyncSequence.swift:65: Fatal error: Received an output without requesting demand Process finished with exit code 0
-> 不要なコードが残ってたのが原因でした(消したら治りました) (edited)import UIKit import Combine @MainActor final class UserViewController: UIViewController { private let state: UserViewState private let iconImageView: UIImageView = .init() private let nameLabel: UILabel = .init() private var cancellables: Set<AnyCancellable> = [] init(id: User.ID) { self.state = UserViewState(id: id) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() // レイアウト iconImageView.translatesAutoresizingMaskIntoConstraints = false iconImageView.layer.cornerRadius = 40 iconImageView.layer.borderWidth = 4 iconImageView.layer.borderColor = UIColor.systemGray3.cgColor iconImageView.clipsToBounds = true view.addSubview(iconImageView) nameLabel.translatesAutoresizingMaskIntoConstraints = false view.addSubview(nameLabel) NSLayoutConstraint.activate([ iconImageView.widthAnchor.constraint(equalToConstant: 80), iconImageView.heightAnchor.constraint(equalToConstant: 80), iconImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), iconImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), nameLabel.centerXAnchor.constraint(equalTo: iconImageView.centerXAnchor), nameLabel.topAnchor.constraint(equalTo: iconImageView.bottomAnchor, constant: 16), ]) // View への反映 let task = Task { [weak self] in guard let state = self?.state else { return } for await _ in state.objectWillChange.values { guard let self = self else { return } self.nameLabel.text = state.user?.name self.iconImageView.image = state.iconImage } } cancellables.insert(.init { task.cancel() }) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Task { await state.loadUser() } } } extension UIImage: @unchecked Sendable {}
import Combine import Foundation import class UIKit.UIImage @MainActor final class UserViewState: ObservableObject { let id: User.ID @Published private(set) var user: User? @Published private(set) var iconImage: UIImage? init(id: User.ID) { self.id = id } func loadUser() async { do { // User の JSON の取得 let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let data = try await downloadData(from: url) // JSON のデコード let user: User = try JSONDecoder().decode(User.self, from: data) // state への反映 self.user = user // アイコン画像の取得 let iconData = try await downloadData(from: user.iconURL) // Data を UIImage に変換 guard let iconImage: UIImage = .init(data: iconData) else { // エラーハンドリング print("The icon image at \(user.iconURL) has an illegal format.") return } // state への反映 self.iconImage = iconImage } catch { // エラーハンドリング print(error) } } }
state.objectWillChange .receive(on: DispatchQueue.main) .sink { [weak self] in guard let self = self else { return } self.nameLabel.text = self.state.user?.name self.iconImageView.image = self.state.iconImage } .store(in: &cancellables)
(edited) NavigationLink { UserView(id: 1234) } label: { Text("User View (SwiftUI)") }
import SwiftUI @MainActor struct UserView: View { @StateObject private var state: UserViewState init(id: User.ID) { self._state = StateObject(wrappedValue: UserViewState(id: id)) } var body: some View { VStack(spacing: 16) { Group { if let iconImage = state.iconImage { Image(uiImage: iconImage) .resizable() } else { Color.clear } } .frame(width: 80, height: 80) .clipShape(Circle()) .overlay(Circle().stroke(Color(uiColor: .systemGray3), lineWidth: 4)) if let name = state.user?.name { Text(name) } Spacer() } .padding(16) .task { await state.loadUser() } } }
次のアクション - 各自リファクタリングを進めて頂く(2/12 まで) - forkしたブランチをできたところまで push - 発表していただける方は2/12の報告会で発表! - 普通に参加したい方も参加枠でぜひ!
guard
の条件反転してバグってハマったりしてたから恥ずかしいLoginViewController
のロジックを VM 的な LoginViewState
に切り出しました。次は AuthService
を抽象化して APIServices への依存をなくし、単体テストを書いていきたいです。 https://github.com/koher/login-challenge今日の発表順: 1. まつじさん 2. ueshun109さん 3. tochi86さん 4. koherさん
callAsFunction
使っていますね!callAsFunction
と static func
の違い(メリ・デリ)がまだ理解できてないcallAsFunction
はインスタンスメソッドなので、例えばインスタンスに他のオブジェクトをDIすることができますねcallAsFunction
はインスタンスメソッドなので、例えばインスタンスに他のオブジェクトをDIすることができますね callAsFunction
を使うと、関数定義に一発でコードジャンプできなくなるので、あまり使わない派ですwif isLoadingUser { return }
はguard
を使って書く派なのですがどっちがわかりやすいんでしょうか? (edited)guard
で書きたい(絶対にその先のコードに進まない条件があることが明確になるから)けど、二重否定みたいな表現になってしまってちょっとだけ脳みそを余計に使うのはイヤだなぁと迷い続けてる。( !list.isEmpty
じゃなく list.notEmpty
的な表現にしたい気持ち。好みの問題だとは思う。) (edited)setup/teardown
をsuccessとfailのテストの間で呼びたくなって)context
の中で beforeEach
を書くようなことをしたいんですね。if let error = error { // 早期脱出 }
guard error == nil else { let error = error! // 早期脱出 }
(edited)any
、 Swift 5.7 の some
と、 Swift らしいプロトコルの使い方について、ライブコーディングで実演しながら説明します。 # 参加方法 日時: 2022 年 4 月 23 日(土) 21:0...MemoryLayout.size(ofValue:)
を -> some Animal
に対して使うと、動的に変化しますね。var: any P = condition ? A() : B()
くらいかなvar: any P = condition ? A() : B()
くらいかな func fooAny() -> [any Animal]
func fooSome() -> [some Animal]
fooSomeは要素がすべて同じ型になるけど、fooAnyは一個一個バラバラ。 (edited)func fooAny() -> [any Animal]
func fooSome() -> [some Animal]
fooSomeは要素がすべて同じ型になるけど、fooAnyは一個一個バラバラ。 (edited)func fooAny() -> [any Animal]
だと動的ディスパッチでパフォーマンスに難がある場合に enum associated values で無理やり 静的ディスパッチにしちゃうアイデアがあったりしますany Sequence
が書けるようになりますが、パラメータ指定ができないので、type erasureのユースケースの代用には不足です。 SE-0353で any Sequence<Int>
とパラメータ指定もできるようになって、代用品として完成します。 (edited)any Sequence
が書けるようになりますが、パラメータ指定ができないので、type erasureのユースケースの代用には不足です。 SE-0353で any Sequence<Int>
とパラメータ指定もできるようになって、代用品として完成します。 (edited)any
を使うべきよくあるケースはそれですね。あとは、前に uhooi さんからツイートでいただいた相互参照になるようなケースは僕の中で答えが出てないんですが、 any
を使わざるを得ないかもしれません。後は、基本は some
で考えてみて、 any
がないと無理そうであれば any
という考え方がいいんじゃないかと考えています。any
を使うべきよくあるケースはそれですね。あとは、前に uhooi さんからツイートでいただいた相互参照になるようなケースは僕の中で答えが出てないんですが、 any
を使わざるを得ないかもしれません。後は、基本は some
で考えてみて、 any
がないと無理そうであれば any
という考え方がいいんじゃないかと考えています。 @StateObject private var state: HomeViewState<UserService>
ObservableObject を用意すると、Usecase層のモックが非常にやりやすくてすごく良かったのですが、 例えばViewのスナップショットテストを実現したいと思ったときに、SwiftUIのViewやUIViewControllerでこのObservableObjectのUserServiceをモックしようとすると、ViewのPropertyは具象のUserServiceに依存してしまっていて、モックが困難に感じます。 現状Propertyに型パラメータをProtocolのまま持たせることはできないと思っているのですが、Swift@5.7のanyなどが入るとこういった問題を解決できたりするのでしょうか? (edited)@StateObject private var state: HomeViewState<UserService>
ObservableObject を用意すると、Usecase層のモックが非常にやりやすくてすごく良かったのですが、 例えばViewのスナップショットテストを実現したいと思ったときに、SwiftUIのViewやUIViewControllerでこのObservableObjectのUserServiceをモックしようとすると、ViewのPropertyは具象のUserServiceに依存してしまっていて、モックが困難に感じます。 現状Propertyに型パラメータをProtocolのまま持たせることはできないと思っているのですが、Swift@5.7のanyなどが入るとこういった問題を解決できたりするのでしょうか? (edited)@StateObject
を保持する View
とレイアウトする View
を分離し、前者が後者を利用する形にすることです。 // state 保持用の View とレイアウト用の View を分離する例 struct FooStateView: View { @StateObject private var state: FooViewState<FooService> ... var body: some View { FooView(bar: state.bar, baz: $state.baz) } } struct FooView: View { let bar: Int @Binding var baz: String ... }
こうしておけば state
とは独立に FooView
の任意の状態を簡単に実現できるので、スナップショットテストやプレビューのためのモックが必要なくなります。↑は SwiftUI の例ですが、UIKit の場合でも、外から渡された状態を再現するだけの VC と、 state
を保持して状態を伝えるだけの VC とに分離することで、同様のことが実現可能です。 ② もう一つは、公開する state
のプロパティをすべて @Publsihed public var
にしてしまうことです。そうすれば、スナップショットテストやプレビューの際に、任意の状態の state
を簡単に作れます。 UserService
等にはダミーのテスト用実装を渡して何の仕事もさせないようにします。なお、 computed property の場合は setter を作れないので、↓のように republish する必要が生じます。 // computed property の代わりに republish が必要になる例 // Before @MainActor public final class FooViewState<FooService: FooServiceProtocol>: ObserbalbleObject { @Published public var baz: String = "" public var isQux: Bool { baz.isEmpty } ... } // After @MainActor public final class FooViewState<FooService: FooServiceProtocol>: ObserbalbleObject { @Published public var baz: String = "" @Published public var isQux: Bool = false ... init(...) { ... $baz.map(\.isEmpty).assign(to: &$isQux) // republish } ... }
①②とも一長一短で、①は実装が冗長になりがちです。②は外から触っていいものといけないもの(本来 private(set)
であるべきもの)の区別が付きづらいです。テスト以外のケースで、本来メソッドを通して状態を変更しなければならないプロパティを setter で変更してしまうと、 state
の状態を壊してしまう可能性があります。 @testable import
を前提に、 internal(set)
にするのも良いかもしれません。@StateObject
を保持する View
とレイアウトする View
を分離し、前者が後者を利用する形にすることです。 // state 保持用の View とレイアウト用の View を分離する例 struct FooStateView: View { @StateObject private var state: FooViewState<FooService> ... var body: some View { FooView(bar: state.bar, baz: $state.baz) } } struct FooView: View { let bar: Int @Binding var baz: String ... }
こうしておけば state
とは独立に FooView
の任意の状態を簡単に実現できるので、スナップショットテストやプレビューのためのモックが必要なくなります。↑は SwiftUI の例ですが、UIKit の場合でも、外から渡された状態を再現するだけの VC と、 state
を保持して状態を伝えるだけの VC とに分離することで、同様のことが実現可能です。 ② もう一つは、公開する state
のプロパティをすべて @Publsihed public var
にしてしまうことです。そうすれば、スナップショットテストやプレビューの際に、任意の状態の state
を簡単に作れます。 UserService
等にはダミーのテスト用実装を渡して何の仕事もさせないようにします。なお、 computed property の場合は setter を作れないので、↓のように republish する必要が生じます。 // computed property の代わりに republish が必要になる例 // Before @MainActor public final class FooViewState<FooService: FooServiceProtocol>: ObserbalbleObject { @Published public var baz: String = "" public var isQux: Bool { baz.isEmpty } ... } // After @MainActor public final class FooViewState<FooService: FooServiceProtocol>: ObserbalbleObject { @Published public var baz: String = "" @Published public var isQux: Bool = false ... init(...) { ... $baz.map(\.isEmpty).assign(to: &$isQux) // republish } ... }
①②とも一長一短で、①は実装が冗長になりがちです。②は外から触っていいものといけないもの(本来 private(set)
であるべきもの)の区別が付きづらいです。テスト以外のケースで、本来メソッドを通して状態を変更しなければならないプロパティを setter で変更してしまうと、 state
の状態を壊してしまう可能性があります。 @testable import
を前提に、 internal(set)
にするのも良いかもしれません。 internal(set)
で対処するのが自分のユースケースにピッタリ合っていて非常に勉強になります ♂️@dynamicMemberLookup enum JSON: ExpressibleByDictionaryLiteral, ExpressibleByArrayLiteral, ExpressibleByStringLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByBooleanLiteral, ExpressibleByNilLiteral, CustomStringConvertible { case object([String: JSON]) case array([JSON]) case string(String) case number(Double) case boolean(Bool) case null init(dictionaryLiteral elements: (String, JSON)...) { self = .object([String: JSON](elements) { a, b in a }) } init(arrayLiteral elements: JSON...) { self = .array(elements) } init(stringLiteral value: String) { self = .string(value) } init(integerLiteral value: Int) { self = .number(Double(value)) } init(floatLiteral value: Double) { self = .number(value) } init(booleanLiteral value: Bool) { self = .boolean(value) } init(nilLiteral: ()) { self = .null } subscript(index: Int) -> JSON { guard case .array(let elements) = self else { preconditionFailure() } return elements[index] } subscript<Key: StringProtocol>(key: Key) -> JSON { guard case .object(let members) = self else { preconditionFailure() } return members[String(key)] ?? .null } subscript(dynamicMember key: String) -> JSON { self[key] } var description: String { switch self { case .object(let members): return members.description case .array(let elements): return elements.description case .string(let value): return value case .number(let value): return value.description case .boolean(let value): return value.description case .null: return "null" } } } // [2, 3.0, "ABC", null] // { "x": 2, "y": { "z": true }} } let a: JSON = [2, 3.0, "ABC", nil] let b: JSON = ["x": 2, "y": ["z": true]] print(a[0]) print(a) print(b["y"]["z"]) print(b.y.z)
(dynamicMember key: String)
をつくってしまうと、ドットの補完がなんでもきくようになってしまいますか?\User.age
struct User { var name: String var age: Int } var user: User = .init(name: "Swift", age: 8) //print(user.age) //user.age = 9 let keyPath: WritableKeyPath<User, Int> = \.age let keyPath2 = \User.age print(user[keyPath: keyPath]) user[keyPath: keyPath] = 9 print(user[keyPath: keyPath]) print(user.age) var userGroups: [String: [User]] = ["id0": [user]] print(userGroups["id0"]?[0].age as Any) let keyPath3: KeyPath<[String: [User]], Int?> = \.["id0"]?[0].age print(userGroups[keyPath: keyPath3] as Any) // AnyKeyPath // PartialKeyPath<Root> // KeyPath<Root, Value> // WritableKeyPath<Root, Value> // ReferenceWritableKeyPath<Root, Value>
struct User { var name: String var age: Int } @dynamicMemberLookup final class Box<Value> { var value: Value init(_ value: Value) { self.value = value } subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> T { get { value[keyPath: keyPath] } set { value[keyPath: keyPath] = newValue } } } //var a: User = .init(name: "Swift", age: 8) //var b = a //a.age = 9 //print(a.age) //print(b.age) let a: Box<User> = .init(User(name: "Swift", age: 8)) let b = a a.age = 9 print(a.age) print(b.age)
struct User: Equatable { var name: String var age: Int } @propertyWrapper final class Box<Value> { var wrappedValue: Value init(wrappedValue value: Value) { self.wrappedValue = value } var projectedValue: Ref<Value> { Ref<Value>( get: { self.wrappedValue }, set: { newValue in self.wrappedValue = newValue} ) } } @propertyWrapper struct Ref<Value> { let get: () -> Value let set: (Value) -> Void var wrappedValue: Value { get { get() } set { set(newValue) } } } do { var a: User = .init(name: "Swift", age: 8) //var b: Ref<User> = .init(get: { a }, set: { newValue in a = newValue }) @Ref var b: User _b = .init(get: { a }, set: { newValue in a = newValue }) b.age = 9 print(a.age) print(b.age) print(a == b) //print(b.wrappedValue.age) @Box var c: User = .init(name: "Swift", age: 8) @Ref var d: User _d = $c }
$
使ってました・・!@propertyWrapper @dynamicMemberLookup struct Ref<Value> { let get: () -> Value let set: (Value) -> Void var wrappedValue: Value { get { get() } nonmutating set { set(newValue) } } subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> Ref<T> { Ref<T>( get: { wrappedValue[keyPath: keyPath] }, set: { newValue in wrappedValue[keyPath: keyPath] = newValue } ) } }
$user
: Binding<User>
$user.age
: Binding<Int>
@resultBuilder
なのか@resultBuilder
は改行が意味を持つってこと…?@resultBuilder struct ArrayBuilder<Element> { static func buildExpression(_ expression: Element) -> [Element] { // 2 -> [2] [expression] } static func buildBlock(_ components: [Element]...) -> [Element] { // [[2, 3, 4], [5, 6]] -> [2, 3, 4 ,5, 6] components.flatMap { $0 } } static func buildOptional(_ component: [Element]?) -> [Element] { component ?? [] } static func buildEither(first component: [Element]) -> [Element] { component } static func buildEither(second component: [Element]) -> [Element] { component } static func buildArray(_ components: [[Element]]) -> [Element] { // [[7], [8], [9], [10]] -> [7, 8, 9, 10] components.flatMap { $0 } } } extension Array { init(@ArrayBuilder<Element> builder: () -> [Element]) { self = builder() } } //let array: [Int] = .init { // [2, 3, 6] //} let array: [Int] = .init { 2 // [2] 3 // [3] if Bool.random() { 4 // [4] 5 // [5] } else { 0 0 } // [4, 5] 6 // [6] for i in 7 ... 10 { if i.isMultiple(of: 2) { i // [i] } } // [8, 10] } print(array)
Background(Padding( Background(Padding( Background(Padding( Background(Padding( Background(Padding( Background(Padding( Background(Padding( Frame( Spacer() , width: 20, height: 20) , 20), Color.violet) , 20), Color.indigo) , 20), Color.blue) , 20), Color.green) , 20), Color.yellow) , 20), Color.orange) , 20), Color.red)
@main
もアットマークがついてますね@Attribute
を見たときに出自を確認しないといけないんですね。~Builder
や~Actor
など、命名規則から判別する運用なのかな (edited)@Attribute
を見たときに出自を確認しないといけないんですね。 @main
は小文字始まりだからアトリビュート…ってコト!?@Published
と @StateObject
( @ObservedObject
) の projectedValue
の違い import SwiftUI import Combine final class UserViewModel: ObservableObject { @Published var user: User = .init(name: "Swift", age: 8) } //let viewModel: UserViewModel = .init() // //viewModel.$user // Publisher<User, Never> struct UserView: View { // var _user: State<User> @StateObject var viewModel: UserViewModel = .init() // @State private var user: User = .init(name: "Swift", age: 8) // @ViewBuilder var body: some View { VStack { Text(viewModel.user.name) .font(.title) AgeCounterView(age: $viewModel // ObservedObject<UserViewModel>.Wrapper .user // Binding<User> .age // Binding<Int> ) } } } struct AgeCounterView: View { @Binding var age: Int init(age: Binding<Int>) { self._age = age } var body: some View { Text("age: \(age)") Button("Next Year") { age += 1 } } } struct UserView_Previews: PreviewProvider { static var previews: some View { UserView() } }
(edited)KeyPath
から文字列表現を取り出す方法は未だに公式 API ってないですよね? _kvcKeyPathString
で一応取れるんですが。例: https://github.com/kishikawakatsumi/RealmTypeSafeQuery/blob/3d0670215e2ce369b2d729da524e0a846a1dced9/RealmTypeSafeQuery/Operators.swift#L28some
) や Existential Type (any
...protocol MyIteratorProtocol<Element> { associatedtype Element mutating func next() -> Element? } protocol MySequence<Element> { associatedtype Element associatedtype Iterator: MyIteratorProtocol<Element> func makeIterator() -> Iterator // func makeIterator() -> any MyIteratorProtocol<Element> } protocol MyCollection<Element>: MySequence { associatedtype Index: Comparable associatedtype Indices: MyCollection where Indices.Element == Index associatedtype SubSequence: MyCollection where SubSequence.Element == Element, SubSequence.Index == Index, SubSequence.SubSequence == SubSequence subscript(index: Index) -> Element { get } subscript(range: Range<Index>) -> SubSequence { get } var indices: Indices { get } }
struct Pair<Element>: MySequence { var elements: (Element, Element) func makeIterator() -> some MyIteratorProtocol<Element> { PairIterator(pair: self) } } struct PairIterator<Element>: MyIteratorProtocol { var pair: Pair<Element> var index: Int = 0 mutating func next() -> Element? { defer { index += 1 } switch index { case 0: return pair.elements.0 case 1: return pair.elements.1 case _: return nil } } }
let pair: Pair<Int> = .init(elements: (2, 3)) //for element in pair { // print(element) //} var iterator = pair.makeIterator() while let element = iterator.next() { print(element) }
// ジェネリクス地獄 class MyIteratorProtocol<Element> { func next() -> Element? { fatalError() } } class MySequence<Element, Iterator: MyIteratorProtocol<Element>> { func makeIterator() -> Iterator { fatalError() } } class MyCollection< Element, Index: Comparable, IndicesIndex, IndicesIterator: MyIteratorProtocol<Index>, Indices: MyCollection< Index, IndicesIndex, IndicesIterator, >, Iterator: MyIteratorProtocol<Element>, SubSequenceIterator: MyIteratorProtocol<Element>, SubSequence: MyCollection< Element, Index, SubSequenceIterator, SubSequenceIterator, SubSequence > >: MySequence<Element, Iterator> { subscript(index: Index) -> Element { fatalError() } subscript(range: Range<Index>) -> SubSequence { fatalError() } var indices: Indices { fatalError() } }
AsyncSequence
を時系列的に発出される値のストリームと考えると、 Combine の Publisher
と同じものを表現していると考えられます。実際に、 Combine 同様のオペレーターを提供する Swift Async A...Zoomには下記URLから参加して下さい。 https://us02web.zoom.us/j/83479844929?pwd=UFhrd1RCdjlzS3lEa2lVZzZFZ1lLQT09 ミーティングID: 834 7984 4929 パスコード: 839294
strongSelf
weakSelf
とか書いてたなぁAnyCancellable
を自作する機運…AsynStore
だと for await count in state.counter
はCurrentValueSubject
と違って購読時の最新の値が飛んでこない可能性があるから、気をつける必要がある? (edited)AsyncStream.Continuation.finish()
を呼ぶ必要がなく、async/await を使いたい場合は init(unfolding:onCancel:)
の方を使う手もあります init(unfolding:onCancel:) | Apple Developer Documentation https://developer.apple.com/documentation/swift/asyncstream/init(unfolding:oncancel:)@rethrows
はprotocolにつけるんですねAsyncChannel
がバッファを持っている件、 send
が async
で消費されるまで待つ件をについて補足Publisher
を単純に values
で for await
で購読すると、バッファを持たない場合に次の next
までの間に発出された値が見逃されてしまう件について補足AsyncStream
を使った場合は next
までの間に yield
されても消失しないみたいですね。バッファに貯められてるっぽいです。 let values: AsyncStream<Int> = .init { continuation in Task { for i in 0 ..< 100 { try? await Task.sleep(nanoseconds: 100_000_000) continuation.yield(i) } continuation.finish() } } for await value in values { print(value) try await Task.sleep(nanoseconds: 1_000_000_000) }
let subject: PassthroughSubject<Int, Never> = .init() let values = subject.values Task { for i in 0 ..< 100 { try? await Task.sleep(nanoseconds: 100_000_000) subject.send(i) } subject.send(completion: .finished) } for await value in values { print(value) try await Task.sleep(nanoseconds: 1_000_000_000) }
Publisher
の values
は危険そうですね・・・。sink()
でやる処理がそのまま values
にできるわけじゃないのかなり罠なんですよね.....<Element>
が余ってて解決できなそうだけどどうなんだmaze.startPosition
あったっけ?startPosition
(edited)for
になるはずですよね、きっとUIGraphicsImageRenderer
使うと機能としてはCoreGraphicsと同じだけど座標系がUIKitに準拠したコンテキストがセットされた状態で使えるので便利ですね (edited)ObservableObject
)...import SwiftUI //var counter: Counter = .init() struct ContentView: View { @StateObject private var state: ContentViewState = .init() // @State private var state: ContentViewState = .init() var body: some View { let _ = print("A") VStack { Text("Foo") CounterView(counter: $state.counter) // CounterView(counter: state.counter) // CounterView(counter: Binding( // get: { // counter // }, set: { newValue in // counter = newValue // } // )) } .padding() } } @MainActor final class ContentViewState: ObservableObject { @Published var counter: Counter = .init() // ... } //@MainActor @Observable //final class ContentViewState { // var counter: Counter = .init() // // nonisolated init() {} //}
(edited)import SwiftUI struct CounterView: View { @Binding var counter: Counter // @Bindable var counter: Counter var body: some View { let _ = print("B") VStack { Text(counter.count.description) Button("Count up") { counter.countUp() } Button("Reset") { counter.reset() } } .padding() } }
struct Counter: Sendable { private(set) var count: Int = 0 mutating func countUp() { count += 1 } mutating func reset() { count = 0 } } //final class Counter { // private(set) var count: Int = 0 // // func countUp() { // count += 1 // } // // func reset() { // count = 0 // } //} //import Observation // //@MainActor @Observable //final class Counter: Sendable { // private(set) var count: Int = 0 // // func countUp() { // count += 1 // } // // func reset() { // count = 0 // } //}
@Observable
が struct にはつけられなくなってしまいましたね… [5.9] Observable protocol non-marker, @Observable class only by natecook1000 · Pull Request #67196 · apple/swift https://github.com/apple/swift/pull/67196@ObservationIgnored
, 実際にマクロを展開したら付けられてますね。// // ContentView.swift // ObservationPlayground // // Created by Yuta Koshizawa on 2023/07/22. // import SwiftUI //var counter: Counter = .init() struct ContentView: View { // @StateObject private var state: ContentViewState = .init() @State private var state: ContentViewState = .init() var body: some View { let _ = print("A") ScrollView { VStack { Text("Sum: \(state.countSum)") ForEach(state.counters.indices, id: \.self) { i in // CounterView(counter: $state.counters[i]) CounterView(counter: state.counters[i]) Divider() } // CounterView(counter: state.counter) // CounterView(counter: Binding( // get: { // counter // }, set: { newValue in // counter = newValue // } // )) } .padding() } } } //@MainActor //final class ContentViewState: ObservableObject { // @Published var counters: [Counter] = (1...100).map { _ in Counter() } // // ... //} @MainActor @Observable final class ContentViewState { var counters: [Counter] = (1...100).map { _ in Counter() } var countSum: Int { counters.lazy.map(\.count).reduce(0, +) } nonisolated init() {} }
import SwiftUI struct CounterView: View { // @Binding var counter: Counter @Bindable var counter: Counter var body: some View { let _ = print("B \(counter.count)") VStack { Text(counter.count.description) Button("Count up") { counter.countUp() } Button("Reset") { counter.reset() } Stepper("", value: $counter.count) } .padding() } }
//struct Counter: Sendable { // private(set) var count: Int = 0 // // mutating func countUp() { // count += 1 // } // // mutating func reset() { // count = 0 // } //} //final class Counter { // private(set) var count: Int = 0 // // func countUp() { // count += 1 // } // // func reset() { // count = 0 // } //} import Observation @MainActor @Observable final class Counter: Sendable { var count: Int = 0 func countUp() { count += 1 } func reset() { count = 0 } }
objectWillChange
みたいなものだと思っていたので、少し前の Observation に含まれていた AsyncSequence
だった values(for:)
や changes(for:)
の方がしっくりきてしまっていましたwobjectWillChange
みたいなものだと思っていたので、少し前の Observation に含まれていた AsyncSequence
だった values(for:)
や changes(for:)
の方がしっくりきてしまっていましたw for await
的な書き方はできなく(なっ)てwithObservationTracking
を使うようになったって認識であっているでしょうか......? (edited)for await
的な書き方はできなく(なっ)てwithObservationTracking
を使うようになったって認識であっているでしょうか......? (edited)AsyncSequence
だった values(for:)
や changes(for:)
などが無くなっちゃいました… Refocus observation to it's core capability by phausler · Pull Request #65528 · apple/swift https://github.com/apple/swift/pull/65528 (edited)@Envinroment(\.id) var id
みたいな型宣言(?)がないプロパティ宣言気持ち悪いと思ってるUIHostingController
の存在は偉大ですよ(遠い目withObservationTracking
使ったコード貼り忘れてました。 import SwiftUI import UIKit struct FooView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> some UIViewController { FooViewController() } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } } final class FooViewController: UIViewController { private let state: FooViewState = .init() private let label: UILabel = .init() override func viewDidLoad() { super.viewDidLoad() label.translatesAutoresizingMaskIntoConstraints = false view.addSubview(label) let countUpButton: UIButton = .init(type: .system) countUpButton.translatesAutoresizingMaskIntoConstraints = false countUpButton.setTitle("Count up", for: .normal) countUpButton.addAction(.init { [state] _ in state.countUp() }, for: .touchUpInside) view.addSubview(countUpButton) let resetButton: UIButton = .init(type: .system) resetButton.translatesAutoresizingMaskIntoConstraints = false resetButton.setTitle("Reset", for: .normal) resetButton.addAction(.init { [state] _ in state.reset() }, for: .touchUpInside) view.addSubview(resetButton) NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: view.centerXAnchor), countUpButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), resetButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), countUpButton.centerYAnchor.constraint(equalTo: view.centerYAnchor), countUpButton.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 8), resetButton.topAnchor.constraint(equalTo: countUpButton.bottomAnchor, constant: 8), ]) updateViews() } private func updateViews() { withObservationTracking { label.text = state.count.description } onChange: { [weak self] in guard let self else { return } Task { @MainActor in self.updateViews() } } } } @MainActor @Observable final class FooViewState { var count: Int = 0 func countUp() { count += 1 } func reset() { count = 0 } }
struct
の Observation が難しい件についての Forums でのコメント、 shiz さんが教えてくれました。 https://discord.com/channels/291054398077927425/306995750418513920/1133125505936273489SwiftUIを使ってiOSを作るときにどうするのがいいのか色々考えてきたんですが、素直にシンプルに作るにはこうすればいいんじゃないかと最近思うことがあるので、明後日21日(土)の21時から #swiftzoomin https://swift-tweets.connpass.com/event/290613/ で実演しながら話そうと思います。
https://twitter.com/koher/status/1714816534768664838 (edited).task { }
だと throws
じゃないのは画面非表示時に裏でタスクをキャンセルするために戻り値が使われているからかなと思います. (edited)@Environment
でDIを可能にしつつ、ViewStateを持つというのは一応できそうです。Observationを使う方なら。 struct UserView: View { let id: User.ID @Environment(UserStore.self) var userStore var body: some View { UserStateView(state: UserViewState(userStore: userStore, id: id)) } } struct UserStateView: View { let state: UserViewState var body: some View { VStack { Text(state.user?.name ?? "User Name") .redacted(reason: state.user == nil ? .placeholder : []) .font(.title) ...
withObservationTracking
とかを使わないとダメなんじゃないかと思います。割と辛そう。@State
になってないから、これじゃViewが更新されるたびにViewStateも作り直されちゃう気が…...
は確かに使ったことなかった〜let a: Int? = Int("2") let b: Int? = Int("x") let sum: Int? = a.flatMap { a in b.map { b in a + b } } print(sum as Any)
guard let a = Int("2") else { // エラーハンドリング } print(a + 1)
var stack = [2, 3, 5] print(stack.popLast() as Any) print(stack.popLast() as Any) print(stack.popLast() as Any) print(stack.popLast() as Any) var queue: ArraySlice<Int> = [2, 3, 5] print(queue.popFirst() as Any) print(queue.popFirst() as Any) print(queue.popFirst() as Any) print(queue.popFirst() as Any)
let array: [Int] = [2, 3, 5, 7, 11] let slice: ArraySlice<Int> = array[...] print(slice)
// Recoverable errors do { try foo() } catch { // エラーハンドリング }
func foo(values: [Int]) throws { if values.isEmpty { return } let shuffled = values.shuffled() guard let first = shuffled.first else { // preconditionFailure("...") try foo() } } do { let array = [2, 3, 5] let a: Int = array[0] let dictionary = ["a": 2, "b": 3, "c": 5] let b: Int? = dictionary["a"] } extension Array where Element: Comparable { mutating func mySort() { for i in indices { for j in indices.dropFirst(i) { // guard let a = self[i], let b = self[j] else { // // ??? // } if self[j] < self[i] { // let t = self[i] // self[i] = self[j] // self[j] = t swapAt(i, j) } } } } } var array = [3, 2, 7, 5] array.mySort() print(array) //extension Array { // func subscript(index: Int) -> Element { // precondition(indices.contains(index), "Index out of range.") // // } //} struct FooError: Error {} func foo() throws -> Never { throw FooError() } print(Array(zip([2, 3, 5, 7], ["a", "b", "c"]))) func myZip<T, U>(_ ts: [T], _ us: [U]) -> [(T, U)] { var result: [(T, U)] = [] for i in ts.indices { guard us.indices.contains(i) else { break } result.append((ts[i], us[i])) } assert(result.count == min(ts.count, us.count)) return result }
// Manual propagation struct FooError: Error {} func foo(string: String) -> Result<Int, FooError> { guard let number = Int(string) else { return .failure(FooError()) } return .success(number) } // Automatic propagation func foo(string: String) throws -> Int { let a = try bar() let b = try baz() return a + b }
// Manual propagation struct FooError: Error {} func foo(string: String) -> Result<Int, FooError> { guard let number = Int(string) else { return .failure(FooError()) } return .success(number) } let a: Result<Int, any Error> = .success(2) let b: Result<Int, any Error> = .failure(FooError()) let sum: Result<Int, any Error> = a.flatMap { a in b.map { b in a + b } } print(sum) // Automatic propagation //func foo(string: String) throws -> Int { // let a = try bar() // let b = try baz() // return a + b //} //do { // try inputStream.readLine() // // try inputStream.readLine() // try inputStream.readLine() // try inputStream.readLine() // //} catch { // // エラーハンドリング //} // do { try foo() bar() try baz() } catch { // エラーハンドリング }
// Typed propagation enum FooError: Error { case bar case baz } func foo() throws -> Int { 42 } func main() { do { print(try foo()) } catch { // エラーハンドリング } }
//let a: Result<Int, any Error> = .success(2) //let b: Result<Int, any Error> = .success(3) // //func foo() throws -> Int //func foo() -> Result<Int, any Error> // //let sum: Result<Int, any Error> = a.flatMap { a in // b.flatMap { b in // .success(a + b) // } //} // //print(sum) let a: Task<Int, Never> = .init { return 2 } let b: Task<Int, Never> = .init { return 3 } extension Task where Failure == Never { func map<T>(_ body: @escaping (Success) -> T) -> Task<T, Never> { Task<T, Never> { body(await value) } } func flatMap<T>(_ body: @escaping (Success) -> Task<T, Never>) -> Task<T, Never> { Task<T, Never> { await body(await value).value } } } let square: Task<Int, Never> = b.map { $0 * $0 } print(await square.value) let sum: Task<Int, Never> = a.flatMap { a in b.map { b in a + b } } print(await sum.value)
enum FooError: Error { case bar case baz(Int) } func foo() throws(FooError) -> Int { if Bool.random() { return 42 } else { throw .bar } } func main() { do { print(try foo()) } catch { switch error { case .bar: print("bar") case .baz(let x): print("baz: \(x)") } } } func main() { do { print(try foo()) } catch .bar { print("bar)") } catch .baz(let x) { print("baz: \(x)") } catch { preconditionFailure("Never reaches here.") } } main()
// 複数のエラー型をthrowするとき enum FooError: Error { case bar case baz(Int) } enum BarError: Error {} func foo() throws(FooError) -> Int { if Bool.random() { return 42 } else { throw .bar } } func bar() throws(BarError) { } func main() { do { print(try foo()) try bar() } catch let error as FooError { print("foo: \(error)") } catch let error as BarError { print("bar: \(error)") } catch { preconditionFailure("Never reaches here.") } }
enum FooError: Error { case bar case baz(Int) } enum BarError: Error {} func foo() throws(FooError) -> Int { if Bool.random() { return 42 } else { throw .bar } } func bar() throws(BarError) { } func main() { do throws(FooError) { print(try foo()) _ = try foo() } catch { switch error { case .bar: print("bar") case .baz(let x): print("baz: \(x)") } } }
func foo() throws(Never) -> Int { 42 } func foo() -> Int { 42 }
func foo() throws(any Error) -> Int { 42 } func foo() throws -> Int { 42 }
enum FooError: Error { case bar case baz(Int) } func foo(_ x: Int) throws(FooError) -> Int { if Bool.random() { throw .bar } return x * x } //func foo() throws -> Int { // 42 //} extension Array { // func myMap<T>(_ transform: (Element) throws -> T) rethrows -> [T] { // var result: [T] = [] // for element in self { // result.append(try transform(element)) // } // return result // } func myMap<T, E: Error>(_ transform: (Element) throws(E) -> T) throws(E) -> [T] { var result: [T] = [] for element in self { result.append(try transform(element)) } return result } } let array = [2, 3, 5] print(array.myMap { $0 * $0 }) do { print(try array.myMap { try foo($0) }) } catch { print(error) } func main() { // do throws(FooError) { // print(try foo()) // _ = try foo() // } catch { // switch error { // case .bar: // print("bar") // case .baz(let x): // print("baz: \(x)") // } // } } let result: Result<Int, FooError> = .failure(.bar) do { try result.get() } catch { print(error) }
enum FooError: Error { case bar case baz(Int) } func foo(_ x: Int) async throws(FooError) -> Int { if Bool.random() { throw .bar } return x * x } Task { async let x = foo(3) do { print(try await x) } catch { print(error) } }
let never: () throws(Never) -> Void = {} let cat: () throws(CatError) -> Void = never let animal: () throws(AnimalError) -> Void = cat let creature: () throws(any Error) -> Void = animal
let creature: (() throws(any Error) -> Void) -> Void = { _ in } let animal: (() throws(AnimalError) -> Void) -> Void = creature let cat: (() throws(CatError) -> Void) -> Void = animal let never: (() throws(Never) -> Void) -> Void = cat