发现写博客想写明白也是一件不容易的事情。
这次拿YYKIt 源码 分析分析。希望这次能写的更好些。
YYKit 系列
我们根据YYKit 系列文件夹分类
yykit文件夹一共有Utility ,Text,Image,Cache,Model,Base
我们先分析Utility 文件夹
1Utility文件夹
1.YYReachability
这个类主要是检测当前网络状态的
1结构
结构比较简单,就几个参数。绿色是public 属性, 紫色是private属性
public property
flag 是当前网络的flag
status是 YYReachabilityStatus 检测是无网络还是WWAN 或者wifi
wwanStatus 是判断要是WWAN网络检测是2G 还是3G 还是4G
reachable 是标志位。
notifyBlock 一个回调block
private property
SCNetworkReachabilityRef 网络句柄指针
scheduled
allowWWAN 这个参数只对当地wifi网络有作用
CTTelephonyNetworkInfo 网络信息
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
};
这两个枚举就是规定当前网络状态。
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初始化方法
最终都调用到了- (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;
}
这个函数比较简单。都能看懂。无非就是其他函数传入的传入的SCNetworkReachabilityRef 不同罢了。
接下来看看SCNetworkReachabilityRef 在不同的初始化函数中是怎么初始化的。
在 - (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函数 SCNetworkReachabilityCreateWithAddress 创建SCNetworkReachabilityRef 传入的是sockaddr_in 结构体。
要求的结构体是sockaddr * 类型指针,我们传入的是sockaddr_in ,其实这两个结构体。这是因为sockaddr_in 结构和sockaddr_in * 内存结构一样。
bzero 函数将 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 ,可以监控ipv4 和ipv6
我们选择的是监控AF_INET 。这个是ipv4 地址族。
+ (instancetype)reachability 调用的就是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 函数。这个函数的作用是将主机数转换成无符号长整型的网络字节顺序
这了有个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]);
这个函数中调用 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);
这个就是根据具体的ip地址来查询当前网络情况啦
3其他
接下来看怎么获取网路状态的。
初始化完成后,获取网络状态都是懒加载方式进行的
- (SCNetworkReachabilityFlags)flags
SCNetworkReachabilityFlags flags = 0;
SCNetworkReachabilityGetFlags(self.ref, &flags);
return flags;
直接调用函数SCNetworkReachabilityGetFlags 获取就可以了
- (YYReachabilityStatus)status
return YYReachabilityStatusFromFlags(self.flags, self.allowWWAN);
这个调用C函数
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;
}
这个函数就是将flag 转换成相应的网络状态。
这里主要要看SCNetworkReachabilityFlags的所有参数了,看看每个参数具体指示什么。
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
};
这里有篇文章 讲解这些参数
摘录文章部分内容
kSCNetworkReachabilityFlagsReachable表明当前指定的节点或地址是可达的。注意:可达不是代表节点或地址接受到了数据,而是代表数据能够离开本地,因此。就算是可达的,也不一定能够发送成功
kSCNetworkReachabilityFlagsConnectionRequired表明要想和指定的节点或地址通信,需要先建立连接。比如说拨号上网。注意:对于手机来说,如果没有返回该标记,就说明手机正在使用蜂窝网路或者WiFi
kSCNetworkReachabilityFlagsConnectionOnTraffic表明要想和指定的节点或地址通信,必须先建立连接,但是在当前的网络配置下,目标是可达的。注意:任何连接到指定的节点或地址的请求都会触发该标记,举个例子,在很多地方需要输入手机,获取验证码后才能联网,就是这个原理
kSCNetworkReachabilityFlagsConnectionOnDemand表明要想和指定的节点或地址通信,必须先建立连接,但是在当前的网络配置下,目标是可达的。但是建立连接必须通过CFSocketStream APIs才行,其他的APIs不能建立连接
kSCNetworkReachabilityFlagsInterventionRequired表明要想和指定的节点或地址通信,必须先建立连接,但是在当前的网络配置下,目标是可达的。需要用户手动提供一些数据,比如密码或者token
kSCNetworkReachabilityFlagsIsWWAN表明是不是通过蜂窝网络连接
这个函数判断节点或者地址是否可达。不可达就直接返回未知网络
在检查要是当前网络必须连接,并且是kSCNetworkReachabilityFlagsTransientConnection 链接。那么未知网络 (就是必须通过类似拨号连接的,网络未知,其实是这里没有做严格区分,)
要是kSCNetworkReachabilityFlagsIsWWAN 网络。那就是wwan 。手机专用
苹果写的方法是
- (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;
}
我认为这样检查更合理。
wwan网络好判断,只要是wwan网络,直接返回的是kSCNetworkReachabilityFlagsIsWWAN 。
不可达的认为是未知网络。
可达中但是要用户需要输入的信息的排除wifi 。其他的情况都是wifi。
我用4G 共享热点测试,返回的状态是 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);
其实2G 3G 4G给人带来的感受就是网络速度的变化。
我认为这张图比较有利说明。
具体就不做讲解了。
- (BOOL)isReachable
return self.status != YYReachabilityStatusNone;
只要不是 YYReachabilityStatusNone 都是可达的。
- (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 异步回调。
而官方的写法是
- (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;
}
运用的是RunLoop 模式。持续监控网络状态。
两种方式都可以,runloop 一般是主线程监控。而yykit 是在serial queue 队列中监控变化
看看这个文件的宏定义
NS_ASSUME_NONNULL_BEGIN && NS_ASSUME_NONNULL_END 成对出现,这样的每个属性或每个方法都不用特别去指定nonnull和nullable,没有警告
NS_AVAILABLE_IOS (7.0) 这个宏经常见,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用来标记function/method弃用的方式(同样适用于clang)
粘贴下用法。
普通函数的语法
__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
当然你也可以使用更具有可读性的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>结构
继承UIGestureRecognizer 增加四个属性,startPoint ,lastPoint currentPoint,还有一个block
typedef NS_ENUM(NSUInteger, YYGestureRecognizerState) {
YYGestureRecognizerStateBegan, ///< gesture start
YYGestureRecognizerStateMoved, ///< gesture moved
YYGestureRecognizerStateEnded, ///< gesture end
YYGestureRecognizerStateCancelled, ///< gesture cancel
};
枚举了yygestureRecognizerstate 四种状态
<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;
源码实现起来简单。这里不做介绍
3.YYFileHash
这个是对文件进行hash
我们这里主要看加密算法,这里有十个加密算法。md2 md4 md5 sha1 sha224 sha256 sha384 sha512 crc32 adler32
前面八个都是用的系统的init update final 函数进行运算的。而后面两个是自己定义的api 为了配合定义的宏定义init_hash(Type,Init,Update,Final,Length)
这里有几个c数组。
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 数组装的是 void * 类型的指针
ctx_init 数组装的是 int ()(void *) 类型的指针
ctx_update 数组装的是 int ()(void *, const void *, CC_LONG) 类型指针
ctx_final 装的是int()(unsigned char *, void *) 类型的指针
看懂这里再往下看。
到#undef init_hash 行以前就是给这些数组赋值。这些数组保存的是函数指针
这里我们先学习下 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 大神怎么在这个里面干嘛。想要看懂这个函数。要了解c语言文件操作的几个函数。
函数原型:FILE * fopen(const char * path, const char * mode); 就是打开文件
int fseeko(FILE *stream, off_t offset, int fromwhere);
参数:
stream:文件指针
fromwhere:偏移起始位置
offset:偏移量
功能:函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere(偏移起始位置:文件头0(SEEK_SET),当前位置1(SEEK_CUR),文件尾2(SEEK_END))为基准,偏移offset(指针偏移量)个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。
ftell
函数 ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数。
size_tfread (void*buffer,size_tsize,size_tcount,FILE*stream) ;
buffer
用于接收数据的内存地址
size
要读的每个数据项的字节数,单位是字节
count
要读count个数据项,每个数据项size个字节.
stream
输入流
int feof(FILE *stream);
参数
流 :FILE结构的指针
feof是C语言标准库函数,其原型在stdio.h中,其功能是检测流上的文件结束符,如果文件结束,则返回非0值,否则返回0,文件结束符只能被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大小 将数据更新到update函数中
这里检查loop 次数。要是读取数据大于8M的话就回调一下block。
以md5加密为例最终结构都更新到CC_MD5_Update 函数中。
没有block 就是不用回调而已。用法一样不做介绍
最后就是收集数据了。
ctx_final[i](digest[i], ctx[i]); 已md5 为例。这个地方就是调用CC_MD5_Final()函数
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]];
}
转换成最后的结果。
将结果保存到相关属性里面。
这里我们主要是要看看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
最近坚持每天看一点yykit大神的代码,要是单纯看,好多地方都不去去注意的。而写博客,会强制自己必须看懂每一个小的地方。
与yykeyChain 相关的类是YYKeychainItem
我们先看YYKeychainItem
官网解释
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 数据是加密的
而kSecClassGenericPassword 对应的所有属性有
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 模型对应上述属性
<1>YYKeychainItem 结构
这个结构有15个变量。并且实现NSCopying 协议
modificationDate 和creationDate对于外部是不可修改的。内部是标记是可以修改的。这样主要方便修改变量。
<2>YYKeychainItem 属性方法
这里我们发现属性passwordObject 和 password 变量都是将传入的object 转换成data的
- (NSMutableDictionary *)queryDic
这个字典里放入了 key kSecClass ,kSecAttrAccount,kSecAttrService,kSecAttrAccessGroup,kSecAttrSynchronizable
- (NSMutableDictionary *)dic
这个字典放入的数据更多点 keykSecClass ,kSecAttrAccount,kSecAttrService,kSecAttrAccessGroup,kSecAttrSynchronizable,kSecAttrLabel,kSecAttrAccessible,kSecValueData,kSecAttrType,kSecAttrCreator,kSecAttrComment,kSecAttrDescription
这里面没有创建日期或者修改日期
- (instancetype)initWithDic:(NSDictionary *)dic
这里给变量赋值。有创建日期或修改日期
- (id)copyWithZone:(NSZone *)zone 实现NScopying协议
- (NSString *)description 覆盖这个方法。打印输出。
这个类很简单。就是需要操作的数据。
<3>YYKeychain 结构
这个类没有新增变量。
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);
这里规定了好多枚举。暂时不做介绍。等下面介绍函数的时候在说。
<4>YYKeychain 方法
这个类类似操作数据库管理类。因此就有类似数据库的证删改查
1.+ (BOOL)insertItem:(YYKeychainItem *)item error:(NSError **)error
没有数据就不用操作数据,所以第一步我们先看增加数据
+ (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;
}
增加数据必须要有service account 和 passwordData 没有这三个是无法将数据插入到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;
}
其实就是调用 SecItemUpdate 方法进行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;
并且返回的数据是用的函数SecItemCopyMatching
可能是DIC 也可能是Array 对其进行比较,获取最红数据
现在看看删除
+ (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;
}
调用 SecItemDelete 删除。
其他的api都是对这四个方法的二次包装而已。不做解释
5.YYWeakProxy
这个类继承NSProxy
NSProxy 实现了NSObject协议,主要用来转发消息用的。
6.YYTimer
yytimer 是用GCD实现的一个timer api是仿照nstimer 的。
这里我们学习下GCD 怎么创建计时器
_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 创建一个source
2.dispatch_source_set_timer 设置计时器的开始时间和间隔时间回调
3.dispatch_source_set_event_handler 设置source 回调的block
4 将source 挂起。
这里的计时器是在主线程里面执行的
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);
这里有个宏定义 看看写法就可以了
其他的地方就是简单的判断。
不过这里获取的属性都是给lock住的。这样锁住不会导致变量发生变化。
7.YYTransaction
我们先看类结构
1结构
这个类结构比较简单,就是一个target 和selector 不过这里的selector 是assign 属性
2 public method
1+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector;
2.- (void)commit;
第一个方法是类方法 ,第二个是实例方法
+ (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 。别的生成实例 ,并保存这两个属性
- (void)commit {
if (!_target || !_selector) return;
YYTransactionSetup();
[transactionSet addObject:self];
}
这里有个c函数 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);
});
}
这里生成 的是一个单例,实例化一个对象transactionSet ,并且创建一个CFRunLoopObserverRef 对象,观察kCFRunLoopBeforeWaiting | kCFRunLoopExit 状态
。回调函数是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 循环一次,都将这次的target调用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 都是指针,将指针转换成了long 进行^ 操作
- (BOOL)isEqual:(id)object 方法简单不做说明
这个类一旦生成runloop 观察就始终启动,在每次runloop 结束时候进行注册方法调用。只调用一次,就清空
8YYSentinel
其实这个类就是对 OSAtomicIncrement32 封装
博客 有对OSAtomicIncrement32 简单讲解,就是线程安全的增加引用计数。
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;
}
简单初始化,这里规定了最大的线程队列数量不能超过MAX_QUEUE_COUNT
关键c函数
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;
}
这个函数有三个参数,第一个name:线程池民名字,第二个queueCount 线程池中的线程,第三个是NSQualityOfService
这里有个结构体 YYDispatchContext 指针
typedef struct {
const char *name;
void **queues;
uint32_t queueCount;
int32_t counter;
} YYDispatchContext;
我们看看如何初始化的
1.YYDispatchContext *context = calloc(1, sizeof(YYDispatchContext)); 在堆上生成context 结构体
2.context->queues = calloc(queueCount, sizeof(void *)); 给context 结构体的queues 赋值一个堆区地址。这个分配的堆区大小是queueCount 个指针大小的区域,以后用来存放生成的queue
由于ios8 以后,gcd 提供了新的api 所以这里做了版本区分。
这里有部分介绍NSQualityOfService 这里不做过多介绍
在ios8 以后 将NSQualityOfService 转换成GCD 的 qos_class_t
在ios8 以前转换将 NSQualityOfService 转换成dispatch_queue_priority_t
在ios8 以前dispatch_set_target_queue(queue, dispatch_get_global_queue(identifier, 0));目的是设置优先级的。
这里还有个name赋值
strdup 用法
功 能: 将串拷贝到新建的位置处
strdup()在内部调用了malloc()为变量分配内存,不需要使用返回的字符串时,需要用free()释放相应的内存空间,否则会造成内存泄漏。
NSQualityOfServiceUserInteractive
与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成
NSQualityOfServiceUserInitiated
由用户发起的并且需要立即得到结果的任务,比如滑动scroll view时去加载数据用于后续cell的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成
NSQualityOfServiceUtility
一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间
NSQualityOfServiceBackground
这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时
NSQualityOfServiceDefault
优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
看 - (dispatch_queue_t)queue
- (dispatch_queue_t)queue {
return YYDispatchContextGetQueue(_context);
}
调用下面的函数
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 线程安全的引用计数。这样保证每次获取的queue 是一次递增的。不停循环
+ (instancetype)defaultPoolForQOS:(NSQualityOfService)qos
这个函数其实就是生成不同的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;
}
}
通过调用YYDispatchQueueGetForQOS c函数。YYDispatchQueueGetForQOS函数调用YYDispatchContextGetForQOS c 函数
YYDispatchContextGetForQOS 这个函数也是生成五个不同等级的NSQualityOfService 线程组。只不过每个的数量是根据内核决定的。
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
是快速获取一个 dispatch_queue_t 的一个方法。一旦生成了dispatch_queue_t 就不能释放掉了。
10YYThreadSafeArray
其实安全数组这里源码比较简单。继承NSMutableArray
将NSMutableArray的大多数方法都加锁重写 而已
#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);
每个数组都绑定了一个dispatch_semaphore_t 信号
11YYThreadSafeDictionary
安全字典和数组一样的实现。不做过多介绍
12.YYAsyncLayer
这个类看类名字我们知道是异步绘制layer
1. YYAsyncLayer 继承CALayer
2.YYAsyncLayer 新增属性displaysAsynchronously 默认是YES。变量_sentinel 控制原子级别的引用计数(递增)
+ (id)defaultValueForKey:(NSString *)key {
if ([key isEqualToString:@"displaysAsynchronously"]) {
return @(YES);
} else {
return [super defaultValueForKey:key];
}
}
3 通过override - (void)setNeedsDisplay 方法来比较layer 是否需要取消上次绘制
4 通过 override - (void)display 方法来给绘制内容
属性和变量都比较简单。我们看看实现- (void)setNeedsDisplay
- (void)setNeedsDisplay {
[self _cancelAsyncDisplay];
[super setNeedsDisplay];
}
这里调用_cancelAsyncDisplay
- (void)_cancelAsyncDisplay {
[_sentinel increase];
}
实现 _sentinel 递增 标记这次绘制取消,进行下次绘制
关键代码是
- (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进行绘制
didDisplay 是获取context 图片用户进行展示。
回到函数- (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 对象。要是该对象没有设置display。就调用下task.willDisplay 和 task.didDisplay ,并且将self.contents 清空
下面关键代码异步绘制简单逻辑
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);
});
}
由于快到文章上限了,所以。就简单罗列下要点
willDisplay 在main 队列中做绘制前 的准备工作
display 要是异步的话,就将到YYAsyncLayerGetDisplayQueue 获取的queue 中进行绘制。因为context 绘制可以在异步绘制的。而显示必须在主线程中进行。
didDisplay 将获取的image 到主线程展示。