目录
- 1.0生命周期及响应者链
- 1.1对象的本质
- 1.2kvc/kvo
- 1.3Category
- 1.4block
- 1.5数据存储
- 1.6内存管理
- 1.7编码及加解密
- 2.1Runtime
- 2.2多线程
- 2.3Runloop
- 2.4Autorelease
- 3.1WebView
1.0生命周期及响应者链
一、生命周期
考察viewDidLoad、viewWillAppear、ViewDidAppear等方法的执行顺序。
假设现在有一个 AViewController(简称 Avc) 和 BViewController (简称 Bvc),通过 navigationController 的push 实现 Avc 到 Bvc 的跳转,调用顺序如下:
1、A viewDidLoad
2、A viewWillAppear
3、A viewDidAppear
4、B viewDidLoad
5、A viewWillDisappear
6、B viewWillAppear
7、A viewDidDisappear
8、B viewDidAppear
如果再从 Bvc 跳回 Avc,调用顺序如下:
1、B viewWillDisappear
2、A viewWillAppear
3、B viewDidDisappear
4、A viewDidAppear
二、响应者链
无法响应的情况
1.Alpha=0、hidden=YES、子视图超出父视图的情况、userInteractionEnabled=NO 视图会被忽略,不会调用hitTest
2.父视图被忽略后其所有子视图也会被忽略,所以View3上的button不会有点击反应
3.出现视图无法响应的情况,可以考虑上诉情况来排查问题
应用示例
限定点击区域
给定一个显示为圆形的视图,实现只有在点击区域在圆形里面才视为有效。
我们可以重写View的pointInside方法来判断点击的点是否在圆内,也就是判断点击的点到圆心的距离是否小于等于半径就可以。
1.1对象的本质
三种类型的对象:实例对象、类对象、元类对象
两个重要指针:isa指针、super指针
内存分布
1.2kvc/kvo
kvc
1、找set方法
2、调用是否允许直接访问变量
3、找成员变量
4、调用setValue:forUndefinedKey抛错
1、找get方法
2、调用是否允许直接访问变量
3、找成员变量
4、调用ValueForUndefinedKey抛错
kvo
在运行时会派生出一个新的类
kvo_person
重写set方法
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:(options) context:@"123"];
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context{
}
-(void)dealloc{
//一定要移除监听,否则引起内存泄漏
[self.person1 removeObserver:self forKeyPath:@"age"];
}
#import "NSKVONotifying_MJPerson.h"
@implementation NSKVONotifying_Person
- (void)setAge:(int)age{
_NSSetIntValueAndNotify();
}
// 伪代码
void _NSSetIntValueAndNotify(){
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
// 通知监听器,某某属性值发生了改变
[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
// 屏幕内部实现,隐藏了NSKVONotifying_MJPerson类的存在
- (Class)class{
return [MJPerson class];
}
- (void)dealloc{
// 收尾工作
}
- (BOOL)_isKVOA{
return YES;
}
@end
为什么要移除观察者?
比如VC是person.name的观察者,当VC被销毁时,需要[person removeObserver:self forKeyPath:@”name”];否则当name属性发生变化时,会给vc发送消息,但vc已经释放,这个时候会报EXC_BAD_ACCESS错误。
1.3Category
+Load方法
程序启动加载类的时候,
只调用一次,直接地址调用
先父类再子类,先父类分类再子类分类
+initialize方法:
类第一次收到消息的时候调用,
至少调用一次,msg_send方式调用
先父类在子类,分类会覆盖类的initialize方法
关联对象:
全局的hash表
#import <objc/runtime.h>
- (void)setWeight:(int)weight{
objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)weight{
// _cmd == @selector(weight)
return [objc_getAssociatedObject(self, _cmd) intValue];
}
1.4block
介绍
block:匿名函数,可以作为参数和返回值
int(^blk)(int a, NSString* str) = ^int(int a, NSString* str){
return 10;
};
变量捕获
被捕获的变量在block内无法修改
要修改:__block int a = 10 修饰
block的本质
block的本质是对象
impl
Desc
捕获的变量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
printf("Block\n age=%d\n",age);
}
block的调用本质是方法
impl下有一个FuncPtr指针
block初始化时传入方法地址
要清楚方法的参数是如何传递的
int main(int argc, char * argv[]) {
int age = 10;
void(*blk)(void)=&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
age));
age = 20;
((__block_impl *)blk)->FuncPtr)(blk);
}
return 0;
}
捕获的变量
自动变量:值捕获 int / Person
静态局部变量:地址捕获
不会捕获全局变量
block的类型
一、block的类型(block是对象)
没有捕获auto变量:NSGlobalBlock:数据段
copy操作无反应
捕获auto变量:NSStackBlock:栈区
copy操作复制到堆空间
NSStackBlock__执行copy后: __NSMallocBlock:堆区
copy操作引用计数加1
二、block的copy
作为返回值会执行copy操作
强指针指向会执行copy操作
usingBlock中做参数会执行copy操作
GCD中做参数会执行copy操作
三、捕获对象类型自动变量
1、如果Block在栈上,不会对对象类型auto变量产生强引用。
2、如果Block在堆上,会根据对象类型auto变量的修饰符 __strong、 __weak、 __unsafe_unretained
做出相应的操作,形成强引用或若引用。
四、desc下的copy方法和dispose方法
Block被拷贝到堆上会调用Block内部的copy函数,对捕获的变量强弱引用
Block从堆上移除会调用Block内部的dispose函数,释放捕获的变量
__block原理
一、_ _block 修饰为什么就能在block内部修改值了
__block int age = 10;
blk b=^(){
age = 20;
};
——block修饰的age会变成一个对象block_age
对象内部有一个属性age
对象block_age会被block捕获
block内修改的是block_age下的age属性而不是
block_age对象
typedef void(*blk)(void);
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
}
二、forwarding指针
这就是为什么是 (age->__forwarding->age) = 20;而不是(age->age) = 20;的原因
block的循环引用
Person * person = [[Person alloc] init];
person.blk = {
person.name
}
Person * person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
person.blk = {
person.name
}
1.5数据存储
1.6内存管理
一、内存管理方式
1、内存分布
2、管理方式
MRC:手动引用计数
ARC:自动引用计数
TaggedPointer
Autorelease
二、内存泄露
1、内存泄露的原因
(1) 循环引用:block的循环引用,block内访问了self或super.
(2) delegate的循环引用,使用了strong修饰了代理对象。
(3) 定时器的循环引用,self持有定时器,定时器target传入self后会持有self,造成循环引用。
(4) 调用了c/c++的malloc开辟了空间而没有手动调用delloc。类似的比如调用CGImageCreate不调用CGImageRelease
2、解决内存泄露的方法
(1) __weak typeof(self) weakSelf = self;破除循环引用。
(2) 代理使用@property(nonatomic, weak)id
(3) 使用定时器的block形式。或者使用代理类NSProxy,将proxy对象作为target传入定时器。proxy对象弱引用self,然后利用消息转发将调用转给self。
(4) 手动delloc或release堆空间
3、内存泄露的检测方法
1.7编码及加解密原理
2.1Runtime
class的结构
一、isa的结构
union isa_t {
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1; //是否是优化过的指针
uintptr_t has_assoc : 1; //是否有关联对象
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1; //是否有弱引用
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1; //是否有引用计数表
uintptr_t extra_rc : 19 //引用计数
};
};
二、cache的结构
三、bits的结构
二维数组
消息机制
1、消息发送是在查找方法实现
2、动态方法解析是给类一个机会自己动态生成对应的方法
3、消息转发是当前类没有处理看看其它的类能不能处理
一、消息发送
objc_msgSend(person, sel_registerName("test"));
消息接收者(receiver):person
消息名称:test
二、动态方法解析
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
NSLog(@"resolveInstance");
//获取method
Method method = class_getInstanceMethod(self, @selector(other));
//动态添加方法
class_addMethod(self,
sel,
method_getImplementation(method),
method_getTypeEncoding(method));
//
return YES;
}
return [super resolveInstanceMethod:sel];
}
三、消息转发
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(test)) {
Class Cat = NSClassFromString(@"Cat");
//返回一个实例对象 就会调用实例对象的test方法
//return [[Cat alloc] init];
//返回nil 就会调用methodSignatureForSelector
return nil;
}
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(test)) {
//注册消息转发到的方法的类型
return [NSMethodSignature signatureWithObjCTypes:"i20@0:8i:4"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s",__func__);
//anInvocation 包含selector targe argument
if (anInvocation.selector == @selector(test)) {
Class Cat = NSClassFromString(@"Cat");
id cat = [[Cat alloc] init];
//更改消息接收者
anInvocation.target =cat;
//更改selector
anInvocation.selector = @selector(catTest:);
//设置新方法参数
int age = 5;
[anInvocation setArgument:&age atIndex:2];
//新方法调用
[anInvocation invoke];
//获取新方法的返回值
int reValue;
[anInvocation getReturnValue:&reValue];
NSLog(@"reValue=%d",reValue);
}
}
@end
几个知识点
一、@synthesize 和 @dynamic
@synthesize的作用就是通知编译器为相关属性自动生成,成员变量、set、get方法。
@synthesize age=_age,height=_height;
@dynamic是告诉编译器不用自动生成成员变量、set、get方法,等到运行时再添加方法实现。
@dynamic age;
二、Super的原理
[super test];
static void _I_Student_test(Student * self, SEL _cmd) {
struct __rw_objc_super arg = {
self,//消息的接收者还是self
self.superClass//查找从父类开始
}
objc_msgSendSuper(arg,sel_registerName("test"));
}
[self class] //Student
[self superclass]//Person
[super class]);//Student
[super superclass]//Person
三、isMemberOfClass 和 isKindOfClass
NSLog(@"%d",[person isMemberOfClass:[Person class]]);//1
NSLog(@"%d",[person isMemberOfClass:[NSObject class]]);//0
NSLog(@"%d",[person isKindOfClass:[Person class]]);//1
NSLog(@"%d",[person isKindOfClass:[NSObject class]]);//1
NSLog(@"%d",[NSObject isMemberOfClass:[NSObject class]]);//0
NSLog(@"%d",[NSObject isKindOfClass:[NSObject class]]);//1
NSLog(@"%d",[Person isMemberOfClass:[Person class]]);//0
NSLog(@"%d",[Person isKindOfClass:[Person class]]);//0
isMemberOf不会顺着super指针向上查找
isKindOf会顺着super向上查找
四、cls指针
-(void)print{
NSLog(@"my name is %@",self.name);
}
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Person class];//cls就是isa指针
void * obj = &cls;//obj就是实例对象
// 是否调用成功,打印什么?
[(__bridge id)obj print];//栈空间的内存分布
}
调用成功打印
my name is <ViewController: 0x100704080>
runtime的应用
一、遍历查看所有的成员变量和属性
1.textfiled修改占位文字的颜色
2.字典转模型:
for循环拿到属性转为字符串作为key
通过key,dic[key]取出value
通过kvc,setValueForKey(key,value)对属性赋值
3.自动归档解档
自定义类型存数据库需要归解档
for循环拿到属性转为字符串作为key
归档:通过kvc拿到value,调用encode(key,value)归档
解档:value = decode(key),然后kvc,setValueForKey(key,value)
二、交换方法实现
1.交换数组的insertObject方法,解决索引越界崩溃问题
三、消息转发解决方法找不到的崩溃问题
实现methodSignature方法
实现forwardInvocation方法,方法内可以做一些提示或上送操作
四、利用关联对象给分类添加属性
2.2多线程
一、进程和线程
1、进程
一个app就是一个进程
操作系统进行资源分配的基本单位,每个进程运行在独立的内存空间内
2、线程
进程的基本执行单元
线程是操作系统进行任务调度的基本单位
3、线程的状态
创建线程 - 就绪 - 运行 - 阻塞 - 死亡
4、线程的优缺点
优点:提高程序执行效率;提高CPU利用率;
缺点:程序变复杂;开启线程本身就会占用空间,开启过多会占用大量内存空间,调度频率也会降低;
二、GCD - sync 和 async
1、几个重要概念
//同步:代码块执行完才返回,在当前线程执行
dispatch_sync(queue,{ });
//异步:立即返回,在新线程执行
dispatch_async(queue,{ });
//串行队列:在队列里的多个任务按先后顺序,一个一个取出。
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
//并发队列:在队列里的多个任务允许同时取出。
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
//主队列:串行队列
dispatch_queue_t mainQueue = dispatch_get_main_queue()
//全局队列:并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
2、重要规则
先看队列是串行并行,再看执行是同步异步
放在串行队列里的任务一定是一个执行完再取下一个,是有顺序的
串行队列里会不会死锁,就看任务能不能正常取出
放在并行队列里的任务可以同时取出
同步执行:在当前线程执行,一定是整个任务块执行完再返回的
异步执行:立即返回
3、典型面试题
- (IBAction)sync2:(id)sender {
//global_queue - main_queue
NSLog(@"current1 %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(0,0), ^{
NSLog(@"current2 %@",[NSThread currentThread]);
//死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"current3 %@",[NSThread currentThread]);
});
});
NSLog(@"current4 %@",[NSThread currentThread]);
/**
2022-04-02 00:12:04.476120+0800 XYApp[79949:3633423] current1 <_NSMainThread: 0x283228600>{number = 1, name = main}
2022-04-02 00:12:04.476297+0800 XYApp[79949:3633423] current2 <_NSMainThread: 0x283228600>{number = 1, name = main}
死锁
*/
}
- (IBAction)sync3:(id)sender {
//main_queue - global_queue
NSLog(@"current1 %@",[NSThread currentThread]);
//死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"current2 %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(0,0), ^{
NSLog(@"current3 %@",[NSThread currentThread]);
});
});
NSLog(@"current4 %@",[NSThread currentThread]);
/**
2022-04-02 00:19:15.663318+0800 XYApp[80099:3637866] current1 <_NSMainThread: 0x280fe8880>{number = 1, name = main}
死锁
*/
}
- (IBAction)sync3:(id)sender {
dispatch_async(mainQueue, ^{//block0
NSLog(@"block0");
});
//不用等async任务执行,就可以直接来到此处
}
- (IBAction)sync6:(id)sender {
[self subThreadWithDoneBlock:^{
NSLog(@"current1 %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(0,0), ^{
NSLog(@"current2 %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"current3 %@",[NSThread currentThread]);
});
});
NSLog(@"current4 %@",[NSThread currentThread]);
}];
NSLog(@"current5 %@",[NSThread currentThread]);
sleep(2);
NSLog(@"current6 %@",[NSThread currentThread]);
/**
2022-04-01 23:20:22.017079+0800 XYApp[79118:3610327] current5 <_NSMainThread: 0x280ea8880>{number = 1, name = main}
2022-04-01 23:20:22.017094+0800 XYApp[79118:3610359] current1 <NSThread: 0x280ef8040>{number = 5, name = (null)}
2022-04-01 23:20:22.017421+0800 XYApp[79118:3610359] current2 <NSThread: 0x280ef8040>{number = 5, name = (null)}
2022-04-01 23:20:24.018549+0800 XYApp[79118:3610327] current6 <_NSMainThread: 0x280ea8880>{number = 1, name = main}
2022-04-01 23:20:24.020729+0800 XYApp[79118:3610327] current3 <_NSMainThread: 0x280ea8880>{number = 1, name = main}
2022-04-01 23:20:24.021011+0800 XYApp[79118:3610359] current4 <NSThread: 0x280ef8040>{number = 5, name = (null)}
*/
}
- (IBAction)async1:(id)sender {
//创建一个串行队列
dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"current1 %@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"current2 %@",[NSThread currentThread]);
});
});
NSLog(@"current3 %@",[NSThread currentThread]);
/**
2022-04-02 23:27:46.457775+0800 XYApp[18862:12321559] current1 <_NSMainThread: 0x280280800>{number = 1, name = main}
2022-04-02 23:27:46.458091+0800 XYApp[18862:12321559] current3 <_NSMainThread: 0x280280800>{number = 1, name = main}
2022-04-02 23:27:46.458149+0800 XYApp[18862:12321571] current2 <NSThread: 0x2802ec280>{number = 7, name = (null)}
*/
}
- (IBAction)async2:(id)sender {
dispatch_queue_t queue = dispatch_queue_create("SERIAL", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"current1 %@",[NSThread currentThread]);
//死锁
dispatch_sync(queue, ^{
NSLog(@"current2 %@",[NSThread currentThread]);
});
});
NSLog(@"current3 %@",[NSThread currentThread]);
/**
2022-04-02 23:28:15.568690+0800 XYApp[18862:12321559] current3 <_NSMainThread: 0x280280800>{number = 1, name = main}
2022-04-02 23:28:15.568787+0800 XYApp[18862:12321573] current1 <NSThread: 0x2802c7f00>{number = 3, name = (null)}
死锁
*/
}
三、GCD - group
1、用法一
-(void)GCD_GroupTest{
//全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
//创建线程组
dispatch_group_t group = dispatch_group_create();
//添加任务
dispatch_group_async(group, globalQueue, ^{
NSLog(@"block0-%@",[NSThread currentThread]);
});
dispatch_group_async(group, globalQueue, ^{
NSLog(@"block1-%@",[NSThread currentThread]);
});
//等待通知
dispatch_group_notify(group, globalQueue, ^{
NSLog(@"block2-%@",[NSThread currentThread]);
});
}
2、用法二
- (void)groupDemo2{
// 问题: 如果 dispatch_group_enter 多 dispatch_group_leave 不会调用通知
// dispatch_group_enter 少 dispatch_group_leave 奔溃
// 成对存在
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第一个走完了");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第二个走完了");
dispatch_group_leave(group);
});
//如果没有 dispatch_group_leave(group); 就不会来到dispatch_group_notify
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成,可以更新UI");
});
}
intptr_t result = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 6));
if (result == 0) {
//enter 与 leave平衡
NSLog(@"current6 %@",[NSThread currentThread]);
} else {
//超时
NSLog(@"current7 %@",[NSThread currentThread]);
}
四、GCD - barrier
// 一定要使用自定义的队列
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
// 两个重要的函数
dispatch_barrier_sync(queue, ^{
NSLog(@"[lilog]:barrier_sync");
});
dispatch_barrier_async(queue, ^{
NSLog(@"[lilog]:barrier_async");
});
五、GCD - semaphore
初始信号量为0,可以设置依赖关系
- (IBAction)semaphoreOrder {
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务1");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务2");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务3");
});
}
初始信号量为1,可以做串行队列
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
intptr_t isSuccess = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"drawMoney-isSuccess = %ld",isSuccess);//成功时返回0
intptr_t process = dispatch_semaphore_signal(semaphore);
NSLog(@"drawMoney-process = %ld",process);//它只表示成功发送信号的次数,不用过多关注
六、GCD - 其它
// 一次执行
-(void)GCD_Once{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"lilog - once");
});
}
// 延迟执行
-(void)GCD_After{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"大牛");
});
NSLog(@"IT民工");
}
七、perform
主线程执行perform相关方法的情况
// 依赖runloop,处理source0时处理
[self performSelector:@selector(test4)];
[self performSelector:@selector(test5:) withObject:@"hehe"];
// 阻塞当前线程,直到test1执行完成
[self performSelector:@selector(test1) onThread:currentThread withObject:nil waitUntilDone:YES];
// 不会阻塞当前线程,立即返回
[self performSelector:@selector(test1) onThread:currentThread withObject:nil waitUntilDone:NO];
// 依赖runloop,处理timer时处理
[self performSelector:@selector(test1) withObject:nil afterDelay:0];
子线程执行perform相关方法的情况
// 以下情况不依赖运行循环。会在当前线程立即调用
[self performSelector:@selector(test4)];
[self performSelector:@selector(test5:) withObject:@"hehe"];
//阻塞当前线程,直到test1执行完成
[self performSelector:@selector(test1) onThread:currentThread withObject:nil waitUntilDone:YES];
// 下面perform开头的方法都会依赖运行循环。在没有开启运行循环的线程里是没发执行的。
[self performSelector:@selector(test1) onThread:currentThread withObject:nil waitUntilDone:NO];
[self performSelector:@selector(test1) withObject:nil afterDelay:0];
另外performSelector:withObject:afterDelay:和 dispatch_after 都是立即返回。当两者延迟都是0,前者的执行时机比后者慢,因为前者依赖运行循环。
记忆技巧:
在子线程中,waitUntilDone:NO和带有afterDelay:的执行需要依赖runloop否则不会执行。
八、interview
1、小米面试题
- (IBAction)interview4:(id)sender {
dispatch_async(dispatch_get_main_queue(), ^{
[self test1];//主队列排队
});
// 本质是定时器,依赖runloop,被放入主队列的时间有可能会延迟
[self performSelector:@selector(test2) withObject:nil afterDelay:0];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self test3];//主队列排队:是在指定时间之后将任务追加到主队列中
});
[self performSelector:@selector(test4)];//当前线程立即执行
[self test5];//当前线程立即执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self test6];//主队列排队
});
[self test7];
[self performSelector:@selector(test8) withObject:nil afterDelay:0];//依赖线程的运行循环,所以不会执行
});
/**
2022-08-19 12:27:15.094636+0800 XYApp[49643:6816750] test4
2022-08-19 12:27:15.094888+0800 XYApp[49643:6816750] test5
2022-08-19 12:27:15.095116+0800 XYApp[49643:6816968] test7
2022-08-19 12:27:15.096323+0800 XYApp[49643:6816750] test1
2022-08-19 12:27:15.096513+0800 XYApp[49643:6816750] test3
2022-08-19 12:27:15.096664+0800 XYApp[49643:6816750] test6
2022-08-19 12:27:15.096876+0800 XYApp[49643:6816750] test2
//如果将执行test3的延迟改为3秒,打印顺序如下
2022-08-19 12:32:51.156387+0800 XYApp[49790:6822158] test4
2022-08-19 12:32:51.156642+0800 XYApp[49790:6822158] test5
2022-08-19 12:32:51.156919+0800 XYApp[49790:6822177] test7
2022-08-19 12:32:51.158391+0800 XYApp[49790:6822158] test1
2022-08-19 12:32:51.158598+0800 XYApp[49790:6822158] test6
2022-08-19 12:32:51.158820+0800 XYApp[49790:6822158] test2
2022-08-19 12:32:54.368531+0800 XYApp[49790:6822158] test3
*/
}
2、面试题
- (IBAction)lg_test3:(id)sender {
self.num = 0;
int time = 0;
while (self.num < 100) {
time ++;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.num ++;
//self.num = self.num + 1;
});
}
NSLog(@"self.num = %d",self.num);
NSLog(@"time = %d",time);
/**
肯定会 >= 100
2022-08-15 16:01:48.269255+0800 XYApp[25881:5838957] self.num = 102
2022-10-11 11:40:28.179999+0800 XYTestModule_Example[57853:4429651] time = 555
*/
}
- (IBAction)lg_test4:(id)sender {
self.num = 0;
for (int i = 0; i < 100; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.num ++;
});
}
NSLog(@"self.num = %d",self.num);
/**
肯定会<=100
2022-08-15 16:03:43.624062+0800 XYApp[25881:5838957] self.num = 79
*/
}
九、NSOperation
NSOperation是对GCD的封装,更加面向对象
//queue:异步并发队列的封装
NSOperationQueue * concurrentQueue = [[NSOperationQueue alloc] init];
concurrentQueue.maxConcurrentOperationCount = 2;//最大并发数为2 可同时执行2个任务
//operation
NSBlockOperation * operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i<10; i++) {
NSLog(@"thread = %@ i = %d",[NSThread currentThread],i);
}
}];
NSBlockOperation * operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int j = 0; j<10; j++) {
NSLog(@"thread = %@ j = %d",[NSThread currentThread],j);
}
}];
[operation2 addDependency:operation1];//依赖关系
// 将operation添加到queue
[queue addOperation:operation1];
[queue addOperation:operation2];
2.3Runloop
一、runloop介绍
1、介绍
事件处理循环,不停的处理输入事件。
线程保活
让线程有工作的时候忙于工作,没工作的时候处于休眠状态。
全局字典里:一个线程(key)对应一个runloop(value)
2、mode
一个RunLoop包含若干个Mode,启动时只能选择其中一个Mode,作为currentMode
Mode起到隔离事件的作用,比如滑动tableview时工作在UITrackingRunLoopMode下,这时候不会进行网络请求。
kCFRunLoopDefaultMode
UITrackingRunLoopMode
NSRunLoopCommonModes
每个Mode又包含若干个Source0/Source1/Timer/Observer
二、runloop的运行逻辑
处理blocks的解释:
这个似乎在别的文章里没有被提到,从 macOS 10.6/iOS 4 开始,可以使用 CFRunLoopPerformBlock 函数往 run loop 中添加 blocks。处理block就是处理的这些。
runloop的相关通知
kCFRunLoopEntry
kCFRunLoopBeforeTimers
kCFRunLoopBeforeSources
kCFRunLoopBeforeWaiting
kCFRunLoopAfterWaiting
kCFRunLoopExit
runloop的源
输入源:source1、source0
定时源:timer
runloop的唤醒
注意:source0不能唤醒runloop
if (被Timer唤醒) {
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
} else if (被GCD唤醒) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg)
} else {
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, $reply)
}
runloop的退出
手动调用 CFRunLoopStop 函数,停止 Runloop。
所有输入源和定时器都被移除。
Runloop 遇到错误。
三、runloop相关
source1(唤醒runloop)
基于port的线程间通讯
系统事件捕获(比如触摸事件)
Timer(唤醒runloop)
NSTimer(可以观察调用栈:__CFRunLoopDoTimer)
performSelector:withObject:afterDelay:(可以观察调用栈:__CFRunLoopDoTimer)
GCD回主线程(唤醒runloop)
可以观察调用栈(CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE),跟source0和source1没关系
source0
触摸事件(事件是如何分发给source0的?)
点击屏幕 - springboard进程捕获点击事件 - 通过端口传给当前进程 -
source1检测到事件唤醒runloop,调用source1的回调 - 在处理source1的回调中触发source0 -
source0再触发_UIApplicationHandleEventQueue() - UIWindow - SubView - SubView
performSelector:onThread:(哪些是依赖runloop的?)
// 下面perform开头的方法都会依赖运行循环。在没有开启运行循环的线程里是没发执行的。
[self performSelector:@selector(test1) onThread:currentThread withObject:nil waitUntilDone:NO];
[self performSelector:@selector(test1) withObject:nil afterDelay:0];
通知(通知也是source0处理的,可以观察调用栈)
Observer
监听runloop的状态
UI刷新(BeforeWaiting)
AutoReleasePool(BeforeWaiting)
手势回调调用(BeforeWaiting)
不管是source1还是source0都是通过回调来处理事件的
四、UI刷新
1、布局
layoutSubviews的调用时机
自身被添加到父view上
自身大小改变
添加或移除子view
子view大小改变
setNeedsLayout:打标记,该方法立即返回,不会立即调用layoutSubviews,下一个运行循环刷新
layoutIfNeeded:有刷新标记时就立即调用layoutSubviews,然后该方法才会返回。
2、绘制
drawRect的调用时机
首次显示
生使视图可见部分失效的事件
setNeedsDisplay:打标记在下一个绘制周期进行重绘
layout_and_display_if_needed
layout_if_needed
-[CALayer layoutSublayers]
-[UIView layoutSubviews]
-[CALayer display]
-[UIView(CALayerDelegate) drawLayer:inContext:]
-[UIView drawRect:]
3、渲染原理
CPU(计算)
视图的创建、布局计算、图片解码、文本绘制等
GPU(渲染)
则在物理层上完成了对图像的渲染(顶点着色器、片元着色器等)
数据提交到帧缓冲区
垂直同步信号
Frame Buffer、视频控制器等相关部件,将图像显示在屏幕上
2.4Autorelease
一种内存自动回收机制,放入自动释放池的对象,在离开作用域的时候不会立即释放,而是在合适的时机释放
一、自动释放池的本质
1、一个重要的对象
2、两个重要的方法
每当调用objc_autoreleasePoolPush即AutoreleasePoolPage::push()方法时,会将POOL_BOUNDARY放到当前page的栈顶,并且返回这个边界对象。
而在调用objc_autoreleasePoolPop即AutoreleasePoolPage::pop()方法时,又会将边界对象以参数传入,这样自动释放池就会向释放池中对象发送release消息,直至找到第一个边界对象为止。
3、自动释放池查看
2021-09-20 21:54:23.661658+0800 KCObjcBuild[16800:319949] objc:<NSObject: 0x101a58ab0>
2021-09-20 21:54:23.663090+0800 KCObjcBuild[16800:319949] objc2:<NSObject: 0x1006282b0>
objc[16800]: ##############
objc[16800]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[16800]: 4 releases pending.
objc[16800]: [0x102016000] ................ PAGE (hot) (cold)
objc[16800]: [0x102016038] ################ POOL 0x102016038
objc[16800]: [0x102016040] 0x101a58ab0 NSObject
objc[16800]: [0x102016048] ################ POOL 0x102016048
objc[16800]: [0x102016050] 0x1006282b0 NSObject
objc[16800]: ##############
// 0x102016038 减去 0x102016000 的大小正好是56个字节
// 第一个哨兵对象:0x102016038边界对象
// 第二个哨兵对象:0x102016048边界对象
二、自动释放池的释放
1、系统释放
自动释放池和runloop的关系
进入runloop时:执行push操作
将要休眠时:先执行pop操作,然后执行push操作
退出时:执行pop操作
2、手动释放
花括号前半:执行push操作
花括号后半:执行pop操作
手动释放的应用:比如for循环里的img对象,防止内存暴涨
三、autorelease对象
使用alloc/new/copy/mutableCopy生成的对象都不是Autorelease
非alloc/new/copy/mutableCopy生成的对象都是Autorelease
为什么方法的返回值在离开作用域的时候没有被释放?
+ (id)array {
id obj = [[NSMutableArray alloc] init];//创建对象
[obj retain];
[obj autorelease];//延迟释放对象(谁创建谁释放)
return obj;
}
NSMutableArray* array = [NSMutableArray array];
[array retain];
// 所以引用计数为2
自动释放池应用:for循环里的img对象,防止内存暴涨
3.1WebView
一、介绍
wkwebview 简化的浏览器,用来加载并显示web页面(本地,网络)
前进后退操作
获取并追加useragent
监听加载进度,实现进度条显示
二、两个协议
1、WKNavigationDelegate
请求发送前(可在此决定是否跳转)
页面开始加载
收到响应后(也可在此决定是否跳转)
内容开始返回
页面加载完成/加载失败
2、WKUIDelegate
页面关闭 window.close()
创建新窗口 <a target="_blank"></a>
alert
confirm
promt
三、通讯
1、原生调用js
self.wkWebView 的 evaluateJavaScript:jsFunc 方法调用
2、js调用原生
config添加messageHandler
[config.userContentController addScriptMessageHandler:(id<WKScriptMessageHandler>)[XYProxy proxyWithTarget:self] name:@"getMessage"];
js调用添加的messageHandler
window.webkit.messageHandlers.getMessage.postMessage(jsonStr)
messageHandler的协议方法内处理调用逻辑
js通过隐藏的iframe和自定义scheme的方式调用
在WKNavigationDelegate代理方法内拦截并处理调用逻辑
四、webkit
webCore:负责解析html css并渲染页面
JavaScriptCore:js引擎,解析js代码
五、JavaScriptCore
封装在wkwebview中的JavaScriptCore
很有必要了解的概念只有4个:JSVM,JSContext,JSValue,JSExport。
JSVM:js的多线程有关
JSContext:js执行环境,从环境中获取变量,方法,向环境中写入变量和方法
JSValue:oc中的js包裹类型,有对应关系表
JSExport:将oc对象暴露给js环境(js环境中存在了一个这样的对象),继承关系对应js的原型链继承
行者常至,为者常成!