16. %d\n Xcode Study — About Design Pattern 由繁入簡(二)
Massive VC?MVC/MVP/MVVM/VIPER
上篇傳值到最後一頁還有幾個重點,一個是轉換時間的formatter,另一個則是比對session跟attendee,在session列出會出席的人名,在attendee列出個人會參加哪幾個會議,像下面這樣
所以在前一頁,session程式碼的地方,一樣加入了fetchOnlineData( ),將之放到viewWillAppear裡呼叫。
那麼它也需要下面這兩個變數
var attendee = Attendee(records: [])
let apiKey = "****"
在segueAction裡改成
let controller = DetailViewController(coder: coder)
guard let row = tableView.indexPathForSelectedRow?.row else { return nil }
let session = sessions[row]
var detailsString = session.description
detailsString += "\n\nAttendees:\n" for i in 0...(attendee.records.count - 1) {
if session.attendeesBadges.contains( UInt8(attendee.records[i].fields.badgeNumber) ?? 0 ) { detailsString +=
"\n - \(attendee.records[i].fields.name)" }
}
controller?.details = detailsString
return controller
藉由遍歷,讓session這個陣列去檢查是否有包含UInt8(attendee.records[i].fields.badgeNumber)
,遍歷範圍是網路抓取資料records這個陣列的長度,UInt8(attendee.records[I].fields.badgeNumber)
即是將讀取到的badgeNumber轉成UInt8,為避免nil,用了一個不可能的0 為default值。
如此便可將會議出席人列在下面,即是方才看到的結果。
那在attendee那邊則相似,最後看到右邊的結果
上圖右表示,點選到的這個人會出席哪些會議。
到此為止,要開始進到下一個階段,不過還有一件事,如果request預設是GET,那麼應該有方法可以更簡潔地去獲取網路資料,看到airTable裡API的說明下面有一項要我們這樣做
EXAMPLE USING QUERY PARAMETER
$ curl https://api.airtable.com/v0/appu4rLL2e1w9u848/Attendees?api_key=YOUR_API_KEY
即是可通過後面的query設置你的API_KEY以取得連線,所以fetchOnlineData可以改成這樣
let urlStr = "https://api.airtable.com/v0/appu4rLL2e1w9u848/Attendees?api_key=\(apiKey)"
guard let url = URL(string: urlStr) else { return }下面就直接可使用urlURLSession.shared.dataTask(with: url) { data, response, error in不需要再用request繞路了
那麼,pushDataOnline可以改成這樣
let urlStr = "https://api.airtable.com/v0/appu4rLL2e1w9u848/Attendees?api_key=\(apiKey)"
guard let url = URL(string: urlStr) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let encoder = JSONEncoder()
request.httpBody = try? encoder.encode(attendee)
簡而言之,即是把下面這兩行
let urlStr = "https://api.airtable.com/v0/appu4rLL2e1w9u848/Attendees" request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
合併為一行
let urlStr = "https://api.airtable.com/v0/appu4rLL2e1w9u848/Attendees?api_key=\(apiKey)"
接著還有值得做的事,重新審視model,將之改為
struct Attendee: Codable {
let records: [Record]
}struct Record: Codable {
let fields: Fields
}
struct Fields: Codable {
let name: String
let nationality: String
let badgeNumber: String
let age: String
let isFirstTimeAttending: String
var description: String {
return """
Name: \(name)
Nationality: \(nationality)
Age: \(age)
Badge Number: \(badgeNumber)
Attending For The First Time: \(isFirstTimeAttending)
"""
}
}
如此便可宣告一個變數如下,如此寫成閉包方便寫出型別
var records = [Record]()原為
var attendee = Attendee(records: [])
不易讀你覺得還好,如果要寫在閉包裡作為型別呢?我們繼續往下看
重頭戲就是改寫fetchOnlineData,以閉包的方式
func fetchOnlineDatas(completion: @escaping ([Record]?) -> Void) {
let apiKey = "keyyyaSglzifqkOXW"
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "api.airtable.com"
urlComponents.path = "/v0/appu4rLL2e1w9u848/Attendees"
urlComponents.query = "api_key=\(apiKey)"
guard let url = urlComponents.url else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
let decoder = JSONDecoder()
if let data = data {
do {
let attendee = try decoder.decode(Attendee.self, from: data)
print(attendee.records)
completion(attendee.records)// self.records = attendee.records
// DispatchQueue.main.async {
//
// self.tableView.reloadData()
// }
} catch {
print(error)
}
} else if let error = error {
print(error)
completion(nil)
}
}.resume()
}
前面那坨url已改成component的寫法,後面else if抓出去錯誤則暫且不管,但閉包裡面的completion皆代入參數,attendee.records是解析成功時丟到completion裡面,讓這個數值在外面還能使用。
這邊發現dispatchQueue的reloadData( )已經註解掉,因為改成下面的寫法
在viewDidLoad裡面fetchOnlineDatas { attendRecords in
if let records = attendRecords {
self.updateUI(with: records)
}
}在viewDidLoad外面func updateUI(with datas: [Record]) {
DispatchQueue.main.async {
self.records = datas
self.tableView.reloadData()
}
}
大抵順序為,呼叫了抓取方法,resume了方法,解析資料,得到閉包資料逃逸給外面用,所以viewDidLoad裡的呼叫,將逃逸的閉包代入updateUI,接著reloadData,update tableview。
只不過updataUI的撰寫,不是那麼必要,所以也可以直接在viewDidLoad裡
fetchOnlineDatas { attendRecords in
if let records = attendRecords {
DispatchQueue.main.async {
self.records = records
self.tableView.reloadData()
}
}
}
接著,將url位址整理整理,將原先
let apiKey = "****"
let urlPath = "/v0/appu4rLL2e1w9u848/Attendees"var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "api.airtable.com"
urlComponents.path = "/v0/appu4rLL2e1w9u848/Attendees"
urlComponents.query = "api_key=\(apiKey)"guard let url = urlComponents.url else { return }
改成這樣
let apiKey = "****"
let urlPath = "/v0/appu4rLL2e1w9u848/Attendees"var urlComponents = URLComponents(string: "https://api.airtable.com/\(urlPath)")
urlComponents?.queryItems = [
URLQueryItem(name: "api_key", value: "\(apiKey)")
]guard let url = urlComponents?.url else { return }
或透過URL的extension,寫成更好看( 炫技? )的
let apiKey = "****"
let urlPath = "/v0/appu4rLL2e1w9u848/Attendees"
let queries = [
"api_key": apiKey
]let baseURL = URL(string: "https://api.airtable.com")?.appendingPathComponent("\(urlPath)").withQueries(queries)guard let url = baseURL else { return }
至此,由於上傳資料僅做測試用,暫時不採用閉包寫法,但本地資料呢?如下:
func fetchLocalData(completion: @escaping ([Session]?) -> Void) {
guard let url = Bundle.main.url(forResource: "WWDCSessions", withExtension: "json") else {
print("Can't find this url.")
return
}
do {
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
let data = try Data(contentsOf: url)
let sessions = try decoder.decode([Session].self, from: data)
completion(sessions)
print("sessions一共 \(sessions.count)項")
} catch {
print("Can't prase jsonData.")
}
}
如此便完成了。
前兩個controller來統計並回顧一下
- sessions:上方,自設計cell(3個元件)、tableview的outlet、viewDidLoad呼叫了兩個方法( fetchOnlineDatas/fetchLocalData )、segueAction、兩個方法、tableview datasource,再加上一點雜的共166行代碼。
- attendees:原設計cell (2個元件,免拉outlet)、viewDidLoad呼叫了三個方法( fetchOnlineDatas/fetchLocalData/pushDataOnline )、一個updateUI、segueAction、三個方法、tableview datasource,再加上一點雜的共228行代碼。
天哪——!這不過是簡單的表格顯示就已經讓vc看起來有點雜了。
並且比對以上兩個vc,可以看到許多重複做的地方,是的,抓取與上傳資料的地方,把它移到新建的swift file,Network裡吧。
Network裡這樣寫,約89行。
這樣原先兩個vc都剩下約100行了,並且vc裡不會塞一堆代碼,看起來清爽多了。
以下reference