面试
(参考答案在文章中,也在底部)
- block的内部原理?
- swift中闭包表达式的种类?
- 闭包和block相比有哪些相同点和不同点?
- 闭包和和闭包表达式的区别?
iOS Block
block是封装了函数以及函数调用环境的OC对象;
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
2021-01-21 13:17:2 [44007:11115273] __NSMallocBlock__
2021-01-21 13:17:2 [44007:11115273] __NSMallocBlock
2021-01-21 13:17:2 [44007:11115273] NSBlock
2021-01-21 13:17:2 [44007:11115273] NSObject
//简单的block 的实现
^{
NSLog(@"this is a block");
}();
void (^blcok)() = ^{
NSLog(@"this is a block");
};
blcok();
__block 中有isa指针(继承于NSObject)、 以及捕获的变量、和封装的方法实现的地址。
封装一个block并且分别捕获auto局部变量(函数执行完就会销毁,所以值传递,要在block内部copy)和static局部变量(main函数之前就已经初始化,一直存储在数据段,所以是引用传递)。如果是全局变量则不会捕获(直接访问);
int age = 10;
static int height = 10;
block = ^{
// age的值捕获进来(capture)
NSLog(@"age is %d, height is %d", age, height);
};
block();
__test_block_impl_0是一个C++的结构体,是block的底层数据结构,
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int age;//捕获变量
int *height;//捕获变量地址
//C++析构函数
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;//block类型
impl.Flags = flags;
impl.FuncPtr = fp; //block 封装的函数地址,就是下面的__test_block_func_0
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
static int height = 10;
//(void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height风别为block的初始化参数
block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));
//FuncPtr存储的是block封装的方法地址
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr) ((__block_impl *)block);
}
return 0;
}
不难发现block 其实是一个包含isa 指针、捕获的局部变量的copy或者地址、以及封装的方法实现的地址的C++结构体对象;凡是block内部访问的局部变量都会捕获到block 内部(Int/NSObject/self)都会捕获。
1.block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
void (^block1)() = ^{
NSLog(@"我是__NSGlobalBlock__");
};
int a = 10;
void (^block2)() = ^{
NSLog(@"我是__NSMallocBlock__%d",a);
};
NSLog(@"%@",[block1 class]);
NSLog(@"%@",[block2 class]);
2021-01-21 14:15:11. __NSGlobalBlock__
2021-01-21 14:15:11. __NSMallocBlock__
因为__NSStackBlock__
是不安全的,所以往往要copy到堆上,变成__NSMallocBlock__
,ARC模式下自动copy到堆上;
被__strong 引用的的block也会自动copy到堆上,系统的参数代use ingblock的也会被copy到堆上。
2.block的内存管理
只要是栈上的block都不会捕获变量。
只要是堆上的block 如果引用了外部的局部变量,会根据局部变量使用__strong 还是用__weak来决定是否强引用;
如果block被拷贝到堆上
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)
typedef void (*Block)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong person;//捕获外部被__strong修饰局部变量,是强引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//通过获取block内部的地址引用找到捕获的对象
Person *__strong person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c41e64_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
//block内部的copy函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
//block内部的dispose函数
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Block block;
{
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c41e64_mi_1);
}
return 0;
}
一旦外部的对象类型的auto局部对象被捕获引用计数+1,当block释放的时候引用计数-1;
Block修改外部变量
Soultion1:用static修饰或者改成全局变量
Soultion2:用__block修饰或者改成全局变量
编译器会将__block变量包装成一个对象,并且被Block强引用:
__block int age = 10;
Block block = ^{
age = 30;
NSLog(@"age is %d", age);
};
block();
typedef void (*Block)(void);
//__block 修饰的age封装成一个结构体对象,并且存储了age的值;
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;//指向自身
int __flags;
int __size;
int age;
};
oldp
//malloc生成__Block_byref_age_0对象
__Block_byref_age_0 *p = malloc(sizeof(struct __Block_byref_age_0))
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;//通过__Block_byref_age_0对象找到age的修改为20
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0, p);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//生成__Block_byref_age_0对象
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_1, &(age.__forwarding->age), block);
}
return 0;
}
Block循环引用问题
1.__block MRC下不会强引用对象所以可以解决循环引用。
ARC下必须要调用,且在调用的block内置为nil才能解决循环应用,解决的事三者之间的循环引用。
2.__weak && __strong可以很好的解决两个对象循环引用;
ARC下block 用strong和copy没有什么区别,因为都会copy到堆上。
Swift闭包和闭包表达式
1.闭包表达式
在swift中可以用func定义一个函数,也可以用闭包表达式定义一个函数:
func sum(v1:Int, v2:Int) -> Int { v1 + v2 }
let sum2 = { (v1:Int, v2:Int) -> Int in
return v1 + v2
}
sum(v1: 10, v2: 20)//func定义的函数
sum2(10, 20)//闭包表达式定义的函数
func sun(param1:Int, param2:Int, test:( Int, Int) -> Int) {
print(test(param1, param2))
}
sun(param1:20 , param2:20, test:{
(v1:Int, v2:Int) -> Int in
return v1 + v2
})
//闭包表达式简写
sun(param1:20 , param2:20, test:{
v1, v2 -> Int in
return v1 + v2
})
sun(param1:20 , param2:20, test:{
v1, v2 in
return v1 + v2
})
sun(param1:20 , param2:20, test:{
v1, v2 in v1 + v2
})
sun(param1:20 , param2:20, test:{
$0 + $1
})
sun(param1: 2, param2: 3, test: +)
尾随闭包
最后一个参数是闭包表达式的时候,可以独立于前面的参数,写在函数参数的外面的闭包表达式,使结构看起来更加清晰整洁,属于结构优化。默认闭包表达式作为参数的时候采用尾随闭包的形式。
sun(param1: 12, param2: 13) { (v1:Int, v2:Int) -> Int in
return v1 + v2
}
//尾随闭包表达式
sun(param1: 12, param2: 13) { v1, v2 in
v1 + v2
}
//尾随闭包表达式简写
sun(param1: 20, param2: 12) { return $0 + $1 }
//尾随闭包表达式简写
sun(param1: 10, param2: 12) { $0 + $1 }
sun(param1: 10, param2: 12) { $0 + $1 }
sun(param1: 2, param2: 3, test: +)
自动闭包
顾名思义根据传入的参数,自动生成闭包;延迟执行,属于性能优化,除此之外也属于结构优化,调用者无需关心参数类型,用 @autoclosure修饰。
@autoclosure只支持()->T的格式的参数,并且不仅仅只支持最后一个参数.
系统的合并运算符??属于自动闭包。
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
对比一下尾随闭包和自动闭包,在()->T的参数格式下,自动闭包非常简洁;
func test(v1:Int, v2:()-> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
test(v1: -10) { 10 }//尾随闭包
test(v1: 1, v2: {() -> Int in return 10 })//闭包表达式
test(v1: 15, v2: {10})//闭包表达式简写
//自动闭包
func test(v1:Int, v2: @autoclosure ()-> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
//参数20会自动封装成一个闭包
test(v1: -1, v2: 20)
自动闭包有性能的优越性:假如other() 是一个耗性能的操作,就体现出了自动闭包的优越性:简洁且高效。other()的值会被封装成一个闭包,仅当v1 < 0的时候才会执行。
func other() -> Int {
var i = 0
for _ in 0...10000 {
i += 1
}
return I
}
test(v1: 1, v2:other())
逃逸闭包
2.闭包
闭包和闭包表达式并不是一个东西,闭包表达式是函数的另外一种定义,而闭包是一个函数和它所捕获的变量/常量环境组合起来,称为闭包:
一般指定义在函数内部的函数;一般它捕获的是外层函数的局部变量/常量:
typealias Test = (Int, Int) -> Int
func creat() -> Test {
var num = 3
//定义在函数内部
func add(v1:Int, v2:Int) -> Int{
//捕获外层函数的局部变量
num += 1
return (v1 + v2) * num
}
//除了func之外也可以通过闭包表达式定义函数
//let add = {(v1:Int, v2:Int) in return num + v2 + v1 }
return add//返回一个闭包:一个函数或者闭包表达式和它所捕获的变量组成的环境;
}
let closure = creat()//获取一个闭包
closure(15, 20)
swift 中的闭包是一种提供了方法和局部变量的数据结构,可以简单把闭包看作是一个类,类的成员变量看作是需要捕获的局部变量,方法比作类的方法。闭包包含24个字节,前8个字节存储闭包的地址,后8个字节存储引用计数,接下来存储捕获的局部变量在堆上的内存地址。
3.闭包的内存管理
面试参考
- block的内部原理?
block 是一个封装了函数调用和局部变量的运行环境的结构体对象;
- 闭包和block相比有哪些相同点和不同点?
本来两个东西没有可比性,但是面试官非要这么问;
相同点:都可以作为参数,都可以捕获局部变量;
从使用上来相比较而言,闭包更灵活,有尾随闭包,自动闭包可供选择,简洁且高效;
从内存管理上来说,前者捕获的是内存地址,后者局部变量是对象时捕获的是地址,当是int,string 时是copy。
- 闭包和和闭包表达式的区别?
闭包表达式是:函数的另外一种表现形式,为了实现简介和高效,有不同的表现形式,例如自动闭包,尾随闭包;
闭包是:封装在函数中的函数或者闭包表达式,且捕获了局部变量的运行环境。它运用了闭包表达式,但闭包表达式不是闭包。
- Swift中闭包表达式的种类?
尾随闭包:当且仅当最后一个参数是闭包表达式的时候,可以独立于参数之外,属于结构优化;
自动闭包:当且仅当参数类型是()->T的时候,且用@autoclosure 修饰,该参数会自动生成闭包表达式,达到延迟执行的效果,属于性能优化;