“宜未雨而绸缪;毋临渴而掘井。”凡事早做打算,方为上上之策!------前言
一、SDWebImage中的weakself&strongself
//#import "SDWebImageDownloaderOperation.h"
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
疑问:
1、为什么使用weakSelf?
因为Block是一个结构体,它会将一个全局变量保存为一个属性(__strong),而self强引用了Block这会造成循环引用,所以使用__weak修饰weakSelf。
2、为什么在Block里面使用strongSelf?
为了保证block在执行完毕之前self不会被释放,而strongSelf是为了保证Block内部执行的时候不会被释放,但是存在执行前就已经被释放的情况,导致strongSelf=nil。注意判空处理,防止出现崩溃。typeof是编译时确定变量类型,所以这里写self 不会被循环引用。
总结:
外部使用了weakSelf,里面使用strongSelf却不会造成循环,究其原因就是因为weakSelf是block截获的属性,而strongSelf是一个局部变量会在“函数”执行完释放。
扩展1:
1、__block可以让block修改局部变量,而__weak不能。
2、MRC中__block是不会引起retain;但在ARC中__block则会引起retain,因为,block也是一个强引用,引起循环引用,会引起循环引用。所以ARC中应该使用__weak。
扩展2:
- 什么情况下Block不循环引用呢?
以下三种情况都是单向“强引用”,只是Block持有self,但self并没有持有block,所以不用考虑“循环引用”的问题。
1、UIView动画
[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
2、NSNotification
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification) {
self.someProperty = xyz; }];
3、NSOperation
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];
- 什么情况下GCD和NSNotification会导致循环引用呢?
iOS循环引用的几种情况
二、SDWebImage为类别添加属性
类别本身不允许为已有的类添加属性,及时你使用@property添加属性,这个属性也是无法使用的,因为它是不会生成setter/getter方法。为了解决这个问题,我们可以使用runtime来为类别添加新的属性并手动生成setter/getter方法。
/*
objc_AssociationPolicy参数使用的策略:
OBJC_ASSOCIATION_ASSIGN; //assign策略
OBJC_ASSOCIATION_COPY_NONATOMIC; //copy策略
OBJC_ASSOCIATION_RETAIN_NONATOMIC; // retain策略
OBJC_ASSOCIATION_RETAIN;
OBJC_ASSOCIATION_COPY;
*/
//#import "UIImageView+WebCache.h"
static char imageURLKey;
static char TAG_ACTIVITY_INDICATOR;
static char TAG_ACTIVITY_STYLE;
static char TAG_ACTIVITY_SHOW;
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
// 参数1:源对象
// 参数2:关联时用来标记属性的key(因为可能要添加很多属性)
// 参数3:关联的对象
// 参数4:关联策略
// 关联方法
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
- (void)setShowActivityIndicatorView:(BOOL)show{
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, [NSNumber numberWithBool:show], OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)showActivityIndicatorView{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
Category为什么可以添加方法,而不可以添加成员变量?
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
- 在objc_class结构体中,ivars是objc_ivar_list指针(成员变量链表);methodLists是指向objc_method_list指针的指针(注意是:**)。
- 在runtime中,objc_class结构体的大小是固定的,不可能向这个结构体中添加数据,只能修改。
- ivars指向的是一个固定区域,只能修改成员变量的值,而不能增加成员变量的个数。
- iMethodList是一个二维数组,所以可以修改methodLists的值来增加成员方法,虽然没办法扩展MethodList的内存区域,但是可以改变这个内存区域的值(存储的是指针)。
- 因此,可以动态添加方法,不能添加成员变量。
RunTime基本概念
三、SDWebImage中的断言NSAssert
在iOS开发中,可以使用宏NSAssert()在程序中进行断言处理。NSAssert()使用正确,可以帮助开发者尽快定位bug。
//#import "SDWebImageManager.h"
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
- NSAssert实际示例
- (void)printMyName:(NSString *)myName
{
NSAssert(myName != nil, @"名字不能为空!");
NSLog(@"My name is %@.",myName);
}
当传给函数的参数(myName)为空时,断言将被执行,程序Crash,并打印出断言中的描述信息。本例中,在控制台打印出了如下的日志:
2018-05-29 11:25:46.846909+0800 autoReleasePoolTest[78930:6611559] *** Assertion failure in -[ViewController printMyName:], /Users/zhangzhangkai/Library/Autosave Information/autoReleasePoolTest/autoReleasePoolTest/ViewController.m:55
2018-05-29 11:25:46.861768+0800 autoReleasePoolTest[78930:6611559] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '名字不能为空!'
四、SDWebImage中GCD的使用
- 设置栅栏,阻塞线程,实现线程同步。dispatch_barrier_sync必须在并行队列且是创建的用户队列(dispatch_queue_t)中才能实现其“栅栏”的功能。
栅栏:在栅栏之前的队列先执行,然后执行栅栏,最后再执行栅栏之后的队列。它其实起到一个分割的作用。我们可以用它来实现GCD的同步。
GCD实现线程同步的方法:
- 组队列(dispatch_group)
- 阻塞任务(dispatch_barrier)
- 信号量机制(dispatch_semaphore)
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
五、内联函数inline
inline定义:内联函数是指用inline关键字修饰的函数。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。
- extern关键字声明的对象为整个项目使用。
- inline内联函数,作用代替宏。
inline内联函数的说明
- 1.内联函数只是我们向编译器提供的申请,编译器不一定采取inline形式调用函数.
- 2.内联函数不能承载大量的代码.如果内联函数的函数体过大,编译器会自动放弃内联.
- 3.内联函数内不允许使用循环语句或开关语句.
- 4.内联函数的定义须在调用之前.
为什么inline能取代宏?
-
优点相比于函数:
1、inline函数避免了普通函数的,在汇编时必须调用call的缺点:取消了函数的参数压栈,减少了调用的开销,提高效率.所以执行速度确比一般函数的执行速度要快.
2、集成了宏的优点,使用时直接用代码替换(像宏一样); -
优点相比于宏:
1、避免了宏的缺点:需要预编译.因为inline内联函数也是函数,不需要预编译.
2、编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3、可以使用所在类的保护成员及私有成员。
OC内联函数传送门
内联函数在开发中的应用
extern UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
//#import "SDWebImageCompat.h"
inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image) {
if (!image) {
return nil;
}
if ([image.images count] > 0) {
NSMutableArray *scaledImages = [NSMutableArray array];
for (UIImage *tempImage in image.images) {
[scaledImages addObject:SDScaledImageForKey(key, tempImage)];
}
return [UIImage animatedImageWithImages:scaledImages duration:image.duration];
}
else {
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
CGFloat scale = 1;
if (key.length >= 8) {
NSRange range = [key rangeOfString:@"@2x."];
if (range.location != NSNotFound) {
scale = 2.0;
}
range = [key rangeOfString:@"@3x."];
if (range.location != NSNotFound) {
scale = 3.0;
}
}
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
image = scaledImage;
}
return image;
}
}
六、iOS网络请求NSURLSession
NSURLSession七、clang diagnostic的使用
SDWebImage中SDWebImageDownloader.m中的设置如下:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop
看到这里我也是一头雾水,不明所以。查过资料之后发现,clang为诊断设置,辅助我们编码。
-Warc-performSelector-leaks的解释为“performSelector may cause a leak because its selector is unknown”因为方法位置,所以可能会导致泄漏。这里就是忽略这个告警,SDWebImage作者八成是个处女座。
- 使用模式如下:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-相关命令"
// 你自己的代码
#pragma clang diagnostic pop
栗子1:忽略弃用的警告⚠️
//1、如果直接写已经废弃的方法会报警
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"" message:@"" delegate:nil cancelButtonTitle:@"" otherButtonTitles:@"", nil];
[alertView show]
//2、忽略方法弃用告警
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
UIAlertView *alertViewTmp = [[UIAlertView alloc]initWithTitle:@"" message:@"" delegate:nil cancelButtonTitle:@"" otherButtonTitles:@"", nil];
[alertViewTmp show];
#pragma clang diagnostic pop
栗子2:忽略不兼容指针类型⚠️
// 不兼容指针类型
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincompatible-pointer-types"
//
#pragma clang diagnostic pop
栗子3:循环引用⚠️
break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
// self.completionBlock = ^ {
// ...
// };
#pragma clang diagnostic pop
栗子4:未使用变量 ⚠️
// 未使用变量
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"
int a;
#pragma clang diagnostic pop
告警使用列表
网友评论