{"id":2211,"date":"2022-02-14T17:14:41","date_gmt":"2022-02-14T17:14:41","guid":{"rendered":"https:\/\/sezeromer.com\/?p=2211"},"modified":"2023-03-04T14:02:53","modified_gmt":"2023-03-04T11:02:53","slug":"swift-generic-service","status":"publish","type":"post","link":"https:\/\/sezeromer.com\/en\/swift-generic-service\/","title":{"rendered":"Swift Generic Service"},"content":{"rendered":"<p>Hello friends, in this article we will talk about how to create a generic service with Swift. With a generic service structure, we can easily change and manage it when we change something extra in the future. We can make service requests with less code.<\/p>\n<p>In the application where we list music, movies, applications and books from Apple&#8217;s services, we wrote a different function for each request. It may seem logical to use it in applications with low service requests, but there is no guarantee that it will not grow in the future. So let&#8217;s make the service structure of this project generic. The current state is as follows.<\/p>\n<div class=\"wp-block-codemirror-blocks code-block \">\n<pre class=\"CodeMirror\" data-setting=\"{&quot;mode&quot;:&quot;swift&quot;,&quot;mime&quot;:&quot;text\/x-swift&quot;,&quot;theme&quot;:&quot;default&quot;,&quot;lineNumbers&quot;:true,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:true,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}\">\/\/\r\n\/\/  Services.swift\r\n\/\/  library\r\n\/\/\r\n\/\/  Created by \u00d6mer Sezer on 13.02.2022.\r\n\/\/\r\n\r\nimport Alamofire\r\nimport AlamofireMapper\r\n\r\nclass Services {\r\n    let baseUrl = \"https:\/\/itunes.apple.com\/\"\r\n    let limit: Int = 25\r\n    \r\n    func searchMovie(searchedText: String, type: SearchTypes, successCompletion: @escaping ((_ json: MoviesModel?) -&gt; Void), errorCompletion: @escaping ((_ error: BaseErrorModel) -&gt; Void)) {\r\n        print(type.type)\r\n        let url = \"\\(baseUrl)search?term=\\(searchedText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? \"\")&amp;offset=\\(limit)&amp;limit=\\(limit)&amp;media=\\(type.type)\"\r\n        Alamofire.request(url, method: .get, encoding: JSONEncoding.default).responseObject { (response: DataResponse&lt;MoviesModel&gt;) in\r\n            switch response.result {\r\n            case .success(let model):\r\n                successCompletion(model)\r\n            case .failure(let error):\r\n                errorCompletion(BaseErrorModel(errorCode: nil, message: nil, errors: [ErrorData(field: APIErrors.Alamofire.rawValue, message: error.localizedDescription)]))\r\n            }\r\n        }\r\n    }\r\n    \r\n    func searchMovie(searchedText: String, page: Int, type: SearchTypes, successCompletion: @escaping ((_ json: MoviesModel?) -&gt; Void), errorCompletion: @escaping ((_ error: BaseErrorModel) -&gt; Void)) {\r\n        print(type.type)\r\n        let url = \"\\(baseUrl)search?term=\\(searchedText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? \"\")&amp;offset=\\(limit * page)&amp;limit=\\(limit)&amp;media=\\(type.type)\"\r\n        \r\n        Alamofire.request(url, method: .get, encoding: JSONEncoding.default).responseObject { (response: DataResponse&lt;MoviesModel&gt;) in\r\n            switch response.result {\r\n            case .success(let model):\r\n                successCompletion(model)\r\n            case .failure(let error):\r\n                errorCompletion(BaseErrorModel(errorCode: nil, message: nil, errors: [ErrorData(field: APIErrors.Alamofire.rawValue, message: error.localizedDescription)]))\r\n            }\r\n        }\r\n    }\r\n}<\/pre>\n<\/div>\n<p>Now we will divide our service structure into 3 different classes. These; <strong>APIConstants<\/strong> class where constant variables are kept, <strong>APIRouter<\/strong> which has enum structure which requests to be thrown, and <strong>Services<\/strong> class where service request is thrown generically.<\/p>\n<h2>APIConstants<\/h2>\n<p>In this class, there are urls in general and what will go when sending the request.<\/p>\n<div class=\"wp-block-codemirror-blocks code-block \">\n<pre class=\"CodeMirror\" data-setting=\"{&quot;mode&quot;:&quot;swift&quot;,&quot;mime&quot;:&quot;text\/x-swift&quot;,&quot;theme&quot;:&quot;default&quot;,&quot;lineNumbers&quot;:true,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:true,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}\">\/\/\r\n\/\/  APIConstants.swift\r\n\/\/  library\r\n\/\/\r\n\/\/  Created by \u00d6mer Sezer on 13.02.2022.\r\n\/\/\r\n\r\nimport Foundation\r\n\r\nstruct K {\r\n    static let prodUrl = \"https:\/\/itunes.apple.com\/\"\r\n    static let limit = 25\r\n}\r\n\r\nenum HTTPHeaderField: String {\r\n    case authentication = \"Authorization\"\r\n    case contentType = \"Content-Type\"\r\n    case acceptType = \"Accept\"\r\n    case acceptEncoding = \"Accept-Encoding\"\r\n}\r\n\r\nenum ContentType: String {\r\n    case json = \"application\/json\"\r\n}<\/pre>\n<\/div>\n<h2>APIRouter<\/h2>\n<p>Our structure here is completely enum. When we create a new request, we can add it here as a case, and then we need to determine which method to send, which token value to get, which url to go to, which parameters to take.<\/p>\n<p>As an example, I have added a structure that takes parameters and has different methods. You can go over this.<\/p>\n<div class=\"wp-block-codemirror-blocks code-block \">\n<pre class=\"CodeMirror\" data-setting=\"{&quot;mode&quot;:&quot;swift&quot;,&quot;mime&quot;:&quot;text\/x-swift&quot;,&quot;theme&quot;:&quot;default&quot;,&quot;lineNumbers&quot;:true,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:true,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}\">\/\/\r\n\/\/  APIRouter.swift\r\n\/\/  library\r\n\/\/\r\n\/\/  Created by \u00d6mer Sezer on 13.02.2022.\r\n\/\/\r\n\r\nimport Alamofire\r\n\r\nenum APIRouter: URLRequestConvertible {\r\n    case search(searchedText: String, page: Int, type: SearchTypes)\r\n    case getExample(exParameter: String)\r\n    case postExample\r\n    case getWithToken\r\n    \r\n    private var baseURL: String {\r\n        let serverUrl = K.prodUrl\r\n        \r\n        switch self {\r\n        default:\r\n            return serverUrl\r\n        }\r\n    }\r\n    \r\n    private var tokenValue: String {\r\n        switch self {\r\n        case .getWithToken:\r\n            return \"Bearer token\"\r\n        default:\r\n            return \"\"\r\n        }\r\n    }\r\n    \r\n    private var method: HTTPMethod {\r\n        switch self {\r\n        case .search, .getWithToken, .getExample:\r\n            return .get\r\n        case .postExample:\r\n            return .post\r\n        }\r\n    }\r\n    \r\n    private var path: String {\r\n        switch self {\r\n        case .search(let searchedText, let page, let type):\r\n            return \"search?term=\\(searchedText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? \"\")&amp;offset=\\(K.limit * page)&amp;limit=\\(K.limit)&amp;media=\\(type.type)\"\r\n        case .getExample(let exParameter):\r\n            return \"test\/\\(exParameter)\"\r\n        case .getWithToken:\r\n            return \"test\"\r\n        case .postExample:\r\n            return \"post\/test\"\r\n        }\r\n    }\r\n    \r\n    private var parameters: Parameters? {\r\n        switch self {\r\n        case .postExample:\r\n            return [\"test\": \"test\"]\r\n        default:\r\n            return nil\r\n        }\r\n    }\r\n    \r\n    func asURLRequest() throws -&gt; URLRequest {\r\n        let url = try baseURL.appending(path).asURL()\r\n        var urlRequest = URLRequest(url: url)\r\n        urlRequest.httpMethod = method.rawValue\r\n        \r\n        print(url)\r\n        print(parameters)\r\n        print(method)\r\n        print(tokenValue)\r\n        \r\n        switch method {\r\n        case .get:\r\n            if let parameters = parameters {\r\n                do {\r\n                    urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)\r\n                } catch {\r\n                    throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))\r\n                }\r\n            }\r\n        default:\r\n            if let parameters = parameters {\r\n                do {\r\n                    urlRequest.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [.prettyPrinted])\r\n                } catch {\r\n                    throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))\r\n                }\r\n            }\r\n        }\r\n        \r\n        urlRequest.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.acceptType.rawValue)\r\n        urlRequest.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue)\r\n        if !tokenValue.isEmpty {\r\n            urlRequest.setValue(tokenValue, forHTTPHeaderField: HTTPHeaderField.authentication.rawValue)\r\n        }\r\n        \r\n        return urlRequest\r\n    }\r\n}<\/pre>\n<\/div>\n<h2>Services<\/h2>\n<p>In this class, we will create a generic function and throw every request through this generic function. I will create a variable named shared with a singleton and proceed over this variable.<\/p>\n<div class=\"wp-block-codemirror-blocks code-block \">\n<pre class=\"CodeMirror\" data-setting=\"{&quot;mode&quot;:&quot;swift&quot;,&quot;mime&quot;:&quot;text\/x-swift&quot;,&quot;theme&quot;:&quot;default&quot;,&quot;lineNumbers&quot;:true,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:true,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}\">\/\/\r\n\/\/  Services.swift\r\n\/\/  library\r\n\/\/\r\n\/\/  Created by \u00d6mer Sezer on 13.02.2022.\r\n\/\/\r\n\r\nimport Alamofire\r\n\r\nclass Services {\r\n    \r\n    static let shared = Services()\r\n    \r\n    private func request&lt;T: Decodable&gt;(route: APIRouter, decoder: JSONDecoder = JSONDecoder(), onSuccess: @escaping ((_ data: T?) -&gt; Void), onError: @escaping ((_ error: String) -&gt; Void)) {\r\n        AF.request(route)\r\n            .validate(statusCode: 200..&lt;300)\r\n            .responseDecodable(decoder: decoder) { (response: AFDataResponse&lt;T&gt;) in\r\n                print(response.result)\r\n                switch response.result {\r\n                case .success(let model):\r\n                    onSuccess(model)\r\n                case .failure(let error):\r\n                    onError(error.localizedDescription)\r\n                }\r\n            }\r\n    }\r\n    \r\n    func search(searchedText: String, page: Int = 1, type: SearchTypes, successCompletion: @escaping ((_ data: MoviesModel?) -&gt; Void), errorCompletion: @escaping ((_ error: String) -&gt; Void)) {\r\n        request(route: .search(searchedText: searchedText, page: page, type: type), onSuccess: successCompletion, onError: errorCompletion)\r\n    }\r\n}<\/pre>\n<\/div>\n<p>When sending a request, I send it as follows.<\/p>\n<div class=\"wp-block-codemirror-blocks code-block \">\n<pre class=\"CodeMirror\" data-setting=\"{&quot;mode&quot;:&quot;swift&quot;,&quot;mime&quot;:&quot;text\/x-swift&quot;,&quot;theme&quot;:&quot;default&quot;,&quot;lineNumbers&quot;:true,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:true,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}\">\/\/\r\n\/\/  SearchInteractor.swift\r\n\/\/  library\r\n\/\/\r\n\/\/  Created by \u00d6mer Sezer on 13.02.2022.\r\n\/\/  \r\n\/\/\r\n\r\nimport UIKit\r\n\r\nprotocol SearchInteractorOutputs: AnyObject {\r\n    func onMoviesSearched(movies: MoviesModel?)\r\n    func onNextPageFetched(movies: MoviesModel?)\r\n    func onError(error: String)\r\n}\r\n\r\nfinal class SearchInteractor: BaseInteractor, Interactorable {\r\n    weak var presenter: SearchInteractorOutputs?\r\n    weak var entities: SearchEntities?\r\n    \r\n    func searchMovie(searchedText: String, type: SearchTypes) {\r\n        Services.shared.search(searchedText: searchedText, type: type) { data in\r\n            self.presenter?.onMoviesSearched(movies: data)\r\n        } errorCompletion: { error in\r\n            self.presenter?.onError(error: error)\r\n        }\r\n    }\r\n    \r\n    func searchMovie(searchedText: String, pageNumber: Int, type: SearchTypes) {\r\n        Services.shared.search(searchedText: searchedText, page: pageNumber, type: type) { data in\r\n            self.presenter?.onNextPageFetched(movies: data)\r\n        } errorCompletion: { error in\r\n            self.presenter?.onError(error: error)\r\n        }\r\n    }\r\n}<\/pre>\n<\/div>\n<p>If you have questions, you can reach us by sending an e-mail or comment. You can access the project <a href=\"https:\/\/github.com\/omersezer\/swift-projects\/tree\/main\/library\">here<\/a>. You can find more articles <a href=\"https:\/\/sezeromer.com\/swift\/\">here<\/a>.<\/p>\n<p>Good work.<\/p>","protected":false},"excerpt":{"rendered":"<p>Hello friends, in this article we will talk about how to create a generic service with Swift. With a generic service structure, we can easily change and manage it when we change something extra in the future. We can make service requests with less code. In the application where we list music, movies, applications and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1706,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[327,706],"tags":[331,879,8,7,395,396,880,440,878,328,707],"class_list":["post-2211","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-swift","category-swiftui","tag-degisken","tag-generic","tag-omer","tag-omer-sezer","tag-service","tag-servis","tag-servis-yapisi","tag-sezer","tag-singleton","tag-swift","tag-swiftui"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/posts\/2211"}],"collection":[{"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/comments?post=2211"}],"version-history":[{"count":6,"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/posts\/2211\/revisions"}],"predecessor-version":[{"id":2685,"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/posts\/2211\/revisions\/2685"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/media\/1706"}],"wp:attachment":[{"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/media?parent=2211"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/categories?post=2211"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sezeromer.com\/en\/wp-json\/wp\/v2\/tags?post=2211"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}