YYKit 源码讲解(1)

发现写博客想写明白也是一件不容易的事情。

这次拿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.

kSecClassGenericPassword

The value that indicates a generic password item.

kSecClassInternetPassword

The value that indicates an Internet password item.

kSecClassCertificate

The value that indicates a certificate item.

kSecClassKey

The value that indicates a cryptographic key item.

kSecClassIdentity

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)

kSecAttrAccessControl

kSecAttrAccessGroup(iOS; also macOS ifkSecAttrSynchronizablespecified)

kSecAttrAccessible(iOS; also macOS ifkSecAttrSynchronizablespecified)

kSecAttrCreationDate

kSecAttrModificationDate

kSecAttrDescription

kSecAttrComment

kSecAttrCreator

kSecAttrType

kSecAttrLabel

kSecAttrIsInvisible

kSecAttrIsNegative

kSecAttrAccount

kSecAttrService

kSecAttrGeneric

kSecAttrSynchronizable



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 到主线程展示。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容