Computer >> คอมพิวเตอร์ >  >> สมาร์ทโฟน >> iPhone

วิธีโทร API ใน Swift

หากคุณต้องการเป็นนักพัฒนา iOS มีทักษะพื้นฐานที่ควรทราบ อันดับแรก การทำความคุ้นเคยกับการสร้างมุมมองตารางเป็นสิ่งสำคัญ ประการที่สอง คุณควรทราบวิธีการเติมข้อมูลในมุมมองตารางเหล่านั้น ประการที่สาม เป็นการดีหากคุณดึงข้อมูลจาก API และใช้ข้อมูลนี้ในมุมมองตารางของคุณ

จุดที่สามคือสิ่งที่เราจะกล่าวถึงในบทความนี้ ตั้งแต่เปิดตัว Codable ใน Swift 4 การเรียก API นั้นง่ายกว่ามาก ก่อนหน้านี้คนส่วนใหญ่ใช้พ็อดเช่น Alamofire และ SwiftyJson (คุณสามารถอ่านเกี่ยวกับวิธีการทำได้ที่นี่) ตอนนี้วิธี Swift นั้นดีกว่ามาก ดังนั้นจึงไม่มีเหตุผลที่จะต้องดาวน์โหลดพ็อด

มาดูองค์ประกอบพื้นฐานที่มักใช้ในการเรียก API กัน เราจะพูดถึงแนวคิดเหล่านี้ก่อน เนื่องจากเป็นส่วนสำคัญในการทำความเข้าใจวิธีการเรียก API

  • ตัวจัดการความสมบูรณ์
  • URLSession
  • DispatchQueue
  • รักษารอบ

ในที่สุดเราจะรวบรวมมันทั้งหมดเข้าด้วยกัน ฉันจะใช้โอเพ่นซอร์ส Star Wars API เพื่อสร้างโครงการนี้ คุณสามารถดูรหัสโครงการทั้งหมดของฉันได้ที่ GitHub

การแจ้งเตือนข้อจำกัดความรับผิดชอบ:ฉันเพิ่งเริ่มเขียนโค้ดและเรียนรู้ด้วยตนเองเป็นส่วนใหญ่ ขออภัยหากบิดเบือนแนวคิดบางอย่าง

ตัวจัดการความสมบูรณ์

วิธีโทร API ใน Swift
ฟีโอบีผู้น่าสงสาร

จำตอนของ 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()
วิธีใช้ URLSession เพื่อดึงข้อมูลบางส่วน

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
}

เอ้ย เราทำได้! ขอบคุณที่อยู่กับฉัน