AFNetworking源碼分析(一)

AFNetworking作為iOS開發(fā)中最常用的網絡請求框架之一驾孔,其內部實現究竟是怎么一步一步實現的呢?現在我們來大致分析下其內部源碼,由于本人水平有限,說錯了的地方請大家積極指正华坦,謝謝!

以3.0.0的版本為例,其源碼結構如下:


源碼結構.png

1.NSURLSession文件夾中的類專門用于網絡通信,其中最核心的類是AFURLSessionManager,其中的AFHTTPSessionManager這個類是前者的子類歹袁,顧名思義,AFHTTPSessionManager這個類是專門用于Http請求,

2.Reachability中的AFNetworkRechabilityManager專門用于檢測網絡變化

3.Security用于網絡安全策略

4.Serilization中的兩個類專門用于請求頭、響應頭的處理
其類與類之間的關系如下圖所示:


類與類之間關聯(lián).png

AF工作流程 .png

可以看到AFSessionManager中實現的最核心的類是Apple提供的系統(tǒng)類NSURLSession和NSURLSessionDataTask,這兩個類提供了網絡請求所需要的全部,由于我們最常使用的類是AFHTTPSessionManager,在進行網絡請求時捷沸,我們一般使用一個AFHTTPSessionManager的實例變量來進行請求,如下代碼所示:

   AFHTTPSessionManager *httpClient = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"www.baidu.com"]];
    httpClient.requestSerializer = [AFHTTPRequestSerializer serializer];
    httpClient.responseSerializer = [AFHTTPResponseSerializer serializer];
    [httpClient POST:@""
          parameters:@{}
             success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                 //獲取responseObject的數據,在主線程做UI刷新操作
             } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {   
  }];

首先設置基地址:

- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
        return nil;
    }
    //http://www.baidu.com 確保完整路徑的基地址
    // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }
    self.baseURL = url;
    //請求和響應序列化工具
    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    return self;
}

這里設置了一個requestSerializer和responseSerializer,這兩個類的作用是設置請求頭序仙、響應頭相關的信息,主要用于傳輸不同類型的文件并設置不同的contentType等等,我們進入requestSerializer看下源碼看具體做了什么:

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    //編碼
    self.stringEncoding = NSUTF8StringEncoding;
    //設置請求頭
    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
    // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
    [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        float q = 1.0f - (idx * 0.1f);
        [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
        *stop = q <= 0.5f;
    }];
    //服務器接收語言
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
    //用戶代理
    NSString *userAgent = nil;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#if TARGET_OS_IOS
    //獲取請求設備的相關信息 用戶代理
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
#pragma clang diagnostic pop
    if (userAgent) {
        if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
            NSMutableString *mutableUserAgent = [userAgent mutableCopy];
            if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
                userAgent = mutableUserAgent;
            }
        }
        [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
    }

    // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

    self.mutableObservedChangedKeyPaths = [NSMutableSet set];
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }
    return self;
}

這些信息主要是和后臺人員溝通確定的粱锐,比如一般的后臺請求蔬崩,我們要設置AcceptableContentType為applicaiont/json跨琳,注明接受類型是已經被后臺人員轉好的json湾宙,在回調中我們就可以直接獲取到json數據死宣,而不用去進行轉換, 有的時候博秫,我們在勤奮求的時候巴碗,報錯"Request failed: unacceptable content-type",就需要在請求頭的contentype列表里面添加一些指定的contentType.
其次就是主體的請求代碼:

[httpClient POST:@""
          parameters:@{}
             success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                 //獲取responseObject的數據,在主線程做UI刷新操作
             } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                 
             }];

我們點進去看看,可以看到所有的請求方法都是基于下面這個方法來構建的:

/**
 構建網絡請求:所有請求(POST、GET、DELETE趋艘、PATCH、HEAD)都基于這個方法來構建起來的
 @param method POST、GET矛绘、DELETE斯够、PATCH抓督、HEAD
 @param URLString 請求地址
 @param parameters 請求參數
 @param uploadProgress 上傳進度回調
 @param downloadProgress 下載進度列表
 @param success 成功回調
 @param failure 失敗回調
 @return NSURLSessionDataTask網絡請求
 */
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure

我們重點來分析下這個方法里面的代碼:

// 根據請求地址、方法阳液、參數構建網絡請求NSMutableURLRequest
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

這里使用了requestSerializer 的私有方法 method揣炕、params來構建了一個NSMutableURLRequest,并設置了請求體畸陡,請求頭信息等等:代碼如下:

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    ///設置請求頭信息
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];
    NSString *query = nil;
    if (parameters) {
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);
            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }
                return nil;
            }
        } else {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }
   //判斷是否是GET/HEAD/DELETE方法罩锐, 對于GET/HEAD/DELETE方法涩惑,把參數加到URL后面,這也就是GET請求里面的參數會暴露在請求地址里面
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        // #2864: an empty string is a valid x-www-form-urlencoded payload
       //一般用于POST請求
        if (!query) {
            query = @"";
        }
        ///設置Content-Type HTTP頭桑驱,告訴服務端body的參數編碼類型
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        //設置請求體信息 將json字符串轉化為NSData類型
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }
    return mutableRequest;
}

接下來的就是構架一個NSURLSessionDataTask:

__block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
//成功失敗回調
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];  //使用這個方法構建的task 需要手動resume 才會啟動任務
  ///使用NSURLSession和NSURLSessionDataTask請求數據
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {
    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
    //這里使用了NSURLSession來實例化一個dataTask
        dataTask = [self.session dataTaskWithRequest:request];
    });
    //為task添加代理來傳輸數據等相關信息
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    return dataTask;
}

///給NSURLSessionDataTask對象實例設置Delegate痊硕。用于實時了解網絡請求
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
//為dataTask設置代理來接受并處理數據
    [self setDelegate:delegate forTask:dataTask];
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);
   //加鎖 避免同時修改數據,造成線程安全問題
    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [delegate setupProgressForTask:task];
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

///這里使用KVO監(jiān)測task的請求過程
- (void)setupProgressForTask:(NSURLSessionTask *)task {
    __weak __typeof__(task) weakTask = task;

    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
    [self.uploadProgress setCancellable:YES];
    [self.uploadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.uploadProgress setPausable:YES];
    [self.uploadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];
    if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.uploadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    [self.downloadProgress setCancellable:YES];
    [self.downloadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.downloadProgress setPausable:YES];
    [self.downloadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];

    if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.downloadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
    [self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
}

//監(jiān)聽下載和上傳任務的數據并回調
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSURLSessionTask class]]) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
            self.downloadProgress.completedUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
            self.downloadProgress.totalUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
            self.uploadProgress.completedUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
            self.uploadProgress.totalUnitCount = [change[@"new"] longLongValue];
        }
    }
    else if ([object isEqual:self.downloadProgress]) {
        //下載回調
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
        //上傳回調
            self.uploadProgressBlock(object);
        }
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
  //獲取NSURLSessionDataTask代理對象
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    //試用delegate追加數據
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];
    //數據接受回調
    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}

//接收數據
- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    [self.mutableData appendData:data];
}
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>

AFURLSessionManagerTaskDelegate接管了NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate的各種回調刚盈,然后做內部數據處理挂脑,崭闲。這也是第三方網絡請求框架的重點,讓網絡請求更加易用牺蹄,好用薄翅。
當然氓奈,由于本人技術有限舀奶,很多東西沒理解透徹斋射,如果有錯誤之處,歡迎大家指正涧至,共同進步,后續(xù)的深入理解會繼續(xù)寫文章大家一起談論,如果親覺著這篇文章對于理解AFN有幫助的話桑包,請下方點贊,謝謝!

-------作者:mrChan1234

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弱左,隨后出現的幾起案子拆火,更是在濱河造成了極大的恐慌,老刑警劉巖们镜,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件憎账,死亡現場離奇詭異胞皱,居然都是意外死亡,警方通過查閱死者的電腦和手機雾鬼,發(fā)現死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門策菜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人又憨,你說我怎么就攤上這事蠢莺。” “怎么了锄弱?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵会宪,是天一觀的道長。 經常有香客問我蚯窥,道長,這世上最難降的妖魔是什么沟沙? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任矛紫,我火速辦了婚禮牌里,結果婚禮上,老公的妹妹穿的比我還像新娘喳篇。我一直安慰自己态辛,他們只是感情好奏黑,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布熟史。 她就那樣靜靜地躺著,像睡著了一般蹂匹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忍啸,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天缎岗,我揣著相機與錄音传泊,去河邊找鬼眷细。 笑死溪椎,一個胖子當著我的面吹牛校读,可吹牛的內容都是我干的。 我是一名探鬼主播养铸,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼兔甘,長吁一口氣:“原來是場噩夢啊……” “哼洞焙!你這毒婦竟也來了澡匪?” 一聲冷哼從身側響起仙蛉,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤荠瘪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后趁餐,有當地人在樹林里發(fā)現了一具尸體篮绰,經...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡吠各,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年贾漏,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梳码。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡掰茶,死狀恐怖蜜笤,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情啊胶,我是刑警寧澤垛贤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布聘惦,位于F島的核電站儒恋,受9級特大地震影響诫尽,放射性物質發(fā)生泄漏禀酱。R本人自食惡果不足惜牧嫉,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一减途、第九天 我趴在偏房一處隱蔽的房頂上張望曹洽。 院中可真熱鬧鳍置,春花似錦、人聲如沸税产。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梧兼。三九已至智听,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間考赛,已是汗流浹背莉测。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工捣卤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸠项。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓祟绊,卻偏偏與公主長得像哥捕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扬舒,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容

  • 最近研究了一下AFNet的源碼,AFNetworking建立在URL 裝載系統(tǒng)框架的頂層讲坎,內置在Cocoa里衣赶,擴展...
    阿基米敬閱讀 457評論 0 7
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現府瞄,斷路器,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • //需要AFN //.h //AFNetworking + (void)post:(NSString *)url ...
    CHADHEA閱讀 778評論 0 0
  • 繼承:AFURLSessionManager:NSObject 遵照:NSCopying, NSSecureCod...
    _阿南_閱讀 6,710評論 0 3
  • 樹葉 一片落下來 又一片落下來 樹葉長在樹上鲸郊,落在樹下 樹葉是樹的孩子 除非是風货邓,帶他離開 我是一棵蒼老的樹 橫枝...
    蔚霐_d38f閱讀 124評論 0 1