項目干貨挖掘3——怎么去封裝一個網絡層?

前言

我之前已經寫過一篇有關網絡層的筆記:關于網絡層的設計(一)——和業務層的對接。在這篇筆記中主要解釋了網絡層設計時所要考慮的因素。給出的代碼例子是以block回調的,以AFNetworking請求網絡的,而且結構是離散型的。
那本篇我們選擇處處與之相對立,即以delegate回調給業務層數據,自己封裝NSURLConnection來請求網絡,結構采用集約型,而且更全面細致:加入了網絡超時的處理,請求隊列的控制,完善的公共參數等。


** HomeViewController.m **

這是業務層的代碼。業務層所有的網絡請求都通過ConnectManager里的該方法完成,這就是所謂集約型結構,所有的業務請求均由一個方法完成,這樣的話開發效率可能會更高,畢竟只使用這個方法嘛。
而且數據從網絡層交付到業務層是通過delegate回調方法的形式,這樣的話若在同一個ViewController有多個不同接口的網絡請求,則需要在代理方法里判斷接口requestInterface,不同接口分別做不同處理。這也是和block回調不同的地方,block方式是每個接口都對應一個block回調,而delegate則只有一個代理方法,所以需要在代理方法里帶上requestInterface用以判別區分出不同的接口。

#import "HomeViewController.h"
#import "ConnectManager.h"


@interface HomeViewController ()

@end

@implementation HomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSDictionary *dict = @{@"stuId":@"666"};
    // 業務層所有網絡請求,均通過該方法,是為集約型。
    [[ConnectManager shareConnectManager] sendRequestWithID:RequestInterface_StudentList
                                                 parameters:dict
                                           callbackDelegate:self
                                                   httpType:GET
                                           removeAllRequest:NO];   
}

// 通過delegate將數據從網絡層交付給業務層
- (void)connectManagerCallback:(RequestInterface)requestInterface requestResult:(RequestResult *)result
{
    if(requestInterface == RequestInterface_StudentList)
    {
        if(result.status == 200){
//            NSArray *stuList = result.resultInfo;
        }
    }
}


@end

** ConnectManager.h **

可以看到我們聲明了生成實例的單例方法,并聲明了那個給業務層調用請求網絡的方法。我們還定義了ConnectManagerDelegate協議,業務層的VC為其代理者,即上面代碼看到的實現。
除此外,我們還定義了兩個枚舉變量,第一個是請求方式HttpType,第二個是代表接口的枚舉變量,所有的接口都在此定義成枚舉變量。業務層傳入的接口參數便為接口的枚舉變量。

#import <Foundation/Foundation.h>
#import "RequestResult.h"



// 請求方式
typedef NS_ENUM(NSInteger, HttpType)
{
    GET = 0,
    POST,
};



// 所有的接口定義成枚舉變量
typedef NS_ENUM (NSInteger,RequestInterface)
{
    RequestInterface_StudentList      = 0,  // 學生列表
    RequestInterface_StudentDetail,         // 學生詳情
};



// 定義ConnectManagerDelegate協議
@protocol ConnectManagerDelegate <NSObject>

- (void)connectManagerCallback:(RequestInterface)requestInterface requestResult:(RequestResult *)result;

@end





@interface ConnectManager : NSObject

@property (nonatomic, weak)id<ConnectManagerDelegate>  managerDelegate; // 回調給業務層的代理


+ (ConnectManager *)shareConnectManager;


- (void)sendRequestWithID:(RequestInterface )requestInterface
               parameters:(NSDictionary *)parama
         callbackDelegate:(id)delegate
                 httpType:(HttpType)httpType
         removeAllRequest:(BOOL)isRemove;

@end

** ConnectManager.m **

我們可以看到該類主要完成了請求隊列的控制,可以控制在將當前請求加入請求隊列時,是否清空請求隊列中的所有請求,或者只是從請求隊列中把舊的該請求清除。
通過調用HTTPConnect的方法,生成網絡連接的對象,并開始連接(等下看HTTPConnect類的詳情)。
而且它作為HTTPConnect的代理,實現了HTTPConnectDelegate協議的方法。并將數據轉換成了RequestResult,并調用了ConnectManagerDelegate協議的方法,將其回調給業務層。

** 注意接口返回格式的約定:**這里需要多說幾句的是。項目開始前,后臺會在接口文檔中寫明接口統一返回格式。所有的接口都遵循這個格式。比如,我們當前的項目,后臺在接口文檔中就給出了“接口返回格式”:返回的json數據最外層有hb兩個key,分別意為headbody,在head的value又是一個字典,分別有code,msg,time三個key,分別代表錯誤碼信息提示,服務器時間。而在body里則是真正需要的數據。
根據這個約定,我們創建了RequestResultmodel對象用以封裝返回數據(后面給出了RequestResult的代碼)。

** 對于Null值的處理:**若后臺返回的某個字段是Null,表示該字段為空,這樣直接使用既不友好,也比較危險。不友好之其一為若要顯示在UI上便會顯示為<null>,其二為在判斷該字段是否為空的地方得要這樣:if([awarder.awarderId isKindOfClass:[NSNull class]]),比較麻煩。危險之處是在OC中若字典以這樣的方式@{@"key1":@"value1",@"key2":@"value2"};建立,此時若value1為空,程序執行到此直接會崩掉。
所以我們需要對后臺返回的json數據都要做Null值處理。我們可以在預編譯文件中定義一個宏,若值為NSNull則將其置為@""

#define DealWithJSONStringValue(_JSONVALUE)    (_JSONVALUE != [NSNull null] && _JSONVALUE!=nil) ?[NSString stringWithFormat:@"%@",_JSONVALUE]:@""
屏幕快照 2016-07-18 下午1.37.12.png
#import "ConnectManager.h"
#import "HTTPConnect.h"
#import "RequestResult.h"


#define ServerAddress @"http://www.runedu.test/api" // 服務器地址
#define Interface_Version   @"v5"  // 接口版本
#define DealWithJSONValue(_JSONVALUE)        ((_JSONVALUE != [NSNull null] && _JSONVALUE!=nil) ? _JSONVALUE:nil)
#define DealWithJSONStringValue(_JSONVALUE)    (_JSONVALUE != [NSNull null] && _JSONVALUE!=nil) ?[NSString stringWithFormat:@"%@",_JSONVALUE]:@""


@interface ConnectManager ()<HTTPConnectDelegate>
{
    NSOperationQueue            *_operationQueue;
    HTTPConnect                 *_httpConnect;
}
@end


@implementation ConnectManager


+ (ConnectManager *)shareConnectManager
{
    static ConnectManager *shareManager = nil;
    
    if(!shareManager){
        shareManager = [[ConnectManager alloc] init];
    }
    
    return shareManager;
}

- (id)init
{
    self = [super init];
    if(self){
        _operationQueue = [[NSOperationQueue alloc] init];
        _operationQueue.suspended = YES;
    }
    
    return self;
}



- (void)sendRequestWithID:(RequestInterface)requestInterface
               parameters:(NSDictionary *)parama
         callbackDelegate:(id)delegate
                 httpType:(HttpType)httpType
         removeAllRequest:(BOOL)isRemove
{
    if(isRemove){
        // 1.移除所有連接
        [self clearAllRequestsConnect];
    }else{
        // 移除當前接口某連接
        [self clearConnectOfTheInterface:(requestInterface)];
    }
    
    _managerDelegate = delegate;
    
    // 2.根據枚舉把接口轉換為相應的接口字符串,并拼接完整URL.
    NSString *urlStr = [self urlStringWithRequestInterface:requestInterface];
    
    // 3.傳入接口地址,URL,參數dict,請求方式等生成請求連接對象。
    _httpConnect = [[HTTPConnect alloc] initWithInterface:requestInterface
                                                urlString:urlStr
                                               parameters:parama
                                         callbackDelegate:self
                                                 httpType:GET];
//    _httpConnect.httpConnectDelegate = self;
    // 4. 開始連接,并加入請求隊列
    [_httpConnect startConnect];
    [_operationQueue addOperation:_httpConnect];
}




// 清除所有連接
- (void)clearAllRequestsConnect
{
    [_operationQueue cancelAllOperations];
}

// 清除當前某接口的連接
- (void)clearConnectOfTheInterface:(RequestInterface)interface
{
    for(HTTPConnect *httpConnect in _operationQueue.operations)
    {
        if(httpConnect.requestInterface == interface){
            [httpConnect cancelConnect];
        }
    }
}



//- (void)clearTheRequestConnectOfInterface:(RequestInterface)requestInterface
//{
//    for(nsop)
//}


// 返回完整的URL
- (NSString *)urlStringWithRequestInterface:(RequestInterface)interface
{
    // 1.先根據枚舉變量找出接口對應的字符串
    NSString *interfaceStr = @"";
    switch (interface)
    {
        case RequestInterface_StudentList:
        {
            interfaceStr = @"student/list";
            break;
        }
        case RequestInterface_StudentDetail:
        {
            interfaceStr = @"student/detail";
            break;
        }
    }
    
    // 2.再把服務器地址、版本號、接口地址拼接成完整的URL
    NSString *urlStr = @"";
    if((int)interface<1000){ // 學生接口
        urlStr = [NSString stringWithFormat:@"%@/student/%@%@",ServerAddress, Interface_Version, interfaceStr];
    }else{ // 公共接口
        urlStr = [NSString stringWithFormat:@"%@/public/%@%@",ServerAddress, Interface_Version, interfaceStr];
    }
    
    
    return urlStr;
}



#pragma mark ---- HTTPConnectDelegate

// 請求成功
- (void)httpConnectDownloadFinish:(HTTPConnect *)httpConnect
{
    RequestResult *result = [[RequestResult alloc] init];
    result.token = [httpConnect.tokenString copy];
    
    //解析頭
    NSDictionary *head = DealWithJSONValue([httpConnect.jsonDic objectForKey:@"h"]);
    result.status = [DealWithJSONStringValue([head objectForKey:@"code"])integerValue];
    result.message = DealWithJSONStringValue( [head objectForKey:@"msg"]);
    result.serverTime = DealWithJSONStringValue( [head objectForKey:@"time"]);
    result.jsonData = [[NSData alloc]initWithData:httpConnect.jsonData];
    
    if(result.status == 200){
        result.resultInfo = DealWithJSONValue([httpConnect.jsonDic objectForKey:@"b"]);
        
        if([result.resultInfo isKindOfClass:[NSDictionary class]]){
            NSDictionary *dataDic = result.resultInfo;
            result.dataTotal = DealWithJSONStringValue([dataDic objectForKey:@"dataTotal"]);
        }
    }
    
    // 代理回調。在每個VC中實現,便可取得數據。
    if(self.managerDelegate && [self.managerDelegate respondsToSelector:@selector(connectManagerCallback:requestResult:)])
    {
        [self.managerDelegate connectManagerCallback:httpConnect.requestInterface requestResult:result];
    }
    
    httpConnect = nil;
}


// 正在下載數據
- (void)httpConnectIsDownloading:(HTTPConnect *)httpConnect progress:(float)progress
{
    
}


// 請求失敗
- (void)httpConnectDownloadFinish:(HTTPConnect *)httpConnect failure:(NSError *)error
{
    RequestResult *result = [[RequestResult alloc] init];
    result.status = -10;
    result.message = @"網絡不給力";

    if(self.managerDelegate && [self.managerDelegate respondsToSelector:@selector(connectManagerCallback:requestResult:)])
    {
        [self.managerDelegate connectManagerCallback:httpConnect.requestInterface requestResult:result];
    }
    
    httpConnect = nil;
}


// 請求無響應
- (void)httpConnectDownloadFinish:(HTTPConnect *)httpConnect stautsCode:(NSInteger)code
{
    RequestResult *result = [[RequestResult alloc] init];
    result.status = -11;
    result.message = @"服務器無響應";
    
    if(self.managerDelegate && [self.managerDelegate respondsToSelector:@selector(connectManagerCallback:requestResult:)])
    {
        [self.managerDelegate connectManagerCallback:httpConnect.requestInterface requestResult:result];
    }
    
    httpConnect = nil;

}


@end

** HTTPConnect.h **

可以看到HTTPConnect是完成網絡連接和數據請求及響應的核心。在此,我們創建網絡連接的對象,并控制開始連接和斷開連接。
值得注意的是HTTPConnect是繼承自NSOperation的。一個網絡連接的對象便是一個“操作”,每個連接對象都會加入到ConnectManager的請求隊列_operationQueue中。
我們還定義了很多HTTPConnect的屬性,主要為網絡響應返回的數據,之所以作為其公開的屬性,是因為要回調給ConnectManager,所以得暴露為公開的屬性。
除此外,我們還定義了HTTPConnectDelegate協議。

#import <Foundation/Foundation.h>
#import "ConnectManager.h"

@class HTTPConnect;
@protocol HTTPConnectDelegate <NSObject> // 定義HTTPConnectDelegate協議

// 請求成功
- (void)httpConnectDownloadFinish:(HTTPConnect *)httpConnect;


// 正在下載數據
- (void)httpConnectIsDownloading:(HTTPConnect *)httpConnect progress:(float)progress;


// 請求失敗
- (void)httpConnectFailure:(HTTPConnect *)httpConnect failure:(NSError *)error;


// 請求無響應
- (void)httpConnectNoResponse:(HTTPConnect *)httpConnect stautsCode:(NSInteger)code;


@end





@interface HTTPConnect : NSOperation


@property (nonatomic,strong) NSString                   *jsonStr;
@property (nonatomic,strong) NSString                   *tokenString; //token,
@property (nonatomic,strong) NSMutableDictionary        *jsonDic;
@property (nonatomic,strong) NSMutableData              *jsonData;
@property (nonatomic,assign) RequestInterface            requestInterface;
@property (nonatomic, weak)id<HTTPConnectDelegate>       httpConnectDelegate;



- (id)initWithInterface:(RequestInterface)interface
              urlString:(NSString *)urlStr
             parameters:(NSDictionary *)parama
       callbackDelegate:(id)delegate
               httpType:(HttpType)httpType;


// 開始連接
- (void)startConnect;


// 斷開連接
- (void)cancelConnect;



@end

** HTTPConnect.m **

首先我們在初始化方法里,初始化了一些變量,并拼接了各種參數,生成了網絡連接對象_urlConnection。然后實現了startConnectcancelConnect方法。值得注意的是我們加入了網絡請求超時的控制,若開始連接后,若計時器撐到了30s還沒有被銷毀,說明網絡請求超時了。
該類是網絡連接的核心,我們實現了NSURLConnectionDataDelegate協議的方法,在該協議的方法里,網絡數據主要在這些協議方法里進行響應,這是重點。我們對響應的數據進行了一些處理,并賦值給HTTPConnect的屬性,然后調用HTTPConnectDelegate協議的方法,回調給ConnectManager,注意,這些協議的方法均有參數httpConnect對象,這樣便把其屬性,即網絡響應返回的數據回調給了ConnectManager;而在ConnectManager中上面我們已經說了,會把數據轉換為RequestResultConnectManager再調用其對應的協議方法,便將RequestResult形式的數據回調給業務層。
這樣就完成了網絡響應的數據從NSURLConnectionDataDelegate的實現方法到HTTPConnectDelegate的實現方法,再到ConnectManagerDelegate的實現方法。即網絡數據從HTTPConnectConnectManager,再到HomeViewController
嗯,整個流程就是這樣。

#import "HTTPConnect.h"


@interface HTTPConnect ()
{
    NSMutableURLRequest         *_urlRequest;
    NSURLConnection             *_urlConnection;
    NSHTTPURLResponse           *_httpResponse;
    
    
    BOOL                         _isConnect;   // 是否已連接
    RequestInterface             _requestInterface; // 請求接口
    
    NSTimer                     *_connectTimer; // 定時器,用來判斷請求超時
    BOOL                         _isTimerFired; // 請求是否超時
    
    
    NSInteger                    _dataSize;//總數據的大小
    NSInteger                    _received;//每節點數據的大小
    
}
@end



@implementation HTTPConnect


- (id)initWithInterface:(RequestInterface)interface
              urlString:(NSString *)urlStr
             parameters:(NSDictionary *)parama
       callbackDelegate:(id)delegate
               httpType:(HttpType)httpType
{
    self = [super init];
    if(self)
    {
        _isConnect = NO;
        _requestInterface = interface;
        _jsonData = [[NSMutableData alloc] init];
        _httpConnectDelegate = delegate;
        NSString *paramStr = [self paramStrWithDict:parama]; // 生成參數字符串(url?stuId=12&stuName=wang)
        
        // 1.根據GET和POST的不同,把參數裝入URL中。
        if(httpType == GET)
        {
            NSString *URLStr = [NSString stringWithFormat:@"%@?%@",urlStr,paramStr]; // 拼接出GET請求下完整的URL
            _urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:URLStr]];
            _urlRequest.HTTPMethod = @"GET";
        }
        else if(httpType == POST)
        {
            _urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlStr]];
            _urlRequest.HTTPMethod = @"POST";
            NSData *paramData = [paramStr dataUsingEncoding:NSUTF8StringEncoding]; // 把參數字符串編碼為二進制流
            if(paramData.length>0){
                _urlRequest.HTTPBody = paramData;
            }
        }
        
        // 2.設置HTTP請求的一些請求頭信息
        // 省略...
        
        // 3. 生成URL連接
        _urlConnection = [[NSURLConnection alloc] initWithRequest:_urlRequest delegate:self];
    }
    
    
    return self;
}




#pragma mark ---- 提供給外部的功能方法

// 開始連接,并開始連接計時,若超過30秒還沒被關掉說明請求超時了。
- (void)startConnect
{
    
    if(_isConnect){
        return;
    }
    _isConnect = YES;
    
    if(_urlConnection){

        [_urlConnection start];  // 真真切切地進行網絡連接
        _connectTimer = [NSTimer scheduledTimerWithTimeInterval:30
                                                         target:self
                                                       selector:@selector(connectTimeFired)
                                                       userInfo:nil
                                                        repeats:NO];
        _isTimerFired = NO;  // 賦初值為NO
    }
}


// 斷開連接
- (void)cancelConnect
{
    if(!_isTimerFired){
        _isTimerFired = YES;
        [_connectTimer invalidate];
        _connectTimer = nil;
    }
    
    if(_urlConnection){
        [_urlConnection cancel];
    }
}





#pragma mark ---- NSURLConnection delegate

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    // 請求超時不做處理
    if(_isTimerFired){
        return;
    }
    
    // 回調給ConnectManager
    if(_httpConnectDelegate && [_httpConnectDelegate respondsToSelector:@selector(httpConnectFailure:failure:)])
    {
        [_httpConnectDelegate httpConnectFailure:self failure:error];
    }
}


- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    
    _httpResponse = (NSHTTPURLResponse *)response;
    if(_httpResponse && [_httpResponse respondsToSelector:@selector(allHeaderFields)])
    {
        
        NSDictionary *httpResponseHeaderFields = [_httpResponse allHeaderFields];
        NSNumber *totle=[NSNumber numberWithLongLong:[[httpResponseHeaderFields objectForKey:@"Content-Length"] longLongValue]];
        _dataSize = [totle integerValue]; // 總數據
        _tokenString = httpResponseHeaderFields[@"x-auth-token"]; // 從響應頭里獲得token
    }
}


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    
    _received += [data length];
    [self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO];
    
    [_jsonData appendData:data];
}

//
//- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
//{
//    
//}

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    
}


- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    
}

- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
        
        [[challenge sender]  useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
        
        [[challenge sender]  continueWithoutCredentialForAuthenticationChallenge: challenge];
    }
}





#pragma mark ---- fuction method

- (void)connectTimeFired
{
    _isTimerFired = YES; // 請求超時
    if([_httpConnectDelegate respondsToSelector:@selector(httpConnectFailure:failure:)]){
        [_httpConnectDelegate httpConnectFailure:self failure:nil];
    }
    
    [self cancelConnect];
}



// 把參數字典轉換為字符串
- (NSString *)paramStrWithDict:(NSDictionary *)dict
{
    if(!dict){
        return nil;
    }
    
    NSMutableString *paramStr = [NSMutableString stringWithString:@""];
    NSInteger index = 0;
    for(NSString *keyStr in dict.allKeys)
    {
        if(index==0){
            [paramStr appendString:[NSString stringWithFormat:@"%@=%@",keyStr, [dict valueForKey:keyStr]]];
        }else{
            [paramStr appendString:[NSString stringWithFormat:@"&%@=%@",keyStr, [dict valueForKey:keyStr]]];
        }
        
        index++;
    }
    
    
    return [paramStr copy];

}


-(void)updateProgress
{
    if (_dataSize==0) {
        return;
    }
    float progress = _received/_dataSize; //計算出進度
    // 回調給ConnectManager
    if([_httpConnectDelegate respondsToSelector:@selector(httpConnectIsDownloading:progress:)])
    {
        [_httpConnectDelegate httpConnectIsDownloading:self progress:progress];
    }
}




@end

** RequestResult.h 和 RequestResult.m **

#import <Foundation/Foundation.h>

@interface RequestResult : NSObject

@property(nonatomic,assign)NSInteger        status; // 狀態碼
@property(nonatomic,strong)NSString         *message;
@property(nonatomic,strong)NSString         *serverTime;

@property(nonatomic,strong)NSString         *token;
@property(nonatomic,strong)id               resultInfo;
@property(nonatomic,strong)NSData           *jsonData;
@property(nonatomic,strong)NSMutableArray   *dataArray;

@property(nonatomic,strong)NSString        *dataTotal;

@end



#import "RequestResult.h"

@implementation RequestResult

@end


補充

上面我們在生成網絡連接對象時將公共參數部分的代碼省略了,它們應該裝入到網絡請求的HTTP請求頭中。一般公共參數有token(登錄憑證)、UA(設備基本信息)、IMEI(設備的唯一標示)等。
代碼補上,如下:

        //用戶登錄token
        _tokenString=[REDUserModel shareInstance].token;
        if(_tokenString.length>0)
        {
            [_request setValue:_tokenString forHTTPHeaderField:@"x-auth-token"];
        }
        
        if ([PubicClassMethod iosAPPUA].length>0) {
            //ua 設備的基本信息
            [_request setValue:[PubicClassMethod iosAPPUA] forHTTPHeaderField:@"UA"];
        }
        
        if ([[UIDevice currentDevice] uniqueDeviceIdentifier].length>0) {
            // 設備的唯一標示
            [_request setValue:[[UIDevice currentDevice] uniqueDeviceIdentifier] forHTTPHeaderField:@"IMEI"];
        }
        
        [_request setValue:@"ios" forHTTPHeaderField:@"_c"];

更新(2016.8.17)——APP的token用戶登錄狀態驗證機制

因為HTTP協議是“無狀態”協議,也就是它是無記憶,每次請求和響應后是不保留信息的。就像在火車站售票窗口買票一樣,賣票的工作人員不可能記住你,等你下次再買時一眼便能認出你是老王,直接把票交給你。買票這個過程也是不保存狀態信息、無狀態的。下次你買票時,還是要和以往一樣進行質詢對話,出示身份證表明身份等。但我們在實際應用中,服務器對于請求得驗證權限,驗證是否是權限內的用戶。所以說保存用戶狀態卻又是實實在在存在的需求。所以我們引進了Cookie技術,用以保持狀態,保存用戶信息。
但是在APP端,因為token使用更簡單,所以大多使用它來完成用戶狀態驗證。其道理是和Cookie一樣的。

其原理是:####

用戶首次登錄成功后,服務器會生成一個token值,保存在數據庫中,并返回給客戶端;
客戶端拿到這個token值后會保存在本地,作為登錄令牌,后續作為公共參數請求服務器;
請求到達服務器后,服務器拿公共參數里的token和數據庫里的作比較。若兩者相同,說明用戶以前登錄過且現在處于已登錄狀態(未過期)。若兩者不同,說明用戶的登錄狀態已失效,讓用戶重新登錄;
更多請閱讀: IOS 中使用token機制來驗證用戶的安全性

** 更新(2016-10-23)**
我已經就這個話題寫了一篇總結筆記:token機制完成登錄狀態保持/身份認證

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內容

  • iOS網絡架構討論梳理整理中。。。 其實如果沒有APIManager這一層是沒法使用delegate的,畢竟多個單...
    yhtang閱讀 5,231評論 1 23
  • AFHTTPRequestOperationManager 網絡傳輸協議UDP、TCP、Http、Socket、X...
    Carden閱讀 4,371評論 0 12
  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,067評論 6 13
  • 13.服務器圖片改了,url沒有變,需求,服務器的圖片變了,就從服務器加載圖片,如果服務器的沒變,就從本地加載 1...
    AlanGe閱讀 1,191評論 0 1
  • 在“你好閱讀”,我得到了一本書《眼》,這是一本繪本書,但它又不同于其他的繪本,翻開第一頁,你就會看到一雙大眼睛望著...
    RuT閱讀 524評論 3 7