在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内部没有动画相关的代码,则直接执行,不需要等待
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 执行实际的回调。