終於寫完了 AFNetworking 的源碼解讀。這一過程耗時數天。當我回過頭又重頭到尾的讀了一篇,又有所收穫。不由讓我想起了當初上學時的種種情景。咱們應該對知識進行反覆的記憶和理解。下邊是我總結的 AFNetworking 中可以學到的知識點。css
1.枚舉(enum)
使用原則:當知足一個有限的並具備統一主題的集合的時候,咱們就考慮使用枚舉。這在不少框架中都驗證了這個原則。最重要的是可以增長程序的可讀性。html
示例代碼:java
/** * 網絡類型 (須要封裝爲一個本身的枚舉) */ typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) { /** * 未知 */ AFNetworkReachabilityStatusUnknown = -1, /** * 無網絡 */ AFNetworkReachabilityStatusNotReachable = 0, /** * WWAN 手機自帶網絡 */ AFNetworkReachabilityStatusReachableViaWWAN = 1, /** * WiFi */ AFNetworkReachabilityStatusReachableViaWiFi = 2, };
2.註釋
咱們必須知道一個事實,註釋的代碼是不會編譯到目標文件的,所以放心大膽的註釋吧。在平日裏的開發中,應該常常問問本身是否把每段代碼都當成寫API那樣對待?ios
曾經看過兩種不一樣的說辭,一種是說把代碼註釋儘可能少些,要求代碼簡介可讀性強。另外一種是說註釋要詳細,着重考慮他人讀代碼的感覺。我的感受仍是寫詳 細一點比較好,由於可能過一段時間以後,本身再去看本身當時寫的代碼可能就不記得了。頗有可能在寫這些繁瑣的註釋的過程當中,可以想到些什麼,好比如何合併 掉一些不必的方法等等。nginx
示例代碼:git
/*! @header SCNetworkReachability @discussion The SCNetworkReachability API allows an application to determine the status of a system's current network configuration and the reachability of a target host. In addition, reachability can be monitored with notifications that are sent when the status has changed. "Reachability" reflects whether a data packet, sent by an application into the network stack, can leave the local computer. Note that reachability does <i>not</i> guarantee that the data packet will actually be received by the host. */ /*! @typedef SCNetworkReachabilityRef @discussion This is the handle to a network address or name. */ typedef const struct CF_BRIDGED_TYPE(id) __SCNetworkReachability * SCNetworkReachabilityRef;
3.BOOL屬性的property書寫規則
一般咱們在定義一個BOOL屬性的時候,要自定義getter方法,這樣作的目的是爲了增長程序的可讀性。Apple中的代碼也是這麼寫的。github
示例代碼:web
/** Whether or not the network is currently reachable. */ @property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable; // setter self.reachable = YES; // getter if (self.isReachable) {}
4.按功能區分代碼
假如咱們寫的一個控制器中大概有500行代碼,咱們應該保證可以快速的找到咱們須要查找的內容,這就須要把代碼按照功能來分隔。算法
一般在.h中 咱們可使用一個自定義的特殊的註釋來分隔,在.m中使用#pragma mark -
來分隔。編程
示例代碼:
///--------------------- /// @name Initialization ///--------------------- ///------------------------------ /// @name Evaluating Server Trust ///------------------------------ #pragma mark - UI ...設置UI相關 #pragma mark - Data ...處理數據 #pragma mark - Action ...點擊事件
5.通知
咱們都知道通知能夠用來傳遞事件和數據,但要想用好它,也不太容易。在 AFNetworking 事件和數據的傳遞使用的是通知和Block,按照AFNetworking對通知的使用習慣。我總結了幾點:
- 原則:若是咱們須要傳遞事件或數據,可採用代理和Block,同時額外增長一個通知。由於通知具備跨多個界面的優勢。
- 釋放問題:在接收通知的頁面,必定要記得移除監聽。
- 使用方法:在.h中
FOUNDATION_EXPORT
+NSString * const
+通知名
在.m中賦值。若是在別的頁面用到這個通知,使用extern
+NSString * const
+通知名
就能夠了。
ps: FOUNDATION_EXPORT 和#define 都能定義常量。FOUNDATION_EXPORT 可以使用==進行判斷,效率略高。並且可以隱藏定義細節(就是實現部分不在.中)
示例代碼:
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification; FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem; /** * 網絡環境發生改變的時候接受的通知 */ NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; /** * 網絡環境發生變化是會發送一個通知,同時攜帶一組狀態數據,根據這個key來去除網絡status */ NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem";
6.國際化的問題
我我的認爲在開發一個APP之初,就應該考慮國際化的問題,無論往後會不會用到這個功能。當你有了國際化的思想以後,在對控件進行佈局的時候,就會 比只在一種語言下考慮的更多,這會讓一我的對控件佈局的視野更加寬闊。好了,這個問題就說這麼多。有興趣的朋友請自行查找相關內容。
7.私有方法
在開發中,不免會使用私有方法來協助咱們達到某種目的或獲取某個數據。在oc中,我看到不少人都會這樣寫:- (void)funName {}
。我的是不同意這樣寫了,除非方法內部使用了self。總之,相似於這樣的方法,其實跟咱們的業務並無太大的關係。我進入一個控制器的文件中,目光應該集中在業務代碼上纔對。
在 AFNetworking 中,通常都會把私有方法,也能夠叫函數,放到頭部,你即便不看這些代碼,對於整個業務的理解也不會受到影響。因此,這種寫法值得推薦。能夠適當的使用內聯函數,提升效率.
示例代碼:
/** * 把枚舉的值轉換成字符串 */ NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusNotReachable: return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWWAN: return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWiFi: return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil); case AFNetworkReachabilityStatusUnknown: default: return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil); } } - (NSString *)AFStringFromNetworkReachabilityStatus:(AFNetworkReachabilityStatus)status { switch (status) { case AFNetworkReachabilityStatusNotReachable: return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWWAN: return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWiFi: return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil); case AFNetworkReachabilityStatusUnknown: default: return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil); } }
8.SCNetworkReachabilityRef(網絡監控核心實現)
SCNetworkReachabilityRef 是獲取網絡狀態的核心對象,建立這個對象有兩個方法:
- SCNetworkReachabilityCreateWithName
- SCNetworkReachabilityCreateWithAddress
咱們看看實現網絡監控的核心代碼:
示例代碼:
- (void)startMonitoring { [self stopMonitoring]; if (!self.networkReachability) { return; } __weak __typeof(self)weakSelf = self; AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } }; SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ SCNetworkReachabilityFlags flags; if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) { AFPostReachabilityStatusChange(flags, callback); } }); }
上邊的方法中涉及了一些 CoreFoundation 的知識,咱們來看看:
SCNetworkReachabilityContext點進去,會發現這是一個結構體,通常c語言的結構體是對要保存的數據的一種描述
示例代碼:
typedef struct { CFIndex version; void * __nullable info; const void * __nonnull (* __nullable retain)(const void *info); void (* __nullable release)(const void *info); CFStringRef __nonnull (* __nullable copyDescription)(const void *info); } SCNetworkReachabilityContext;
- 第一個參數接受一個signed long 的參數
- 第二個參數接受一個void * 類型的值,至關於oc的id類型,void * 能夠指向任何類型的參數
- 第三個參數 是一個函數 目的是對info作retain操做
- 第四個參數是一個函數,目的是對info作release操做
- 第五個參數是 一個函數,根據info獲取Description字符串
設置網絡監控分爲下邊幾個步驟:
1.咱們先新建上下文
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
2.設置回調
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
3.加入RunLoop池
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
9.鍵值依賴
註冊鍵值依賴,這個可能你們平時用的比較少。能夠了解一下。舉個例子:
好比說一個類User中有兩個屬性
還有一個卡片的類card
咱們寫一個info的setter 和 getter 方法,
這麼作的目的是,若是我監聽info這個屬性,當user中的name或者age有一個改變了,可以出發info的這個監聽事件。
示例代碼:
@interface User :NSObject @property (nonatomic,copy)NSString *name; @property (nonatomic,assign)NSUInteger age; @end @interface card :NSObject @property (nonatomic,copy)NSString *info; @property (nonatomic,strong)User *user; @end @implementation card - (NSString *)info { return [NSString stringWithFormat:@"%@/%lu",_user.name,(unsigned long)_user.age]; } - (void)setInfo:(NSString *)info { NSArray *array = [info componentsSeparatedByString:@"/"]; _user.name = array[0]; _user.age = [array[1] integerValue]; } + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet * keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; NSArray * moreKeyPaths = nil; if ([key isEqualToString:@"info"]) { moreKeyPaths = [NSArray arrayWithObjects:@"user.name", @"user.age", nil]; } if (moreKeyPaths) { keyPaths = [keyPaths setByAddingObjectsFromArray:moreKeyPaths]; } return keyPaths; } @end
10.HTTP
- HTTP協議用於客戶端和服務器端之間的通訊
- 經過請求和相應的交換達成通訊
- HTTP是不保存狀態的協議
- HTTP自身不會對請求和相應之間的通訊狀態進行保存。什麼意思呢?就是說,當有新的請求到來的時候,HTTP就會產生新的響應,對以前的請求和響應的保溫信息不作任何存儲。這也是爲了快速的處理事務,保持良好的可伸展性而特地設計成這樣的。
- 請求URI定位資源
- URI算是一個位置的索引,這樣就能很方便的訪問到互聯網上的各類資源。
- 告知服務器意圖的HTTP方法
- ①GET: 直接訪問URI識別的資源,也就是說根據URI來獲取資源。
- ②POST: 用來傳輸實體的主體。
- ③PUT: 用來傳輸文件。
- ④HEAD: 用來獲取報文首部,和GET方法差很少,只是響應部分不會返回主體內容。
- ⑤DELETE: 刪除文件,和PUT偏偏相反。按照請求的URI來刪除指定位置的資源。
- ⑥OPTIONS: 詢問支持的方法,用來查詢針對請求URI指定的資源支持的方法。
- ⑦TRACE: 追蹤路徑,返回服務器端以前的請求通訊環信息。
- ⑧CONNECT: 要求用隧道協議鏈接代理,要求在與代理服務器通訊時創建隧道,實現用隧道協議進行TCP通訊。SSL(Secure Sockets Layer)和TLS(Transport Layer Security)就是把通訊內容加密後進行隧道傳輸的。
- 管線化讓服務器具有了相應多個請求的能力
- Cookie讓HTTP有跡可循
11.HTTPS
HTTPS是一個通訊安全的解決方案,能夠說相對已經很是安全。爲何它會是一個很安全的協議呢?下邊會作出解釋。你們能夠看看這篇文章,解釋的頗有意思 。《簡單粗暴系列之HTTPS原理》.
HTTP + 加密 + 認證 + 完整性保護 = HTTPS
其實HTTPS是身披SSL外殼的HTTP,這句話怎麼理解呢?
你們應該都知道HTTP是應用層的協議,但HTTPS並不是是應用層的一種新協議,只是HTTP通訊接口部分用SSL或TLS協議代替而已。
一般 HTTP 直接和TCP通訊,當使用SSL時就不一樣了。要先和SSL通訊,再由SSL和TCP通訊。
這裏再說一些關於加密的題外話:
現現在,一般加密和解密的算法都是公開的。舉個例子: a * b = 200,加入a是你知道的密碼,b是須要被加密的數據,200 是加密後的結果。那麼這裏這個*號就是一個很簡單的加密算法。這個算法是如此簡單。可是若是想要在不知道a和b其中一個的狀況下進行破解也是很困難的。就 算咱們知道了200 而後獲得a b 這個也很難。假設知道了密碼a 那麼b就很容易算出b = 200 / a 。
實際中的加密算法比這個要複雜的多。
介紹兩種經常使用加密方法:
-
共享密鑰加密
-
公開密鑰加密
共享密鑰加密就是加密和解密通用一個密鑰,也稱爲對稱加密。優勢是加密解密速度快,缺點是一旦密鑰泄露,別人也能解密數據。
公開密鑰加密偏偏能解決共享密鑰加密的困難,過程是這樣的:
-
①發文方使用對方的公開密鑰進行加密
-
②接受方在使用本身的私有密鑰進行解密
關於公開密鑰,也就是非對稱加密 能夠看看這篇文章 RSA算法原理
原理都是同樣的,這個不一樣於剛纔舉得a和b的例子,就算知道告終果和公鑰,破解出被機密的數據是很是難的。這裏邊主要涉及到了複雜的數學理論。
HTTPS採用混合加密機制
HTTPS採用共享密鑰加密和公開密鑰加密二者並用的混合加密機制。
注意黃色的部分,這個指明瞭,咱們平時使用的一個場景。這篇文章會很長,不只僅是爲了解釋HTTPS,還爲了可以增長記憶,當往後想看看的時候,就能經過讀這邊文章想起大部分的HTTPS的知識。下邊解釋一些更加詳細的HTTPS過程。
12.如何獲取證書中的PublicKey
// 在證書中獲取公鑰 static id AFPublicKeyForCertificate(NSData *certificate) { id allowedPublicKey = nil; SecCertificateRef allowedCertificate; SecCertificateRef allowedCertificates[1]; CFArrayRef tempCertificates = nil; SecPolicyRef policy = nil; SecTrustRef allowedTrust = nil; SecTrustResultType result; // 1. 根據二進制的certificate生成SecCertificateRef類型的證書 // NSData *certificate 經過CoreFoundation (__bridge CFDataRef)轉換成 CFDataRef // 看下邊的這個方法就能夠知道須要傳遞參數的類型 /* SecCertificateRef SecCertificateCreateWithData(CFAllocatorRef __nullable allocator, CFDataRef data) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0); */ allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate); // 2.若是allowedCertificate爲空,則執行標記_out後邊的代碼 __Require_Quiet(allowedCertificate != NULL, _out); // 3.給allowedCertificates賦值 allowedCertificates[0] = allowedCertificate; // 4.新建CFArra: tempCertificates tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL); // 5. 新建policy爲X.509 policy = SecPolicyCreateBasicX509(); // 6.建立SecTrustRef對象,若是出錯就跳到_out標記處 __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out); // 7.校驗證書的過程,這個不是異步的。 __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out); // 8.在SecTrustRef對象中取出公鑰 allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust); _out: if (allowedTrust) { CFRelease(allowedTrust); } if (policy) { CFRelease(policy); } if (tempCertificates) { CFRelease(tempCertificates); } if (allowedCertificate) { CFRelease(allowedCertificate); } return allowedPublicKey; }
在二進制的文件中獲取公鑰的過程是這樣
- ① NSData *certificate -> CFDataRef -> (SecCertificateCreateWithData) -> SecCertificateRef allowedCertificate
- ②判斷SecCertificateRef allowedCertificate 是否是空,若是爲空,直接跳轉到後邊的代碼
- ③allowedCertificate 保存在allowedCertificates數組中
- ④allowedCertificates -> (CFArrayCreate) -> SecCertificateRef allowedCertificates[1]
- ⑤根據函數SecPolicyCreateBasicX509() -> SecPolicyRef policy
- ⑥SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust) -> 生成SecTrustRef allowedTrust
- ⑦SecTrustEvaluate(allowedTrust, &result) 校驗證書
- ⑧(__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust) -> 獲得公鑰id allowedPublicKey
這個過程咱們平時也不怎麼用,瞭解下就好了,真須要的時候知道去哪裏找資料就好了。
這裏邊值得學習的地方是:
__Require_Quiet 和 __Require_noErr_Quiet 這兩個宏定義。
咱們看看他們內部是怎麼定義的
能夠看出這個宏的用途是:當條件返回false時,執行標記之後的代碼
能夠看出這個宏的用途是:當條件拋出異常時,執行標記之後的代碼
這樣就有不少使用場景了。當必需要對條件進行判斷的時候,咱們有下邊幾種方案了
-
#ifdef
這個是編譯特性 -
if else
代碼層次的判斷 -
__Require_XXX
宏
_out 就是一個標記,這段代碼__Require_Quiet 到_out之間的代碼不會執行
13.URL編碼
關於什麼叫URI編碼和爲何要編碼,請看我轉載的這篇文章url 編碼(percentcode 百分號編碼)
根據RFC 3986的規定:URL百分比編碼的保留字段分爲:
- ':' '#' '[' ']' '@' '?' '/'
- '!' '$' '&' ''' '(' ')' '*' '+' ',' ';' '='
在對查詢字段百分比編碼時,'?'和'/'能夠不用編碼,其餘的都要進行編碼。我記得在使用支付寶支付時,在對數據進行URL編碼時要求編碼'/'.
NSString * AFPercentEscapedStringFromString(NSString *string) { static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4 static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;="; // '?'和'/'在query查詢容許不被轉譯,所以!$&'()*+,;=和:#[]@都要被轉譯,也就是在URLQueryAllowedCharacterSet中刪除掉這些字符 NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]]; // FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028 // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; static NSUInteger const batchSize = 50; NSUInteger index = 0; NSMutableString *escaped = @"".mutableCopy; while (index < string.length) { //http://www.jianshu.com/p/eb03e20f7b1c #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wgnu" NSUInteger length = MIN(string.length - index, batchSize); #pragma GCC diagnostic pop NSRange range = NSMakeRange(index, length); // To avoid breaking up character sequences such as 👴🏻👮🏽 range = [string rangeOfComposedCharacterSequencesForRange:range]; NSString *substring = [string substringWithRange:range]; NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; [escaped appendString:encoded]; index += range.length; } return escaped; }
上邊的這個方法能夠做爲URL編碼的通用方法,能夠直接使用,也能夠寫到NSString的分類中。YYModel就有這個方法。
這裏值得注意的是:
- 字符串須要通過過濾 ,過濾法則經過 NSMutableCharacterSet 實現。添加規則後,只對規則內的因子進行編碼。
- 爲了處理相似emoji這樣的字符串,rangeOfComposedCharacterSequencesForRange 使用了while循環來處理,也就是把字符串按照batchSize分割處理完再拼回。
14.HTTPBody
咱們有必要了解下請求提body的組成部分。先看下一個HTTTP請求是什麼樣的?
某app的一個登陸POST請求:
POST / HTTP/1.1 Host: log.nuomi.com Content-Type: multipart/form-data; boundary=Boundary+6D3E56AA6EAA83B7 Cookie: access_log=7bde65268e2260bb0a85c7de2c67c468; BAIDUID=428D86FDBA6028DE2A5496BE3E7FC308:FG=1; BAINUOCUID=4368e1b7499c455dcd437da336ca1ca9feb8f57d; BDUSS=Ecwa3NvN1NjNWhsVGxWZktFfkc2bzJxQjZ3RFJpTFBiUzZqZUJZU0ZTSmZsN0ZXQVFBQUFBJCQAAAAAAAAAAAEAAABxbLRYWXV1dXV3dXV1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF8KilZfCopWR; bn_na_copid=60139b4b2ba75706fc384d987c2e4007; bn_na_ctag=W3siayI6Imljb25fMSIsInMiOiJ0dWFuIiwidiI6IjMyNiIsInQiOiIxNDUxODg2OTE0In1d; channel=user_center%7C%7C; channel_content=; channel_webapp=webapp; condition=6.0.3; domainUrl=sh; na_qab=6be39bfce918bb7b51887412e009faa6; UID=1488219249 Connection: keep-alive Accept: */* User-Agent: Bainuo/6.1.0 (iPhone; iOS 9.0; Scale/2.00) Accept-Language: zh-Hans-CN;q=1, en-CN;q=0.9 Content-Length: 22207 Accept-Encoding: gzip, deflate --Boundary+6D3E56AA6EAA83B7 /// 開始 Content-Disposition: form-data; name="app_version" 6.1.0 --Boundary+6D3E56AA6EAA83B7
HTTP請求頭咱們就暫時不說了,看這個body的內容
--Boundary+6D3E56AA6EAA83B7 /// 開始 Content-Disposition: form-data; name="app_version" 6.1.0 --Boundary+6D3E56AA6EAA83B7
組成分爲4個部分: 1.初始邊界 2.body頭 3.body 4.結束邊界。 下邊就會用着這些知識。
15.保證方法在主線程執行
有時候咱們必需要確保某個方法在主線程調用,就可使用下邊的思路來作。
- (BOOL)transitionToNextPhase { // 保證代碼在主線程 if (![[NSThread currentThread] isMainThread]) { dispatch_sync(dispatch_get_main_queue(), ^{ [self transitionToNextPhase]; }); return YES; } }
16.代碼跟思想的碰撞
示例代碼:
- (BOOL)transitionToNextPhase { // 保證代碼在主線程 if (![[NSThread currentThread] isMainThread]) { dispatch_sync(dispatch_get_main_queue(), ^{ [self transitionToNextPhase]; }); return YES; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcovered-switch-default" switch (_phase) { case AFEncapsulationBoundaryPhase: _phase = AFHeaderPhase; break; case AFHeaderPhase: // 打開流,準備接受數據 [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [self.inputStream open]; _phase = AFBodyPhase; break; case AFBodyPhase: // 關閉流 [self.inputStream close]; _phase = AFFinalBoundaryPhase; break; case AFFinalBoundaryPhase: default: _phase = AFEncapsulationBoundaryPhase; break; } // 重置offset _phaseReadOffset = 0; #pragma clang diagnostic pop return YES; }
回過頭來看這段代碼,我又有新的想法。本來對數據的操做,對body的操做,是一件很複雜的事情。但做者的思路很是清晰。就像上邊這個方法同樣,它 只實現一個功能,就是切換body組成部分。它只作了這一件事,咱們在開發中,如遇到有些複雜的功能,在寫方法的時候,可能考慮了不少東西,當時全部的考 慮可能都寫到一個方法中了。
能不能寫出一個思路圖,先無論思路的實現如何,先一一列出來,最後在一一實現,一一拼接起來。
17.NSInputStream
NSInputStream有好幾種類型,根據不一樣的類型返回不一樣方法建立的NSInputStream
示例代碼:
- (NSInputStream *)inputStream { if (!_inputStream) { if ([self.body isKindOfClass:[NSData class]]) { _inputStream = [NSInputStream inputStreamWithData:self.body]; } else if ([self.body isKindOfClass:[NSURL class]]) { _inputStream = [NSInputStream inputStreamWithURL:self.body]; } else if ([self.body isKindOfClass:[NSInputStream class]]) { _inputStream = self.body; } else { _inputStream = [NSInputStream inputStreamWithData:[NSData data]]; } } return _inputStream; }
18.對文件的操做
- NSParameterAssert() 用來判斷參數是否爲空,若是爲空就拋出異常
- 使用isFileURL 判斷一個URL是否爲fileURL 使用checkResourceIsReachableAndReturnError判斷路徑可以到達
- 使用 [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error] 獲取本地文件屬性
- lastPathComponent ,https://www.baidu.com/abc.html 結果就是abc.html
- pathExtension https://www.baidu.com/abc.html 結果就是html
19.NSURLRequestCachePolicy緩存策略
這個要仔細介紹下,在某些特殊的場景下仍是能用到的。咱們點開NSURLRequestCachePolicy 能夠看到是一個枚舉值
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy) { NSURLRequestUseProtocolCachePolicy = 0, NSURLRequestReloadIgnoringLocalCacheData = 1, NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData, NSURLRequestReturnCacheDataElseLoad = 2, NSURLRequestReturnCacheDataDontLoad = 3, NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented };
NSURLRequestUseProtocolCachePolicy 這個是默認的緩存策略,緩存不存在,就請求服務器,緩存存在,會根據response中的Cache-Control字段判斷下一步操做,如: Cache-Control字段爲must-revalidata, 則詢問服務端該數據是否有更新,無更新的話直接返回給用戶緩存數據,若已更新,則請求服務端。
- NSURLRequestReloadIgnoringLocalCacheData 這個策略是無論有沒有本地緩存,都請求服務器。
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData 這個策略會忽略本地緩存和中間代理 直接訪問源server
- NSURLRequestReturnCacheDataElseLoad 這個策略指,有緩存就是用,無論其有效性,即Cache-Control字段 ,沒有就訪問源server
- NSURLRequestReturnCacheDataDontLoad 這個策略只加載本地數據,不作其餘操做,適用於沒有網路的狀況
- NSURLRequestReloadRevalidatingCacheData 這個策略標示緩存數據必須獲得服務器確認才能使用,未實現。
20.管線化
在HTTP鏈接中,通常都是一個請求對應一個鏈接,每次創建tcp鏈接是須要必定時間的。管線化,容許一次發送一組請求而沒必要等到相應。但因爲目前 並非全部的服務器都支持這項功能,所以這個屬性默認是不開啓的。管線化使用同一tcp鏈接完成任務,所以可以大大提交請求的時間。可是響應要和請求的順 序 保持一致才行。使用場景也有,好比說首頁要發送不少請求,能夠考慮這種技術。但前提是創建鏈接成功後纔可使用。
21.網絡服務類型NSURLRequestNetworkServiceType
示例代碼:
typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType) { NSURLNetworkServiceTypeDefault = 0, // Standard internet traffic NSURLNetworkServiceTypeVoIP = 1, // Voice over IP control traffic NSURLNetworkServiceTypeVideo = 2, // Video traffic NSURLNetworkServiceTypeBackground = 3, // Background traffic NSURLNetworkServiceTypeVoice = 4 // Voice data };
能夠經過這個值來指定當前的網絡類型,系統會跟據制定的網絡類型對不少方面進行優化,這個就設計到很細微的編程技巧了,可做爲一個優化的點備用。
22.Authorization字段
在請求頭中能夠添加Authorization字段。
Authorization: Basic YWRtaW46YWRtaW4= 其中Basic表示基礎認證,固然還有其餘認證,若是感興趣,能夠看看本文開始提出的那本書。後邊的YWRtaW46YWRtaW4= 是根據username:password 拼接後而後在通過Base64編碼後的結果。
若是header中有 Authorization這個字段,那麼服務器會驗證用戶名和密碼,若是不正確的話會返回401錯誤。
23.使用流寫數據
下邊的代碼能夠說是使用流寫數據的經典案例。可直接拿來使用。
示例代碼:
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request writingStreamContentsToFile:(NSURL *)fileURL completionHandler:(void (^)(NSError *error))handler { NSParameterAssert(request.HTTPBodyStream); NSParameterAssert([fileURL isFileURL]); // 加上上邊的兩個判斷,下邊的這些代碼就是把文件寫到另外一個地方的典型使用方法了 NSInputStream *inputStream = request.HTTPBodyStream; NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO]; __block NSError *error = nil; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inputStream open]; [outputStream open]; // 讀取數據 while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) { uint8_t buffer[1024]; NSInteger bytesRead = [inputStream read:buffer maxLength:1024]; if (inputStream.streamError || bytesRead < 0) { error = inputStream.streamError; break; } NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead]; if (outputStream.streamError || bytesWritten < 0) { error = outputStream.streamError; break; } if (bytesRead == 0 && bytesWritten == 0) { break; } } [outputStream close]; [inputStream close]; if (handler) { dispatch_async(dispatch_get_main_queue(), ^{ handler(error); }); } }); NSMutableURLRequest *mutableRequest = [request mutableCopy]; mutableRequest.HTTPBodyStream = nil; return mutableRequest; }
24.NSIndexSet
定義:NSIndexSet是一個有序的,惟一的,無符號整數的集合。
咱們先看個例子:
NSMutableIndexSet *indexSetM = [NSMutableIndexSet indexSet]; [indexSetM addIndex:19]; [indexSetM addIndex:4]; [indexSetM addIndex:6]; [indexSetM addIndex:8]; [indexSetM addIndexesInRange:NSMakeRange(20, 10)]; [indexSetM enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"%lu",idx); }];
打印結果以下:
2016-08-10 11:39:00.826 xxxx[3765:100078] 4 2016-08-10 11:39:00.827 xxxx[3765:100078] 6 2016-08-10 11:39:00.827 xxxx[3765:100078] 8 2016-08-10 11:39:00.827 xxxx[3765:100078] 19 2016-08-10 11:39:00.827 xxxx[3765:100078] 20 2016-08-10 11:39:00.828 xxxx[3765:100078] 21 2016-08-10 11:39:00.828 xxxx[3765:100078] 22 2016-08-10 11:39:00.828 xxxx[3765:100078] 23 2016-08-10 11:39:00.828 xxxx[3765:100078] 24 2016-08-10 11:39:00.828 xxxx[3765:100078] 25 2016-08-10 11:39:00.828 xxxx[3765:100078] 26 2016-08-10 11:39:00.828 xxxx[3765:100078] 27 2016-08-10 11:39:00.828 xxxx[3765:100078] 28 2016-08-10 11:39:00.829 xxxx[3765:100078] 29
這充分說明了一下幾點
- 它是一個無符號整數的集合
- 用addIndex方法能夠添加單個整數值,使用addIndexesInRange能夠添加一個範圍,好比NSMakeRange(20, 10) 取20包括20後邊十個整數
- 惟一性,集合內的整數不會重複出現
- 具體用法就再也不次作詳細的解釋了,有興趣的朋友能夠看看這篇文章: NSIndexSet 用法
25.NSUnderlyingErrorKey優先錯誤
當出現可能會有兩個錯誤的狀況下,能夠考慮使用NSUnderlyingErrorKey處理這種狀況。
示例代碼:
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) { if (!error) { return underlyingError; } if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) { return error; } NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy]; mutableUserInfo[NSUnderlyingErrorKey] = underlyingError; return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo]; }
26.NSJSONReadingOptions
這個選項能夠設置json的讀取選項,咱們點進去能夠看到:
typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) { NSJSONReadingMutableContainers = (1UL << 0), NSJSONReadingMutableLeaves = (1UL << 1), NSJSONReadingAllowFragments = (1UL << 2) } NS_ENUM_AVAILABLE(10_7, 5_0);
- NSJSONReadingMutableContainers 這個解析json成功後返回一個容器
- NSJSONReadingMutableLeaves 返回中的json對象中字符串爲NSMutableString
- NSJSONReadingAllowFragments 容許JSON字符串最外層既不是NSArray也不是NSDictionary,但必須是有效的JSON Fragment。例如使用這個選項能夠解析 @「123」 這樣的字符串
咱們舉個例子說明一下NSJSONReadingMutableContainers:
NSString *str = @"{\"name\":\"zhangsan\"}"; NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]; // 應用崩潰,不選用NSJSONReadingOptions,則返回的對象是不可變的,NSDictionary [dict setObject:@"male" forKey:@"sex"]; NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil]; // 沒問題,使用NSJSONReadingMutableContainers,則返回的對象是可變的,NSMutableDictionary [dict setObject:@"male" forKey:@"sex"]; NSLog(@"%@", dict);
若是服務器返回的json的最外層並非以NSArray 或者 NSDictionary ,而是一個有效的json fragment ,好比 就返回了一個@"abc"。 那麼咱們使用NSJSONReadingAllowFragments這個選項也可以解析出來。
27.圖片解壓雜談
AFJSONResponseSerializer使用系統內置的NSJSONSerialization解析json,NSJSON只支持解析 UTF8編碼的數據(還有UTF-16LE之類的,都不經常使用),因此要先把返回的數據轉成UTF8格式。這裏會嘗試用HTTP返回的編碼類型和本身設置的 stringEncoding去把數據解碼轉成字符串NSString,再把NSString用UTF8編碼轉成NSData,再用 NSJSONSerialization解析成對象返回。
上述過程是NSData->NSString->NSData->NSObject,這裏有個問題,若是你能肯定服務端返回的是 UTF8編碼的json數據,那NSData->NSString->NSData這兩步就是無心義的,並且這兩步進行了兩次編解碼,很浪費 性能,因此若是肯定服務端返回utf8編碼數據,就建議本身再寫個JSONResponseSerializer,跳過這兩個步驟。
此外AFJSONResponseSerializer專門寫了個方法去除NSNull,直接把對象裏值是NSNull的鍵去掉,還蠻貼心,若不去掉,上層很容易忽略了這個數據類型,判斷了數據是否nil沒判斷是否NSNull,進行了錯誤的調用致使core。
圖片解壓
當咱們調用UIImage的方法imageWithData:方法把數據轉成UIImage對象後,其實這時UIImage對象還沒準備好須要渲染 到屏幕的數據,如今的網絡圖像PNG和JPG都是壓縮格式,須要把它們解壓轉成bitmap後才能渲染到屏幕上,若是不作任何處理,當你把UIImage 賦給UIImageView,在渲染以前底層會判斷到UIImage對象未解壓,沒有bitmap數據,這時會在主線程對圖片進行解壓操做,再渲染到屏幕 上。這個解壓操做是比較耗時的,若是任由它在主線程作,可能會致使速度慢UI卡頓的問題。
AFImageResponseSerializer除了把返回數據解析成UIImage外,還會把圖像數據解壓,這個處理是在子線程 (AFNetworking專用的一條線程,詳見AFURLConnectionOperation),處理後上層使用返回的UIImage在主線程渲染 時就不須要作解壓這步操做,主線程減輕了負擔,減小了UI卡頓問題。
具體實現上在AFInflatedImageFromResponseWithDataAtScale裏,建立一個畫布,把UIImage畫在畫布 上,再把這個畫布保存成UIImage返回給上層。只有JPG和PNG纔會嘗試去作解壓操做,期間若是解壓失敗,或者遇到CMKY顏色格式的jpg,或者 圖像太大(解壓後的bitmap太佔內存,一個像素3-4字節,搞很差內存就爆掉了),就直接返回未解壓的圖像。
另外在代碼裏看到iOS才須要這樣手動解壓,MacOS上已經有封裝好的對象NSBitmapImageRep能夠作這個事。
關於圖片解壓,還有幾個問題不清楚:
1.原本覺得調用imageWithData方法只是持有了數據,沒有作解壓相關的事,後來看到調用堆棧發現已經作了一些解壓操做,從調用名字看進行了huffman解碼,不知還會繼續作到解碼jpg的哪一步。
UIImage_jpg
2.以上圖片手動解壓方式都是在CPU進行的,若是不進行手動解壓,把圖片放進layer裏,讓底層自動作這個事,是會用GPU進行的解壓的。不知 用GPU解壓與用CPU解壓速度會差多少,若是GPU速度很快,就算是在主線程作解壓,也變得能夠接受了,就不須要手動解壓這樣的優化了,不過目前沒找到 方法檢測GPU解壓的速度。
28.獲取系統版本
#ifndef NSFoundationVersionNumber_iOS_8_0 #define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11 #else #define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0 #endif
上邊的這個宏的目的是經過NSFoundation的版原本判斷當前ios版本,關鍵是這個宏的調試目標是IOS,來看看系統是怎麼定義的:
那麼咱們就可以聯想到,目前咱們可以判斷系統版本號的方法有幾種呢?最少三種:
- [UIDevice currentDevice].systemVersion
- 經過比較Foundation框架的版本號,iOS系統升級的同時Foundation框架的版本也會提升
- 經過在某系版本中新出現的方法來判斷,UIAlertController 這個類是iOS8以後纔出現的 NS_CLASS_AVAILABLE_IOS(8_0),若是當前系統版本沒有這個類 NSClassFromString(@"UIAlertController" == (null),從而判斷當前版本是否大於等於iOS8
29.dispatch_queue_create()
AFNetworking中全部的和建立任務相關的事件都放到了一個單例的隊列中,咱們平時可能會使用這些方法,但仍是可能會忽略一些內 容,dispatch_queue_create()這個是隊列的方法,第一個參數是隊列的identifier,第二個參數則表示這個隊列是串行隊列還 是並行隊列。
若是第二個參數爲DISPATCH_QUEUE_SERIAL或NULL 則表示隊列爲串行隊列。若是爲DISPATCH_QUEUE_CONCURRENT則表示是並行隊列。
關於隊列的小的知識點,參考了這篇文章 Objective C 高級進階— GCD隊列淺析(一).
30.dispatch_block_t
這個方法還有一個小知識點:dispatch_block_t ,點擊去能夠看到:
typedef void (^dispatch_block_t)(void);
關於這個Block咱們應該注意幾點:
- 非ARC狀況下,Block被allocated或者copied到堆後,必定要記得釋放它,經過[release]或者Block_release()
- 非ARC狀況下,Block被allocated或者copied到堆後,必定要記得釋放它,經過[release]或者Block_release()
31.NSKeyValueObservingOptions
- NSKeyValueObservingOptionNew 把更改以前的值提供給處理方法
- NSKeyValueObservingOptionOld 把更改以後的值提供給處理方法
- NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦註冊,立馬就會調用一次。一般它會帶有新值,而不會帶有舊值
- NSKeyValueObservingOptionPrior 分2次調用。在值改變以前和值改變以後
32.task delegate出發問題
task一共有4個delegate,只要設置了一個,就表明四個所有設置,有時候一些delegate不會被觸發的緣由在於這四種 delegate是針對不一樣的URLSession類型和URLSessionTask類型來進行響應的,也就是說不一樣的類型只會觸發這些 delegate中的一部分,而不是觸發全部的delegate。
舉例說明以下:
-
觸發NSURLSessionDataDelegate
//使用函數dataTask來接收數據 -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data //則NSURLSession部分的代碼以下 NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"]; NSURLSessionDataTask* dataTask=[session dataTaskWithURL:url]; [dataTask resume];
-
觸發NSURLSessionDownloadDelegate
//使用函數downloadTask來接受數據 -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location //則NSURLSession部分的代碼以下 NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"]; NSURLSessionDownloadTask* dataTask=[session downloadTaskWithURL:url]; [dataTask resume];
這兩段代碼的主要區別在於NSURLSessionTask的類型的不一樣,形成了不一樣的Delegate被觸發.
33.@unionOfArrays
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
這麼使用以前確實不太知道,若是是我,可能就直接賦值給數組了。那麼@unionOfArrays.self又是什麼意思呢?
- @distinctUnionOfObjects 清楚重複值
- unionOfObjects 保留重複值
34.相對路徑(relative)
假若有一個基礎路徑NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
咱們暫時命名爲baseURL.所謂 相對 確定跟這個baseURL有關係
咱們能夠經過 NSURL +URLWithString:relativeToUL:
這個方法來獲取一個路徑,至於怎麼使用,咱們經過一個例子來講明:
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"]; [NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz [NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo [NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo [NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/ [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
至於 絕對路徑 和 相對路徑 的定義,使用方法,優缺點,這裏就不提了,你們能夠自行了解。說一下爲何使用相對路徑吧。
在真實開發中,通常都會有一個線上的服務器和一下測試服務器,固然也可能多個。在ios開發中切換開發環境有好幾種方法
- 經過target
- 自定義一個字段HTTPURL,用它來控制路徑
- 經過相對路徑來切換接口
35.dispatch_barrier_async
barrier 這個單詞的意思是障礙,攔截的意思,也便是說 dispatch_barrier_async 必定是有攔截事件的做用。
看下邊這段代碼:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-1"); }); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-2"); }); dispatch_barrier_async(concurrentQueue, ^(){ NSLog(@"dispatch-barrier"); }); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-3"); }); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-4"); });
打印結果:
2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-1 2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2 2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier 2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3 2016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4
這個說明了 dispatch_barrier_async 可以攔截它前邊的異步事件,等待兩個異步方法都完成以後,調用 dispatch_barrier_async。
咱們稍微改動一下:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-1"); }); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-2"); }); dispatch_barrier_sync(concurrentQueue, ^(){ NSLog(@"dispatch-barrier"); }); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-3"); }); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-4"); });
打印結果:
2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-1 2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2 2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier 2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3 2016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4
36.dispatch_sync() 和 dispatch_async()
大概說下 dispatch_sync() 和 dispatch_async() 這兩個方法。
示例代碼:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); dispatch_sync(concurrentQueue, ^(){ NSLog(@"2"); [NSThread sleepForTimeInterval:5]; NSLog(@"3"); }); NSLog(@"4");
輸出爲:
2016-08-25 11:50:51.601 xxxx[1353:102804] 1 2016-08-25 11:50:51.601 xxxx[1353:102804] 2 2016-08-25 11:50:56.603 xxxx[1353:102804] 3 2016-08-25 11:50:56.603 xxxx[1353:102804] 4
再看:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); dispatch_async(concurrentQueue, ^(){ NSLog(@"2"); [NSThread sleepForTimeInterval:5]; NSLog(@"3"); }); NSLog(@"4");
輸出爲:
2016-08-25 11:52:29.022 xxxx[1392:104246] 1 2016-08-25 11:52:29.023 xxxx[1392:104246] 4 2016-08-25 11:52:29.023 xxxx[1392:104284] 2 2016-08-25 11:52:34.029 xxxx[1392:104284] 3
經過上邊的兩個例子,咱們能夠總結出:
- dispatch_sync(),同步添加操做。他是等待添加進隊列裏面的操做完成以後再繼續執行
- dispatch_async ,異步添加進任務隊列,它不會作任何等待
37.NSURLCache
咱們簡單介紹下NSURLCache。NSURLCache 爲您的應用的 URL 請求提供了內存中以及磁盤上的綜合緩存機制。網絡緩存減小了須要向服務器發送請求的次數,同時也提高了離線或在低速網絡中使用應用的體驗。當一個請求完成 下載來自服務器的迴應,一個緩存的迴應將在本地保存。下一次同一個請求再發起時,本地保存的迴應就會立刻返回,不須要鏈接服務器。NSURLCache 會 自動 且 透明 地返回迴應。
爲了好好利用 NSURLCache,你須要初始化並設置一個共享的 URL 緩存。在 iOS 中這項工做須要在 -application:didFinishLaunchingWithOptions: 完成,而 OS X 中是在 –applicationDidFinishLaunching::
示例代碼:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil]; [NSURLCache setSharedURLCache:URLCache]; }
NSURLRequest 有個 cachePolicy 屬性,咱們平時最經常使用的有四個屬性:
- NSURLRequestUseProtocolCachePolicy: 對特定的 URL 請求使用網絡協議中實現的緩存邏輯。這是默認的策略
- NSURLRequestReloadIgnoringLocalCacheData:數據須要從原始地址加載。不使用現有緩存。
- NSURLRequestReturnCacheDataElseLoad:不管緩存是否過時,先使用本地緩存數據。若是緩存中沒有請求所對應的數據,那麼從原始地址加載數據
- NSURLRequestReturnCacheDataDontLoad:不管緩存是否過時,先使用本地緩存數據。若是緩存中沒有請求所對應的數據,那麼放棄從原始地址加載數據,請求視爲失敗(即:「離線」模式)。
38.@synchronized()鎖
synchronized是一種鎖,這種鎖無論是在oc中仍是java中用的都挺多的,並且這種鎖鎖得是對象。具體原理,能夠看這篇文章後邊的 參考 那一部分。
總結一下,鎖通常用於多線程環境下對數據的操做中。在 AFNetworking 中咱們見到了3種不一樣的鎖,分別是:
NSLock
dispatch_semaphore_wait
@synchronized
39.UIWebView+AFNetworking
UIWebView的這個分類是這幾個分類中最讓我驚訝的一個。讓我真正認識到條條大路通羅馬究竟是什麼意思。有時候人的思想確實會被固有的思惟所束縛。這裏只是用了UIWebView 的loadData:(NSData )data MIMEType:(NSString )MIMEType textEncodingName:(NSString )textEncodingName baseURL:(NSURL )baseURL方法
你會發現使用這個分類配合UIWebView,全部的事情都變得很簡單。
示例代碼
- (void)loadRequest:(NSURLRequest *)request MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success failure:(void (^)(NSError *error))failure { // 檢查參數 NSParameterAssert(request); // 若是正處於運行或者暫停裝狀態,就取消以前的任務task並設置爲nil if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) { [self.af_URLSessionTask cancel]; } self.af_URLSessionTask = nil; __weak __typeof(self)weakSelf = self; NSURLSessionDataTask *dataTask; dataTask = [self.sessionManager GET:request.URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { __strong __typeof(weakSelf) strongSelf = weakSelf; // 請求成功後,調用success block if (success) { success((NSHTTPURLResponse *)task.response, responseObject); } // 顯示數據 [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[task.currentRequest URL]]; // 調用webViewDidFinishLoad if ([strongSelf.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { [strongSelf.delegate webViewDidFinishLoad:strongSelf]; } } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) { if (failure) { failure(error); } }]; self.af_URLSessionTask = dataTask; // 設置progress,這個來自於self.sessionManager if (progress != nil) { *progress = [self.sessionManager downloadProgressForTask:dataTask]; } // 開啓任務 [self.af_URLSessionTask resume]; // 調用webViewDidStartLoad方法 if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { [self.delegate webViewDidStartLoad:self]; } }
總結
AFNetworking 的源碼解讀到此就結束了。我曾經說要寫一個網絡框架,但隨着對網絡的更進一步的瞭解。一個好的框架不是隨隨便便寫的。須要對使用者負責。所以我又找了幾個目前比較流行的框架,對他們的思想研究研究。實屬拿來主義。