2019 iOS 面试 -中级篇之 Block

1. block的实质是什么?一共有几种block?都是什么情况下生成的?

block的实质是什么?

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象
  • block是封装函数及其上下文的OC对象

查看block源码:

  • 打开终端,在main.m所在目录下键入clang -rewrite-objc main.m即可在当前目录下生成一个main.cpp文件;
  • 当引用了OC中的Foundation或者UIKit框架时,通过 clang -rewrite-objc 指定文件名 命令将指定文件转换成C++代码会报错;
  • 可通过 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 指定文件名

block定义:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
 
struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */

一共有几种block?

  • _NSConcreteGlobalBlock 全局静态
  • _NSConcreteStackBlock 保存在栈中,出函数作用域就销毁
  • _NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁而ARC和MRC中,还略有不同;

都是什么情况下生成的?

  • 1、在全局区域声明定义一个block

  • 2、block表达式中没有使用捕获的自动变量时

  • 以上情况生成的block都是NSConcreteGlobalBlock类型,只生成一个结构体实例;除此情况下,生成的都是NSConcreteStackBlock类型的block,且都是存储在栈上。

  • 参考1:block 系列文章之一

  • 参考2:又见block系列(值得再看一遍)

  • 参考3:《Objective-C高级编程 iOS与OS X多线程和内存管理》电子书参考第二章

2. 为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?

Block只捕获Block中会用到的变量。由于只捕获了自动变量(自动变量是以值传递方式传递到Block的构造函数里面)的值,并非内存地址,所以Block内部不能改变自动变量的值。Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量。

参考:深入研究Block捕获外部变量和__block实现原理

如何在block中修改外部自动变量的值,主要有以下2种方法:

  • a、修改C语言中的静态变量、静态全局变量和全局变量
  • b、使用__block修饰需要修改的自动变量

3. 模拟一下循环引用的一个情况?block实现界面反向传值如何实现?

循环引用:

#import <Foundation/Foundation.h>
typedef void(^Study)();  
@interface Student : NSObject
@property (copy , nonatomic) NSString *name;
@property (copy , nonatomic) Study study;
@end


#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
  
    Student *student = [[Student alloc]init];
    student.name = @"Hello World";
 
    student.study = ^{
        NSLog(@"my name is = %@",student.name);
    };

block界面反向传值实例:
1、在第二个视图控制器的.h文件中定义声明Block属性:

// NextViewController.h
// 定义block
@property (nonatomic,copy) void (^NextViewControllerBlock)
(NSString *tfText);
// NextViewController.m
@interface NextViewController ()
@property (weak, nonatomic) IBOutlet UITextField *inputTF;
@end
 
- (IBAction)BtnAction:(id)sender {
    //判断block是否为空
    if (self.NextViewControllerBlock) {
        self.NextViewControllerBlock(self.inputTF.text);   
    }
    [self.navigationController popViewControllerAnimated:YES];
}

2、在第一个视图中获得第二个视图控制器,并且用第二个视图控制器来调用定义的属性:

// AViewController.m
@interface AViewController ()
@property (weak, nonatomic) IBOutlet UILabel *nextVCInfoLabel;
@end
 
- (IBAction)btnClicked:(id)sender {
    NextViewController *nextVC = [[NextViewController alloc]init];
    nextVC.NextViewControllerBlock = ^(NSString *tfText){
        self.nextVCInfoLabel.text = tfText;
    };
    [self.navigationController pushViewController:nextVC animated:YES];
}

4. NSTImer的循环引用问题

使用NSTimer可能会碰到循环引用的问题。特别是当类具有NSTimer类型的成员变量,并且需要反复执行计时任务时。例如

_timer = [NSTimer scheduledTimerWithTimeInterval:5.0
                                          target:self
                                        selector:@selector(startCounting)   userInfo:nil
                                         repeats:YES];

类有一个成员变量_timer,给_timer设置的target为这个类本身。这样类保留_timer,_timer又保留了这个类,就会出现循环引用的问题,最后导致类无法正确释放。

解决这个问题的方式也很简单,当类的使用者能够确定不需要使用这个计时器时,就调用

[_timer invalidate];
_timer = nil;

这样就打破了保留环,类也可以正确释放。但是,这种依赖于开发者手动调用方法,才能让内存正确释放的方式不是一个非常好的处理方式。所以需要另外一种解决方案。
如下所示:

@interface NSTimer (JQUsingBlock)
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
                                     block:(void(^)())block
                                   repeats:(BOOL)repeats;
@end
 
@implementation NSTimer (JQUsingBlock)
 
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
                                     block:(void(^)())block
                                   repeats:(BOOL)repeats{
    return [self scheduledTimerWithTimeInterval:ti
                                     target:self                                                                        selector:@selector(jq_blockInvoke:)
                                   userInfo:[block copy]
                                    repeats:repeats];
}
 
+ (void)jq_blockInvoke:(NSTimer *)timer{
    void(^block)() = timer.userInfo;
    if (block) {
        block();
    }
}
 
@end

定义一个NSTimer的类别,在类别中定义一个类方法。类方法有一个类型为块的参数(定义的块位于栈上,为了防止块被释放,需要调用copy方法,将块移到堆上)。
使用这个类别的方式如下:

__weak ViewController *weakSelf = self;
_timer = [NSTimer jq_scheduledTimerWithTimeInterval:5.0
                        block:^{
                                __strong ViewController *strongSelf = weakSelf;
                                [strongSelf startCounting];
                                }
                      repeats:YES];

使用这种方案就可以防止NSTimer对类的保留,从而打破了循环引用的产生。__strong ViewController *strongSelf = weakSelf主要是为了防止执行块的代码时,类被释放了。在类的dealloc方法中,记得调用[_timer invalidate]。

相关阅读:

1、iOS 面试题 --- 基础部分
2、iOS 面试题 --- 中级篇 Runtime
3、iOS 面试题 --- 中级篇 Runloop
4、iOS 面试题 --- 高级篇

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,087评论 1 32
  • 面向对象的三大特性:封装、继承、多态 OC内存管理 _strong 引用计数器来控制对象的生命周期。 _weak...
    运气不够技术凑阅读 1,083评论 0 10
  • 《招聘一个靠谱的 iOS》—参考答案(下) 说明:面试题来源是微博@我就叫Sunny怎么了的这篇博文:《招聘一个靠...
    韩发发吖阅读 1,551评论 0 8
  • 有人说我傻乎乎的!还妄图成为我想成为的那种人,让我羡慕他一辈子!哼(¬︿̫̿¬☆) Runaway蛮好听的~ 烟火...
    舞荷阅读 361评论 4 1
  • 关键词:沟通题主:女问:冷大,你好!我23岁,是一名大三的学生。我的男朋友24岁,已经工作两年。他在童年时候,他经...
    冷爱阅读 275评论 0 0