简体   繁体   中英

How to add a function that automatically moves to the second API key in Rxswift

One API key can only make 100 requests per day. So one API key can't handle a lot of requests per day. There are other ways to solve this problem, but I would like to solve this problem by entering various API keys. For example, if the first API key makes 100 requests and the request value returns as an error, I want to add a function that automatically moves to the second API key. Can you tell me how to make it with Rxswift?

I would appreciate any help you can provide.

The code is as below.

private func loadTopNews() {
   let resource = Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode[0])&sortBy=%20popularity&apiKey=\(apiKey[0])")!)
        
   URLRequest.load(resource: resource)
      .subscribe(onNext: { articleResponse in
         let topArticle = articleResponse.articles.first
         self.articleVM = ArticleViewModel(topArticle!)
   }).disposed(by: disposeBag)
}


struct Resource<T: Decodable> {
    let url: URL
}

extension URLRequest {
    
    static func load<T>(resource: Resource<T>) -> Observable<T> {
        return Observable.just(resource.url)
            .flatMap { url -> Observable<Data> in
                let request = URLRequest(url: url)
                return URLSession.shared.rx.data(request: request)
            }.map { data -> T in
                return try JSONDecoder().decode(T.self, from: data)
            }
    }
}
struct ArticleResponse: Decodable {
    let articles: [Article]
}

struct Article: Decodable {
    let title: String
    let publishedAt: String
    let urlToImage: String?
    let url: String
}

struct ArticleListViewModel {
    
    let articlesVM: [ArticleViewModel]
}

extension ArticleListViewModel {
    
    init(_ articles: [Article]) {
        self.articlesVM = articles.compactMap(ArticleViewModel.init)
    }
}

extension ArticleListViewModel {
    
    func articleAt(_ index: Int) -> ArticleViewModel {
        return self.articlesVM[index]
    }
}


struct ArticleViewModel {
    
    let article: Article
    
    init(_ article: Article) {
        self.article = article
    }
}

extension ArticleViewModel {
    
    var title: Observable<String> {
        return Observable<String>.just(article.title)
    }
    
    var publishedAt: Observable<String> {
        return Observable<String>.just(article.publishedAt)
    }
    
    var urlToImage: Observable<String> {
        return Observable<String>.just(article.urlToImage ?? "NoImage")
    }
    
    var url: Observable<String> {
        return Observable<String>.just(article.url)
    }
}

I wrote an article covering this very thing (albeit in a different context): RxSwift and Handling Invalid Tokens

The above article will help if you are making multiple requests at the same time and need to restart all of them with the new token. It might be overkill in this specific case.

To solve this problem, you need:

  1. A function that will build a resource with a given api key
  2. An Observable that emits a different API key whenever it's subscribed to.

Once you have those two pieces, you can just retry your subscription until one of the keys works.

Solution

I suggest you use the above as clues and try to solve the problem yourself. Then you can check your answer against the solution below...

For item 1, I see that you need two arguments to create a resource. So I suggest making a function factory that will produce a function that takes an apiKey. Like this:

func makeResource(selectedLanguagesCode: String) -> (String) -> Resource<ArticleResponse> {
    { apiKey in
        Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode)&sortBy=%20popularity&apiKey=\(apiKey)")!)
    }
}

Note that this function is not part of the class. It doesn't need self .

For item 2, we need a function that takes the array of apiKeys and produces an Observable that will emit a different key each time it's subscribed to:

Something like this should work:

func produceApiKey(apiKeys: [String]) -> Observable<String> {
    var index = 0
    return Observable.create { observer in
        observer.onNext(apiKeys[index % apiKeys.count])
        observer.onCompleted()
        index += 1
        return Disposables.create()
    }
}

Again, this function doesn't need self so it's not part of the class.

Now that you have these two elements, you can use them in your loadTopNews() method. Like this:

private func loadTopNews() {
    produceApiKey(apiKeys: apiKey)
        .map(makeResource(selectedLanguagesCode: selectedLanguagesCode[0]))
        .flatMap(URLRequest.load(resource:))
        .retry(apiKey.count - 1)
        .subscribe(onNext: { articleResponse in
            let topArticle = articleResponse.articles.first
            self.articleVM = ArticleViewModel(topArticle!)
        })
        .disposed(by: disposeBag)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM