YYKit(Base模組)學習筆記

kim_jin發表於2017-12-26

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中找到了答案:

Paste_Image.png

關於__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型指標指向的編碼來進行判斷的:

編碼 代表型別
r const
n in
N inout
o out
O bycopy
R byref
V oneway
v void
B bool
c char
C unsigned char
s short
S unsigned short
i int
I unsigned int
l int
L unsigned int
q long long
Q unsigned long long
f float
d double
D long 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

相關文章