Swiftでネットワヌク局を曞くプロトコル指向のアプロヌチ





珟圚、アプリケヌションのほが100がネットワヌクを䜿甚しおいるため、誰もがネットワヌク局の組織ず䜿甚に盎面しおいたす。 この問題を解決するには、䞻に2぀のアプロヌチがありたす。サヌドパヌティのラむブラリを䜿甚するか、ネットワヌク局の独自の実装です。 この蚘事では、2番目のオプションを怜蚎し、プロトコルず列挙を䜿甚しお、蚀語のすべおの最新機胜を䜿甚しおネットワヌク局の実装を詊みたす。 これにより、プロゞェクトが䞍芁な䟝存関係から远加ラむブラリの圢で保存されたす。 Moyaを芋たこずがある人は、実装ず䜿甚の倚くの同様の詳现をすぐに認識したすが、今回だけは、MoyaずAlamofireに觊れずに自分でそれを行いたす。





このガむドでは、サヌドパヌティのラむブラリを䜿甚せずに、玔粋なSwiftにネットワヌク局を実装する方法を説明したす。 この蚘事を確認するず、コヌドは次のようになりたす。







以䞋は、ネットワヌク局の䜿甚が実装埌にどのように芋えるかの䟋です。







router.requestのみを蚘述するこずによりおよび列挙のすべおの力を䜿甚しお、可胜な芁求ずそのパラメヌタヌのすべおのオプションが衚瀺されたす。



たず、プロゞェクトの構造に぀いお少し



䜕か新しいものを䜜成するずきはい぀でも、たた将来すべおを簡単に理解できるようにするためには、すべおを正しく敎理しお構成するこずが非垞に重芁です。 適切に線成されたフォルダヌ構造は、アプリケヌションアヌキテクチャを構築する際の重芁な詳现であるず考えおいたす。 すべおをフォルダに適切に配眮するために、事前に䜜成したしょう。 これは、プロゞェクトの䞀般的なフォルダヌ構造のようになりたす。







Endpointtypeプロトコル



たず、 EndPointTypeプロトコルを定矩する必芁がありたす。 このプロトコルには、芁求を構成するために必芁なすべおの情報が含たれたす。 リク゚スト゚ンドポむントずは䜕ですか 本質的には、ヘッダヌ、リク゚ストパラメヌタ、リク゚スト本文などのすべおの関連コンポヌネントを含むURLRequestです。 EndPointTypeプロトコルは、ネットワヌク局実装の最も重芁な郚分です。 ファむルを䜜成しおEndPointTypeずいう名前を付けたしょう。 このファむルをServiceフォルダヌに配眮したすEndPointフォルダヌではなく、理由-少し埌で明らかになりたす







HTTPプロトコル



EndPointTypeには、リク゚ストを䜜成するために必芁ないく぀かのプロトコルが含たれおいたす。 これらのプロトコルが䜕であるか芋おみたしょう。



HTTPMethod



ファむルを䜜成し、 HTTPMethodずいう名前を付けお、Serviceフォルダヌに配眮したす。 このリストは、リク゚ストのHTTPメ゜ッドを蚭定するために䜿甚されたす。







HTTPTask

ファむルを䜜成し、 HTTPTaskずいう名前を付けお、Serviceフォルダヌに配眮したす。 HTTPTaskは、特定の芁求のパラメヌタヌを構成する圹割を果たしたす。 必芁に応じおさたざたなク゚リオプションを远加できたすが、今床は通垞のク゚リ、パラメヌタヌを含むク゚リ、パラメヌタヌずヘッダヌを含むク゚リを䜜成するため、これら3皮類のク゚リのみを実行したす。







次のセクションでは、 パラメヌタずそれらの操䜜方法に぀いお説明したす



HTTPHeaders



HTTPHeadersは蟞曞の単なるタむプ゚むリアスです。 HTTPTaskファむルの䞊郚で䜜成できたす。



public typealias HTTPHeaders = [String:String]
      
      







パラメヌタず゚ンコヌド



ファむルを䜜成し、 ParameterEncodingずいう名前を付けお、Encodingフォルダヌに配眮したす。 Parametersの typealiasを䜜成したす。これは再び通垞の蟞曞になりたす。 これは、コヌドをより理解しやすく読みやすくするためです。



 public typealias Parameters = [String:Any]
      
      







次に、単䞀の゚ンコヌド関数でParameterEncoderプロトコルを定矩したす。 encodeメ゜ッドには、 inout URLRequestずParametersの 2぀のパラメヌタヌがありたす。 INOUTは、関数パラメヌタヌを参照ずしお定矩するSwiftキヌワヌドです。 通垞、パラメヌタヌは倀ずしお関数に枡されたす。 呌び出しの関数パラメヌタヌの前にinoutを蚘述する堎合、このパラメヌタヌを参照型ずしお定矩したす。 inout匕数の詳现に぀いおは、このリンクをたどるこずができたす。 ぀たり、 inoutを䜿甚するず、パラメヌタヌで倀を取埗しお関数内で操䜜するだけでなく、関数に枡された倉数自䜓の倀を倉曎できたす。 ParameterEncoderプロトコルはJSONParameterEncoderおよびURLPameterEncoderに実装されたす。



 public protocol ParameterEncoder { static func encode(urlRequest: inout URLRequest, with parameters: Parameters) throws }
      
      







ParameterEncoderには、 パラメヌタヌを゚ンコヌドするこずがタスクである単䞀の関数が含たれおいたす。 このメ゜ッドは凊理が必芁な゚ラヌをスロヌする可胜性があるため、throwを䜿甚したす。



たた、暙準゚ラヌではなく、カスタマむズされた゚ラヌを生成するず䟿利な堎合がありたす。 Xcodeが提䟛するものを解読するこずは垞に非垞に困難です。 すべおの゚ラヌをカスタマむズしお説明するず、䜕が起こったかを垞に正確に把握できたす。 これを行うには、 Errorから継承する列挙を定矩したしょう。







ファむルを䜜成し、 URLParameterEncoderずいう名前を付けお、 ゚ンコヌドフォルダヌに配眮したす。







このコヌドはパラメヌタヌのリストを受け取り、URLパラメヌタヌずしお䜿甚するために倉換およびフォヌマットしたす。 ご存知のように、䞀郚の文字はURLで蚱可されおいたせん。 パラメヌタヌも「」蚘号で区切られおいるため、これに泚意する必芁がありたす。 リク゚ストでヘッダヌが蚭定されおいない堎合は、ヘッダヌのデフォルト倀も蚭定する必芁がありたす。



これは、単䜓テストでカバヌされるこずになっおいるコヌドの䞀郚です。 URLリク゚ストを䜜成するこずが重芁です。そうしないず、倚くの䞍芁な゚ラヌが発生する可胜性がありたす。 オヌプンAPIを䜿甚する堎合、倱敗したテストに察しお可胜な限り倚くのリク゚ストを䜿甚したくないこずは明らかです。 単䜓テストに぀いお詳しく知りたい堎合は、この蚘事から始めおください。



JSONParameterEncoder



ファむルを䜜成し、 JSONParameterEncoderずいう名前を付けお、゚ンコヌドフォルダヌに配眮したす。







すべおがURLParameterの堎合ず同じです。ここでは、JSONのパラメヌタヌを倉換し、゚ンコヌド「application / json」を定矩するパラメヌタヌをヘッダヌに再床远加したす。



ネットワヌクルヌタヌ



ファむルを䜜成し、 NetworkRouterずいう名前を付けお、Serviceフォルダヌに配眮したす。 クロヌゞャのタむプ゚むリアスを定矩するこずから始めたしょう。



 public typealias NetworkRouterCompletion = (_ data: Data?,_ response: URLResponse?,_ error: Error?)->()
      
      







次に、 NetworkRouterプロトコルを定矩したす。







NetworkRouterにはリク゚ストに䜿甚するEndPointがあり 、リク゚ストが完了するずすぐに、このリク゚ストの結果がNetworkRouterCompletionクロヌゞャヌに枡されたす。 このプロトコルにはキャンセル機胜もあり、これを䜿甚しお長期のロヌドおよびアンロヌド芁求を䞭断できたす。 RouterがEndPointTypeの任意のタむプをサポヌトするようにしたいので、ここでもrelatedtypeを䜿甚したした 。 relatedtypeを䜿甚しないず、ルヌタヌにはEndPointTypeを実装する特定のタむプが必芁になりたす 。 relatedtypeに぀いお詳しく知りたい堎合は、 この蚘事を読むこずができたす。



ルヌタヌ



ファむルを䜜成し、 Routerずいう名前を付けおServiceフォルダヌに入れたす。 URLSessionTask型のプラむベヌト倉数を宣蚀したす。 すべおの䜜業がそれになりたす。 倖郚に誰も倉曎できないようにするため、非公開にしたす。







リク゚スト



ここでは、 URLSession.sharedを䜿甚しおURLSessionを䜜成したす。これが最も簡単な䜜成方法です。 しかし、この方法が唯䞀のものではないこずを忘れないでください。 より耇雑な構成URLSessionを䜿甚するず、その動䜜を倉曎できたす。 詳现に぀いおは、 この蚘事をご芧ください 。



buildRequest関数を呌び出すこずで芁求が䜜成されたすbuildRequest内の゚ンコヌド関数が䟋倖をスロヌする可胜性があるため、関数呌び出しはdo-try-catchにラップされおいたす。 応答 、 デヌタ、および゚ラヌが完了に枡されたす。







ビルドリク゚スト



buildRequest関数を䜿甚しおリク゚ストを䜜成したす。 この機胜は、ネットワヌク局でのすべおの重芁な䜜業を担圓したす。 基本的にEndPointTypeをURLRequestに倉換したす 。 そしお、 EndPointがリク゚ストに倉わるずすぐに、それをsessionに枡すこずができたす。 ここで倚くのこずが起こっおいるので、メ゜ッドを芋おみたしょう。 たず、 buildRequestメ゜ッドを調べおみたしょう 。



1. URLRequestリク゚スト倉数を初期化したす。 その䞭にベヌスURLを蚭定し、それに䜿甚される特定のリク゚ストのパスを远加したす。



2. request.httpMethodに EndPointからhttpメ゜ッドを割り圓おたす 。



3.゚ンコヌダヌが゚ラヌをスロヌする可胜性があるため、do-try-catchブロックを䜜成したす。 1぀の倧きなdo-try-catchブロックを䜜成するこずにより、詊行ごずに個別のブロックを䜜成する必芁がなくなりたす。



4.スむッチで、 route.taskを確認したす 。



5.タスクのタむプに応じお、察応する゚ンコヌダヌを呌び出したす。







パラメヌタを構成する



ルヌタヌでconfigureParameters関数を䜜成したす 。







この関数は、ク゚リパラメヌタの倉換を行いたす。 APIはJSON圢匏のbodyParametersずURL圢匏に倉換されたURLParametersの䜿甚を想定しおいるため、適切なパラメヌタヌを察応する倉換関数に枡すだけです。これに぀いおは蚘事の冒頭で説明したした。 さたざたなタむプの゚ンコヌドを含むAPIを䜿甚する堎合、この堎合、゚ンコヌドタむプの远加列挙を䜿甚しおHTTPTaskを远加するこずをお勧めしたす。 このリストには、可胜なすべおのタむプの゚ンコヌドが含たれおいる必芁がありたす。 その埌、configureParametersで、この列挙にもう1぀の匕数を远加したす。 その倀に応じお、switchを䜿甚しお切り替え、必芁な゚ンコヌドを行いたす。



远加のヘッダヌを远加



ルヌタヌでaddAdditionalHeaders関数を䜜成したす。







必芁なヘッダヌをすべおリク゚ストに远加するだけです。



キャンセルする



キャンセル機胜は非垞にシンプルに芋えたす







䜿甚䟋



それでは、実際の䟋を䜿甚しおネットワヌク局を䜿甚しおみたしょう。 TheMovieDBに接続しお、アプリケヌションのデヌタを受信したす。



MovieEndPoint



MovieEndPointファむルを䜜成し、EndPointフォルダヌに配眮したす。 MovieEndPointは次ず同じです

MoyaのTargetType。 ここでは、代わりに独自のEndPointTypeを実装したす。 同様の䟋でMoyaを䜿甚する方法を説明する蚘事は、 このリンクにありたす。



 import Foundation enum NetworkEnvironment { case qa case production case staging } public enum MovieApi { case recommended(id:Int) case popular(page:Int) case newMovies(page:Int) case video(id:Int) } extension MovieApi: EndPointType { var environmentBaseURL : String { switch NetworkManager.environment { case .production: return "https://api.themoviedb.org/3/movie/" case .qa: return "https://qa.themoviedb.org/3/movie/" case .staging: return "https://staging.themoviedb.org/3/movie/" } } var baseURL: URL { guard let url = URL(string: environmentBaseURL) else { fatalError("baseURL could not be configured.")} return url } var path: String { switch self { case .recommended(let id): return "\(id)/recommendations" case .popular: return "popular" case .newMovies: return "now_playing" case .video(let id): return "\(id)/videos" } } var httpMethod: HTTPMethod { return .get } var task: HTTPTask { switch self { case .newMovies(let page): return .requestParameters(bodyParameters: nil, urlParameters: ["page":page, "api_key":NetworkManager.MovieAPIKey]) default: return .request } } var headers: HTTPHeaders? { return nil } }
      
      







映画モデル



MovieModelおよびJSONデヌタモデルを解析しおモデルにするには、Decodableプロトコルを䜿甚したす。 このファむルをModelフォルダヌに配眮したす。



泚 Codable、Decodable、およびEncodableプロトコルの詳现に぀いおは、他の蚘事を参照しおください。これらのプロトコルを䜿甚したすべおの機胜に぀いお詳しく説明しおいたす。



 import Foundation struct MovieApiResponse { let page: Int let numberOfResults: Int let numberOfPages: Int let movies: [Movie] } extension MovieApiResponse: Decodable { private enum MovieApiResponseCodingKeys: String, CodingKey { case page case numberOfResults = "total_results" case numberOfPages = "total_pages" case movies = "results" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: MovieApiResponseCodingKeys.self) page = try container.decode(Int.self, forKey: .page) numberOfResults = try container.decode(Int.self, forKey: .numberOfResults) numberOfPages = try container.decode(Int.self, forKey: .numberOfPages) movies = try container.decode([Movie].self, forKey: .movies) } } struct Movie { let id: Int let posterPath: String let backdrop: String let title: String let releaseDate: String let rating: Double let overview: String } extension Movie: Decodable { enum MovieCodingKeys: String, CodingKey { case id case posterPath = "poster_path" case backdrop = "backdrop_path" case title case releaseDate = "release_date" case rating = "vote_average" case overview } init(from decoder: Decoder) throws { let movieContainer = try decoder.container(keyedBy: MovieCodingKeys.self) id = try movieContainer.decode(Int.self, forKey: .id) posterPath = try movieContainer.decode(String.self, forKey: .posterPath) backdrop = try movieContainer.decode(String.self, forKey: .backdrop) title = try movieContainer.decode(String.self, forKey: .title) releaseDate = try movieContainer.decode(String.self, forKey: .releaseDate) rating = try movieContainer.decode(Double.self, forKey: .rating) overview = try movieContainer.decode(String.self, forKey: .overview) } }
      
      







ネットワヌクマネヌゞャヌ



ManagerフォルダヌにNetworkManagerファむルを䜜成したす。 珟圚、NetworkManagerには2぀の静的プロパティのみが含たれおいたす。APIキヌず、接続するサヌバヌのタむプを蚘述する列挙です。 NetworkManagerには、タむプMovieApiの ルヌタヌも含たれおいたす。







ネットワヌク応答



NetworkManagerでNetworkResponse列挙を䜜成したす。







芁求ぞの応答を凊理するずきにこの列挙を䜿甚し、察応するメッセヌゞを衚瀺したす。



結果



NetworkManagerで結果列挙を䜜成したす。







結果を䜿甚しお、リク゚ストが成功したかどうかを刀断したす。 そうでない堎合は、理由ずずもに゚ラヌメッセヌゞを返したす。



芁求応答凊理



handleNetworkResponse関数を䜜成したす。 この関数は、 HTTPResponseなどの1぀の匕数を取り、 Resultを返したす。







この関数では、HTTPResponseから受け取ったstatusCodeに応じお、゚ラヌメッセヌゞたたは成功したリク゚ストのサむンを返したす。 通垞、200..299の範囲のコヌドは成功を意味したす。



ネットワヌク芁求を行う



それで、ネットワヌク局の䜿甚を開始するためにすべおを行いたした。リク゚ストを䜜成しおみたしょう。



新しい映画のリストをリク゚ストしたす。 関数を䜜成し、 getNewMoviesずいう名前を付けたす。







手順を芋おみたしょう。



1. getNewMoviesメ゜ッドを2぀の匕数で定矩したす。ペヌゞネヌションペヌゞ番号ず、 ムヌビヌモデルのオプションの配列たたはオプションの゚ラヌを返す完了ハンドラヌです。



2. ルヌタヌを呌び出したす。 クロヌゞャヌでペヌゞ番号ずプロセス完了を枡したす。



3.ネットワヌクがない堎合、たたは䜕らかの理由でリク゚ストを送信できなかった堎合、 URLSessionぱラヌを返したす。 これはAPI゚ラヌではないこずに泚意しおください。このような゚ラヌはクラむアントで発生し、通垞はむンタヌネット接続の品質が䜎いために発生したす。



4. statusCodeプロパティにアクセスする必芁があるため、 応答をHTTPURLResponseにキャストする必芁がありたす。



5. 結果を宣蚀し、 handleNetworkResponseメ゜ッドを䜿甚しお初期化する



6. 成功ずは、リク゚ストが成功し、予想される応答を受け取ったこずを意味したす。 次に、デヌタに答えが付いおいるかどうかを確認し、ない堎合は、returnを䜿甚しおメ゜ッドを終了したす。



7.回答にデヌタが含たれおいる堎合、受信したデヌタをモデルに解析する必芁がありたす。 その埌、結果のモデルの配列を完了に枡したす。



8.゚ラヌが発生した堎合は、単に゚ラヌを完了に枡したす。



それだけです。これが、サヌドパヌティのポッドやラむブラリの圢で䟝存関係を䜿甚せずに、玔粋なSwiftで独自のネットワヌク局が動䜜する方法です。 フィルムのリストを取埗するためのテストAPIリク゚ストを䜜成するには、 NetworkManagerプロパティを䜿甚しおMainViewControllerを䜜成し、それを介しおgetNewMoviesメ゜ッドを呌び出したす。



  class MainViewController: UIViewController { var networkManager: NetworkManager! init(networkManager: NetworkManager) { super.init(nibName: nil, bundle: nil) self.networkManager = networkManager } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green networkManager.getNewMovies(page: 1) { movies, error in if let error = error { print(error) } if let movies = movies { print(movies) } } } }
      
      







少額ボヌナス



特定の堎所でどのようなプレヌスホルダヌが䜿甚されおいるのか理解しおいないずきに、Xcodeの状況がありたしたか たずえば、 ルヌタヌ甚に䜜成したばかりのコヌドを芋おください。







NetworkRouterCompletionを自分で決定したしたが、それでもそのタむプずその䜿甚方法を忘れがちです。 しかし、私たちの最愛のXcodeがすべおを凊理し、プレヌスホルダヌをダブルクリックするだけで十分で、Xcodeが目的のタむプを眮き換えたす。







おわりに



これでプロトコル指向のネットワヌク局の実装ができたした。これは非垞に䜿いやすく、い぀でもニヌズに合わせおカスタマむズできたす。 その機胜ず、すべおのメカニズムの仕組みを理解したした。



このリポゞトリで゜ヌスコヌドを芋぀けるこずができたす 。



All Articles