Skip to content

Commit b266725

Browse files
author
Elvis Shi
authored
Merge pull request #18 from yumemi-inc/feature/concurrency
Swift ConcurrencyのAsync課題を追加
2 parents ff98006 + 66cd105 commit b266725

File tree

7 files changed

+113
-13
lines changed

7 files changed

+113
-13
lines changed

Documentation/Concurrency.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# コンカレンシー
2+
3+
iOS 15以降のSwiftランタイムはコンカレンシー(並行演算)をサポートし、またXcode 13.2以降ではこの機能をiOS 13までバックデプロイしました。
4+
コンカレンシーを使うと、これまでのようにスレッドをブロックすることなく、あたかも同期的に処理が書けますが、内部的にはきちんと非同期に動きます。
5+
例えば`URLSession`[`data(from:delegate:)`](https://developer.apple.com/documentation/foundation/urlsession/3767353-data)などがこの機能を利用しています。
6+
7+
非同期処理にも関わらず、スレッドをブロックせずに同期的のようにコードが書けるので、コールバックを用いる方法よりも遥かに書きやすくなるでしょう。
8+
9+
## 課題
10+
- Delegateで受け取っていたAPIの結果を、コンカレンシー形式で受け取るように変更する
11+
- ViewControllerを閉じた時に`deinit`が呼ばれることを確認する

Documentation/YumemiWeather.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ Json文字列
9999

100100
---
101101

102-
## Async ver
102+
## Callback ver
103103
天気予報を取得するAPIです。
104-
非同期に処理します
104+
非同期に処理し、結果をコンプリーションハンドラーに渡します
105105
```swift
106-
static func asyncFetchWeather(_ jsonString: String, completion: @escaping (Result<String, YumemiWeatherError>) -> Void)
106+
static func callbackFetchWeather(_ jsonString: String, completion: @escaping (Result<String, YumemiWeatherError>) -> Void)
107107
```
108108

109109
### Parameters
@@ -124,11 +124,40 @@ Void
124124

125125
---
126126

127+
## Async ver
128+
天気予報を取得するAPIです。
129+
非同期に処理します。
130+
```swift
131+
static func asyncFetchWeather(_ jsonString: String) async throws -> String
132+
```
133+
134+
### Parameters
135+
#### jsonString
136+
Json文字列
137+
|Key||フォーマット||
138+
|:--|:--|:--|:--|
139+
|area|String|自由|tokyo|
140+
|data|String|yyyy-MM-dd'T'HH:mm:ssZZZZZ|2020-04-01T12:00:00+09:00|
141+
142+
### Throws
143+
YumemiWeatherError型
144+
145+
### Returns
146+
Json文字列
147+
|Key||フォーマット||
148+
|:--|:--|:--|:--|
149+
|weather|String|sunny or cloudy or rainy|sunny|
150+
|maxTemp|Int|--|20|
151+
|minTemp|Int|--|-20|
152+
|date|String|yyyy-MM-dd'T'HH:mm:ssZZZZZ|2020-04-01T12:00:00+09:00|
153+
154+
---
155+
127156
## Error type
128157
YumemiWeatherError
129158
```swift
130159
public enum YumemiWeatherError: Swift.Error {
131160
case invalidParameterError
132161
case unknownError
133162
}
134-
```
163+
```

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ Session1がレビュー待ちの場合...
5151
- [Session9](Documentation/ThreadBlock.md)
5252
- [Session10](Documentation/Delegate.md)
5353
- [Session11](Documentation/Closure.md)
54-
- [Session12](Documentation/BugFix.md)
54+
- [Session12](Documentation/Concurrency.md)
55+
- [Session13](Documentation/BugFix.md)
5556

5657
**(注1)**
5758
このようなケースで `rebase` コマンドを使うことが必ずしも正しいとは限りません。

Sources/YumemiWeather/YumemiWeather.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ final public class YumemiWeather {
127127
return try self.fetchWeather(jsonString)
128128
}
129129

130-
/// 擬似 天気予報API Async ver
130+
/// 擬似 天気予報API Callback ver
131131
/// - Parameters:
132132
/// - jsonString: 地域と日付を含むJson文字列
133133
/// example:
@@ -136,7 +136,7 @@ final public class YumemiWeather {
136136
/// "date": "2020-04-01T12:00:00+09:00"
137137
/// }
138138
/// - completion: 完了コールバック
139-
public static func asyncFetchWeather(_ jsonString: String, completion: @escaping (Result<String, YumemiWeatherError>) -> Void) {
139+
public static func callbackFetchWeather(_ jsonString: String, completion: @escaping (Result<String, YumemiWeatherError>) -> Void) {
140140
DispatchQueue.global().asyncAfter(deadline: .now() + apiDuration) {
141141
do {
142142
let response = try fetchWeather(jsonString)
@@ -150,4 +150,22 @@ final public class YumemiWeather {
150150
}
151151
}
152152
}
153+
154+
/// 擬似 天気予報API Async ver
155+
/// - Parameter jsonString: 地域と日付を含むJson文字列
156+
/// example:
157+
/// {
158+
/// "area": "tokyo",
159+
/// "date": "2020-04-01T12:00:00+09:00"
160+
/// }
161+
/// - Throws: YumemiWeatherError パラメータが正常でもランダムにエラーが発生する
162+
/// - Returns: Json文字列
163+
@available(iOS 13, macOS 10.15, *)
164+
public static func asyncFetchWeather(_ jsonString: String) async throws -> String {
165+
return try await withCheckedThrowingContinuation { continuation in
166+
callbackFetchWeather(jsonString) { result in
167+
continuation.resume(with: result)
168+
}
169+
}
170+
}
153171
}

Tests/LinuxMain.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@ import XCTest
33
import YumemiWeatherTests
44

55
var tests = [XCTestCaseEntry]()
6-
tests += YumemiWeatherTests.allTests()
6+
tests += YumemiWeatherTests.allNonConcurrentTests()
7+
if #available(iOS 13, macOS 10.15) {
8+
tests += YumemiWeatherTests.allConcurrentTests
9+
}
710
XCTMain(tests)
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import XCTest
22

33
#if !canImport(ObjectiveC)
4-
public func allTests() -> [XCTestCaseEntry] {
4+
public func allNonConcurrentTests() -> [XCTestCaseEntry] {
55
return [
6-
testCase(YumemiWeatherTests.allTests),
6+
testCase(YumemiWeatherTests.allNonConcurrentTests),
7+
]
8+
}
9+
public func allConcurrentTests() -> [XCTestCaseEntry] {
10+
return [
11+
testCase(YumemiWeatherTests.allConcurrentTests),
712
]
813
}
914
#endif

Tests/YumemiWeatherTests/YumemiWeatherTests.swift

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,15 @@ final class YumemiWeatherTests: XCTestCase {
7272
XCTAssertGreaterThanOrEqual(Date().timeIntervalSince(beginDate), YumemiWeather.apiDuration)
7373
}
7474

75-
func test_fetchWeather_jsonString_async() {
75+
func test_fetchWeather_jsonString_callback() {
7676
let parameter = """
7777
{
7878
"area": "tokyo",
7979
"date": "2020-04-01T12:00:00+09:00"
8080
}
8181
"""
8282
let exp = expectation(description: #function)
83-
YumemiWeather.asyncFetchWeather(parameter) { result in
83+
YumemiWeather.callbackFetchWeather(parameter) { result in
8484
exp.fulfill()
8585
switch result {
8686
case .success(let jsonString):
@@ -97,11 +97,44 @@ final class YumemiWeatherTests: XCTestCase {
9797
self.wait(for: [exp], timeout: YumemiWeather.apiDuration + 0.1)
9898
}
9999

100-
static var allTests = [
100+
@available(iOS 13, macOS 10.15, *)
101+
func test_fetchWeather_jsonString_async() async {
102+
let beginDate = Date()
103+
let parameter = """
104+
{
105+
"area": "tokyo",
106+
"date": "2020-04-01T12:00:00+09:00"
107+
}
108+
"""
109+
do {
110+
let responseJson = try await YumemiWeather.asyncFetchWeather(parameter)
111+
let dateFormatter = DateFormatter()
112+
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
113+
let decoder = JSONDecoder()
114+
decoder.keyDecodingStrategy = .convertFromSnakeCase
115+
decoder.dateDecodingStrategy = .formatted(dateFormatter)
116+
_ = try decoder.decode(Response.self, from: Data(responseJson.utf8))
117+
}
118+
catch let error as YumemiWeatherError {
119+
XCTAssertEqual(error, YumemiWeatherError.unknownError)
120+
}
121+
catch {
122+
XCTFail()
123+
}
124+
125+
XCTAssertGreaterThanOrEqual(Date().timeIntervalSince(beginDate), YumemiWeather.apiDuration)
126+
}
127+
128+
static var allNonConcurrentTests = [
101129
("test_fetchWeather", test_fetchWeather),
102130
("test_fetchWeather_at", test_fetchWeather_at),
103131
("test_fetchWeather_jsonString", test_fetchWeather_jsonString),
104132
("test_fetchWeather_jsonString_sync", test_fetchWeather_jsonString_sync),
133+
("test_fetchWeather_jsonString_callback", test_fetchWeather_jsonString_callback),
134+
]
135+
136+
@available(iOS 13, macOS 10.15, *)
137+
static var allConcurrentTests = [
105138
("test_fetchWeather_jsonString_async", test_fetchWeather_jsonString_async),
106139
]
107140
}

0 commit comments

Comments
 (0)