runtime
isa指针的含义
分为指针型isa:isa的値代表Class的地址
非指针型isa :isa的値的部分代表Class的地址.
关于对象指向的是类对象.关于类对象指向的是元类对象
实例对象\类对象\元类对象的区别
1.实例对象可以通过isa指针找到类对象,类对象存储实例方法列表(即对象方法列表)等相关信息;
2.类对象和元类对象都是objc_class的数据结构, 这个数据结构由于继承了objc_object,所以也有isa指针,进而有下面的
类对象也可以通过isa指针找到元类对象,从而可以访问类方法列表等相关信息.
元类对象存储类方法列表等信息。任何一个元类对象其isa指针都指向根元类对象.根元类的superclass指针指向根类对象.这就决定了,调用了一个类方法, 在直至根元类的类方法列表中都找不到的时候,会去根类对象中查找,所以这也就导致了找不到类方法,但是有同名的实例方法的时候,程序不会崩溃,
runtime的使用场景
- Method Swizzling
使有键盘的界面消失的时候,收起键盘,界面出现的时候,展示键盘
@implementation UIViewController (Swizzling)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
swizzling_exchangeMethod([UIViewController class] ,@selector(viewWillAppear:), @selector(swizzling_viewWillAppear:));
swizzling_exchangeMethod([UIViewController class] ,@selector(viewDidAppear:), @selector(swizzling_viewDidAppear:));
swizzling_exchangeMethod([UIViewController class] ,@selector(viewWillDisappear:), @selector(swizzling_viewWillDisappear:));
}
static char UIFirstResponderViewAddress;
#pragma mark - ViewDidAppear
- (void)swizzling_viewDidAppear:(BOOL)animated{
[self swizzling_viewDidAppear:animated];
UIView *view = objc_getAssociatedObject(self, &UIFirstResponderViewAddress);
[view becomeFirstResponder];
}
#pragma mark - ViewWillDisappear
- (void)swizzling_viewWillDisappear:(BOOL)animated{
[self swizzling_viewWillDisappear:animated];
UIView *view = (UIView *)[UIResponder currentFirstResponder];
if ([view isKindOfClass:[UIView class]] && view.viewController == self) {
objc_setAssociatedObject(self, &UIFirstResponderViewAddress, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[view resignFirstResponder];
}else{
objc_setAssociatedObject(self, &UIFirstResponderViewAddress, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
2.实现给分类增加属性
1>扩大按钮的点击范围
#import "UIButton+EnlargeTouchArea.h"
#import <objc/runtime.h>
@implementation UIButton (EnlargeTouchArea)
static char topNameKey;
static char rightNameKey;
static char bottomNameKey;
static char leftNameKey;
- (void) setEnlargeEdgeWithTop:(CGFloat) top right:(CGFloat) right bottom:(CGFloat) bottom left:(CGFloat) left
{
objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (CGRect) enlargedRect
{
NSNumber* topEdge = objc_getAssociatedObject(self, &topNameKey);
NSNumber* rightEdge = objc_getAssociatedObject(self, &rightNameKey);
NSNumber* bottomEdge = objc_getAssociatedObject(self, &bottomNameKey);
NSNumber* leftEdge = objc_getAssociatedObject(self, &leftNameKey);
if (topEdge && rightEdge && bottomEdge && leftEdge)
{
return CGRectMake(self.bounds.origin.x - leftEdge.floatValue,
self.bounds.origin.y - topEdge.floatValue,
self.bounds.size.width + leftEdge.floatValue + rightEdge.floatValue,
self.bounds.size.height + topEdge.floatValue + bottomEdge.floatValue);
}
else
{
return self.bounds;
}
}
- (UIView*) hitTest:(CGPoint) point withEvent:(UIEvent*) event
{
CGRect rect = [self enlargedRect];
if (CGRectEqualToRect(rect, self.bounds))
{
return [super hitTest:point withEvent:event];
}
return CGRectContainsPoint(rect, point) ? self : nil;
}
@end
2>设置UILabel可以复制的属性
#import "UILabel+Copy.h"
@implementation UILabel (Copy)
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
return (action == @selector(copyText:));
}
- (void)attachTapHandler {
self.userInteractionEnabled = YES;
UILongPressGestureRecognizer *g = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[self addGestureRecognizer:g];
}
// 处理手势相应事件
- (void)handleTap:(UIGestureRecognizer *)g {
if (g.state != UIGestureRecognizerStateBegan) return ; //加上这一句就不会闪烁了
//注意::这句必须加上,否则拷贝框出不来
[self becomeFirstResponder];
NSLog(@"长按label........");
UIMenuItem *item = [[UIMenuItem alloc] initWithTitle:@"复制" action:@selector(copyText:)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:item]];
[[UIMenuController sharedMenuController] setTargetRect:self.frame inView:self.superview];
[[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
// self.backgroundColor = [UIColor lightGrayColor] ;
}
// 复制时执行的方法
- (void)copyText:(id)sender {
// 通用的粘贴板
UIPasteboard *pBoard = [UIPasteboard generalPasteboard];
// 有些时候只想取UILabel的text中的一部分
if (objc_getAssociatedObject(self, @"expectedText")) {
pBoard.string = objc_getAssociatedObject(self, @"expectedText");
} else {
// 因为有时候 label 中设置的是attributedText
// 而 UIPasteboard 的string只能接受 NSString 类型
// 所以要做相应的判断
if (self.text) {
pBoard.string = self.text;
} else {
pBoard.string = self.attributedText.string;
}
}
}
- (BOOL)canBecomeFirstResponder {
return [objc_getAssociatedObject(self, @selector(isCopyable)) boolValue];
}
- (void)setIsCopyable:(BOOL)number {
objc_setAssociatedObject(self, @selector(isCopyable), [NSNumber numberWithBool:number], OBJC_ASSOCIATION_ASSIGN);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willHideEditMenu:)name:UIMenuControllerWillHideMenuNotification object:nil];
[self attachTapHandler];
}
- (void)willHideEditMenu:(NSNotification *)notification
{
// self.backgroundColor = [UIColor clearColor];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (BOOL)isCopyable {
return [objc_getAssociatedObject(self, @selector(isCopyable)) boolValue];
}
@end
更多应用场景:
https://blog.csdn.net/SandyLoo/article/details/80174890
runloop
https://xiaopengmonsters.github.io/2017/12/01/RunLoop%20本质和事件循环机制/
runloop是一个事件循环,用于处理事件和消息,以及对他们的管理
runloop和线程的关系
如何创建一个常驻线程
#import "MCObject.h"
@implementation MCObject
static NSThread *thread = nil;
// 标记是否要继续事件循环
static BOOL runAlways = YES;
+ (NSThread *)threadForDispatch{
if (thread == nil) {
@synchronized(self) {
if (thread == nil) {
// 线程的创建
thread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequest) object:nil];
[thread setName:@"com.imooc.thread"];
//启动
[thread start];
}
}
}
return thread;
}
+ (void)runRequest
{
// 创建一个Source
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 创建RunLoop,同时向RunLoop的DefaultMode下面添加Source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 如果可以运行
while (runAlways) {
@autoreleasepool {
// 令当前RunLoop运行在DefaultMode下面
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
}
// 某一时机 静态变量runAlways = NO时 可以保证跳出RunLoop,线程退出
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
@end
多线程
GCD主要特点:
1)GCG 是iOS4.0推出的,主要针对多核CPU 做了优化
2)GCD是 C 语言的技术
3)GCD 提供了一些 NSOperation 不具备的功能,比如一次性执行(创建单例),延迟执行,调度组.
NSOperation 特点:
- NSOperation 是 iOS2.0后推出的,iOS4.0之后重写了NSOperation.
- NSOperation 将操作(异步的任务)添加到队列(并发队列),就会执行制定操作的函数.
- NSOperation里可以方便的设置操作:
️最大并发数
️队列的暂停/继续
️取消所有的操作
️指定操作之间的依赖关系(GCD可以用同步实现)
**使用NSOperation 需要注意几点点: - 注意避免产生循环依赖
- 要先设置依赖关系,然后添加到队列
GCD 和 NSOperation的区别主要表现在以下几方面:
GCD是一套 C 语言API,执行和操作简单高效,因此NSOperation底层也通过GCD实现,这是他们之间最本质的区别.因此如果希望自定义任务,建议使用NSOperation;
依赖关系,NSOperation可以设置操作之间的依赖(可以跨队列设置),GCD无法设置依赖关系,不过可以通过同步来实现这种效果;
KVO(键值对观察),NSOperation容易判断操作当前的状态(是否执行,是否取消等),对此GCD无法通过KVO进行判断;
优先级,NSOperation可以设置自身的优先级,但是优先级高的不一定先执行,GCD只能设置队列的优先级,如果要区分block任务的优先级,需要很复杂的代码才能实现;
继承,NSOperation是一个抽象类.实际开发中常用的是它的两个子类:NSInvocationOperation和NSBlockOperation,同样我们可以自定义NSOperation,GCD执行任务可以自由组装,没有继承那么高的代码复用度;
效率,直接使用GCD效率确实会更高效,NSOperation会多一点开销,但是通过NSOperation可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己;
7)可以随时取消准备执行的任务(已经在执行的不能取消),GCD没法停止已经加入queue 的 block(虽然也能实现,但是需要很复杂的代码)
基于GCD简单高效,更强的执行能力,操作不太复杂的时候,优先选用GCD;而比较复杂的任务可以自己通过NSOperation实现.
GCD\NSOperation\NSThread
死锁原因:队列引起的循环等待,而不是线程
分析:通过异步方式分派到全局队列之后,这个block会在GCD底层所维护的线程池中的某一个线程上面去执行和处理,而GCD底层分派的这些线程默认是没有开启对应的runloop的,而下面的performSelector方法即使是延迟0秒也会创建相应的一个提交任务到runloop的,所以performSelector方法会失效的.所以performSelector这个方法要想有效执行,必须是方法调用所属的当前线程是开启了runloop的.
利用GCD实现多读单写
#import "UserCenter.h"
@interface UserCenter()
{
// 定义一个并发队列
dispatch_queue_t concurrent_queue;
// 用户数据中心, 可能多个线程需要数据访问
NSMutableDictionary *userCenterDic;
}
@end
// 多读单写模型
@implementation UserCenter
- (id)init
{
self = [super init];
if (self) {
// 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 创建数据容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key
{
__block id obj;
// 同步读取指定数据
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key
{
// 异步栅栏调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}
@end
如何使用GCD实现这个需求: A、B、C三个任务并发,完成后执行任务D ?
#import "GroupObject.h"
@interface GroupObject()
{
dispatch_queue_t concurrent_queue;
NSMutableArray <NSURL *> *arrayURLs;
}
@end
@implementation GroupObject
- (id)init
{
self = [super init];
if (self) {
// 创建并发队列
concurrent_queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
arrayURLs = [NSMutableArray array];
}
return self;
}
- (void)handle
{
// 创建一个group
dispatch_group_t group = dispatch_group_create();
// for循环遍历各个元素执行操作
for (NSURL *url in arrayURLs) {
// 异步组分派到并发队列当中
dispatch_group_async(group, concurrent_queue, ^{
//根据url去下载图片
NSLog(@"url is %@", url);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 当添加到组中的所有任务执行完成之后会调用该Block
NSLog(@"所有图片已全部下载完成");
});
}
@end
锁
https://www.jianshu.com/p/0b40a63c6436
@synchronized
一般在创建单例对象的时候使用
atomic
修饰属性的关键字
对被修饰对象进行原子操作(不负责使用)
OSSpinLock 自旋锁
循环等待询问,不释放当前资源.如果有别的线程正在使用当前锁,那当前线程会一直等着锁释放,释放了会第一时间获取锁,所以是"忙等"的锁.用于轻量级数据访问,简单的int值 +1/-1操作
递归锁
dispatch_ semaphore_ _t
dispatch_ semaphore_ create(1);
dispatch_ semaphore_ _wait(semaphore, DISPATÇH_ TIME_ FOREVER);
dispatch_ semaphore_ _signal(semaphore);
dispatch_ semaphore_ create( )
struct semaphore {
int value ;
List <thread> ;
}
dispatch_ semaphore_ _wait()
{
S.value = S.value - 1 ;
if S.value < O then Block(S.List) ;//阻塞是个主动行为
}
dispatch_ semaphore_ _singal()
{
S.value = S.value + 1 ;
if S.value <= 0 then wakeup(S.List) ; //唤醒是个被动行为
}
几种多线程技术各自的特点是?
利用GCD实现简单的线程同步,包括子线程的分派,包括实现多读单写的场景;
NSOperation比如常见第三方框架AFN,SDWebImage,方便对任务状态的控制,方便添加依赖和移除依赖.
NSThread常用来实现一个常驻线程.
block
block是将函数及其执行上下文封装起来的对象.
使用[ clang -rewrite-objc file.m]查看编译之 后的文件内容
block调用即是函数的调用
截获变量
- 对于基本数据类型的局部变量截获其值。
- 对于对象类型的局部变量连同所有权修饰符一 起截获。
- 以指针形式截获局部静态变量。
-
不截获全局变量、静态全局变量。
一般情况下,对被截获变量进行赋值操作需添加_ block修饰符
{
NSMutableArray *array = [ NSMutableArrayse array] ;
void(^Block)(void) = ^{
[array addObject :@123];//这里由于是使用不是赋值,所以前面不需要加__block
};
Block( );
}
{
NSMutableArray *array = nil ;
void(^Block)(void) = ^{
array = [NSMutableArray array] ;
);
Block() ;
}
//需要在array声明处加上__block
对变量进行赋值时
_forwarding存在的意 义
不论在任何内存位置,
都可以顺利的访问同一个 _block变量。
[图片上传中...(image.png-a97889-1562736870476-0)]
上面这段代码是有问题的,如下
解决方案
为什么block会产生循环引用?
1.如果当前block对当前对象的某一成员变量进行截获了,这个block会对相应成员变量有一个强引用,而当前block又由于当前对象对它有个强引用就产生了自循环引用的循环引用.这时候就需要用__weak修饰来消除这种循环引用.
2.但是用__block也会导致上面的多环循环引用,区分场景的.
怎样理解block截获变量的特性?
- 对于基本数据类型的局部变量截获其值。
- 对于对象类型的局部变量连同所有权修饰符一 起截获,并强引用。
- 以指针形式截获局部静态变量。
- 不截获全局变量、静态全局变量。
你遇见过哪些循环引用?
NSTimer
Block:1>block捕获的变量也是当前对象的一个成员变量,而此block也是当前对象的一个成员变量,造成自循环式的循环引用.
2>__block也会造成循环引用.
内存管理
内存布局
代码区:存放函数二进制文件代码;
数据区:系统运行时申请内存并初始化,系统退出时由系统释放,存放静态变量,常量,全局变量;
堆区:由低地址向上的不连续内存空间,频繁的new/delete会造成内存碎片,降低运行效率。堆需要手动释放,回收内存。堆的内存空间更大,更灵活。
栈区:由高地址向下开辟的一块连续的内存空间,先进后出,所以不会有碎片问题,由编译器控制释放,无需手动释放,因此更有效率,分为静态分配(编译器完成分配,如局部变量)和动态分配(如alloc函数进行分配,由编译器自动释放)。栈的大小是编译器自动分配的,所以如果申请的内存空间大于剩余内存大小,会overflow。
ps:oc中基本数据类型(int,double,float...)是在栈区,无需手动释放,超过作用域自动释放;对象(NSObject及其子类)是在堆区,需要手动释放release,回收内存。
pps:这里插一下,为什么block要用copy修饰符,因为block本质上是代码块,在栈区,为了防止生命周期结束自动被释放需要copy到堆区。
内存管理方案
- TaggedPointer
- NONPOINTER ISA
-
散列表sideTables,本质上就是一个哈希表
引用计数表实质上也是一个哈希表.
弱引用表实质上也是一个哈希表.
MRC:手动引用计数来管理内存
需要注意的两点:
1>autorelease那么当前对象会在releasePool结束的时候调用release方法进行引用计数减1
2>dealloc方法,需要显示的调用super dealloc方法
ARC:自动引用计数来管理内存
什么是ARC?(看图)
1>除了由编译器自动插入retain和release操作外,还需要runtime的功能进行支持,然后由编译器和runtime共同协作才能组成ARC的全部功能.
2>可以重写dealloc方法,但不能调用super dealloc方法
引用计数管理
实现原理分析
- alloc
- retain
- release
- retainCount
- dealloc
1.alloc实现
经过一系列调用,最终调用了C函数calloc。
此时并没有设置引|用计数为1。
2.retain实现
3.release实现
4.retainCount实现
5.dealloc实现
objc_destructInstance()实现
![image.png](https://upload-images.jianshu.io/upload_images/2332921-efa5ac9a1e1833f8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1 240)
clearDeallocating()实现
系统是怎么把一个weak变量添加到对应的弱引用表中的?
一个被声明为__weak的对象指针,经过编译器的编译之后会调用相应的objc_initWeak()方法,然后经过一系列的函数调用栈最终在weak_register_no_lock()方法中进行弱引用变量的添加,具体添加的位置是通过hash算法进行位置查找的,如果查找的位置已存在当前对象的弱引用数组,就把新的弱引用变量添加到数组中,如果没有的话,就新建一个弱引用数组,并把第0个位置添加上最新的weak指针,后面的都初始化为nil.
如果一个对象被释放或者废弃之后,weak变量是如何处理的?
总结:当一个对象被废弃 dealloc之后,在dealloc的内部实现中会去调用弱引用清除的方法,在清除的内部实现中会根据当前被废弃的对象指针去哈希查找弱引用表,把当前对象的相对应的弱引用都拿出来,这是一个数组,之后遍历这个数组中的弱引用指针分别置为nil.
自动释放池
https://www.jianshu.com/p/3dbc169f97be
本质上就是延迟调用release方法。自动释放池(Autorelease pool)是OC的一种内存自动回收机制,可以将一些临时变量通过自动释放池来回收统一释放。自动释放池本身销毁的时候,池子里面所有的对象都会做一次release操作
自动释放池什么时候销毁?
第一次创建:启动runloop时候
最后一次销毁:runloop退出的时候
其他时候的创建和销毁:当runloop即将睡眠时销毁之前的释放池,重新创建一个新的。
编译器会将@autoreleasepool{}改写为:
void *ctx = objc_ autoreleasePoolPush();
{ }中的代码
objc_ autoreleasePoolPop(ctx);
怎么理解一次pop操作实际上相当于一次批量的pop操作?
@autoreleasepool{}其中花括号中的所有对象都会添加到自动释放池中,当进行pop操作之后,{}中所有的对象都会执行release操作.
自动释放池的定义?即苹果是如何实现AutoreleasePool的?
- 是以栈为结点通过双向链表的形式组合而成一个数据结构。
-
是和线程一一对应的。
循坏引用
- 自循珎引用
- 相互循珎引用
- 多循珎引用
如何破除循环引|用? - 避免产生循环弓引用
-
在合适的时机手动断环
网络相关问题
HTTP
怎样理解HTTP?HTTP协议包含哪些内容?
超文本传输协议
- 请求/响应报文
- 连接建立流程
-
HTTP的特点
GET和POST方式的区别(非标准答案)
- GET请求参数以?分割拼接到URL后面, POST请求参数在Body里面
- GET参数长度限制2048个字符, POST-般没有该限制
-
GET请求不安全, POST请求比较安全
HTTP的特点
-无连接
HTTP的持久连接
-无状态
Cookie/Session
HTTPS和HTTP有啥区别?
HTTPS是安全的HTTP,安全是由SSL或者TSL插在应用层之下、传输层之上的一个协议保障的.
HTTPS的连接建立流程是怎样的?
HTTPS都使用了哪些加密手段?为什么?
- 连接建立过程使用非对称加密,非对称加密很耗时的!因为加密和解密的手段不一样所以耗时。
- 后续通信过程使用对称加密
非对称加密
对称加密
对称加密比非对称加密更安全。因为对称加密的对称密钥会通过tcp连接进行传输,serve端才能通过密钥解密,client端才能通过密钥加密,一旦在网络中进行传输了,那就很有可能受到中间人攻击,对密钥进行劫持。而非对称加密是安全的,因为只有公钥会在网络传输中进行传递,而私钥是一直保存在serve端的,不在网络端进行传递。
TCP/UDP
传输层协议:
- TCP,传输控制协议
- UDP, 用户数据报协议
UDP用户数据报协议
特点:无连接、尽最大努力交付、面向报文
功能:复用、分用、差错检测
差错检测
TCP
特点:面向连接、可靠传输、面向字节流、流量控制、拥塞控制
面向连接
-
数据传输开始之前, 需要建立连接
三次握手
为什么要进行三次握手?而不是两次
考虑发送的SYN同步报文在网络超时的情况下,同步报文会在网络环境中逗留,发生超时之后,客户端会启用超时重传策略,重新发送一个SYN同步报文,然而当服务端收到超时的那个同步报文之后会回复客户端一个SYN同步确认报文,如果只有两次握手,那就可以建立tcp连接了。而如果之后服务端收到了重新发送的SYN同步报文,这时候服务端就会认为客户端又要建立一个tcp连接,但实际上是启用超时重传策略重新发送的一个SYN同步报文。 而三次握手就能避免这个问题。服务端最终通过是否收到客户端发送的ACK报文确认是否是超时报文(收到ACK不是超时的,否则不是)。 -
数据传输结束之后,需要释放连接
四次挥手
可靠传输
无差错、不丢失、不重复、按序到达
可靠传输在tcp层面是通过停止等待协议来实现的
可以通过四方面来理解这个协议:无差错情况、超时重传、确认丢失、确认迟到
DNS解析
DNS查询方式
递归查询:
迭代查询:
DNS解析存在哪些常见的问题?
1)DNS劫持问题
DNS劫持与HTTP的关系是怎样的?
(没有关系.
- DNS解析发生在HTTP建立连接之前
-
DNS解析请求使用UDP数据报,端口号53)
2)DNS解析转发问题
怎样解决DNS劫持问题?
1)httpDNS
DNS解析:使用DNS协议向DNS服务器的53端口进行请求
httpDNS解析:使用HTTP协议向DNS服务器的80端口进行请求
2)长连接
session/cookie
- 客户端发送的cookie在http请求报文的Cookie首部字段中
- 服务器端设置http响应报文的Set-Cookie首部字段
怎样修改Cookie ? - 新cookie覆盖旧cookie
- 覆盖规则: name、 path、 domain等需要与原cookie- 致
怎样删除Cookie ? - 新cookie覆盖旧cookie
- 覆盖规则:name、path、 domain等需要与原cookie- 致
- 设置cookie的expires=过去的一个时间点,或者maxAge=0
怎样保证Cookie的安全? - 对Cookie进行加密处理
- 只在https.上携带Cookie
- 设置Cookie为httpOnly ,防止跨站脚本攻击
session
Session也是用来记录用户状态,区分用户的;状态存放在服务器端。
Session和Cookie的关系是怎样的?
-
Session需要依赖于Cookie机制
单例设计模式
#import <Foundation/Foundation.h>
@interface Mooc : NSObject
+ (id)sharedInstance;
@end
#import "Mooc.h"
@implementation Mooc
+ (id)sharedInstance
{
// 静态局部变量
static Mooc *instance = nil;
// 通过dispatch_once方式 确保instance在多线程环境下只被创建一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 创建实例
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
// 重写方法【必不可少】
+ (id)allocWithZone:(struct _NSZone *)zone{
return [self sharedInstance];
}
// 重写方法【必不可少】
- (id)copyWithZone:(nullable NSZone *)zone{
return self;
}
@end
架构和框架
作用:模块化、分层、解耦、降低代码重合度
图片读写
图片通过什么方式进行读写, 过程是怎样的?
-
以图片URL的单向Hash值作为Key
淘汰策略有哪些?
1)队列的先进先出
2)通过最近最久未使用的LRU算法。优先选择右侧的提高检测触发频率
网络设计
网络部分的设计需要考虑哪些问题? - 图片请求最大并发量
- 请求超时策略
-
请求优先级
独立于APP的通用层:任何APP的底层支撑作用。比如网络第三方库、崩溃统计、页面时长统计。
通用业务层:比如给当前公司业务自定义的布局控件、特殊的UIImageView的封装。
中间层:协调和解耦作用
第三方库
ReactiveCocoa
函数响应式编程的一个库
RACStream提供了下面几个抽象方法:
empty、return、bind、contact、zipWith
AsyncDisplayKit
主要解决的问题:主要是通过减轻主线程的压力来把更多的事情放到子线程去做。
Objective-C的语言特性
MVVM
(一)MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点:1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xml代码。 4. 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
(二)MVVM模式的缺点:1.数据绑定使得Bug很难被调试。如果界面发生错误,有可能是View的代码有问题,也有可能是Model的代码有问题。数据绑定使得一个位置的Bug被快速传递到别的位置,要想定位原始出问题的地方,就变得不那么容易了。2.对于大型项目,数据绑定和数据转化需要花费更多的内存(成本)。主要在于:数组内容的转化成本较高:数组里面每项都要转化成Item对象,如果Item对象中还有类似数组,就很头疼。转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需二次转化。只有在API返回的数据高度标准化时,这些对象原型(Item)的可复用程度才高,否则容易出现类型膨大,提高维护成本。3.调试时通过对象原型查看数据内容,不如直接通过NSDictionary或者NSArray这样直观。4.同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。
为什么dispatch_get_current_queue被废弃?
https://www.jianshu.com/p/f73ca215268d
GCD队列是按照层级结构来组织的,无法单用某个队列对象来描述“当前队列”
dispatch_get_current_queue函数可能返回与预期不一致的结果
误用dispatch_get_current_queue可能导致死锁
设置队列specific可以把任意数据以键值对的形式关联到队列里,从而得到需要的指定队列
解决方法
https://blog.csdn.net/yiyaaixuexi/article/details/17752925
- dispatch_queue_set_specific 标记队列
- 递归锁
lldb常用的调试命令
https://www.jianshu.com/p/695ade70bf10
1)expression:既可以打印值也可以修改值、声明变量
- p:可以查看基本数据类型的值;如果使用p命令查看的是对象,那么只会返回对象的指针地址。 p命令后面除了可以接变量、常量,还可以接表达式。
- po:打印对象。功能与p命令类似,也可以打印常量、变量,打印表达式返回的对象等。p 和 po 的区别在于使用 po 只会输出对应的值,而 p 则会返回值的类型以及命令结果的引用名。
print 简写p 是 expression -- 别名,打印基本数据类型。
po 是 expr -o -- 的别名。
2)breakpoint:设置断点。 - 通过函数名字设置断点
(lldb) breakpoint set --name "-[NSString stringWithFormat:]"
(lldb) br s -n "-[NSString stringWithFormat:]"
(lldb) b -[NSString stringWithFormat:]
- 通过地址设置断点
(lldb) breakpoint set --address 0x00000001c44441d0
(lldb) br s -a 0x00000001c44441d0
- 列举所有断点
(lldb) breakpoint list
(lldb) br l
- 删除断点
(lldb) breakpoint delete 1
(lldb) br del 1
3)单步调试
step-in和step-over,step-in会进入函数调用,而step-over会跳过函数调用。
- step-in
(lldb) thread step-in
(lldb) step
(lldb) s
每执行一次上述命令断点会跳转到下一行源代码位置,如果下一行是一个函数调用,会进入函数调用内部
- step-over
(lldb) thread step-over
(lldb) next
(lldb) n
每执行一次上述命令断点会跳转到下一行源代码位置,如果下一行是一个函数调用,则不会进入函数调用内部
总结:s和n都是跳转到断点的下一行源代码位置,区别为,如果下一行源代码有函数调用,s会进入函数内部,n则会跳过函数执行。如果没有函数调用则两组命令没有任何区别。
手写单例
// 实现copy协议
@interface SignalModel()<NSCopying, NSMutableCopying>
@end
@implementation SignalModel
+ (instancetype)shareInstance {
static SignalModel *_instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[super allocWithZone:NULL] init];
});
return _instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [self shareInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return self;
}
@end
SDWebImage加载url不变,但图片更新的解决方法
https://www.jianshu.com/p/8cc494e77507
https://blog.csdn.net/modalyin/article/details/77944195