中级Block
1、block的实质是什么?一共有几种block?都是什么情况下生成的?
block的实质是什么?
block本质是一个OC对象,它内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。
一共有几种block?
__NSGlobalBlock__
直到程序结束才会被回收,不过我们很少使用到__NSGlobalBlock__
类型的block,因为这样使用block没有什么意义。
__NSStackBlock__
类型的block存放在栈中,我们直到栈中的内存自动分配和释放,作用域执行完毕之后就会被立即释放,而在相同的作用域中定义block并且调用block石斛也多此一举。
__NSMallocBlock__
实在平时编码过程中最常使用到的。存放在堆中需要我们自己进行内存管理。
都是什么情况下生成的?
没有访问auto变量的block是__NSGlobalBlock__
类型的,存放在数据段中。访问auto变量的block是__NSStackBlock__
类型的,存放在栈中。__NSStackBlock__
类型的block调用copy成为__NSMallocBlock__
类型并被复制存放在堆中。
什么情况下ARC会自动将block进行一次copy操作?
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名包含usingVBlock的方法参数时
- block作为GCD API的方法参数时
参考链接:
https://juejin.im/post/5b0181e15188254270643e88#heading-13
2、使用系统的某些block api,是否考虑引用循环问题?
系统的某些block api中,UIView的block版本写动画时不需要考虑,但是也有一些api需要考虑:
[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakSelf;
[strongSelf dismissModalViewControllerAnimated:YES];
}];
self --> _observer --> block --> self 显然这也是一个循环引用。
检测代码中是否存在循环引用问题,可使用 Facebook 开源的一个检测工具 FBRetainCycleDetector 。
参考链接:
https://www.zybuluo.com/qidiandasheng/note/492266
3、谈谈block的理解?并写出一个使用block执行UIVew动画?
block优点:
- 回调的block代码块定义在委托对象函数内部,使代码更为紧凑
- 被委托对象不再需要实现具体某个protocol,代码更为简洁
block缺点:
- delegate运行成本低,block成本很高。block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除;delegate只是保存了一个对象指针,直接回调,没有额外消耗。
- 如果在block里面使用了self,容易导致循环引用问题,要用weak
[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
Runtime
4、runtime如何实现weak属性?
weak对象会放入一个hash表中。用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc。假如weak指向的对象内存地址是addr,那么就会以addr为键,在这个weak表中搜索,找到所有以addr为键的weak对象,设置为nil。
5、runtime如何通过selector找到对应的IMP地址?
类对象中有类方法和实例方法的列表,列表中记录着方法的名称、参数和实现,而selector本质就是方法名称,runtime通过这个方法名称就可以在列表中找到该方法对应的实现。
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class
const char *name
long version
long info
long instance_size
struct objc_ivar_list *ivars
struct objc_method_list **methodLists
struct objc_cache *cache
struct objc_protocol_list *protocols
#endif
} OBJC2_UNAVAILABLE;
这里声明了一个指向struct objc_method_list指针的指针,可以包括类列表和实例方法列表。
在照IMP地址时,runtime提供了两种方法
IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)
第一种方法:
- (void)getIMP_class_getMethodImplementationFromSelector:(SEL)aSelector{
const char *className = object_getClassName([self class]);
// 获取实例的IMP
IMP instanceIMP = class_getMethodImplementation(objc_getClass(className), aSelector);
// 获取类的IMP
IMP classIMP = class_getMethodImplementation(objc_getMetaClass(className), aSelector);
NSLog(@"instanceIMP:%p classIMP:%p",instanceIMP,classIMP);
}
第二种方法:
- (void)getIMP_method_getImplementationFromSelector:(SEL)aSelector{
const char *className = object_getClassName([self class]);
// 获取类中的某个实例方法
Method instanceMethod = class_getInstanceMethod(objc_getClass(className), aSelector);
// 获取类中的某个类方法
Method classMethod = class_getClassMethod(objc_getClass(className), aSelector);
// 获取实例的IMP
IMP instanceIMP = method_getImplementation(instanceMethod);
// 获取类的IMP
IMP classIMP = method_getImplementation(classMethod);
NSLog(@"instanceIMP:%p classIMP:%p",instanceIMP,classIMP);
}
对于method_getImplementation
而言,传入的参数只有method,区分类方法和实例方法在于封装的method的函数。
类方法:
Method class_getClassMethod(Class cls, SEL name)
实例方法:
Method class_getInstanceMethod(Class cls, SEL name)
方法列表中保存着下面方法的结构体,结构体中包含这个方法的实现,selector本质就是方法的名称,通过该方法名称,即可在结构提中找到相应的实现。
struct objc_method {
SEL method_name
char *method_types
IMP method_imp
}
6、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
- 不能向编译后的类中添加实例变量
- 能向运行时创建的类中添加实例变量
解释:
- 因为编译后的类已经注册到runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime会调用class_setIvarLayout或class_setWeakIvarLayout来处理strong weak引用。所以不能向存在的类中添加实例变量。
- 运行时创建的类是可以添加实例变量,调用class_addIvar函数。但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。
参考链接:
https://www.jianshu.com/p/4341611499f9
7、runtime如何实现weak变量的自动置nil?
weak 对象会放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc。假如 weak 指向的对象内存地址是addr,那么就会以addr为键, 在这个 weak 表中搜索,找到所有以addr为键的 weak 对象,从而设置为 nil。
8、在开发中如何使用runtime?什么应用场景?
首先,归纳下runtime的几个使用场景
- 用户埋点统计
- 处理异常崩溃(NSDictionary,NSMutableDictionary,NSArray,NSMutableArray 的处理)
- 按钮最小点击区设置
- 按钮重复点击设置
- 手势的重复点击处理
- UIButton点击时间带多参数
- MJRefresh封装
- 服务器控制页面跳转
- 字典转模型
参考链接:
https://blog.csdn.net/SandyLoo/article/details/80174890
类结构
9、isa指针?(对象的isa,类对象的isa,元类的isa都要说)
image.png10、类方法和实例方法有什么区别?
- 静态方法在程序开始时生成内存,实例方法在程序运行中生成内存。所以静态方法可以直接调用。
- 实例方法要先生成实例,通过实例调用方法,静态速度很快,但是多了会占内存。
- 静态内存是连续的,因为是在程序开始时就生成了,而实例申请的是离散的空间,所以当然没有静态方法快,而且静态内存是有限制的,太多了程序会启动不了。
- 类方法常驻内存,实例方法不是,所以类方法效率高但占内存。
- 类方法在堆上分配内存,实例方法在堆栈上。
- 实例方法需要先创建实例才可以调用,比较麻烦,类方法不用,比较简单。
- 类方法也称为静态方法,指的是用static关键字修饰的方法。此方法属于类本身的方法,不属于类的某个实例(对象)。
11、介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?
分类(category)是OC特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能添加成员(实例)变量。
- 分类中可以写property,但是不会生成setter/getter方法,也不会生成实现以及私有的成员变量(编译时会报警告)
- 可以在分类中访问原有类.h中的属性
- 如果分类中有和原来同名的方法,会有限调用分类中的方法,就是会忽略原有类中的方法。所以同名方法调用的优先级为
分类>本类>父类
。因此在开发中尽量不要覆盖原有类方法。 - 如果多个分类中都有和原来类中同名的方法,那么调用该方法的时候执行谁由编译器决定。编译器会执行最后一个参与编译的分类中的方法。
分类能做什么?
- 能够在不改变原有类内容的基础上,为类增加一些方法
- 可以将类的实现写带好几个分类里面,可以减少单个文件的体积、可以把不同的功能组织到不同的category里、可以由多个开发者共同完成一个类、按需要加载想要的category。
- 声明私有方法的(匿名分类 类扩展 Extension)
- 模拟多继承
内部是如何实现的?
我们知道,无论我们有没有主动引入category的头文件,category中的方法都会被添加到主类中。我们可以通过- performSelector:
等方式对category中的对应方法进行调用,之所以需要在调用的地方引入category的头文件,只是为了让编译器知道而已。
- 将category和它的主类(或元类)注册到哈希表中
- 如果主类(或元类)已实现,那么重建它的方法列表
在这里分了两种情况进行处理:category中的实例方法和属性被整合到主类中:而类方法整合到元类中。另外category中的协议同样被整合到主类和元类中。
我们注意到,不管是哪种情况,最终都是通过调用static void remethodizeClass(Class cls)
函数来重新整理类的数据的。
系统是在运行时将分类中对应的实例方法、类方法等插入到了原来类或元类的方法列表中,且是在列表的前边。所以方法调用通过isa去对应的类或元类的列表中查找对应方法时先查找到分类中的方法。查到后就直接调用不再继续查找,这就是‘覆盖’的本质。
当存在多个分类时,最后编译的分类中的对应的信息会整合到类或元类对应列表的最前面。所以是调用最后编译的分类中的方法。
11、运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?
能向运行时创建的类中添加实例变量
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
- (void)setRy_time:(NSTimeInterval)ry_time{
objc_setAssociatedObject(self, RY_CLICKKEY, @(ry_time), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSTimeInterval)ry_time{
return [objc_getAssociatedObject(self, RY_CLICKKEY) doubleValue];
}
参考链接:
https://www.jianshu.com/p/47b10b37d126
12、objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)
在OC中向nil发送消息是完全有效的——只是在运行时不会有任何作用
- 如果方法的返回值是一个对象,那么发送给nil的消息将返回0(nil)
- 如果方法返回值是指针类型,其指针大小为小于或者等于sizeof(void *),float,double,long double或者long long的整型标量,发送给nil的消息将返回0。
- 如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0
- 如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到对象实际所属的类,然后在该类中的方法列表以及其父类的方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用是执行的。那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
网友评论