category :有翻译为分类,有翻译为类别,个人感觉这种翻译多多少少有些误导,所以我就不翻译了,直接喊英文
category 个人觉得是oc语法中的一大亮点
不破坏老得类的结构去进行,扩充方法,扩充属性,也可以将庞大的类分门别类的存放
扩充方法比较常用也比较简答,这边主要讲一下扩充属性
还是用demo解释,效果图如下:
** 调用方式:(这么简单的调用是不是很爽?)**
#import "ViewController.h"
#import "UIViewController+Loading.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//一句话调用
[self showLoading];
//在不需要loading的时候
//[self hideLoading];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
实现
这里主要用到了运行时的概念,首先我们创建UIViewController的category UIViewController+Loading
.h文件
#import <UIKit/UIKit.h>
@interface UIViewController (Loading)
- (void)showLoading;
- (void)hideLoading;
@end
.m文件
#import "UIViewController+Loading.h"
#import "UIImage+animatedGIF.h"
#import <objc/runtime.h>
const char *loadingViewKey = "loadingViewKey";
@interface UIViewController (Private)
@property (nonatomic, strong)UIImageView* loadingView;
@end
@implementation UIViewController (Loading)
- (UIImageView*)loadingView{
UIImageView* view = objc_getAssociatedObject(self, &loadingViewKey);
if (!view) {
view = [[UIImageView alloc]initWithImage:[UIImage animatedImageWithAnimatedGIFData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"loading_img" ofType:@"gif"]]]];
[view sizeToFit];
view.center = self.view.center;
self.loadingView = view;
}
return view;
}
- (void)setLoadingView:(UIImageView *)loadingView{
objc_setAssociatedObject(self, &loadingViewKey, loadingView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)showLoading{
[self.view addSubview:self.loadingView];
}
- (void)hideLoading{
[self.loadingView removeFromSuperview];
}
@end
** 讲解: **
- import <objc/runtime.h>(这个正在学习中,目前停留在会用)
- 在Loading这个category中新增loadingView属性,重写其get set方法,因为category是不允许新增属性的,但是我们知道.(点)语法无非也就是调用get set
-
objc_getAssociatedObject (请看下图官方文档)
-
objc_setAssociatedObject
object : 参数作为待扩展的对象实例
key: 作为该对象实例的属性的键
value: 就是对象实例的属性的值
policy: 作为关联的策略
- 这个key一定要保持唯一,所以我们用常量指针地址作为key
下面是加载gif UIImageView的 category (gitHub上的)
#import <UIKit/UIKit.h>
/**
UIImage (animatedGIF)
This category adds class methods to `UIImage` to create an animated `UIImage` from an animated GIF.
*/
@interface UIImage (animatedGIF)
/*
UIImage *animation = [UIImage animatedImageWithAnimatedGIFData:theData];
I interpret `theData` as a GIF. I create an animated `UIImage` using the source images in the GIF.
The GIF stores a separate duration for each frame, in units of centiseconds (hundredths of a second). However, a `UIImage` only has a single, total `duration` property, which is a floating-point number.
To handle this mismatch, I add each source image (from the GIF) to `animation` a varying number of times to match the ratios between the frame durations in the GIF.
For example, suppose the GIF contains three frames. Frame 0 has duration 3. Frame 1 has duration 9. Frame 2 has duration 15. I divide each duration by the greatest common denominator of all the durations, which is 3, and add each frame the resulting number of times. Thus `animation` will contain frame 0 3/3 = 1 time, then frame 1 9/3 = 3 times, then frame 2 15/3 = 5 times. I set `animation.duration` to (3+9+15)/100 = 0.27 seconds.
*/
+ (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)theData;
/*
UIImage *image = [UIImage animatedImageWithAnimatedGIFURL:theURL];
I interpret the contents of `theURL` as a GIF. I create an animated `UIImage` using the source images in the GIF.
I operate exactly like `+[UIImage animatedImageWithAnimatedGIFData:]`, except that I read the data from `theURL`. If `theURL` is not a `file:` URL, you probably want to call me on a background thread or GCD queue to avoid blocking the main thread.
*/
+ (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)theURL;
@end
#import "UIImage+animatedGIF.h"
#import <ImageIO/ImageIO.h>
#if __has_feature(objc_arc)
#define toCF (__bridge CFTypeRef)
#define fromCF (__bridge id)
#else
#define toCF (CFTypeRef)
#define fromCF (id)
#endif
@implementation UIImage (animatedGIF)
static int delayCentisecondsForImageAtIndex(CGImageSourceRef const source, size_t const i) {
int delayCentiseconds = 1;
CFDictionaryRef const properties = CGImageSourceCopyPropertiesAtIndex(source, i, NULL);
if (properties) {
CFDictionaryRef const gifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
if (gifProperties) {
NSNumber *number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime);
if (number == NULL || [number doubleValue] == 0) {
number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime);
}
if ([number doubleValue] > 0) {
// Even though the GIF stores the delay as an integer number of centiseconds, ImageIO “helpfully” converts that to seconds for us.
delayCentiseconds = (int)lrint([number doubleValue] * 100);
}
}
CFRelease(properties);
}
return delayCentiseconds;
}
static void createImagesAndDelays(CGImageSourceRef source, size_t count, CGImageRef imagesOut[count], int delayCentisecondsOut[count]) {
for (size_t i = 0; i < count; ++i) {
imagesOut[i] = CGImageSourceCreateImageAtIndex(source, i, NULL);
delayCentisecondsOut[i] = delayCentisecondsForImageAtIndex(source, i);
}
}
static int sum(size_t const count, int const *const values) {
int theSum = 0;
for (size_t i = 0; i < count; ++i) {
theSum += values[i];
}
return theSum;
}
static int pairGCD(int a, int b) {
if (a < b)
return pairGCD(b, a);
while (true) {
int const r = a % b;
if (r == 0)
return b;
a = b;
b = r;
}
}
static int vectorGCD(size_t const count, int const *const values) {
int gcd = values[0];
for (size_t i = 1; i < count; ++i) {
// Note that after I process the first few elements of the vector, `gcd` will probably be smaller than any remaining element. By passing the smaller value as the second argument to `pairGCD`, I avoid making it swap the arguments.
gcd = pairGCD(values[i], gcd);
}
return gcd;
}
static NSArray *frameArray(size_t const count, CGImageRef const images[count], int const delayCentiseconds[count], int const totalDurationCentiseconds) {
int const gcd = vectorGCD(count, delayCentiseconds);
size_t const frameCount = totalDurationCentiseconds / gcd;
UIImage *frames[frameCount];
for (size_t i = 0, f = 0; i < count; ++i) {
UIImage *const frame = [UIImage imageWithCGImage:images[i]];
for (size_t j = delayCentiseconds[i] / gcd; j > 0; --j) {
frames[f++] = frame;
}
}
return [NSArray arrayWithObjects:frames count:frameCount];
}
static void releaseImages(size_t const count, CGImageRef const images[count]) {
for (size_t i = 0; i < count; ++i) {
CGImageRelease(images[i]);
}
}
static UIImage *animatedImageWithAnimatedGIFImageSource(CGImageSourceRef const source) {
size_t const count = CGImageSourceGetCount(source);
CGImageRef images[count];
int delayCentiseconds[count]; // in centiseconds
createImagesAndDelays(source, count, images, delayCentiseconds);
int const totalDurationCentiseconds = sum(count, delayCentiseconds);
NSArray *const frames = frameArray(count, images, delayCentiseconds, totalDurationCentiseconds);
UIImage *const animation = [UIImage animatedImageWithImages:frames duration:(NSTimeInterval)totalDurationCentiseconds / 100.0];
releaseImages(count, images);
return animation;
}
static UIImage *animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceRef CF_RELEASES_ARGUMENT source) {
if (source) {
UIImage *const image = animatedImageWithAnimatedGIFImageSource(source);
CFRelease(source);
return image;
} else {
return nil;
}
}
+ (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)data {
return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithData(toCF data, NULL));
}
+ (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)url {
return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithURL(toCF url, NULL));
}
@end