發(fā)現(xiàn)寫博客想寫明白也是一件不容易的事情。
這次拿YYKIt 源碼 分析分析。希望這次能寫的更好些。
YYKit 系列
我們根據(jù)YYKit 系列文件夾分類
yykit文件夾一共有Utility ,Text,Image,Cache,Model,Base
我們先分析Utility 文件夾
1Utility文件夾
1.YYReachability
這個類主要是檢測當(dāng)前網(wǎng)絡(luò)狀態(tài)的
1結(jié)構(gòu)
結(jié)構(gòu)比較簡單,就幾個參數(shù)。綠色是public 屬性, 紫色是private屬性
public property
flag 是當(dāng)前網(wǎng)絡(luò)的flag
status是?YYReachabilityStatus ?檢測是無網(wǎng)絡(luò)還是WWAN 或者wifi
wwanStatus 是判斷要是WWAN網(wǎng)絡(luò)檢測是2G 還是3G 還是4G
reachable 是標(biāo)志位。
notifyBlock 一個回調(diào)block
private property
SCNetworkReachabilityRef 網(wǎng)絡(luò)句柄指針
scheduled ?
allowWWAN ? 這個參數(shù)只對當(dāng)?shù)豾ifi網(wǎng)絡(luò)有作用
CTTelephonyNetworkInfo 網(wǎng)絡(luò)信息
typedef NS_ENUM(NSUInteger, YYReachabilityStatus) {
YYReachabilityStatusNone? = 0, ///< Not Reachable
YYReachabilityStatusWWAN? = 1, ///< Reachable via WWAN (2G/3G/4G)
YYReachabilityStatusWiFi? = 2, ///< Reachable via WiFi
};
typedef NS_ENUM(NSUInteger, YYReachabilityWWANStatus) {
YYReachabilityWWANStatusNone? = 0, ///< Not Reachable vis WWAN
YYReachabilityWWANStatus2G = 2, ///< Reachable via 2G (GPRS/EDGE)? ? ? 10~100Kbps
YYReachabilityWWANStatus3G = 3, ///< Reachable via 3G (WCDMA/HSDPA/...) 1~10Mbps
YYReachabilityWWANStatus4G = 4, ///< Reachable via 4G (eHRPD/LTE)? ? ? 100Mbps
};
這兩個枚舉就是規(guī)定當(dāng)前網(wǎng)絡(luò)狀態(tài)。
2初始化
/// Create an object to check the reachability of the default route.
+ (instancetype)reachability;
/// Create an object to check the reachability of the local WI-FI.
+ (instancetype)reachabilityForLocalWifi DEPRECATED_MSG_ATTRIBUTE("unnecessary and potentially harmful");
/// Create an object to check the reachability of a given host name.
+ (nullable instancetype)reachabilityWithHostname:(NSString *)hostname;
/// Create an object to check the reachability of a given IP address
/// @param hostAddress You may pass `struct sockaddr_in` for IPv4 address or `struct sockaddr_in6` for IPv6 address.
+ (nullable instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress;
初始化有四個自定義的初始化方法和一個init初始化方法
最終都調(diào)用到了- (instancetype)initWithRef:(SCNetworkReachabilityRef)ref 這個初始化方法
- (instancetype)initWithRef:(SCNetworkReachabilityRef)ref {
if (!ref) return nil;
self = super.init;
if (!self) return nil;
_ref = ref;
_allowWWAN = YES;
if (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_7_0) {
_networkInfo = [CTTelephonyNetworkInfo new];
}
return self;
}
這個函數(shù)比較簡單。都能看懂。無非就是其他函數(shù)傳入的傳入的SCNetworkReachabilityRef 不同罷了。
接下來看看SCNetworkReachabilityRef 在不同的初始化函數(shù)中是怎么初始化的。
在?- (instancetype)init 方法中
struct sockaddr_in zero_addr;
bzero(&zero_addr, sizeof(zero_addr));
zero_addr.sin_len = sizeof(zero_addr);
zero_addr.sin_family = AF_INET;
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&zero_addr);
通過C函數(shù)?SCNetworkReachabilityCreateWithAddress 創(chuàng)建SCNetworkReachabilityRef 傳入的是sockaddr_in 結(jié)構(gòu)體。
要求的結(jié)構(gòu)體是sockaddr * 類型指針,我們傳入的是sockaddr_in ,其實(shí)這兩個結(jié)構(gòu)體。這是因為sockaddr_in 結(jié)構(gòu)和sockaddr_in * 內(nèi)存結(jié)構(gòu)一樣。
bzero 函數(shù)將?zero_addr 全部清0 ,這里有個主意的地方。
The address 0.0.0.0, which reachability treats as a special token that
causes it to actually monitor the general routing status of the device,
both IPv4 and IPv6.
/*
See Apple's Reachability implementation and readme:
The address 0.0.0.0, which reachability treats as a special token that
causes it to actually monitor the general routing status of the device,
both IPv4 and IPv6.
https://developer.apple.com/library/ios/samplecode/Reachability/Listings/ReadMe_md.html#//apple_ref/doc/uid/DTS40007324-ReadMe_md-DontLinkElementID_11
*/
將ip 地址改成0.0.0.0 ,可以監(jiān)控ipv4 和ipv6
我們選擇的是監(jiān)控AF_INET ?。這個是ipv4 地址族。
+ (instancetype)reachability 調(diào)用的就是init 初始化方法
+ (instancetype)reachabilityForLocalWifi?
struct sockaddr_in localWifiAddress;
bzero(&localWifiAddress, sizeof(localWifiAddress));
localWifiAddress.sin_len = sizeof(localWifiAddress);
localWifiAddress.sin_family = AF_INET;
localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
這里有個?htonl 函數(shù)。這個函數(shù)的作用是將主機(jī)數(shù)轉(zhuǎn)換成無符號長整型的網(wǎng)絡(luò)字節(jié)順序
這了有個ip地址IN_LINKLOCALNETNUM ?
Apps that have a specific requirement can use reachabilityWithAddress to monitor IN_LINKLOCALNETNUM (that is, 169.254.0.0).
Note: ONLY apps that have a specific requirement should be monitoring IN_LINKLOCALNETNUM.? For the overwhelming majority of apps, monitoring this address is unnecessary and potentially harmful.
+ (instancetype)reachabilityWithHostname:(NSString *)hostname
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
這個函數(shù)中調(diào)用?SCNetworkReachabilityCreateWithName 將hostName 生成一個ref
hostName 怎么是什么呢?舉例說明:"www.baidu.com" ?"https://www.baidu.com"
+ (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress?
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)hostAddress);
這個就是根據(jù)具體的ip地址來查詢當(dāng)前網(wǎng)絡(luò)情況啦
3其他
接下來看怎么獲取網(wǎng)路狀態(tài)的。
初始化完成后,獲取網(wǎng)絡(luò)狀態(tài)都是懶加載方式進(jìn)行的
- (SCNetworkReachabilityFlags)flags
SCNetworkReachabilityFlags flags = 0;
SCNetworkReachabilityGetFlags(self.ref, &flags);
return flags;
直接調(diào)用函數(shù)SCNetworkReachabilityGetFlags 獲取就可以了
- (YYReachabilityStatus)status
return YYReachabilityStatusFromFlags(self.flags, self.allowWWAN);
這個調(diào)用C函數(shù)
static YYReachabilityStatus YYReachabilityStatusFromFlags(SCNetworkReachabilityFlags flags, BOOL allowWWAN) {
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) {
return YYReachabilityStatusNone;
}
if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & kSCNetworkReachabilityFlagsTransientConnection)) {
return YYReachabilityStatusNone;
}
if ((flags & kSCNetworkReachabilityFlagsIsWWAN) && allowWWAN) {
return YYReachabilityStatusWWAN;
}
return YYReachabilityStatusWiFi;
}
這個函數(shù)就是將flag 轉(zhuǎn)換成相應(yīng)的網(wǎng)絡(luò)狀態(tài)。
這里主要要看SCNetworkReachabilityFlags的所有參數(shù)了,看看每個參數(shù)具體指示什么。
typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) {
kSCNetworkReachabilityFlagsTransientConnection = 1<<0,
kSCNetworkReachabilityFlagsReachable = 1<<1,
kSCNetworkReachabilityFlagsConnectionRequired = 1<<2,
kSCNetworkReachabilityFlagsConnectionOnTraffic = 1<<3,
kSCNetworkReachabilityFlagsInterventionRequired = 1<<4,
kSCNetworkReachabilityFlagsConnectionOnDemand = 1<<5, // __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_0)
kSCNetworkReachabilityFlagsIsLocalAddress = 1<<16,
kSCNetworkReachabilityFlagsIsDirect = 1<<17,
#if TARGET_OS_IPHONE
kSCNetworkReachabilityFlagsIsWWAN = 1<<18,
#endif // TARGET_OS_IPHONE
kSCNetworkReachabilityFlagsConnectionAutomatic = kSCNetworkReachabilityFlagsConnectionOnTraffic
};
這里有篇文章 講解這些參數(shù)
摘錄文章部分內(nèi)容
kSCNetworkReachabilityFlagsReachable表明當(dāng)前指定的節(jié)點(diǎn)或地址是可達(dá)的。注意:可達(dá)不是代表節(jié)點(diǎn)或地址接受到了數(shù)據(jù),而是代表數(shù)據(jù)能夠離開本地,因此。就算是可達(dá)的,也不一定能夠發(fā)送成功
kSCNetworkReachabilityFlagsConnectionRequired表明要想和指定的節(jié)點(diǎn)或地址通信,需要先建立連接。比如說撥號上網(wǎng)。注意:對于手機(jī)來說,如果沒有返回該標(biāo)記,就說明手機(jī)正在使用蜂窩網(wǎng)路或者WiFi
kSCNetworkReachabilityFlagsConnectionOnTraffic表明要想和指定的節(jié)點(diǎn)或地址通信,必須先建立連接,但是在當(dāng)前的網(wǎng)絡(luò)配置下,目標(biāo)是可達(dá)的。注意:任何連接到指定的節(jié)點(diǎn)或地址的請求都會觸發(fā)該標(biāo)記,舉個例子,在很多地方需要輸入手機(jī),獲取驗證碼后才能聯(lián)網(wǎng),就是這個原理
kSCNetworkReachabilityFlagsConnectionOnDemand表明要想和指定的節(jié)點(diǎn)或地址通信,必須先建立連接,但是在當(dāng)前的網(wǎng)絡(luò)配置下,目標(biāo)是可達(dá)的。但是建立連接必須通過CFSocketStream APIs才行,其他的APIs不能建立連接
kSCNetworkReachabilityFlagsInterventionRequired表明要想和指定的節(jié)點(diǎn)或地址通信,必須先建立連接,但是在當(dāng)前的網(wǎng)絡(luò)配置下,目標(biāo)是可達(dá)的。需要用戶手動提供一些數(shù)據(jù),比如密碼或者token
kSCNetworkReachabilityFlagsIsWWAN表明是不是通過蜂窩網(wǎng)絡(luò)連接
這個函數(shù)判斷節(jié)點(diǎn)或者地址是否可達(dá)。不可達(dá)就直接返回未知網(wǎng)絡(luò)
在檢查要是當(dāng)前網(wǎng)絡(luò)必須連接,并且是kSCNetworkReachabilityFlagsTransientConnection 鏈接。那么未知網(wǎng)絡(luò) (就是必須通過類似撥號連接的,網(wǎng)絡(luò)未知,其實(shí)是這里沒有做嚴(yán)格區(qū)分,)
要是kSCNetworkReachabilityFlagsIsWWAN 網(wǎng)絡(luò)。那就是wwan 。手機(jī)專用
蘋果寫的方法是
- (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags
{
PrintReachabilityFlags(flags, "networkStatusForFlags");
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
{
// The target host is not reachable.
return NotReachable;
}
NetworkStatus returnValue = NotReachable;
if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
{
/*
If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi...
*/
returnValue = ReachableViaWiFi;
}
if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
{
/*
... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs...
*/
if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
{
/*
... and no [user] intervention is needed...
*/
returnValue = ReachableViaWiFi;
}
}
if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
{
/*
... but WWAN connections are OK if the calling application is using the CFNetwork APIs.
*/
returnValue = ReachableViaWWAN;
}
return returnValue;
}
我認(rèn)為這樣檢查更合理。
wwan網(wǎng)絡(luò)好判斷,只要是wwan網(wǎng)絡(luò),直接返回的是kSCNetworkReachabilityFlagsIsWWAN 。
不可達(dá)的認(rèn)為是未知網(wǎng)絡(luò)。
可達(dá)中但是要用戶需要輸入的信息的排除wifi 。其他的情況都是wifi。
我用4G 共享熱點(diǎn)測試,返回的狀態(tài)是?kSCNetworkReachabilityFlagsReachable
- (YYReachabilityWWANStatus)wwanStatus
if (!self.networkInfo) return YYReachabilityWWANStatusNone;
NSString *status = self.networkInfo.currentRadioAccessTechnology;
if (!status) return YYReachabilityWWANStatusNone;
static NSDictionary *dic;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dic = @{CTRadioAccessTechnologyGPRS : @(YYReachabilityWWANStatus2G),? // 2.5G? 171Kbps
CTRadioAccessTechnologyEdge : @(YYReachabilityWWANStatus2G),? // 2.75G? 384Kbps
CTRadioAccessTechnologyWCDMA : @(YYReachabilityWWANStatus3G), // 3G? ? 3.6Mbps/384Kbps
CTRadioAccessTechnologyHSDPA : @(YYReachabilityWWANStatus3G), // 3.5G? 14.4Mbps/384Kbps
CTRadioAccessTechnologyHSUPA : @(YYReachabilityWWANStatus3G), // 3.75G? 14.4Mbps/5.76Mbps
CTRadioAccessTechnologyCDMA1x : @(YYReachabilityWWANStatus3G), // 2.5G
CTRadioAccessTechnologyCDMAEVDORev0 : @(YYReachabilityWWANStatus3G),
CTRadioAccessTechnologyCDMAEVDORevA : @(YYReachabilityWWANStatus3G),
CTRadioAccessTechnologyCDMAEVDORevB : @(YYReachabilityWWANStatus3G),
CTRadioAccessTechnologyeHRPD : @(YYReachabilityWWANStatus3G),
CTRadioAccessTechnologyLTE : @(YYReachabilityWWANStatus4G)}; // LTE:3.9G 150M/75M? LTE-Advanced:4G 300M/150M
});
NSNumber *num = dic[status];
if (num != nil) return num.unsignedIntegerValue;
else return YYReachabilityWWANStatusNone;
檢測wwan 是什么信號,必須依賴coreTelephony.frame 庫
這個庫在ios 7 以后才有
這里我們要搞明白的事情是Radio Access
/*
* Radio Access Technology values
*/
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyGPRS? ? ? ? ? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyEdge? ? ? ? ? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyWCDMA? ? ? ? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyHSDPA? ? ? ? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyHSUPA? ? ? ? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMA1x? ? ? ? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMAEVDORev0? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMAEVDORevA? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMAEVDORevB? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyeHRPD? ? ? ? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyLTE? ? ? ? ? __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
其實(shí)2G 3G 4G給人帶來的感受就是網(wǎng)絡(luò)速度的變化。
我認(rèn)為這張圖比較有利說明。
具體就不做講解了。
- (BOOL)isReachable
return self.status != YYReachabilityStatusNone;
只要不是?YYReachabilityStatusNone 都是可達(dá)的。
- (void)setNotifyBlock:(void (^)(YYReachability *reachability))notifyBlock?
_notifyBlock = [notifyBlock copy];
self.scheduled = (self.notifyBlock != nil);
這里通過判斷是否有notifyBlock 來開啟?scheduled
- (void)setScheduled:(BOOL)scheduled {
if (_scheduled == scheduled) return;
_scheduled = scheduled;
if (scheduled) {
SCNetworkReachabilityContext context = { 0, (__bridge void *)self, NULL, NULL, NULL };
SCNetworkReachabilitySetCallback(self.ref, YYReachabilityCallback, &context);
SCNetworkReachabilitySetDispatchQueue(self.ref, [self.class sharedQueue]);
} else {
SCNetworkReachabilitySetDispatchQueue(self.ref, NULL);
}
}
static void YYReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
YYReachability *self = ((__bridge YYReachability *)info);
if (self.notifyBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.notifyBlock(self);
});
}
}
這里的queue 是一個單例。
這里yykit大神用的是SCNetworkReachabilitySetDispatchQueue 異步回調(diào)。
而官方的寫法是
- (BOOL)startNotifier
{
BOOL returnValue = NO;
SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context))
{
if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
{
returnValue = YES;
}
}
return returnValue;
}
運(yùn)用的是RunLoop 模式。持續(xù)監(jiān)控網(wǎng)絡(luò)狀態(tài)。
兩種方式都可以,runloop 一般是主線程監(jiān)控。而yykit 是在serial queue 隊列中監(jiān)控變化
看看這個文件的宏定義
NS_ASSUME_NONNULL_BEGIN &&?NS_ASSUME_NONNULL_END 成對出現(xiàn),這樣的每個屬性或每個方法都不用特別去指定nonnull和nullable,沒有警告
NS_AVAILABLE_IOS (7.0) 這個宏經(jīng)常見,ios版本7.0以后有效
DEPRECATED_MSG_ATTRIBUTE 這個宏是干嘛的呢
找到源文件
#if defined(__has_feature) && defined(__has_attribute)
#if __has_attribute(deprecated)
#define DEPRECATED_ATTRIBUTE? ? ? ? __attribute__((deprecated))
#if __has_feature(attribute_deprecated_with_message)
#define DEPRECATED_MSG_ATTRIBUTE(s) __attribute__((deprecated(s)))
#else
#define DEPRECATED_MSG_ATTRIBUTE(s) __attribute__((deprecated))
#endif
#else
#define DEPRECATED_ATTRIBUTE
#define DEPRECATED_MSG_ATTRIBUTE(s)
#endif
#endif
這里主要看__attribute__((deprecated))
這里有篇文章 講解這個的用法的
__attribute__((deprecated))是gcc用來標(biāo)記function/method棄用的方式(同樣適用于clang)
粘貼下用法。
普通函數(shù)的語法
__attribute__((deprecated))
void f(...) {
...
}
// gcc 4.5+ / clang
__attribute__((deprecated("g has been deprecated please use g2 instead")))
void g(...) {
...
}
Objective-C的語法
// 棄用一個方法
@interface MyClass : NSObject { ... }
-(void)f:(id)x __attribute__((deprecated));
...
@end
// 棄用一個類
__attribute__((deprecated))
@interface DeprecatedClass : NSObject { ... }
...
@end
當(dāng)然你也可以使用更具有可讀性的DEPRECATED_ATTRIBUTE
在usr/include/AvailabilityMacros.h,蘋果定義了兩個宏
#define DEPRECATED_ATTRIBUTE? ? ? ? __attribute__((deprecated))
#define DEPRECATED_MSG_ATTRIBUTE(msg) __attribute((deprecated((msg))))
示例:
// 棄用一個方法
@interface MyClass : NSObject { ... }
-(void)foo:(id)x DEPRECATED_ATTRIBUTE;
// 棄用一個方法,并指定一個提示信息
-(void)bar:(id)x DEPRECATED_MSG_ATTRIBUTE("Use baz: method instead.");
...
@end
// 棄用一個類
DEPRECATED_ATTRIBUTE
@interface DeprecatedClass : NSObject { ... }
...
@end
2YYGestureRecognizer
<1>結(jié)構(gòu)
繼承UIGestureRecognizer? ?增加四個屬性,startPoint ,lastPoint?currentPoint,還有一個block
typedef NS_ENUM(NSUInteger, YYGestureRecognizerState) {
YYGestureRecognizerStateBegan, ///< gesture start
YYGestureRecognizerStateMoved, ///< gesture moved
YYGestureRecognizerStateEnded, ///< gesture end
YYGestureRecognizerStateCancelled, ///< gesture cancel
};
枚舉了yygestureRecognizerstate 四種狀態(tài)
<2>初始化
沿用父類的初始化方法。
<3>public 方法
只有一個方法
- (void)cancel;
<4>override方法
這個類主要是是override 方法多一共四個
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event ;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event ;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event ;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event ;
- (void)reset;
源碼實(shí)現(xiàn)起來簡單。這里不做介紹
3.YYFileHash
這個是對文件進(jìn)行hash
我們這里主要看加密算法,這里有十個加密算法。md2 md4 md5 sha1 sha224 sha256 sha384 sha512 crc32 ?adler32
前面八個都是用的系統(tǒng)的init ?update final 函數(shù)進(jìn)行運(yùn)算的。而后面兩個是自己定義的api 為了配合定義的宏定義init_hash(Type,Init,Update,Final,Length)
這里有幾個c數(shù)組。
int hash_type_total = 10;
void *ctx[hash_type_total];
int(*ctx_init[hash_type_total])(void *);
int(*ctx_update[hash_type_total])(void *, const void *, CC_LONG);
int(*ctx_final[hash_type_total])(unsigned char *, void *);
long digist_length[hash_type_total];
unsigned char *digest[hash_type_total];
ctx 數(shù)組裝的是 void * 類型的指針
ctx_init 數(shù)組裝的是 int ()(void *) 類型的指針
ctx_update 數(shù)組裝的是 int ()(void *, const void *, CC_LONG) 類型指針
ctx_final 裝的是int()(unsigned char *, void *) 類型的指針
看懂這里再往下看。
到#undef init_hash ?行以前就是給這些數(shù)組賦值。這些數(shù)組保存的是函數(shù)指針
這里我們先學(xué)習(xí)下 md5 ?用CC_MD5_Init?CC_MD5_Update?CC_MD5_Final 使用
NSFileHandle?*handle=?[NSFileHandle?fileHandleForReadingAtPath:path];
if(handle==?nil?)?{
return?nil;
}
CC_MD5_CTX?md5;
CC_MD5_Init(&md5);
BOOLdone=NO;
while(!done)
{
NSData*fileData=?[handle?readDataOfLength:?256?];
CC_MD5_Update(&md5,?[fileData?bytes],?[fileData?length]);
if(?[fileData?length]?==?0?)done=YES;
}
unsigned?char?digest[CC_MD5_DIGEST_LENGTH];
CC_MD5_Final(digest,?&md5);
NSString*s=?[NSString?stringWithFormat:?@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[0],?digest[1],
digest[2],?digest[3],
digest[4],?digest[5],
digest[6],?digest[7],
digest[8],?digest[9],
digest[10],?digest[11],
digest[12],?digest[13],
digest[14],?digest[15]];
這就是用法。我們看看yykit 大神怎么在這個里面干嘛。想要看懂這個函數(shù)。要了解c語言文件操作的幾個函數(shù)。
函數(shù)原型:FILE * fopen(const char * path, const char * mode); 就是打開文件
?int fseeko(FILE *stream, off_t offset, int fromwhere);
參數(shù):
stream:文件指針
fromwhere:偏移起始位置
offset:偏移量
功能:函數(shù)設(shè)置文件指針stream的位置。如果執(zhí)行成功,stream將指向以fromwhere(偏移起始位置:文件頭0(SEEK_SET),當(dāng)前位置1(SEEK_CUR),文件尾2(SEEK_END))為基準(zhǔn),偏移offset(指針偏移量)個字節(jié)的位置。如果執(zhí)行失敗(比如offset超過文件自身大小),則不改變stream指向的位置。
ftell?
函數(shù) ftell 用于得到文件位置指針當(dāng)前位置相對于文件首的偏移字節(jié)數(shù)。
size_tfread (void*buffer,size_tsize,size_tcount,FILE*stream) ;
buffer
用于接收數(shù)據(jù)的內(nèi)存地址
size
要讀的每個數(shù)據(jù)項的字節(jié)數(shù),單位是字節(jié)
count
要讀count個數(shù)據(jù)項,每個數(shù)據(jù)項size個字節(jié).
stream
輸入流
int feof(FILE *stream);
參數(shù)
流 :FILE結(jié)構(gòu)的指針
feof是C語言標(biāo)準(zhǔn)庫函數(shù),其原型在stdio.h中,其功能是檢測流上的文件結(jié)束符,如果文件結(jié)束,則返回非0值,否則返回0,文件結(jié)束符只能被clearerr()清除。
fd = fopen(path, "rb");
if (!fd) goto cleanup;
if (fseeko(fd, 0, SEEK_END) != 0) goto cleanup;
file_size = ftell(fd);
if (fseeko(fd, 0, SEEK_SET) != 0) goto cleanup;
if (file_size < 0) goto cleanup;
yykit 大神寫的這段代碼的意思 就是讀取文件大小。
if (block) {
while (!done && !stop) {
size_t size = fread(buf, 1, BUF_SIZE, fd);
if (size < BUF_SIZE) {
if (feof(fd)) done = YES;? ? // finish
else { stop = YES; break; }? // error
}
for (int i = 0; i < hash_type_total; i++) {
if (ctx[i]) ctx_update[i](ctx[i], buf, (CC_LONG)size);
}
readed += size;
if (!done) {
loop++;
if ((loop % BLOCK_LOOP_FACTOR) == 0) {
block(file_size, readed, &stop);
}
}
}
}
要是配置block 的話。讀取文件每次都去?BUF_SIZE =512k大小 將數(shù)據(jù)更新到update函數(shù)中
這里檢查loop 次數(shù)。要是讀取數(shù)據(jù)大于8M的話就回調(diào)一下block。
以md5加密為例最終結(jié)構(gòu)都更新到CC_MD5_Update 函數(shù)中。
沒有block 就是不用回調(diào)而已。用法一樣不做介紹
最后就是收集數(shù)據(jù)了。
ctx_final[i](digest[i], ctx[i]); 已md5 為例。這個地方就是調(diào)用CC_MD5_Final()函數(shù)
NSUInteger type = 1 << i;
NSData *data = [NSData dataWithBytes:digest[i] length:digist_length[i]];
NSMutableString *str = [NSMutableString string];
unsigned char *bytes = (unsigned char *)data.bytes;
for (NSUInteger d = 0; d < data.length; d++) {
[str appendFormat:@"%02x", bytes[d]];
}
轉(zhuǎn)換成最后的結(jié)果。
將結(jié)果保存到相關(guān)屬性里面。
這里我們主要是要看看crc32 ?和Adler32 類型的算法
crc32 用法
Usage example:
uLong crc = crc32(0L, Z_NULL, 0);
while (read_buffer(buffer, length) != EOF) {
crc = crc32(crc, buffer, length);
}
if (crc != original_crc) error();
yykit大神將其拆解成 init update final 形式
adler32用法和 crc32 用法相同。
我們這里不對每個算法做原理分析和使用場景分析。
4YYKeychain
最近堅持每天看一點(diǎn)yykit大神的代碼,要是單純看,好多地方都不去去注意的。而寫博客,會強(qiáng)制自己必須看懂每一個小的地方。
與yykeyChain 相關(guān)的類是YYKeychainItem
我們先看YYKeychainItem
官網(wǎng)解釋
Keychain items come in a variety of classes according to the kind of data they hold, such as passwords, cryptographic keys, and certificates. The item's class dictates which attributes apply and enables the system to decide whether or not the data should be encrypted on disk. For example, passwords obviously require encryption, but certificates don't because they are not secret.
Use the key and one of the corresponding values listed here to specify the class for a new item you create with a call to theSecItemAddfunction by placing the key/value pair in theattributesdictionary.
Later, use this same pair in thequerydictionary when searching for an item with one of theSecItemCopyMatching,SecItemUpdate, orSecItemDeletefunctions.
從官方文檔我們知道kSecClass?有一下幾種模式
Values you use with thekSecClasskey.
The value that indicates a generic password item.
The value that indicates an Internet password item.
The value that indicates a certificate item.
The value that indicates a cryptographic key item.
The value that indicates an identity item.
yykit 選擇的是?kSecClassGenericPassword?數(shù)據(jù)是加密的
而kSecClassGenericPassword?對應(yīng)的所有屬性有
Discussion
The following keychain item attributes apply to an item of this class:
kSecAttrAccess(macOS only)
kSecAttrAccessGroup(iOS; also macOS ifkSecAttrSynchronizablespecified)
kSecAttrAccessible(iOS; also macOS ifkSecAttrSynchronizablespecified)
YYKeychainItem 模型對應(yīng)上述屬性
<1>YYKeychainItem 結(jié)構(gòu)
這個結(jié)構(gòu)有15個變量。并且實(shí)現(xiàn)NSCopying 協(xié)議
modificationDate 和creationDate對于外部是不可修改的。內(nèi)部是標(biāo)記是可以修改的。這樣主要方便修改變量。
<2>YYKeychainItem 屬性方法
這里我們發(fā)現(xiàn)屬性passwordObject 和 ?password 變量都是將傳入的object 轉(zhuǎn)換成data的
- (NSMutableDictionary *)queryDic
這個字典里放入了 key?kSecClass ?,kSecAttrAccount,kSecAttrService,kSecAttrAccessGroup,kSecAttrSynchronizable
- (NSMutableDictionary *)dic
這個字典放入的數(shù)據(jù)更多點(diǎn)?keykSecClass ?,kSecAttrAccount,kSecAttrService,kSecAttrAccessGroup,kSecAttrSynchronizable,kSecAttrLabel,kSecAttrAccessible,kSecValueData,kSecAttrType,kSecAttrCreator,kSecAttrComment,kSecAttrDescription
這里面沒有創(chuàng)建日期或者修改日期
- (instancetype)initWithDic:(NSDictionary *)dic
這里給變量賦值。有創(chuàng)建日期或修改日期
- (id)copyWithZone:(NSZone *)zone 實(shí)現(xiàn)NScopying協(xié)議
- (NSString *)description 覆蓋這個方法。打印輸出。
這個類很簡單。就是需要操作的數(shù)據(jù)。
<3>YYKeychain 結(jié)構(gòu)
這個類沒有新增變量。
typedef NS_ENUM (NSUInteger, YYKeychainErrorCode) {
YYKeychainErrorUnimplemented = 1, ///< Function or operation not implemented.
YYKeychainErrorIO, ///< I/O error (bummers)
YYKeychainErrorOpWr, ///< File already open with with write permission.
YYKeychainErrorParam, ///< One or more parameters passed to a function where not valid.
YYKeychainErrorAllocate, ///< Failed to allocate memory.
YYKeychainErrorUserCancelled, ///< User cancelled the operation.
YYKeychainErrorBadReq, ///< Bad parameter or invalid state for operation.
YYKeychainErrorInternalComponent, ///< Internal...
YYKeychainErrorNotAvailable, ///< No keychain is available. You may need to restart your computer.
YYKeychainErrorDuplicateItem, ///< The specified item already exists in the keychain.
YYKeychainErrorItemNotFound, ///< The specified item could not be found in the keychain.
YYKeychainErrorInteractionNotAllowed, ///< User interaction is not allowed.
YYKeychainErrorDecode, ///< Unable to decode the provided data.
YYKeychainErrorAuthFailed, ///< The user name or passphrase you entered is not.
};
typedef NS_ENUM (NSUInteger, YYKeychainAccessible) {
YYKeychainAccessibleNone = 0, ///< no value
/** Item data can only be accessed
while the device is unlocked. This is recommended for items that only
need be accesible while the application is in the foreground.? Items
with this attribute will migrate to a new device when using encrypted
backups. */
YYKeychainAccessibleWhenUnlocked,
/** Item data can only be
accessed once the device has been unlocked after a restart.? This is
recommended for items that need to be accesible by background
applications. Items with this attribute will migrate to a new device
when using encrypted backups.*/
YYKeychainAccessibleAfterFirstUnlock,
/** Item data can always be accessed
regardless of the lock state of the device.? This is not recommended
for anything except system use. Items with this attribute will migrate
to a new device when using encrypted backups.*/
YYKeychainAccessibleAlways,
/** Item data can
only be accessed while the device is unlocked. This class is only
available if a passcode is set on the device. This is recommended for
items that only need to be accessible while the application is in the
foreground. Items with this attribute will never migrate to a new
device, so after a backup is restored to a new device, these items
will be missing. No items can be stored in this class on devices
without a passcode. Disabling the device passcode will cause all
items in this class to be deleted.*/
YYKeychainAccessibleWhenPasscodeSetThisDeviceOnly,
/** Item data can only
be accessed while the device is unlocked. This is recommended for items
that only need be accesible while the application is in the foreground.
Items with this attribute will never migrate to a new device, so after
a backup is restored to a new device, these items will be missing. */
YYKeychainAccessibleWhenUnlockedThisDeviceOnly,
/** Item data can
only be accessed once the device has been unlocked after a restart.
This is recommended for items that need to be accessible by background
applications. Items with this attribute will never migrate to a new
device, so after a backup is restored to a new device these items will
be missing.*/
YYKeychainAccessibleAfterFirstUnlockThisDeviceOnly,
/** Item data can always
be accessed regardless of the lock state of the device.? This option
is not recommended for anything except system use. Items with this
attribute will never migrate to a new device, so after a backup is
restored to a new device, these items will be missing.*/
YYKeychainAccessibleAlwaysThisDeviceOnly,
};
/**
Whether the item in question can be synchronized.
*/
typedef NS_ENUM (NSUInteger, YYKeychainQuerySynchronizationMode) {
/** Default, Don't care for synchronization? */
YYKeychainQuerySynchronizationModeAny = 0,
/** Is not synchronized */
YYKeychainQuerySynchronizationModeNo,
/** To add a new item which can be synced to other devices, or to obtain
synchronized results from a query*/
YYKeychainQuerySynchronizationModeYes,
} NS_AVAILABLE_IOS (7_0);
這里規(guī)定了好多枚舉。暫時不做介紹。等下面介紹函數(shù)的時候在說。
<4>YYKeychain 方法
這個類類似操作數(shù)據(jù)庫管理類。因此就有類似數(shù)據(jù)庫的證刪改查
1.+ (BOOL)insertItem:(YYKeychainItem *)item error:(NSError **)error?
沒有數(shù)據(jù)就不用操作數(shù)據(jù),所以第一步我們先看增加數(shù)據(jù)
+ (BOOL)insertItem:(YYKeychainItem *)item error:(NSError **)error {
if (!item.service || !item.account || !item.passwordData) {
if (error) *error = [YYKeychain errorWithCode:errSecParam];
return NO;
}
NSMutableDictionary *query = [item dic];
OSStatus status = status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (status != errSecSuccess) {
if (error) *error = [YYKeychain errorWithCode:status];
return NO;
}
return YES;
}
增加數(shù)據(jù)必須要有service?account 和?passwordData 沒有這三個是無法將數(shù)據(jù)插入到keychain中。
再看看改
+ (BOOL)updateItem:(YYKeychainItem *)item error:(NSError **)error {
if (!item.service || !item.account || !item.passwordData) {
if (error) *error = [YYKeychain errorWithCode:errSecParam];
return NO;
}
NSMutableDictionary *query = [item queryDic];
NSMutableDictionary *update = [item dic];
[update removeObjectForKey:(__bridge id)kSecClass];
if (!query || !update) return NO;
OSStatus status = status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
if (status != errSecSuccess) {
if (error) *error = [YYKeychain errorWithCode:status];
return NO;}
return YES;
}
其實(shí)就是調(diào)用?SecItemUpdate 方法進(jìn)行update 不能含有kSecClass key
看看查
+ (YYKeychainItem *)selectOneItem:(YYKeychainItem *)item error:(NSError **)error {
if (!item.service || !item.account) {
if (error) *error = [YYKeychain errorWithCode:errSecParam];
return nil;
}
NSMutableDictionary *query = [item dic];
query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
query[(__bridge id)kSecReturnAttributes] = @YES;
query[(__bridge id)kSecReturnData] = @YES;
OSStatus status;
CFTypeRef result = NULL;
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
if (status != errSecSuccess) {
if (error) *error = [[self class] errorWithCode:status];
return nil;
}
if (!result) return nil;
NSDictionary *dic = nil;
if (CFGetTypeID(result) == CFDictionaryGetTypeID()) {
dic = (__bridge NSDictionary *)(result);
} else if (CFGetTypeID(result) == CFArrayGetTypeID()){
dic = [(__bridge NSArray *)(result) firstObject];
if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
}
if (!dic.count) return nil;
return [[YYKeychainItem alloc] initWithDic:dic];
}
這里的查詢字典多了三個屬性
query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
query[(__bridge id)kSecReturnAttributes] = @YES;
query[(__bridge id)kSecReturnData] = @YES;
并且返回的數(shù)據(jù)是用的函數(shù)SecItemCopyMatching
可能是DIC 也可能是Array 對其進(jìn)行比較,獲取最紅數(shù)據(jù)
現(xiàn)在看看刪除
+ (BOOL)deleteItem:(YYKeychainItem *)item error:(NSError **)error {
if (!item.service || !item.account) {
if (error) *error = [YYKeychain errorWithCode:errSecParam];
return NO;
}
NSMutableDictionary *query = [item dic];
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status != errSecSuccess) {
if (error) *error = [YYKeychain errorWithCode:status];
return NO;
}
return YES;
}
調(diào)用?SecItemDelete 刪除。
其他的api都是對這四個方法的二次包裝而已。不做解釋
5.YYWeakProxy
這個類繼承NSProxy?
NSProxy 實(shí)現(xiàn)了NSObject協(xié)議,主要用來轉(zhuǎn)發(fā)消息用的。
6.YYTimer
yytimer 是用GCD實(shí)現(xiàn)的一個timer api是仿照nstimer 的。
這里我們學(xué)習(xí)下GCD 怎么創(chuàng)建計時器
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, (start * NSEC_PER_SEC)), (interval * NSEC_PER_SEC), 0);
dispatch_source_set_event_handler(_source, ^{[_self fire];});
dispatch_resume(_source);
1.dispatch_source_create 創(chuàng)建一個source ?
2.dispatch_source_set_timer 設(shè)置計時器的開始時間和間隔時間回調(diào)
3.dispatch_source_set_event_handler 設(shè)置source 回調(diào)的block?
4 將source 掛起。
這里的計時器是在主線程里面執(zhí)行的
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);
這里有個宏定義 ?看看寫法就可以了
其他的地方就是簡單的判斷。
不過這里獲取的屬性都是給lock住的。這樣鎖住不會導(dǎo)致變量發(fā)生變化。
7.YYTransaction
我們先看類結(jié)構(gòu)
1結(jié)構(gòu)
這個類結(jié)構(gòu)比較簡單,就是一個target 和selector 不過這里的selector 是assign 屬性
2 public method
1+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector;
2.- (void)commit;
第一個方法是類方法 ,第二個是實(shí)例方法
+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{
if (!target || !selector) return nil;
YYTransaction *t = [YYTransaction new];
t.target = target;
t.selector = selector;
return t;
}
這個就是檢測target 或者 selector 有一個為nil 就返回nil 。別的生成實(shí)例 ,并保存這兩個屬性
- (void)commit {
if (!_target || !_selector) return;
YYTransactionSetup();
[transactionSet addObject:self];
}
?這里有個c函數(shù) YYTransactionSetup()
static void YYTransactionSetup() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
transactionSet = [NSMutableSet new];
CFRunLoopRef runloop = CFRunLoopGetMain();
CFRunLoopObserverRef observer;
observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
kCFRunLoopBeforeWaiting | kCFRunLoopExit,
true,? ? ? // repeat
0xFFFFFF,? // after CATransaction(2000000)
YYRunLoopObserverCallBack, NULL);
CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
CFRelease(observer);
});
}
這里生成 的是一個單例,實(shí)例化一個對象transactionSet ,并且創(chuàng)建一個CFRunLoopObserverRef 對象,觀察kCFRunLoopBeforeWaiting | kCFRunLoopExit 狀態(tài)
。回調(diào)函數(shù)是YYRunLoopObserverCallBack
static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
if (transactionSet.count == 0) return;
NSSet *currentSet = transactionSet;
transactionSet = [NSMutableSet new];
[currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[transaction.target performSelector:transaction.selector];
#pragma clang diagnostic pop
}];
}
每次runloop 循環(huán)一次,都將這次的target調(diào)用sel方法。并且重置transactionSet。
3 override method方法
- (NSUInteger)hash {
long v1 = (long)((void *)_selector);
long v2 = (long)_target;
return v1 ^ v2;
}
- (BOOL)isEqual:(id)object {
if (self == object) return YES;
if (![object isMemberOfClass:self.class]) return NO;
YYTransaction *other = object;
return other.selector == _selector && other.target == _target;
}
- (NSUInteger)hash 方法是返回的target 和sel 都是指針,將指針轉(zhuǎn)換成了long 進(jìn)行^ 操作
- (BOOL)isEqual:(id)object 方法簡單不做說明
這個類一旦生成runloop 觀察就始終啟動,在每次runloop 結(jié)束時候進(jìn)行注冊方法調(diào)用。只調(diào)用一次,就清空
8YYSentinel
其實(shí)這個類就是對?OSAtomicIncrement32 封裝
博客?有對OSAtomicIncrement32 簡單講解,就是線程安全的增加引用計數(shù)。
9YYDispatchQueuePool
這個類是個線程池
1public method
1.- (instancetype)init UNAVAILABLE_ATTRIBUTE;
2.+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
3.- (instancetype)initWithName:(nullable NSString *)name queueCount:(NSUInteger)queueCount qos:(NSQualityOfService)qos;
4.- (dispatch_queue_t)queue;
5.+ (instancetype)defaultPoolForQOS:(NSQualityOfService)qos;
前面兩個方法是unavailable 的。我們從第三個開始看起
- (instancetype)initWithName:(NSString *)name queueCount:(NSUInteger)queueCount qos:(NSQualityOfService)qos {
if (queueCount == 0 || queueCount > MAX_QUEUE_COUNT) return nil;
self = [super init];
_context = YYDispatchContextCreate(name.UTF8String, (uint32_t)queueCount, qos);
if (!_context) return nil;
_name = name;
return self;
}
簡單初始化,這里規(guī)定了最大的線程隊列數(shù)量不能超過MAX_QUEUE_COUNT
關(guān)鍵c函數(shù)
static YYDispatchContext *YYDispatchContextCreate(const char *name,
uint32_t queueCount,
NSQualityOfService qos) {
YYDispatchContext *context = calloc(1, sizeof(YYDispatchContext));
if (!context) return NULL;
context->queues =? calloc(queueCount, sizeof(void *));
if (!context->queues) {
free(context);
return NULL;
}
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
dispatch_qos_class_t qosClass = NSQualityOfServiceToQOSClass(qos);
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, qosClass, 0);
dispatch_queue_t queue = dispatch_queue_create(name, attr);
context->queues[i] = (__bridge_retained void *)(queue);
}
} else {
long identifier = NSQualityOfServiceToDispatchPriority(qos);
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_t queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue, dispatch_get_global_queue(identifier, 0));
context->queues[i] = (__bridge_retained void *)(queue);
}
}
context->queueCount = queueCount;
if (name) {
context->name = strdup(name);
}
return context;
}
這個函數(shù)有三個參數(shù),第一個name:線程池民名字,第二個queueCount 線程池中的線程,第三個是NSQualityOfService?
這里有個結(jié)構(gòu)體?YYDispatchContext 指針
typedef struct {
const char *name;
void **queues;
uint32_t queueCount;
int32_t counter;
} YYDispatchContext;
我們看看如何初始化的
1.YYDispatchContext *context = calloc(1, sizeof(YYDispatchContext)); 在堆上生成context 結(jié)構(gòu)體
2.context->queues = calloc(queueCount, sizeof(void *)); 給context 結(jié)構(gòu)體的queues 賦值一個堆區(qū)地址。這個分配的堆區(qū)大小是queueCount 個指針大小的區(qū)域,以后用來存放生成的queue
由于ios8 以后,gcd 提供了新的api 所以這里做了版本區(qū)分。
這里有部分介紹NSQualityOfService? 這里不做過多介紹
在ios8 以后 將NSQualityOfService?轉(zhuǎn)換成GCD 的?qos_class_t ?
在ios8 以前轉(zhuǎn)換將?NSQualityOfService?轉(zhuǎn)換成dispatch_queue_priority_t
在ios8 以前dispatch_set_target_queue(queue, dispatch_get_global_queue(identifier, 0));目的是設(shè)置優(yōu)先級的。
這里還有個name賦值
strdup 用法
功 能: 將串拷貝到新建的位置處
strdup()在內(nèi)部調(diào)用了malloc()為變量分配內(nèi)存,不需要使用返回的字符串時,需要用free()釋放相應(yīng)的內(nèi)存空間,否則會造成內(nèi)存泄漏。
NSQualityOfServiceUserInteractive
與用戶交互的任務(wù),這些任務(wù)通常跟UI級別的刷新相關(guān),比如動畫,這些任務(wù)需要在一瞬間完成
NSQualityOfServiceUserInitiated
由用戶發(fā)起的并且需要立即得到結(jié)果的任務(wù),比如滑動scroll view時去加載數(shù)據(jù)用于后續(xù)cell的顯示,這些任務(wù)通常跟后續(xù)的用戶交互相關(guān),在幾秒或者更短的時間內(nèi)完成
NSQualityOfServiceUtility
一些可能需要花點(diǎn)時間的任務(wù),這些任務(wù)不需要馬上返回結(jié)果,比如下載的任務(wù),這些任務(wù)可能花費(fèi)幾秒或者幾分鐘的時間
NSQualityOfServiceBackground
這些任務(wù)對用戶不可見,比如后臺進(jìn)行備份的操作,這些任務(wù)可能需要較長的時間,幾分鐘甚至幾個小時
NSQualityOfServiceDefault
優(yōu)先級介于user-initiated 和 utility,當(dāng)沒有 QoS信息時默認(rèn)使用,開發(fā)者不應(yīng)該使用這個值來設(shè)置自己的任務(wù)
看?- (dispatch_queue_t)queue
- (dispatch_queue_t)queue {
return YYDispatchContextGetQueue(_context);
}
調(diào)用下面的函數(shù)
static dispatch_queue_t YYDispatchContextGetQueue(YYDispatchContext *context) {
uint32_t counter = (uint32_t)OSAtomicIncrement32(&context->counter);
void *queue = context->queues[counter % context->queueCount];
return (__bridge dispatch_queue_t)(queue);
}
這里用到OSAtomicIncrement32 線程安全的引用計數(shù)。這樣保證每次獲取的queue 是一次遞增的。不停循環(huán)
+ (instancetype)defaultPoolForQOS:(NSQualityOfService)qos
這個函數(shù)其實(shí)就是生成不同的NSQualityOfService?YYDispatchQueuePool 線程池。都是單例
dispatch_queue_t YYDispatchQueueGetForQOS(NSQualityOfService qos) {
return YYDispatchContextGetQueue(YYDispatchContextGetForQOS(qos));
}
static YYDispatchContext *YYDispatchContextGetForQOS(NSQualityOfService qos) {
static YYDispatchContext *context[5] = {0};
switch (qos) {
case NSQualityOfServiceUserInteractive: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[0] = YYDispatchContextCreate("com.ibireme.yykit.user-interactive", count, qos);
});
return context[0];
} break;
case NSQualityOfServiceUserInitiated: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[1] = YYDispatchContextCreate("com.ibireme.yykit.user-initiated", count, qos);
});
return context[1];
} break;
case NSQualityOfServiceUtility: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[2] = YYDispatchContextCreate("com.ibireme.yykit.utility", count, qos);
});
return context[2];
} break;
case NSQualityOfServiceBackground: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[3] = YYDispatchContextCreate("com.ibireme.yykit.background", count, qos);
});
return context[3];
} break;
case NSQualityOfServiceDefault:
default: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[4] = YYDispatchContextCreate("com.ibireme.yykit.default", count, qos);
});
return context[4];
} break;
}
}
通過調(diào)用YYDispatchQueueGetForQOS c函數(shù)。YYDispatchQueueGetForQOS函數(shù)調(diào)用YYDispatchContextGetForQOS c 函數(shù)
YYDispatchContextGetForQOS 這個函數(shù)也是生成五個不同等級的NSQualityOfService 線程組。只不過每個的數(shù)量是根據(jù)內(nèi)核決定的。
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
是快速獲取一個?dispatch_queue_t 的一個方法。一旦生成了dispatch_queue_t 就不能釋放掉了。
10YYThreadSafeArray
其實(shí)安全數(shù)組這里源碼比較簡單。繼承NSMutableArray
將NSMutableArray的大多數(shù)方法都加鎖重寫 而已
#define INIT(...) self = super.init; \
if (!self) return nil; \
__VA_ARGS__; \
if (!_arr) return nil; \
_lock = dispatch_semaphore_create(1); \
return self;
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);
每個數(shù)組都綁定了一個dispatch_semaphore_t 信號
11YYThreadSafeDictionary
安全字典和數(shù)組一樣的實(shí)現(xiàn)。不做過多介紹
12.YYAsyncLayer
這個類看類名字我們知道是異步繪制layer
1.?YYAsyncLayer 繼承CALayer
2.YYAsyncLayer 新增屬性displaysAsynchronously 默認(rèn)是YES。變量_sentinel 控制原子級別的引用計數(shù)(遞增)
+ (id)defaultValueForKey:(NSString *)key {
if ([key isEqualToString:@"displaysAsynchronously"]) {
return @(YES);
} else {
return [super defaultValueForKey:key];
}
}
3 通過override?- (void)setNeedsDisplay 方法來比較layer 是否需要取消上次繪制
4?通過 override ?- (void)display 方法來給繪制內(nèi)容
屬性和變量都比較簡單。我們看看實(shí)現(xiàn)- (void)setNeedsDisplay
- (void)setNeedsDisplay {
[self _cancelAsyncDisplay];
[super setNeedsDisplay];
}
這里調(diào)用_cancelAsyncDisplay
- (void)_cancelAsyncDisplay {
[_sentinel increase];
}
實(shí)現(xiàn)?_sentinel 遞增 標(biāo)記這次繪制取消,進(jìn)行下次繪制
關(guān)鍵代碼是
- (void)display {
super.contents = super.contents;
[self _displayAsync:_displaysAsynchronously];
}
我們分析下- (void)_displayAsync:(BOOL)async 方法
在這個方法中有個新的類YYAsyncLayerDisplayTask?
YYAsyncLayerDisplayTask 有三個block
@property (nullable, nonatomic, copy) void (^willDisplay)(CALayer *layer);
@property (nullable, nonatomic, copy) void (^display)(CGContextRef context, CGSize size, BOOL(^isCancelled)(void));
@property (nullable, nonatomic, copy) void dDisplay)(CALayer *layer, BOOL finished);
三個block 的作用是?
willDisplay 在繪制前用戶需要做的事情
display 是用戶用獲取的?context進(jìn)行繪制?
didDisplay 是獲取context 圖片用戶進(jìn)行展示。
回到函數(shù)- (void)_displayAsync:(BOOL)async 中 看代碼
__strong iddelegate = (id)self.delegate;
YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
if (!task.display) {
if (task.willDisplay) task.willDisplay(self);
self.contents = nil;
if (task.didDisplay) task.didDisplay(self, YES);
return;
}
通過代理獲取YYAsyncLayerDisplayTask 對象。要是該對象沒有設(shè)置display。就調(diào)用下task.willDisplay 和?task.didDisplay ,并且將self.contents 清空
下面關(guān)鍵代碼異步繪制簡單邏輯
if (task.willDisplay) task.willDisplay(self);
dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
task.display(context, size, isCancelled);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.contents = (__bridge id)(image.CGImage);
if (task.didDisplay) task.didDisplay(self, YES);
});
}
由于快到文章上限了,所以。就簡單羅列下要點(diǎn)
willDisplay ?在main 隊列中做繪制前 的準(zhǔn)備工作
display 要是異步的話,就將到Y(jié)YAsyncLayerGetDisplayQueue 獲取的queue 中進(jìn)行繪制。因為context 繪制可以在異步繪制的。而顯示必須在主線程中進(jìn)行。
didDisplay 將獲取的image 到主線程展示。