iOS几个面试题

在B站看了一个大佬的直播iOS面试技巧分享,这个大佬就是写出《招聘一个靠谱的 iOS》的那个大佬。
视频主要是介绍他自己的面试流程,很有学习借鉴意义。里面有几道面试题,看着确实都很平常,但是,你自己平时不会这么用,也就不知道能不能这样使用,或者说,这样使用的后果是什么。细想原因,还是对很基础的知识点考察。待会举个例子,大家就明白了。

在这里插入图片描述

对象释放问题

//下面对象分别在什么时候被释放
- (void)ARCProblem{
    id obj0 = @"sun";
    __weak id obj1 = obj0;
    id obj2 = [NSObject new];
    __weak id obj3 = [NSObject new];
    {
        id obj4 = [NSObject new];
    }
    __autoreleasing id obj5 = [NSObject new];
    __unsafe_unretained id obj6 = self;
}

为了更好的观察,对象是否被释放,我们建了一个新类YZPerson,在其dealloc方法里面进行打印,从而看出对象是否被释放。

#import "YZPerson.h"
@implementation YZPerson
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

首先,我们很容易的看出obj2、obj3、obj4对象的释放情况:

- (void)ARCProblem{
    NSLog(@"---begin---");
    id obj2 = [[YZPerson alloc] init];
    NSLog(@"---end---");
}

2020-07-13 11:01:00.963107+0800 block2[1052:22760] ---begin---
2020-07-13 11:01:05.787927+0800 block2[1052:22760] ---end---
2020-07-13 11:01:07.863077+0800 block2[1052:22760] -[YZPerson dealloc]

因为obj2是局部变量,因此,在大括号结束的时候obj2被释放。
YZPerson没有了指针引用,因此,YZPerson同时也被释放。

- (void)ARCProblem{
    NSLog(@"---begin---");
    __weak id obj3 = [[YZPerson alloc] init];
    NSLog(@"---end---");
}

2020-07-13 11:03:21.875476+0800 block2[1074:24695] ---begin---
2020-07-13 11:03:21.875622+0800 block2[1074:24695] -[YZPerson dealloc]
2020-07-13 11:03:21.875702+0800 block2[1074:24695] ---end---

因为obj3是弱引用,在创建后没有强指针指向YZPerson对象,因此,在创建完毕后,YZPerson对象就被释放了。

obj4是在局部变量,因此,在obj4最近的大括号结束后,YZPerson就被释放了。


obj0和obj1就有点复杂了,涉及到了String。
由于是id obj0 = @"sun";,等号右边的@"sun"存储在常量区。

id obj0 = @"sun";//存储在常量区
id obj1 = @"sundaopjd2221";//存储在常量区
id obj2 = [NSString stringWithFormat:@"sun"];
id obj3 = [NSString stringWithFormat:@"sundaopjd2221"];//存储在堆

NSLog(@"%p, %p, %p, %p, %p", &obj0, &obj1, &obj2, &obj3);
NSLog(@"%@, %@, %@, %@", [obj0 class], [obj1 class], [obj2 class], [obj3 class]);
NSLog(@"%p, %p, %p, %p, %p", obj0, obj1, obj2, obj3);
NSLog(@"%d, %d, %d, %d, %d", [obj0 retainCount],[obj1 retainCount], [obj2 retainCount], [obj3 retainCount]);

结果:
0x7ffee6f5af88, 0x7ffee6f5af80, 0x7ffee6f5af78, 0x7ffee6f5af70
__NSCFConstantString, __NSCFConstantString, NSTaggedPointerString, __NSCFString
0x108ca4208, 0x108ca4228, 0xb10f56511db54acd, 0x6000026280c0
-1, -1, -1, 1

从第一行的打印结果可以看出:obj0、obj1、obj2、obj3本身都是存储在栈里面。
并且打印是从高到低,也侧面印证了是存储在栈里面。

从第二行的打印结果可以看出:
obj0、obj1属于常量区。
obj2是属于TaggedPointer类型
obj3是属于NSSring类型

从第三行的打印结果可以看出:
obj0和obj1地址相差不大,存储在常量区。
obj2是TaggedPointer类型,其内容存储在isa指针里面。
obj3是NSSring类型,存储在堆里面。

从第四行的打印结果可以看出:
存储在常量区的字符串,不受引用计数器的控制,也就不受strong、weak的控制。
TaggedPointer类型,不受引用计数器的控制,也就不受strong、weak的控制。
NSSring类型,受引用计数器的控制,跟strong、weak有关。

引用计数器是针对对象类型
堆上的对象才需要我们程序员手动去管理


明白了以上知识点,我们就知道了:

id obj0 = @"sun";
__weak id obj1 = obj0;

obj0和obj1指针指向的对象,存储在常量区,不需要程序员自己手动管理,因此,其生命周期一直存在。

__autoreleasing id obj5 = [NSObject new];将obj5加入自动释放池里面,什么时候释放?
是在最近的一个autoreleasepop调用的时候,释放

__unsafe_unretained id obj6 = self;
在ARC下
__unsafe_unretained跟__weak差不多,不会对引用计数器发生改变。并且在销毁的时候,不会对引用的对象进行置空操作。


self.view = nil;问题

这样写会发生什么?

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view = nil;
}

结果:
在这里插入图片描述

其实,这个不全面。

如果在self.view = nil;后,不再访问self.view,那么程序就是上图所示,是黑屏。

如果在后面访问了self.view,例如:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view = nil;
    [self performSelector:@selector(test) withObject:self afterDelay:0.2];
}

- (void)test
{
    NSLog(@"------");
    self.view.backgroundColor = [UIColor redColor];
}

结果是:
黑屏
循环打印-----

这是因为:
self.view是一个懒加载方法
viewDidLoad是在view加载完毕后调用。

self.view = nil;view被赋值为nil
test访问了self.view,则将其初始化,初始化完毕后调用viewDidLoad
viewDidLoad将self.view = nil;

一直循环调用


UITableViewData代理问题

UITableViewDataSource, UITableViewDelegate主要方法有哪些?
调用顺序如何?

UITableViewDataSource数据源,多少个元素,cell长啥样

@protocol UITableViewDataSource<NSObject>

@required

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

@optional

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;              // Default is 1 if not implemented

- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;    
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;


- (nullable NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView;                               // return list of section titles to display in section index view (e.g. "ABCD...Z#")
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;  // tell table which section corresponds to section title/index (e.g. "B",1))

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;


- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;

@end

UITableViewDelegate各种高度布局

@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>

@optional
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section API_AVAILABLE(ios(7.0));
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section API_AVAILABLE(ios(7.0));

// Section header & footer information. Views are preferred over title should you decide to provide both

- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;   // custom view for header. will be adjusted to default or specified header height
- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;

block循环引用

如何解决下面代码问题?

@property (copy, nonatomic) void(^block)(void);

- (void)blockRetainCycleProblem
{
    self.block = ^{
        NSLog(@"123---%@", @[self]);
    };
    self.block();
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
}

self里面有一个block,
block里面引用了self,
从而造成循环引用问题。
在控制器消失的时候,没有打印dealloc函数
打印结果:

123---(
    "<ViewController: 0x7f948a51cdd0>"
)
解决方法一:
- (void)blockRetainCycleProblem
{
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"123---%@", @[weakSelf]);
    };
    self.block();
}

打印结果:

123---(
    "<ViewController: 0x7fac0750ade0>"
)

-[ViewController dealloc]

如果@[weakSelf]里面的weakSelf为nil,那么就会在数组里面加入空元素,导致崩溃

解决方法二:
- (void)blockRetainCycleProblem
{
    __weak typeof(self) weakSelf = self;
    self.block = ^{
    	__strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"123---%@", @[strongSelf]);
    };
    self.block();
}

打印结果:

123---(
    "<ViewController: 0x7fac0750ade0>"
)

-[ViewController dealloc]

引用__strong的目的,是防止在block内部,self被提前释放

- (void)viewDidLoad {
    [super viewDidLoad];
    
    YZPerson *person = [[YZPerson alloc]init];
    person.name = @"123";
    __weak typeof(person) weakPerson = person;
    person.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"my name is = %@",weakPerson.name);
        });
    };

    person.block();
}


输出:
2020-07-13 15:30:28.951062+0800 block2[2486:145460] -[YZPerson dealloc]
2020-07-13 15:30:30.951134+0800 block2[2486:145460] my name is = (null)

可以看出,YZPerson在大括号前过后就被释放。2.0秒后,才去打印了my name is,此时,由于weakPerson是弱指针指向person,person都消失了,weakSelf也消失了。所以,打印出来的是my name is = (null)

由于里面需要用到person的值,所以要保住person的命
我们使用__strong typeof(weakSelf) strongPerson = weakSelf;

- (void)viewDidLoad {
    [super viewDidLoad];
    YZPerson *person = [[YZPerson alloc] init];//1
    person.name = @"123";
    __weak typeof(person) weakPerson = person;//弱引用还是1
    person.block = ^{
        //__strong typeof(person) strongPerson = weakPerson;
        __strong typeof(weakPerson) strongPerson = weakPerson;//2:strongPerson是+1,block引用外部弱引用不动
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"my name is = %@",strongPerson.name);//3
            //block内部引用auto外部强变量,会生成并拥有同名变量strongPerson
            //同名变量strongPerson对person强引用+1,person的引用计数器为3
            //block对同名变量strongPerson有强引用
        });//block执行完毕,block消失,block内部的同名变量strongPerson消失,person的引用计数器减1,为2
    };//strongPerson为局部变量,在{}结束后,减1,现在为1

    person.block();
}//大括号结束,person消失,指向的对象-1,现在为0
	

2020-07-13 15:41:40.674035+0800 block2[2617:154160] my name is = 123
2020-07-13 15:41:40.674186+0800 block2[2617:154160] -[YZPerson dealloc]

在加上__strong typeof(weakPerson) strongPerson = weakPerson;这句话后,person的引用计数器+1,现在是2。
在最后的大括号结束后,person的引用计数器减1(YZPerson *person消失了),但是引用计数器是1,所以,person对象不销毁。

由于strongPerson是局部变量,
^{ NSLog(@"my name is = %@",strongPerson.name); }
里面引用了局部变量strongPerson,所以,该block会生成并拥有同名变量strongPerson,同名变量strongPerson对person强引用+1。
block对同名变量strongPerson有强引用


^{ NSLog(@"my name is = %@",strongPerson.name); }
执行完毕后,block消失,block内部的同名变量strongPerson消失,person的引用计数器减1,为2。

person.block = ^{
	__strong typeof(weakPerson) strongPerson = weakPerson;
};

大括号执行完毕后,局部变量strongPerson消失,person对象的引用计数器-1。现在person的引用计数器为1

- (void)viewDidLoad { YZPerson *person = [[YZPerson alloc] init]; }
大括号结束,person消失,指向的对象-1,现在为0。
完美解决循环引用问题。

解决方法三:
- (void)blockRetainCycleProblem
{
    __weak typeof(self) weakSelf = self;
    self.block = ^{
    	__strong typeof(weakSelf) strongSelf = weakSelf;
    	if(strongSelf){
    		NSLog(@"123---%@", @[strongSelf]);
    	}
    };
    self.block();
}

打印结果:

123---(
    "<ViewController: 0x7fac0750ade0>"
)

-[ViewController dealloc]

最主要的是:@[strongSelf]不能有nil元素,所以if(strongSelf){}判断很重要。
更加完美。

更多学习:深入研究 Block 用 weakSelf、strongSelf、@weakify、@strongify 解决循环引用


UIView、block动画相关

- (void)blockRetainCycleProblem
{
    //self为何不用weak
    [UIView animateWithDuration:1.0 animations:^{
        self.view.frame = CGRectMake(1, 2, 3, 4);
        NSLog(@"123");
    }];
    
    
    [UIView animateWithDuration:1.0 delay:10000 options:0 animations:^{
        self.view.frame = CGRectMake(1, 2, 3, 4);
        NSLog(@"456");
    } completion:nil];
}

在这里插入图片描述
简化后:

objc_msgSend(objc_getClass("UIView"), sel_registerName("animateWithDuration:animations:"), 1., (&__ViewController__viewDidLoad_block_impl_0(__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, self, 570425344)));

在这里插入图片描述
首先,block里面有self,但是,self没有拥有block,因此,不会形成循环引用。

而,up直播主想告诉我们的是关于:

在block内部的代码,什么时候调用?

是CALayer的delegate调用了一个代理方法actionForLayer:forKey:

调用actionForLayer:forKey:来获取一个返回值,这个返回值在声明的时候是一个id对象,当然在运行时它可能是任何对象。这时CALayer拿到返回值,将进行判断:
如果返回的对象是一个nil,则进行默认的隐式动画;
如果返回的对象是一个[NSNull null] ,则CALayer不会做任何动画;
如果是一个正确的实现了CAAction协议的对象,则CALayer用这个对象来生成一个CAAnimation,并加到自己身上进行动画。

block内部的代码为何瞬间被执行,而不是1.0秒后?

这是因为:
如果block内部有动画相关的代码,则会1.0秒后执行。
如果block内部没有动画相关的代码,则直接执行,不需要等待

更多学习iOS CoreAnimation专题


MVC、MVVM

主要是MVVM中,ViewModel和View之间的通信,是通过KVO完成的。


内存管理相关

stack和heap分别的使用,如何管理
ARC如何实现
Autorelease对象何时释放
AutoreleasePool如何实现


在这里插入图片描述具体可参考:
Runtime的本质(三)----objc_msgSend


在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述


NSURLConnection与Runloop

• CFSocket 是最底层的接口,只负责 socket 通信。
• CFNetwork 是基于 CFSocket 等接口的上层封装,ASIHttpRequest 工作于这一层。
• NSURLConnection 是基于 CFNetwork 的更高层的封装,提供面向对象的接口,AFNetworking 工作于这一层。
• NSURLSession 是 iOS7 中新增的接口,表面上是和 NSURLConnection 并列的,但底层仍然用到了 NSURLConnection 的部分功能 (比如 com.apple.NSURLConnectionLoader 线程),AFNetworking2 和 Alamofire 工作于这一层。

下面主要介绍下 NSURLConnection 的工作过程。

通常使用 NSURLConnection 时,你会传入一个 Delegate,当调用了 [connection start] 后,这个 Delegate 就会不停收到事件回调。实际上,start 这个函数的内部会会获取 CurrentRunLoop,然后在其中的 DefaultMode 添加了4个 Source0 (即需要手动触发的Source)。CFMultiplexerSource 是负责各种 Delegate 回调的,CFHTTPCookieStorage 是处理各种 Cookie 的。

当开始网络传输时,我们可以看到 NSURLConnection 创建了两个新线程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private。

其中 CFSocket 线程是处理底层 socket 连接的。NSURLConnectionLoader 这个线程内部会使用 RunLoop 来接收底层 socket 的事件,并通过之前添加的 Source0 通知到上层的 Delegate。

NSURLConnectionLoader 中的 RunLoop 通过一些基于 mach port 的 Source 接收来自底层 CFSocket 的通知。当收到通知后,其会在合适的时机向 CFMultiplexerSource 等 Source0 发送通知,同时唤醒 Delegate 线程的 RunLoop 来让其处理这些通知。CFMultiplexerSource 会在 Delegate 线程的 RunLoop 对 Delegate 执行实际的回调。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值