jjzjj

iOS - 在 NSURLSession 中重新尝试失败的 NSURLRequests

coder 2023-09-26 原文

在我的应用程序中,在我的 API 类的 NSURLSession 中可以同时(异步)对我的服务器进行 2-4 个 API 调用。为了向我的服务器发出 API 请求,我必须在每个 NSURLRequestHTTPHeaderField 中提供身份验证 token 。 token有效期为一天,如果过了一天就失效了,我需要刷新token。

我在 API 类的以下代码中执行此操作:

/*!
 * @brief sends a request as an NSHTTPURLResponse. This method is private.
 * @param request The request to send.
 * @param success A block to be called if the request is successful.
 * @param error A block to be called if the request fails.
 */
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
    NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
    {
        [self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
        {
            //if auth token expired and getting "not authenticated" error (status 401)
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 401) {
                [self refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
                    self.authToken = response[@"token"];
                    //attempt to re-try the request that failed due to token expiration
                    [self sendTask:request successCallback:success errorCallback:errorCallback];
                } errorCallback:^(NSString *error) {
                    //two weeks have passed and the token is no longer refreshable
                    NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
                }];
            }
        }];
    }];
    [task resume];
}

这个 sendTask 方法会随着我在应用程序中发出的每个 API 请求而执行,所以我才意识到这是一种糟糕的做法。如果 3 个 API 请求由于 token 无效而失败(一天过去了),那么这 3 个 API 请求都将尝试进行 API 调用以刷新身份验证 token 。

如果其中一个 API 请求失败,我有没有办法只刷新一次身份验证 token ,然后重新尝试失败的 API 调用?

编辑

我编辑了问题的标题以表明我正在使用 NSURLSession

进展

到目前为止,为了防止多个失败的 API 请求同时尝试刷新身份验证 token ,我有一个用于所有失败请求的 NSArray 和一个 NSNumber用作锁以确保身份验证 token 仅尝试刷新一次。我在以下代码中执行此操作:

-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
    NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
    {
        MyAPIInterface *__weak weakSelf = self;
        [self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
        {
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 401) {
                if ([error isEqualToString:@"invalid_credentials"]) {
                    errorCallback(@"Invalid username and/or password");
                }
                else if ([error isEqualToString:@"Unknown error"]) {
                    errorCallback(error);
                }
                else {
                    if (!weakSelf.alreadyRefreshingToken.boolValue) {

                        //lock alreadyRefreshingToken boolean
                        weakSelf.alreadyRefreshingToken = [NSNumber numberWithBool:YES];
                        NSLog(@"NOT REFRESHING TOKEN");

                        // add failed request to failedRequests array
                        NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
                        [mutableFailedRequests addObject:request];
                        weakSelf.failedRequests = [mutableFailedRequests copy];

                        // refresh auth token
                        [weakSelf refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {

                            //store authToken
                            weakSelf.authToken = response[@"token"];
                            NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
                            [defaults setObject:weakSelf.authToken forKey:@"authToken"];
                            [defaults synchronize];

                            //attempt to re-try all requests that failed due to token expiration
                            for (NSURLRequest *failedRequest in weakSelf.failedRequests) {
                                [weakSelf sendTask:failedRequest successCallback:success errorCallback:errorCallback];
                            }

                            //clear failedRequests array and unlock alreadyRefreshingToken boolean
                            [weakSelf clearFailedRequests];
                            weakSelf.alreadyRefreshingToken = [NSNumber numberWithBool:NO];

                            NSLog(@"TOKEN REFRESHING SUCCESSFUL THO");

                        } errorCallback:^(NSString *error) {

                            NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");

                            //clear failedRequests array
                            [weakSelf clearFailedRequests];

                            errorCallback(@"Your login session has expired");

                        }];
                    }
                    else  {
                        NSLog(@"ALREADY REFRESHING TOKEN. JUST ADD TO FAILED LIST");
                        NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
                        [mutableFailedRequests addObject:request];
                        weakSelf.failedRequests = [mutableFailedRequests copy];
                    }
                }
            }
            else {
                NSLog(@"ERROR STRING THO: %@", error);
                errorCallback(error);
            }
        }];
    }];
    [task resume];
}

#pragma mark Custom Methods

-(void)clearFailedRequests {
    NSMutableArray *mutableFailedRequests = [self.failedRequests mutableCopy];
    [mutableFailedRequests removeAllObjects];
    self.failedRequests = [mutableFailedRequests copy];
}

我的做法是否正确?我偏执的一个部分是我并没有真正在某些点调用 successerror 回调。这会导致问题吗?

最佳答案

尝试使用 [weakSelf sendTask] 而不是 [self sendTask:]。检查以下代码:

-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{

   __weak __typeof(self)weakSelf = self;
    NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
    {
        [self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
        {
            //if auth token expired and getting "not authenticated" error (status 401)
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 401) {
                [self refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
                    self.authToken = response[@"token"];
                    //attempt to re-try the request that failed due to token expiration
                    [weakSelf sendTask:request successCallback:success errorCallback:errorCallback];
                } errorCallback:^(NSString *error) {
                    //two weeks have passed and the token is no longer refreshable
                    NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
                }];
            }
        }];
    }];
    [task resume];
}

关于iOS - 在 NSURLSession 中重新尝试失败的 NSURLRequests,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34823840/

有关iOS - 在 NSURLSession 中重新尝试失败的 NSURLRequests的更多相关文章

  1. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  2. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  3. ruby-on-rails - active_admin 目录中的常量警告重新声明 - 2

    我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA

  4. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  5. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  6. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  7. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  8. ruby - 在 Ruby 中重新分配常量时抛出异常? - 2

    我早就知道Ruby中的“常量”(即大写的变量名)不是真正常量。与其他编程语言一样,对对象的引用是唯一存储在变量/常量中的东西。(侧边栏:Ruby确实具有“卡住”引用对象不被修改的功能,据我所知,许多其他语言都没有提供这种功能。)所以这是我的问题:当您将一个值重新分配给常量时,您会收到如下警告:>>FOO='bar'=>"bar">>FOO='baz'(irb):2:warning:alreadyinitializedconstantFOO=>"baz"有没有办法强制Ruby抛出异常而不是打印警告?很难弄清楚为什么有时会发生重新分配。 最佳答案

  9. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  10. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

随机推荐