หากคุณต้องการเป็นนักพัฒนา iOS มีทักษะพื้นฐานที่ควรทราบ อันดับแรก การทำความคุ้นเคยกับการสร้างมุมมองตารางเป็นสิ่งสำคัญ ประการที่สอง คุณควรทราบวิธีการเติมข้อมูลในมุมมองตารางเหล่านั้น ประการที่สาม เป็นการดีหากคุณดึงข้อมูลจาก API และใช้ข้อมูลนี้ในมุมมองตารางของคุณ
จุดที่สามคือสิ่งที่เราจะกล่าวถึงในบทความนี้ ตั้งแต่เปิดตัว Codable
ใน Swift 4 การเรียก API นั้นง่ายกว่ามาก ก่อนหน้านี้คนส่วนใหญ่ใช้พ็อดเช่น Alamofire และ SwiftyJson (คุณสามารถอ่านเกี่ยวกับวิธีการทำได้ที่นี่) ตอนนี้วิธี Swift นั้นดีกว่ามาก ดังนั้นจึงไม่มีเหตุผลที่จะต้องดาวน์โหลดพ็อด
มาดูองค์ประกอบพื้นฐานที่มักใช้ในการเรียก API กัน เราจะพูดถึงแนวคิดเหล่านี้ก่อน เนื่องจากเป็นส่วนสำคัญในการทำความเข้าใจวิธีการเรียก API
- ตัวจัดการความสมบูรณ์
URLSession
DispatchQueue
- รักษารอบ
ในที่สุดเราจะรวบรวมมันทั้งหมดเข้าด้วยกัน ฉันจะใช้โอเพ่นซอร์ส Star Wars API เพื่อสร้างโครงการนี้ คุณสามารถดูรหัสโครงการทั้งหมดของฉันได้ที่ GitHub
การแจ้งเตือนข้อจำกัดความรับผิดชอบ:ฉันเพิ่งเริ่มเขียนโค้ดและเรียนรู้ด้วยตนเองเป็นส่วนใหญ่ ขออภัยหากบิดเบือนแนวคิดบางอย่าง
ตัวจัดการความสมบูรณ์
จำตอนของ Friends ที่ Pheobe ติดโทรศัพท์เป็นเวลาหลายวันเพื่อรอพูดคุยกับฝ่ายบริการลูกค้าหรือไม่? ลองนึกภาพว่าเมื่อเริ่มรับสาย คนน่ารักชื่อพิพพูดว่า:"ขอบคุณที่โทรมานะ ฉันไม่รู้ว่าคุณจะต้องรอนานแค่ไหน แต่ฉันจะโทรกลับหาคุณเมื่อเราพร้อม สำหรับคุณ." มันจะไม่ตลกเท่า แต่ Pip เสนอให้ Pheobe เป็นผู้จัดการที่สมบูรณ์
คุณใช้ตัวจัดการความสมบูรณ์ในฟังก์ชันเมื่อคุณรู้ว่าฟังก์ชันจะใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์ คุณไม่รู้หรอกว่านานแค่ไหน และคุณไม่ต้องการที่จะหยุดชีวิตของคุณชั่วคราวเพื่อรอให้มันจบ ดังนั้นคุณจึงขอให้ Pip แตะไหล่คุณเมื่อเธอพร้อมที่จะให้คำตอบกับคุณ ด้วยวิธีนี้ คุณจะสามารถดำเนินชีวิต ทำธุระ อ่านหนังสือ และดูทีวี เมื่อ Pip แตะไหล่คุณพร้อมคำตอบ คุณก็สามารถเอาคำตอบของเธอไปใช้ได้เลย
นี่คือสิ่งที่เกิดขึ้นกับการเรียก API คุณส่งคำขอ URL ไปยังเซิร์ฟเวอร์เพื่อขอข้อมูลบางอย่าง คุณหวังว่าเซิร์ฟเวอร์จะส่งคืนข้อมูลอย่างรวดเร็ว แต่คุณไม่รู้ว่าจะใช้เวลานานแค่ไหน แทนที่จะให้ผู้ใช้ของคุณรออย่างอดทนเพื่อให้เซิร์ฟเวอร์ให้ข้อมูลแก่คุณ คุณใช้ตัวจัดการความสมบูรณ์แทน ซึ่งหมายความว่าคุณสามารถบอกให้แอปปิดและทำสิ่งอื่นได้ เช่น โหลดส่วนที่เหลือของหน้า
คุณบอกให้ตัวจัดการความสมบูรณ์แตะแอปของคุณบนไหล่เมื่อมีข้อมูลที่คุณต้องการ คุณสามารถระบุได้ว่าข้อมูลนั้นคืออะไร ด้วยวิธีนี้ เมื่อแอปของคุณถูกแตะบนไหล่ แอปจะสามารถนำข้อมูลจากตัวจัดการความสมบูรณ์มาทำอะไรกับมันได้ โดยปกติสิ่งที่คุณจะทำคือโหลดมุมมองตารางซ้ำเพื่อให้ข้อมูลปรากฏต่อผู้ใช้
นี่คือตัวอย่างหน้าตาของตัวจัดการความสมบูรณ์ ตัวอย่างแรกคือการตั้งค่าการเรียก API เอง:
func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
// Setup the variable lotsOfFilms
var lotsOfFilms: [Film]
// Call the API with some code
// Using data from the API, assign a value to lotsOfFilms
// Give the completion handler the variable, lotsOfFilms
completionHandler(lotsOfFilms)
}
ตอนนี้เราต้องการเรียกใช้ฟังก์ชัน fetchFilms
. สิ่งที่ควรทราบ:
- คุณไม่จำเป็นต้องอ้างอิง
completionHandler
เมื่อคุณเรียกใช้ฟังก์ชัน ครั้งเดียวที่คุณอ้างอิงcompletionHandler
อยู่ในการประกาศฟังก์ชัน - ตัวจัดการความสมบูรณ์จะคืนข้อมูลให้เราใช้ จากฟังก์ชันที่เราได้เขียนไว้ข้างต้น เราทราบถึงข้อมูลที่เป็นประเภท
[Film]
. เราจำเป็นต้องตั้งชื่อข้อมูลเพื่อให้เราสามารถอ้างอิงได้ ด้านล่างผมใช้ชื่อfilms
แต่อาจเป็นrandomData
หรือชื่อตัวแปรอื่น ๆ ที่ฉันต้องการ
รหัสจะมีลักษณะดังนี้:
fetchFilms() { (films) in
// Do something with the data the completion handler returns
print(films)
}
URLSession
URLSession
เป็นเหมือนผู้จัดการทีม ผู้จัดการไม่ได้ทำอะไรด้วยตัวเอง งานของเธอคือการแบ่งปันงานกับผู้คนในทีมของเธอ และพวกเขาจะทำงานให้สำเร็จ ทีมของเธอคือ dataTasks
. ทุกครั้งที่คุณต้องการข้อมูล เขียนถึงหัวหน้าแล้วใช้ URLSession.shared.dataTask
.
คุณสามารถให้ dataTask
ข้อมูลประเภทต่างๆ เพื่อช่วยให้คุณบรรลุเป้าหมาย ให้ข้อมูลกับ dataTask
เรียกว่าการเริ่มต้น ฉันเริ่มต้น dataTasks
. ของฉัน ด้วย URL dataTasks
ยังใช้ตัวจัดการความสมบูรณ์เป็นส่วนหนึ่งของการเริ่มต้น นี่คือตัวอย่าง:
let url = URL(string: "https://www.swapi.co/api/films")
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
// your code here
})
task.resume()
dataTasks
ใช้ตัวจัดการความสมบูรณ์ และจะส่งคืนข้อมูลประเภทเดียวกันเสมอ:data
, response
และ error
. คุณตั้งชื่อข้อมูลประเภทต่างๆ เหล่านี้ได้ เช่น (data, res, err)
หรือ (someData, someResponse, someError)
. เพื่อประโยชน์ของการประชุม เป็นการดีที่สุดที่จะยึดติดกับสิ่งที่ชัดเจนมากกว่าที่จะโกงด้วยชื่อตัวแปรใหม่
เริ่มกันที่ error
. ถ้า dataTask
ส่งกลับ error
คุณจะต้องการรู้ล่วงหน้าว่า หมายความว่าคุณสามารถกำหนดรหัสของคุณเพื่อจัดการกับข้อผิดพลาดได้อย่างสวยงาม นอกจากนี้ยังหมายความว่าคุณจะไม่ต้องพยายามอ่านข้อมูลและดำเนินการใดๆ กับข้อมูลดังกล่าว เนื่องจากมีข้อผิดพลาดในการส่งคืนข้อมูล
ด้านล่างนี้ฉันกำลังจัดการกับข้อผิดพลาดเพียงแค่พิมพ์ข้อผิดพลาดไปที่คอนโซลและออกจากฟังก์ชัน มีหลายวิธีที่คุณสามารถจัดการกับข้อผิดพลาดได้หากต้องการ ลองนึกถึงว่าข้อมูลนี้เป็นพื้นฐานสำหรับแอปของคุณอย่างไร ตัวอย่างเช่น หากคุณมีแอปธนาคารและการเรียก API นี้แสดงให้ผู้ใช้เห็นยอดเงินคงเหลือ คุณอาจต้องการจัดการกับข้อผิดพลาดด้วยการนำเสนอรูปแบบต่างๆ แก่ผู้ใช้ที่ระบุว่า "ขออภัย เรากำลังประสบปัญหาในขณะนี้ โปรดลอง อีกครั้งในภายหลัง"
if let error = error {
print("Error accessing swapi.co: /(error)")
return
}
ต่อไปเราจะดูการตอบสนอง คุณสามารถส่งคำตอบให้เป็น httpResponse
. ด้วยวิธีนี้ คุณสามารถดูรหัสสถานะและตัดสินใจตามรหัสได้ ตัวอย่างเช่น หากรหัสสถานะคือ 404 แสดงว่าคุณไม่พบหน้าดังกล่าว
รหัสด้านล่างใช้ guard
เพื่อตรวจสอบว่ามีสองสิ่ง หากมีทั้งสองอย่าง ก็จะอนุญาตให้โค้ดไปต่อในคำสั่งถัดไปต่อจาก guard
ข้อ หากคำสั่งใดล้มเหลว เราจะออกจากฟังก์ชัน นี่เป็นกรณีการใช้งานทั่วไปของ guard
ข้อ คุณคาดว่ารหัสหลังประโยคป้องกันจะเป็นขั้นตอนของวันแห่งความสุข (เช่น ขั้นตอนง่าย ๆ โดยไม่มีข้อผิดพลาด)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
ในที่สุดคุณจัดการข้อมูลเอง ขอให้สังเกตว่าเราไม่ได้ใช้ตัวจัดการการเสร็จสิ้นสำหรับ error
หรือ response
. นั่นเป็นเพราะตัวจัดการความสมบูรณ์กำลังรอข้อมูลจาก API หากไม่เข้าสู่ส่วนข้อมูลของโค้ด ก็ไม่จำเป็นต้องเรียกใช้ตัวจัดการ
สำหรับข้อมูล เราใช้ JSONDecoder
เพื่อแยกวิเคราะห์ข้อมูลในทางที่ดี สิ่งนี้ค่อนข้างดี แต่คุณต้องสร้างแบบจำลอง โมเดลของเราชื่อ FilmSummary
. ถ้า JSONDecoder
ยังใหม่สำหรับคุณ ลองดูวิธีใช้งานและวิธีใช้ Codable
. ทางออนไลน์ . มันง่ายมากใน Swift 4 ขึ้นไป เมื่อเทียบกับ Swift 3 วัน
ในโค้ดด้านล่าง ขั้นแรกเราจะตรวจสอบว่ามีข้อมูลอยู่หรือไม่ เราค่อนข้างมั่นใจว่าควรมีอยู่เพราะไม่มีข้อผิดพลาดและไม่มีการตอบสนอง HTTP แปลก ๆ ประการที่สอง เราตรวจสอบว่าเราสามารถแยกวิเคราะห์ข้อมูลที่เราได้รับในแบบที่เราคาดหวัง หากทำได้ เราจะคืนบทสรุปภาพยนตร์ไปยังตัวจัดการความสมบูรณ์ ในกรณีที่ไม่มีข้อมูลที่จะส่งคืนจาก API เรามีแผนสำรองของอาร์เรย์ว่าง
if let data = data,
let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
completionHandler(filmSummary.results ?? [])
}
ดังนั้นโค้ดแบบเต็มสำหรับการเรียก API จะมีลักษณะดังนี้:
func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
let url = URL(string: domainUrlString + "films/")!
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error {
print("Error with fetching films: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
if let data = data,
let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
completionHandler(filmSummary.results ?? [])
}
})
task.resume()
}
รักษารอบ
หมายเหตุ:ฉันใหม่มากในการทำความเข้าใจวงจรการรักษา! นี่คือส่วนสำคัญของสิ่งที่ฉันค้นคว้าทางออนไลน์
วงจรการเก็บรักษาเป็นสิ่งสำคัญที่ต้องทำความเข้าใจสำหรับการจัดการหน่วยความจำ โดยทั่วไปคุณต้องการให้แอปของคุณล้างหน่วยความจำที่ไม่ต้องการอีกต่อไป ฉันถือว่าสิ่งนี้ทำให้แอปมีประสิทธิภาพมากขึ้น
มีหลายวิธีที่ Swift ช่วยคุณทำสิ่งนี้โดยอัตโนมัติ อย่างไรก็ตาม มีหลายวิธีที่คุณสามารถเก็บรหัสรอบในแอพของคุณโดยไม่ได้ตั้งใจ วงจรการคงไว้หมายความว่าแอปของคุณจะมีหน่วยความจำสำหรับโค้ดบางส่วนเสมอ โดยทั่วไปจะเกิดขึ้นเมื่อคุณมีสองสิ่งที่มีจุดแข็งต่อกันและกัน
เพื่อแก้ปัญหานี้ ผู้คนมักใช้ weak
. เมื่อโค้ดด้านหนึ่งคือ weak
คุณไม่มีวงจรการคงไว้ และแอปของคุณจะสามารถปล่อยหน่วยความจำได้
สำหรับจุดประสงค์ของเรา รูปแบบทั่วไปคือการใช้ [weak self]
เมื่อเรียกใช้ API เพื่อให้แน่ใจว่าเมื่อตัวจัดการการเสร็จสิ้นส่งคืนโค้ดบางส่วน แอปจะปล่อยหน่วยความจำได้
fetchFilms { [weak self] (films) in
// code in here
}
ส่งคิว
Xcode ใช้เธรดต่าง ๆ เพื่อรันโค้ดแบบขนาน ข้อดีของการมีเธรดหลายชุดหมายความว่าคุณไม่ต้องรอคอยสิ่งหนึ่งให้เสร็จก่อนจึงจะไปยังหัวข้อถัดไปได้ หวังว่าคุณจะเริ่มเห็นลิงก์ไปยังตัวจัดการความสมบูรณ์ได้ที่นี่
ดูเหมือนว่าเธรดเหล่านี้จะถูกเรียกว่าคิวการส่ง การเรียก API ได้รับการจัดการในคิวเดียว โดยทั่วไปจะเป็นคิวในเบื้องหลัง เมื่อคุณได้รับข้อมูลจากการเรียก API แล้ว เป็นไปได้มากว่าคุณต้องการแสดงข้อมูลนั้นให้ผู้ใช้เห็น ซึ่งหมายความว่าคุณจะต้องการรีเฟรชมุมมองตารางของคุณ
มุมมองตารางเป็นส่วนหนึ่งของ UI และการปรับแต่ง UI ทั้งหมดควรทำในคิวการจัดส่งหลัก นี่หมายถึงที่ใดที่หนึ่งในไฟล์ตัวควบคุมการดูของคุณ ซึ่งมักจะเป็นส่วนหนึ่งของ viewDidLoad
คุณควรมีโค้ดเล็กน้อยที่บอกให้มุมมองตารางของคุณรีเฟรช
เราต้องการให้มุมมองตารางรีเฟรชเมื่อมีข้อมูลใหม่จาก API เท่านั้น ซึ่งหมายความว่าเราจะใช้ตัวจัดการความสมบูรณ์เพื่อแตะเราที่ไหล่และบอกเราเมื่อการเรียก API นั้นเสร็จสิ้น เราจะรอจนกว่าจะแตะนั้นก่อนจะรีเฟรชตาราง
รหัสจะมีลักษณะดังนี้:
fetchFilms { [weak self] (films) in
self.films = films
// Reload the table view using the main dispatch queue
DispatchQueue.main.async {
tableView.reloadData()
}
}
viewDidLoad กับ viewDidAppear
สุดท้าย คุณต้องตัดสินใจว่าจะโทรหา fetchfilms
. ของคุณที่ไหน การทำงาน. จะอยู่ภายในตัวควบคุมมุมมองที่จะใช้ข้อมูลจาก API มีสองที่ที่ชัดเจนที่คุณสามารถเรียก API นี้ได้ อันหนึ่งอยู่ใน viewDidLoad
และอีกอันอยู่ภายใน viewDidAppear
.
นี่เป็นสองสถานะที่แตกต่างกันสำหรับแอปของคุณ ความเข้าใจของฉันคือ viewDidLoad
ถูกเรียกในครั้งแรกที่คุณโหลดมุมมองนั้นในเบื้องหน้า viewDidAppear
ถูกเรียกทุกครั้งที่คุณกลับมาที่มุมมองนั้น เช่น เมื่อคุณกดปุ่มย้อนกลับเพื่อกลับมาที่มุมมองนั้น
หากคุณคาดว่าข้อมูลของคุณจะเปลี่ยนแปลงในช่วงเวลาที่ผู้ใช้นำทางไปและกลับจากมุมมองนั้น คุณอาจต้องการเรียกใช้ API ของคุณใน viewDidAppear
. อย่างไรก็ตาม ฉันคิดว่าสำหรับเกือบทุกแอป viewDidLoad
ก็เพียงพอแล้ว Apple ขอแนะนำ viewDidAppear
สำหรับการเรียก API ทั้งหมด แต่ดูเหมือนว่าจะเกินความสามารถ ฉันคิดว่ามันจะทำให้แอปของคุณมีประสิทธิภาพน้อยลงเนื่องจากมีการเรียก API ที่จำเป็นมากขึ้น
รวมขั้นตอนทั้งหมดเข้าด้วยกัน
ขั้นแรก:เขียนฟังก์ชันที่เรียกใช้ API ด้านบนนี่คือ fetchFilms
. สิ่งนี้จะมีตัวจัดการความสมบูรณ์ซึ่งจะส่งคืนข้อมูลที่คุณสนใจ ในตัวอย่างของฉัน ตัวจัดการความสมบูรณ์จะส่งคืนอาร์เรย์ของภาพยนตร์
ประการที่สอง:เรียกใช้ฟังก์ชันนี้ในตัวควบคุมมุมมองของคุณ คุณทำเช่นนี้เนื่องจากคุณต้องการอัปเดตข้อมูลพร็อพเพอร์ตี้ตามข้อมูลจาก API ในตัวอย่างของฉัน ฉันกำลังรีเฟรชมุมมองตารางเมื่อ API ส่งคืนข้อมูล
ประการที่สาม:ตัดสินใจว่าคุณต้องการเรียกใช้ฟังก์ชันนี้ในตัวควบคุมมุมมองของคุณ ในตัวอย่างของฉัน ฉันเรียกมันว่า viewDidLoad
.
ประการที่สี่:ตัดสินใจว่าจะทำอย่างไรกับข้อมูลจาก API ในตัวอย่างของฉัน ฉันกำลังรีเฟรชมุมมองตาราง
ข้างใน NetworkManager.swift
(ฟังก์ชันนี้สามารถกำหนดได้ใน view controller หากคุณต้องการ แต่ฉันใช้รูปแบบ MVVM)
func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
let url = URL(string: domainUrlString + "films/")!
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error {
print("Error with fetching films: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
if let data = data,
let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
completionHandler(filmSummary.results ?? [])
}
})
task.resume()
}
ข้างใน FilmsViewController.swift
:
final class FilmsViewController: UIViewController {
private var films: [Film]?
override func viewDidLoad() {
super.viewDidLoad()
NetworkManager().fetchFilms { [weak self] (films) in
self?.films = films
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
// other code for the view controller
}
เอ้ย เราทำได้! ขอบคุณที่อยู่กับฉัน