当前位置: 首页 > 工具软件 > YYKit > 使用案例 >

YYKit(Base模块)学习笔记

壤驷雅达
2023-12-01

Base

1.看到weakify和strongify这两个宏定义如下:

#ifndef weakify
  #if DEBUG
  	#if __has_feature(objc_arc)
  	#define weakify(object) autoreleasepool{} __weak __typeof(object) weak##_##object = object;
  	#else
  	#define weakify(object) autoreleasepool{} __block __typeof(object) weak##_##object = object;
  	#endif
  #else
  	#if __has_feature(objc_arc)
  	#define weakify(object) try{} @finally{} {} __weak __typeof(object) weak##_##object = object;
  	#else
  	#define weakify(object) try{} @finally{} {} __block __typeof(object) weak##_##object = object;
  	#endif
  #endif
#endif

#ifndef strongify
  #if DEBUG
  	#if __has_feature(objc_arc)
  	#define strongify(object) autoreleasepool{} __typeof(object) object = weak##_##object;
  	#else
  	#define strongify(object) autoreleasepool{} __typeof(object) object = block##_##object;
  	#endif
  #else
  	#if __has_feature(objc_arc)
  	#define strongify(object) try{} @finally{} {} __typeof(object) object = weak##_##object;
  	#else
  	#define strongify(object) try{} @finally{} {} __typeof(object) object = block##_##object;
  	#endif
  #endif
#endif
复制代码

使用的时候:

 @weakify(self);
    void(^mylock)() = ^{
        @strongify(self);
        NSLog(@"Hello %p", self);
    };
复制代码

在代码定义的时候,不是很明白为什么要加上@autoreleasepool{} {}等这样的代码,并且也不知道使用的时候为啥要加上@。。

然后在YYKit的issue中找到了答案:

关于__block__weak的话,__weak是为了防止出现循环引用,在block内保持对外部变量的弱引用,而__block的话则是将在内部引用了变量的地址,方便在block内进行赋值修改。

2.CFAutorelease

static inline CFTypeRef YYCFAutorelease(CFTypeRef CF_RELEASES_ARGUMENT arg) {
  // 1
  if (((long)CFAutorelease + 1) != 1) {
      return CFAutorelease(arg);
  } else {
    // 2
      id __autoreleasing obj = CFBridgingRelease(arg);
      return (__bridge CFTypeRef)obj;
  }
}
复制代码

CFAutoRelease做的事情就是ARC对NSObject的计数操作。在ARC环境下,ARC并不会对Core Foundation对象进行计数,所以Core Foundation的操作需要我们自己完成。CFAutoRelease会将对象添加到自动释放池中,这就意味着在下一轮的loop中,该对象会接收到CFRelease的信息,或者在自动释放池已经满了的时候进行释放。

  1. 用于iOS 6以上,(long)CFAutorelease表示的是CFAutorelease这个函数的所在的内存地址。这里我的理解是当函数不存在的时候,其内存地址会为0,但是又不明白为什么要加1后去判断是否为1.(求解答)
  2. 为了适配iOS 6添加的代码,使用__autoreleasing修饰的话,会将对象强行添加到自动释放池中。

3.获取某段代码的运行时间

// <QuartzCore/QuartzCore.h>版本
// extern double CACurrentMediaTime(void);
double begin, end, ms;
begin = CACurrentMediaTime();
// do something
end = CACurrentMediaTime();
ms = (end - begin) * 1000; // 转化为毫秒

// <sys/time.h>版本
struct timeval t0, t1;
gettimeofday(&t0, NULL);
// do something
gettimeofday(&t1, NULL);
double ms = (double)(t1.tv_sec - t0.tv_sec) * 1e3 + (double) (t1.tv_usec - t0.tv_usec) * 1e-3; // 结果是毫秒
复制代码

4.使用__DATE____TIME__获取当前日期和时间 5.dispatch_time在设备休眠的时候会停止计时,而dispatch_wall_time在设备休眠时还会继续计时 6.pthread_main_np()获取主线程,如果其值为不等于0的话,表明当前处于主线程 7.volatile表示的是系统每次都会从内存中访问该变量的数据,而不会在cache或者是寄存器的备份中读取,且会要求编译器不对该变量的访问进行优化。该变量随时可能会发生变化. 8.深拷贝一个对象:

深拷贝(Deep Copy)是指将整个对象的内存直接拷贝到另一块内存中,浅拷贝(Shallow Copy)仅仅拷贝指向对象的指针:

即浅拷贝是指针拷贝,深拷贝是内容拷贝

实现深拷贝的方法,可以将对象先归档后再进行读档,如下:

id obj = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:originalObj]];
复制代码

举个例子:

NSArray<NSMutableString *> *originalArray = @[[NSMutableString stringWithString:@"1"], [NSMutableString stringWithString:@"2"], [NSMutableString stringWithString:@"3"]];
    NSArray<NSMutableString *> *copyArray = [originalArray copy];
    NSMutableString *one = [originalArray objectAtIndex:0];
    [one setString:@"5"];
复制代码

上面的代码中,当你把两个Array都打印出来的话,你会发现copyArray的内容也跟着修改了,copy方法执行的指针复制。

NSArray<NSMutableString *> *originalArray = @[[NSMutableString stringWithString:@"1"], [NSMutableString stringWithString:@"2"], [NSMutableString stringWithString:@"3"]];
    NSArray<NSMutableString *> *deepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:originalArray]];
    NSMutableString *one = [originalArray objectAtIndex:0];
    [one setString:@"5"];
复制代码

而这段代码执行的就是深拷贝,通过归档后解档获取内容一致的集合,并且该集合又存在另一块内存上。互不影响。

9.递归锁的实现

static inline void pthread_mutex_init_recursive(pthread_mutex_t *mutex, bool recursive) {
#define YYMUTEX_ASSERT_ON_ERROR(x_) do { \
__unused volatile int res = (x_); \
assert(res == 0); \
} while(0)
  assert(mutex != NULL);
  if (!recursive) {
    YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init(mutex, NULL));
  } else {
    pthread_mutexattr_t attr;
    YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_init(&attr));
    YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE));
    YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init(mutex, &attr));
    YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_destroy(&attr));
  }
#undef YYMUTEX_ASSERT_ON_ERROR
}
复制代码

volatile关键字用于告诉编译器其后面的变量随时可能发生变化,编译后的程序如果需要存储或者读取的时候都要从变量地址中直接读取。

Foundation

NSObject+YYAdd

在这个Category中,主要是添加了可变参数的方法,runtime的Method Swizzling, Associate value和深拷贝的方法。

  1. 在实现可变参数函数的过程中,我们需要用到:
  • va_list args - 指向可变参数列表的指针
  • va_start(args, firstArg) - 初始化va_list变量,这个宏的第二个参数为第一个可变参数的前一个参数
  • va_arg(args, type) - 获取返回的可变参数,第二个参数为对应参数的类型
  • va_end(args) - 清空参数列表,并置参数指针args无效

?:

- (void)test:(NSString *)firstArg, ... {
  va_list args;
  va_start(args, firstArg);
  if (firstArg) {
      NSString *parameter;
      while ((parameter = va_arg(args, id))) {
          NSLog(@"%@", parameter);
      }
  }

  va_end(args);
}

- (void)viewDidLoad {
  [super viewDidLoad];
  [self test:@"first", @"Hello", @"World", nil];
}
复制代码

YYKit作者通过使用NSMethodSignatureNSInvocation实现了可变参数的方法实现。

2.在获取函数返回值类型的时候,是根据char型指针指向的编码来进行判断的:

编码代表类型
rconst
nin
Ninout
oout
Obycopy
Rbyref
Voneway
vvoid
Bbool
cchar
Cunsigned char
sshort
Sunsigned short
iint
Iunsigned int
lint
Lunsigned int
qlong long
Qunsigned long long
ffloat
ddouble
Dlong double
@id
#Class
如果是返回其他的类型,比方说struct / union / SEL / void * / unknown的话,可能通过NSValuevalueWithBytes:objCType:方法进行类型的转换

3.在交换两个方法的实现时,需要注意实例方法和类方法的区别:

// 实例方法
Method instanceMethod = class_getInstanceMethod(self, sel);

// 类方法
Method classMethod = class_getInstanceMethod(objc_getClass(self), sel);
复制代码

4.设置关联对象时,strong的话使用OBJC_ASSOCIATION_RETAIN_NONATOMIC进行修饰,weak对象使用OBJC_ASSOCIATION_ASSIGN

5.实现真正的深复制的话,可以通过归档/解档来进行操作:

id obj = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:o]];
复制代码

NSObject+YYAddForARC

NSObject+YYAddForKVO

这个Category的作用是将KVO改写成block的调用。

将block声明为_YYNSObjectKVOBlockTarget的属性,通过Associated Object为NSObject添加一个字典属性,其中,键值对为keypath和一个存储了_YYNSObjectKVOBlockTarget实例的数组。用数组大概是因为可能会有多个object监听同一个属性?

NSString+YYAdd

1.提供了加密的方法,加密的方式有: md2, md4, md5, sha1, sha224, sha256, sha384, sha512, hmac***, crc32. 2.base64和utf-8的编解码 3.字符串的size 4.正则表达式匹配 5.将NSString转化为NSNumber 6.获取UUID 7.转化为UTF32(NSString是UTF16的) 8.其他工具类

在加密的时候,需要先将NSString转化为NSData类型之后再进行加密操作。在YYKit中,使用NSStringdataUsingEncoding:方法,将字符串通过UTF8编码为NSData。关于加密的函数,在NSData的Category中在进行解析。

2.获取UUID的话,可以通过Core Foundation的方法进行获取

CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
NSString *uuidString = (__bridge_transfer NSString*)string;
复制代码

NSNumber+YYAdd

这个Category提供的,就是将一个能够转化为NSNumber类的字符串转化为NSNumber类型

1.将十六进制的字符串转为NSNumber, 使用NSScanner

// 比方说0xf0f0f0等等
NSScanner *scan = [NSScanner scannerWithString:str];
unisigned num = -1;
BOOL suc = [scan scanHexInt:&num];
if (suc) {
  NSNumber *num = [NSNumber numberWithLong:num];
}
复制代码

NSData+YYAdd

这里就是将数据加密的类

在iOS中,要将数据加密的话,可以使用`<CommomCrypto/CommonCrypto.h>这个框架提供的方法来将给定的数据进行加密。

1.将char类型输出为字符串的话,使用%x,一般用两位来代表char,也就是%02x

2.md2, md4, md5加密的方法为CC_MD?(const void *data, CC_LONG len, unsigned char *md)。其中问好?用2/4/5进行替代。第一个参数代表要加密的数据,第二个参数代表加密数据的长度,最后一个代表输出结果。另外要注意的是,输出结果的长度根据不同的加密方法在初始化时填入的参数有所不同。md2加密的长度是CC_MD2_DIGEST_LENGTH,md4和5的话就替换掉2就行了。

3.sha1, sha224, sha256, sha384, sha512加密的话,跟上面mdx的加密方式差不多,不过使用的是CC_SHA?(const void *data, CC_LONG len, unsigned char *md)方法(就是没明白为什么在输出为字符串的时候,字符串的初始化长度要*2 -- 答案:What is the length of a hashed string with SHA512?, 大概就是sha加密出来的字节数,比我们输出为hex字符串的字节数少了一半,所以要乘以2)

4.hmac加密的话,使用ccHmac(CCHmacAlgorithm algorithm, const void *key, size_t keyLength, const void *data, size_t dataLength, void *macOut)加密,从参数不难看出具体的含义,要注意的是,传入的加密的key为字符串时,要将其转为char类型。

5.AES256加密使用的是CCCrypt方法

6.在将NSData类型的数据输出为十六进制字符串的时候,一般会将字符串的长度设置为数据长度*2.原因就参照上面sha加密时的解释

7.将hex的字符串转为NSData的话

+ (NSData *)dataWithHexString:(NSString *)hexStr {
  hexStr = [hexStr stringByReplacingOccurrencesOfString:@" " withString:@""];
  hexStr = [hexStr lowercaseString];
  NSUInteger len = hexStr.length;
  if (!len) return nil;
  unichar *buf = malloc(sizeof(unichar) * len);
  if (!buf) return nil;
  [hexStr getCharacters:buf range:NSMakeRange(0, len)];
  NSMutableData *result = [NSMutableData data];
  unsigned char bytes;
  char str[3] = { '\0', '\0'. '\0' }; // 有3个的原因是。。以'\0'结尾。。第三个理解为做结束标志位
  int i;
  for (i = 0; i < len / 2; i++) {
    str[0] = buf[i * 2];
    str[1] = buf[i * 2 + 1];
    bytes = strtol(str, NULL, 16); // 将字符串转为16位进制
    [result appendBytes:&bytes length:1];
  }
  free(buf);
  return result;
}
复制代码

8.base64加密的太长了。。就不讲了,原理谷歌一下大概都能清楚。 9.gzipInflategzipDeflate的话用于字符串的加压和解压操作,要进行zip操作的话,需要导入<zlib.h>头文件.

NSArray+YYAdd

一些常用的数组方法,比方说从plist文件中生成数组等

1.使用NSPropertyListSerialization将Property list来进行NSObject对象的转化。

NSDictionary+YYAdd

一些常用的字典方法,比方说从plist文件中生成字典,字典的有序的key,有序的value数组等

NSDate+YYAdd

NSDate常用方法

1.对于时间的加减的话,通常都是使用NSCalendar来进行计算。

NSNotificationCenter+YYAdd

NSNotificationCenter+YYAdd这个类提供的方法主要是将消息在主线程post出来,因为NSNotificaitonCenter消息的发送取决于发送的线程,方法的执行也是在该线程中执行,所以有需要在主线程中进行执行话,得在主线程中发送notification

1.在上面说过,YYKit的主线程判断使用的是pthread的pthread_main_np方法

NSKeyedUnarchiver+YYAdd

主要是为解档时添加了try/catch方法来捕获错误

NSTimer+YYAdd

NSTimer的计时的selector转为了block执行

NSBundle+YYAdd

根据屏幕的scale来获取对应的文件的路径

NSThread+YYAdd

为当前线程的runloop添加自动释放池


UIKit

UIColor+YYAdd

提供了从hex转为了UIColor的方法,并且也提供了RGB, HSB, HSL和CMYK之间的转化方法

1.对于RGB颜色来说,有RGB, RGBA, RRGGBB, RRGGBBAA这几种表现形式,前缀可能会出现'#'或者'0x' 2.sscanf(string str, string fmt, mixed var1, mixed var2 ...)从指定的字符串中读入与指定格式相符的数据

3.获取两个颜色相加后的结果的话,可以使用Core Graphics.先创建位图,然后在位图当中进行颜色的填充,获取结果值

UIImage+YYAdd

使用Core Graphic和vImage来处理图片

1._yy_CGImageSourceGetGIFFrameDelayAtIndex(CGImageSourceRef source, size_t index)方法用于获取当前这帧图片的播放时长 2.根据NSData的类型的数据来判断图片是否是GIF的话,有两个条件:

  • data.bytes & 0xFFFFFF == '0FIG'
  • CGImageSourceGetCount() > 1 3.创建纯色图片
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size {
  if (!color || size.width <= 0 || size.height <= 0) {
      return nil;
  }

  CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
  UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  CGContextRef context = UIGraphicsGetCurrentContext();
  if (!context) {
      return nil;
  }
  CGContextSetFillColorWithColor(context, color.CGColor);
  CGContextFillRect(context, rect);
  UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();
  return image;
}
复制代码

4.判断图片是否有alpha通道的话,使用CGImageGetAlphaInfo()方法,并与kCGBitmapAlphaInfoMask进行与运算,之后判断该值是否等于CGImageAlphaInfo中带alpha通道的枚举值

UIControl+YYAdd

UIBarButtonItem+YYAdd

UIGestureRecognizer+YYAdd

UIView +YYAdd

1.在生成屏幕截图时,在snapshotImage中,会先将layer绘制到当前的上下文中,而在snapshotImageAfterScreenUpdates:函数中,则是通过drawViewHierarchyInRect:afterScreenUpdates:来进行,两个方法的不同之处在于前者会忽略所有图形的处理效果,后者允许我们对截图进行图形处理

UIScrollView+YYAdd

1.在计算scrollview的内容位置的时候,需要考虑上contentInset的影响

UITableView+YYAdd

UITextField+YYAdd

UIScreen+YYAdd

UIDevice+YYAdd

1.判断设备是否为ipad:UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad; 2.(厉害了。。。还能判断越狱与否)判断越狱设备的话,根据'/Applications/Cydia.app', '/private/var/lib/apt/', '/private/var/lib/cydia/', '/private/var/stash'这几个路径来进行判断。或者试着在'/private/'下进行文件读写,能写入的表明是越狱设备。 3.获取设备已经使用了多长时间:

- (NSDate *)systemUptime {
  NSTimeInterval time = [[NSProcessInfo processInfo] systemUptime];
  return [[NSDate alloc] initWithTimeIntervalSinceNow:(0 - time)];
}
复制代码

UIApplication+YYAdd

1.通过[NSBundle mainBundle]objectForInfoDictionaryKey:获取bundle名称,id,app版本和build版本:'CFBundleName, CFBundleIdentifier, CFBundleShortVersionString, CFBundleVersion'

UIFont+YYAdd

1.使用UIFont实例的fontDescriptor.symbolicTraits可以获取到字体的一些属性,比方说是否粗体,斜体等

UIBezierPath+YYAdd

Quartz

CALayer+YYAdd

YYCGUtilities

转载于:https://juejin.im/post/5a30e259f265da43176a2202

 类似资料: