block kvc 多线程 一些整理

作者 汪小祯 日期 2017-11-07
iOS
block kvc 多线程 一些整理

自用,还没来得及组织语言

最近在看《objective-C 高级编程》,深刻的体会到代码虐我千百倍,我待代码如初念的感觉。block这部分的实现真的是看巨多的次数,这里做这个学习的整理记录.

Block是什么?形式是什么样的

带自动变量(局部变量)的匿名函数,在这里可以看到block的用法

Block的实现是什么样的?

用clang重写一段后(这里我直接复制参考文章中的代码,在末尾附录)

void foo_(){
int i = 2;
NSNumber *num = @3;
long (^myBlock)(void) = ^long() {
return i * num.intValue;
};
long r = myBlock();
}

再出来的代码中,我们找到关键的部分

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __foo_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __foo_block_impl_0*, struct __foo_block_impl_0*);
void (*dispose)(struct __foo_block_impl_0*);
};
//myBlock的数据结构定义
struct __foo_block_impl_0 {
struct __block_impl impl;
struct __foo_block_desc_0* Desc;
int i;
NSNumber *num;
};
//block数据的描述
static struct __foo_block_desc_0 __foo_block_desc_0_DATA = {
0,
sizeof(struct __foo_block_impl_0),
__foo_block_copy_0,
__foo_block_dispose_0
};
//block中的方法
static long __foo_block_func_0(struct __foo_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy
NSNumber *num = __cself->num; // bound by copy
return i * num.intValue;
}
void foo(){
int i = 2;
NSNumber *num = @3;
struct __foo_block_impl_0 myBlockT;
struct __foo_block_impl_0 *myBlock = &myBlockT;
myBlock->impl.isa = &_NSConcreteStackBlock;
myBlock->impl.Flags = 570425344;
myBlock->impl.FuncPtr = __foo_block_func_0;
myBlock->Desc = &__foo_block_desc_0_DATA;
myBlock->i = i;
myBlock->num = num;
long r = myBlock->impl.FuncPtr(myBlock);
}

可以看到创建block时,会初始化一个结构体struct,并初始化该struct的成员。
而在执行block的时候,就是把这个struct的指针传递过去。

可以看到这个结构体的名称叫做foo_block_impl_0
foo代表我们的函数名称,如果是main函数中的block就叫做
main_block_impl_0
最后那个数字表示这个函数中第几个block,如果有第二个block就叫做__foo_block_impl_1

Block怎么捕获的变量

首先要知道block的主要三种类是

  • _NSConcreteStackBlock 栈上创建的block
  • _NSConcreteMallocBlock 堆上创建的block
  • _NSConcreteGlobalBlock 作为全局变量的block

当struct第一次被创建时,存在该函数的栈帧上。其捕获的变量是会赋值到结构体的成员上,所以当block初始化完成后,捕获到的变量不能更改。
当函数返回时,函数的栈帧被销毁,这个block的内存也会被清除。所以在函数结束后仍然需要这个block时,就必须用Block_copy()方法将它拷贝到堆上。

而在截获自动变量值得时候,会首先把变量追加到foo_block_imp_o的结构体中。并且在foo_block_func_0中访问结构体中的这个变量对齐赋值。

参考资料

objc中的block

KVC 和 KVO

KVC的使用方法

User *user=[[User alloc]init];
[user setValue:@"123" forKey:@"TrueName"];

KVO的使用方法

User *user=[[User alloc]init];
[user addObserver:self forKeyPath:@"TrueName" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{
NSLog(@"%@",change);
}

参考文章

IOS 中 KVO,KVC 的区别与联系 KVO 底层实现机制

多线程总结

NSOperation

队列组

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//A队列
dispatch_group_async(group, q, ^{
dispatch_group_enter(group);
dispatch_group_leave(group);
});
//B队列
dispatch_group_async(group, q, ^{
dispatch_group_enter(group);
dispatch_group_leave(group);
});
//队列完成后
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});

GCD 串行(一个接一个,排队跑步,保持队形)队列

- (void)gcdDemo1
{
// 将操作放在队列中
// 在C语言函数中,定义类型,绝大多数的结尾是_t或者ref
// 使用串行队列,的异步任务非常非常非常有用!新建子线程是有开销的,不能无休止新建线程
// 即可以保证效率(新建一个子线程),用能够实现并发
// 应用案例:
// 1> 从网络上上下载图片
// 2> 滤镜(高光,红眼...)
dispatch_queue_t q = dispatch_queue_create("com.yoferzhang.gcddemo", DISPATCH_QUEUE_SERIAL);
// 非ARC开发时,千万别忘记release
// dispatch_release(q);
// 串行行队列的同步任务,同样会在主线程上运行
// 提示:在开发中极少用d
// 面试中有可能会问!
for (int i = 0; i < 10; ++i) {
// 同步任务顺序执行
dispatch_sync(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
for (int i = 0; i < 10; ++i) {
// 异步任务,并发执行,但是如果在串行队列中,仍然会依次顺序执行
dispatch_async(q, ^{
// [NSThread currentThread] 可以在开发中,跟踪当前线程
// num = 1,表示主线程
// num = 2,表示第2个子线程。。。
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
}

GCD 并行(并排跑,类似于赛跑)

- (void)gcdDemo2
{
// 特点:没有队形,执行顺序程序员不能控制!
// 应用场景:并发执行任务,没有先后顺序关系
// 并行队列容易出错!并行队列不能控制新建线程的数量!
dispatch_queue_t q = dispatch_queue_create("com.yoferzhang.gcd2", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; ++i) {
// 异步任务
dispatch_async(q, ^{
// [NSThread currentThread] 可以在开发中,跟踪当前线程
// num = 1,表示主线程
// num = 2,表示第2个子线程。。。
NSLog(@"异步%@ %d", [NSThread currentThread], i);
});
}
for (int i = 0; i < 10; ++i) {
// 同步任务顺序执行
dispatch_sync(q, ^{
NSLog(@"同步%@ %d", [NSThread currentThread], i);
});
}
}

全局队列(苹果为了方便多线程的设计,提供一个全局队列,供所有的APP共同使用)

- (void)gcdDemo3
{
// 全局队列与并行队列的区别
// 1> 不需要创建,直接GET就能用
// 2> 两个队列的执行效果相同
// 3> 全局队列没有名称,调试时,无法确认准确队列
// 记住:在开发中永远用DISPATCH_QUEUE_PRIORITY_DEFAULT
// 多线程的优先级反转!低优先级的线程阻塞了高优先级的线程!
dispatch_queue_t q =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10; ++i) {
// 同步任务顺序执行
dispatch_sync(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
for (int i = 0; i < 10; ++i) {
// 异步任务,并发执行,但是如果在穿行队列中,仍然会依次顺序执行
dispatch_async(q, ^{
// [NSThread currentThread] 可以在开发中,跟踪当前线程
// num = 1,表示主线程
// num = 2,表示第2个子线程。。。
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
}

主(线程)队列,保证操作在主线程上执行

- (void)gcdDemo4
{
// 每一个应用程序都只有一个主线程
// 为什么需要在主线程上工作呢?
// 在iOS开发中,所有UI的更新工作,都必须在主线程上执行!
dispatch_queue_t q = dispatch_get_main_queue();
// 主线程是由工作的,而且除非将程序杀掉,否则主线程的工作永远不会结束!
// 阻塞了!!!
// dispatch_sync(q, ^{
// NSLog(@"come here baby!");
// });
// 异步任务,在主线程上运行,同时是保持队形的
for (int i = 0; i < 10; ++i) {
dispatch_async(q, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
}

超出父类响应方法

//重写hitTest处理
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var view = super.hitTest(point, with: event)
if view == nil {
for subView in self.subviews.reversed() {
let tp = subView.convert(point, from: self)
if subView.bounds.contains(tp) {
print(subView)
view = subView
}
}
}
return view
}

参考文章

关于iOS多线程,你看我就够了
iOS并发编程对比总结,NSThread,NSOperation,GCD - iOS
GCD 死锁分析