2019年年初iOS招人心得笔记 答案 (三)

中级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操作?

  1. block作为函数返回值时
  2. 将block赋值给__strong指针时
  3. block作为Cocoa API中方法名包含usingVBlock的方法参数时
  4. block作为GCD API的方法参数时
copy使用.png

参考链接:
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的几个使用场景

  1. 用户埋点统计
  2. 处理异常崩溃(NSDictionary,NSMutableDictionary,NSArray,NSMutableArray 的处理)
  3. 按钮最小点击区设置
  4. 按钮重复点击设置
  5. 手势的重复点击处理
  6. UIButton点击时间带多参数
  7. MJRefresh封装
  8. 服务器控制页面跳转
  9. 字典转模型

参考链接:
https://blog.csdn.net/SandyLoo/article/details/80174890

类结构

9、isa指针?(对象的isa,类对象的isa,元类的isa都要说)

image.png

10、类方法和实例方法有什么区别?

  1. 静态方法在程序开始时生成内存,实例方法在程序运行中生成内存。所以静态方法可以直接调用。
  2. 实例方法要先生成实例,通过实例调用方法,静态速度很快,但是多了会占内存。
  3. 静态内存是连续的,因为是在程序开始时就生成了,而实例申请的是离散的空间,所以当然没有静态方法快,而且静态内存是有限制的,太多了程序会启动不了。
  4. 类方法常驻内存,实例方法不是,所以类方法效率高但占内存。
  5. 类方法在堆上分配内存,实例方法在堆栈上。
  6. 实例方法需要先创建实例才可以调用,比较麻烦,类方法不用,比较简单。
  7. 类方法也称为静态方法,指的是用static关键字修饰的方法。此方法属于类本身的方法,不属于类的某个实例(对象)。

11、介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?

分类(category)是OC特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能添加成员(实例)变量。

  1. 分类中可以写property,但是不会生成setter/getter方法,也不会生成实现以及私有的成员变量(编译时会报警告)
  2. 可以在分类中访问原有类.h中的属性
  3. 如果分类中有和原来同名的方法,会有限调用分类中的方法,就是会忽略原有类中的方法。所以同名方法调用的优先级为分类>本类>父类。因此在开发中尽量不要覆盖原有类方法。
  4. 如果多个分类中都有和原来类中同名的方法,那么调用该方法的时候执行谁由编译器决定。编译器会执行最后一个参与编译的分类中的方法。

分类能做什么?

  1. 能够在不改变原有类内容的基础上,为类增加一些方法
  2. 可以将类的实现写带好几个分类里面,可以减少单个文件的体积、可以把不同的功能组织到不同的category里、可以由多个开发者共同完成一个类、按需要加载想要的category。
  3. 声明私有方法的(匿名分类 类扩展 Extension)
  4. 模拟多继承

内部是如何实现的?

我们知道,无论我们有没有主动引入category的头文件,category中的方法都会被添加到主类中。我们可以通过- performSelector:等方式对category中的对应方法进行调用,之所以需要在调用的地方引入category的头文件,只是为了让编译器知道而已。

  1. 将category和它的主类(或元类)注册到哈希表中
  2. 如果主类(或元类)已实现,那么重建它的方法列表

在这里分了两种情况进行处理: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发送消息是完全有效的——只是在运行时不会有任何作用

  1. 如果方法的返回值是一个对象,那么发送给nil的消息将返回0(nil)
  2. 如果方法返回值是指针类型,其指针大小为小于或者等于sizeof(void *),float,double,long double或者long long的整型标量,发送给nil的消息将返回0。
  3. 如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0
  4. 如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到对象实际所属的类,然后在该类中的方法列表以及其父类的方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用是执行的。那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。

参考链接:
https://www.jianshu.com/p/886ea24ac5d2

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容