面试

综合面试题(答案)

2016-12-08  本文已影响157人  sellse
1. 单例写法

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

//GCD 方式创建
static id _instance;   

+ (instancetype)sharedInstance  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [[self alloc] init];   
    });   
    return _instance;   
}   

+ (instancetype)allocWithZone:(struct _NSZone *)zone  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [super allocWithZone:zone];   
    });   
    return _instance;   
}   

- (id)copyWithZone:(NSZone *)zone   
{   
    return _instance;   
}  

- (id)mutableCopyWithZone:(NSZone *)zone {   
    return _instance;   
}
2. 深拷贝浅拷贝 举出使用实例

iOS提供了copy和mutablecopy方法,顾名思义,copy就是复制了一个imutable的对象,而mutablecopy就是复制了一个mutable的对象。

3.@property 相关问题总汇:

实例变量+基本数据类型变量=成员变量
属性 (property)有两大概念:ivar(实例变量)+存取方法(getter + setter)

3.1 ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些
3.2 什么情况使用 weak 关键字,相比 assign 有什么不同
3.3 NSString 属性什么时候用copy,什么时候用strong(ARC环境)

我们定义一个类,并为其声明两个字符串属性,如下所示:

@interface TestStringClass ()
@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyedString;
@end

首先,我们用一个不可变字符串来为这两个属性赋值,

- (void)test {
    NSString *string = [NSString stringWithFormat:@"abc"];
    self.strongString = string;
    self.copyedString = string;
    NSLog(@"origin string: %p, %p", string, &string);
    NSLog(@"strong string: %p, %p", _strongString, &_strongString);
    NSLog(@"copy string: %p, %p", _copyedString, &_copyedString);
}

其输出结果是:

origin string: 0x7fe441592e20, 0x7fff57519a48
strong string: 0x7fe441592e20, 0x7fe44159e1f8
copy string: 0x7fe441592e20, 0x7fe44159e200

我们可以看到,这种情况下,不管是strong还是copy属性的对象,其指向的地址都是同一个,即为string指向的地址。如果我们换作MRC环境,打印string的引用计数的话,会看到其引用计数值是3,即strong操作和copy操作都使原字符串对象的引用计数值加了1。
接下来,我们把string由不可变改为可变对象,看看会是什么结果。即将下面这一句

NSString *string = [NSString stringWithFormat:@"abc"];

改成:

NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];

其输出结果是:

origin string: 0x7ff5f2e33c90, 0x7fff59937a48
strong string: 0x7ff5f2e33c90, 0x7ff5f2e2aec8
copy string: 0x7ff5f2e2aee0, 0x7ff5f2e2aed0

可以发现,此时copy属性字符串已不再指向string字符串对象,而是深拷贝了string字符串,并让_copyedString对象指向这个字符串。在MRC环境下,打印两者的引用计数,可以看到string对象的引用计数是2,而_copyedString对象的引用计数是1。

此时,我们如果去修改string字符串的话,可以看到:因为_strongString与string是指向同一对象,所以_strongString的值也会跟随着改变(需要注意的是,此时_strongString的类型实际上是NSMutableString,而不是NSString);而_copyedString是指向另一个对象的,所以并不会改变。
结论 由于NSMutableString是NSString的子类,所以一个NSString指针可以指向NSMutableString对象——让我们的strongString指针指向一个可变字符串是可以的。
当源字符串是NSString时:由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。
当源字符串是NSMutableString时:strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。
这里还有一个性能问题,即在源字符串是NSMutableString,strong是单纯的增加对象的引用计数,而copy操作是执行了一次深拷贝,所以性能上会有所差异。而如果源字符串是NSString时,则没有这个问题。
所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,建议使用copy,以免因可变字符串的修改导致的一些非预期问题。

3.4 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

因为父类指针可以指向子类对象NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作。使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可变对象,我本身持有的就是一个不可变的副本。
如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

copy此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。
当属性类型为NSString时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个NSMutableString类的实例,这个类是NSString的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” 的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” ,就应该在设置新属性值时拷贝一份。

3.5 这个写法会出什么问题: @property (copy) NSMutableArray *array;

两个问题:
1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为copy就是复制一个不可变NSArray的对象;
2、使用了atomic属性会严重影响性能。

4. #include #import @class

#includeC/C++导入头文件的关键字
#importObjective-c导入头文件的关键字,,使用#import头文件会自动只导入一次,不会重复导入,相当于#include#pragma once#import<>用来包含系统的头文件,#import””用来包含用户头文件。
@class:告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含

5.循环引用相关问题汇总:

循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个ViewController,则在dismiss或者pop之后其dealloc无法被调用,在频繁的push或者present之后内存暴增,然后APP就挂了

5.1 使用block时什么情况会发生引用循环,如何解决

一个对象中强引用了block,在block中又使用了该对象,就会发射循环引用。 解决方法是将该对象使用 _weak或者_block修饰符修饰之后再在block中使用。
id weak weakSelf = self; 或者 __weak typeof(self) weakSelf = self;该方法可以设置宏
id __block weakSelf = self;

5.2 使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?

系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api 需要考虑:

所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:
1.
[UIView animateWithDuration:duration animations:^{ 
    [self.superview layoutIfNeeded]; 
}]; 
2.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ 
    self.someProperty = xyz; 
}]; 
3.
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" 
                                                  object:nil 
                                                   queue:[NSOperationQueue mainQueue]                                                   
                                              usingBlock:^(NSNotification * notification) {
                                                    self.someProperty = xyz; 
                                               }];
这些情况不需要考虑“引用循环”。

但如果你使用一些参数中可能含有实例变量的系统 api 如 GCD 、NSNotificationCenter就要小心一点:

比如GCD 内部如果引用了 self,而且 GCD 的其他参数是实例变量,则要考虑到循环引用:
1.
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
    __typeof__(self) strongSelf = weakSelf;
    [strongSelf doSomething];
    [strongSelf doSomethingElse];
} );

类似的:
2.
__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 显然这也是一个循环引用。

这篇文章介绍了weakself和strongself的用法:到底什么时候才需要在ObjC的Block中使用weakSelf/strongSelf

5.3 NSTimer导致循环引用的例子

一方面,NSTimer经常会被作为某个类的成员变量,而NSTimer初始化时要指定self为target,容易造成循环引用。
另一方面,若timer一直处于validate的状态,则其引用计数将始终大于0。比如当定时器销毁的时机不对,在dealloc里面销毁的时候,内存就不会释放,就会造成循环引用

.h文件
#import <Foundation/Foundation.h>
@interface Friend : NSObject
{
    NSTimer *_timer;
}
- (void)cleanTimer;
@end

.m文件
@implementation Friend
- (id)init
{
    if (self = [super init]) {
     _timer = [NSTimer scheduledTimerWithTimeInterval:1 
                       target:self 
                       selector:@selector(handleTimer:)
                       userInfo:nil 
                       repeats:YES];
     }
    return  self;
 } 
- (void)handleTimer:(id)sender
{
   NSLog(@"%@ say: Hi!", [self class]);
 }
- (void)cleanTimer
{
   [_timer invalidate];
    _timer = nil;
}
 - (void)dealloc
 {
   [self cleanTimer];
  NSLog(@"[Friend class] is dealloced");
 }

在main.m中声明并且调用,通过函数让Friend类延时5秒后引用计数减一

#import "Friend.h"
//循环引用
//是一个很麻烦的一件事,完全靠经验
int main(int argc, const char * argv[]) {   
     Friend *friend = [[Friend alloc] init];
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 95*NSEC_PER_SEC), 
                    dispatch_get_main_queue(), ^{
                    [friend release];
    });
    return 0;
 }
我们所期待的结果是,初始化5秒后,friend对象被release,friend的dealloc方法被调用,在dealloc里面timer失效,对象被析构。但结果却是从未停下..

这是为什么呢?主要是因为从timer的角度,timer认为调用方(Friend对象)被析构时会进入dealloc,在dealloc可以顺便将timer的计时停掉并且释放内存;但是从Friend的角度,他认为timer不停止计时不析构,那我永远没机会进入dealloc。循环引用,互相等待,无穷尽。问题的症结在于-(void)cleanTimer函数的调用时机不对,显然不能想当然地放在调用者的dealloc中。

一个比较好的解决方法是开放这个函数,让Friend的调用者显式地调用来清理现场。如下:

int main(int argc, const char * argv[]) {   
     Friend *friend = [[Friend alloc] init];
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 95*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
             [friend cleanTimer];
             [friend release];
    });
    return 0;
 }
6.UIView和CALayer的区别与联系
7.详细描述一下响应者链的含义(最好能图释)

iOS 系统检测到手指触摸 (Touch) 操作时会将其打包成一个 UIEvent 对象,并放入当前活动Application的事件队列,单例UIApplication会从事件队列中取出触摸事件并传递给单例UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。
hitTest:withEvent:方法的处理流程如下:

如果最终 hit-test 没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果 UIWindow 实例和 UIApplication 实例都不能处理该事件,则该事件会被丢弃(这个过程即上面提到的响应值链);

8.如何高效的剪切圆角图片

CALayer 的 border、圆角、阴影、遮罩(mask),CASharpLayer 的矢量图形显示,通常会触发离屏渲染(offscreen rendering),而离屏渲染通常发生在 GPU 中。当一个列表视图中出现大量圆角的 CALayer,并且快速滑动时,可以观察到 GPU 资源已经占满,而 CPU 资源消耗很少。这时界面仍然能正常滑动,但平均帧数会降到很低。为了避免这种情况,可以尝试开启 CALayer.shouldRasterize 属性,但这会把原本离屏渲染的操作转嫁到 CPU 上去。对于只需要圆角的某些场合,也可以用一张已经绘制好的圆角图片覆盖到原本视图上面来模拟相同的视觉效果。最彻底的解决办法,就是把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。

9.const/static分别用法,修饰类时又如何 #define

宏:

#define HSCoder @"汉斯哈哈哈"

变量:

NSString *HSCoder = @"汉斯哈哈哈";

常量:

四种写法:
static const NSString *HSCoder = @"汉斯哈哈哈";

const NSString *HSCoder = @"汉斯哈哈哈";   //"*HSCoder"不能被修改, "HSCoder"能被修改
NSString const *HSCoder = @"汉斯哈哈哈";   //"*HSCoder"不能被修改, "HSCoder"能被修改(与上个没啥区别)
NSString * const HSCoder = @"汉斯哈哈哈";  //"HSCoder"不能被修改,"*HSCoder"能被修改

结论:const右边的总不能被修改

所以一般我们定义一个常量又不想被修改应该选择最后一种方案:

NSString * const HSCoder = @"汉斯哈哈哈";

.h文件中这样写:

UIKIT_EXTERN NSString *const HSCoder
10.如何使用Objective-C实现多重继承?并给出代码示例
11.关于app性能优化你是怎么理解的?请详细描述一下
// in your .h or inside a class extension
@property (nonatomic, strong) NSDateFormatter *formatter;

// inside the implementation (.m)
// When you need, just use self.formatter
- (NSDateFormatter *)formatter {
    if (! _formatter) {
        _formatter = [[NSDateFormatter alloc] init];
        _formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; // twitter date format
    }
    return _formatter;
}
12. tableview优化

为了保证table view平滑滚动,确保你采取了以下的措施:

13. 对象没销毁的原因有哪些
[NSTimer scheduledTimerWithTimeInterval:1.0 
           target:self 
           selector:@selector(updateTime:) 
           userInfo:nil 
           repeats:YES];

时,这个target:self就增加了VC的RetainCount,如果你不将这个timer invalidate,就别想调用dealloc。需要在viewWillDisappear之前需要把控制器用到的NSTimer销毁。

[timer invalidate]; // 销毁timer
timer = nil; // 置nil
14.多线程操作
1.先创建线程类,再启动
NSThread *thread = [[NSThread alloc] initWithTarget:self 
                                     selector:@selector(run:) 
                                     object:nil];
[thread start];

2.创建并自动启动
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];

3.使用 NSObject 的方法创建并自动启动
[self performSelectorInBackground:@selector(run:) withObject:nil];
创建队列
1.主队列:这是一个特殊的 `串行队列`
dispatch_queue_t queue = dispatch_get_main_queue();
2.全局并行队列:只要是并行任务一般都加入到这个队列。这是系统提供的一个并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3.自己创建的队列:
 //串行队列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
  //并行队列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

创建任务
1.同步任务: 会阻塞当前线程 (SYNC)
 dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });
2.异步任务:不会阻塞当前线程 (ASYNC)
dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

队列组
//1.创建队列组
dispatch_group_t group = dispatch_group_create();
//2.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//3.多次使用队列组的方法执行任务, 只有异步方法
//3.1.执行3次循环
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"group-01 - %@", [NSThread currentThread]);
    }
});

//3.2.主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue(), ^{
    for (NSInteger i = 0; i < 8; i++) {
        NSLog(@"group-02 - %@", [NSThread currentThread]);
    }
});

//3.3.执行5次循环
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"group-03 - %@", [NSThread currentThread]);
    }
});

//4.都完成后会自动通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"完成 - %@", [NSThread currentThread]);
});
添加任务
1.NSInvocationOperation : 需要传入一个方法名
  //1.创建NSInvocationOperation对象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
  //2.开始执行
 [operation start];

2.NSBlockOperation
  //1.创建NSBlockOperation对象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];
  //2.开始任务
 [operation start];
NSBlockOperation还有一个方法:addExecutionBlock: 通过这个方法可以给Operation添加多个执行Block。
这样Operation中的任务会并发执行,它会在主线程和其它的多个线程执行这些任务


创建队列:
只要添加到队列,会自动调用任务的 start() 方法

1.主队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];

2.其他队列
//1.创建一个其他队列    
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//2.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];
//3.添加多个Block
for (NSInteger i = 0; i < 5; i++) {
    [operation addExecutionBlock:^{
        NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
    }];
}
//4.队列添加任务
[queue addOperation:operation];

将NSOperationQueue与GCD的队列相比较就会发现,这里没有串行队列,那如果我想要10个任务在其他线程串行的执行怎么办?
这就是苹果封装的妙处,你不用管串行、并行、同步、异步这些名词。NSOperationQueue有一个参数maxConcurrentOperationCount最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为1的时候,他不就是串行了嘛!
NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了

串行队列和并行队列

总体上说: 使用 dispatch group,然后 wait forever 等待完成, 或者采取 group notify 来通知回调。
细节:
1. 创建异步队列
2. 创建dispatch_group dispatch_group_t = dispatch_group_create()
3. 通过组来执行异步下载任务 dispatch_group_async(queueGroup, aQueue, ^{
NSLog(@"下载图片."); });
4.等到所有任务完成 dispatch_group_wait(queueGroup, DISPATCH_TIME_FOREVER);
5.合成图片

  使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。

  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  dispatch_group_t group = dispatch_group_create();
  dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
  dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
  dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 
  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 合并图片
  });

######15. ARC内存管理的理解
《Effective Objective-C 2.0》
理解引用计数
- 引用计数工作原理
- 属性存取方法中的内存管理
- 自动释放池
- 保留环

以arc简化引用计数
- 使用ARC时必须遵循的方法命名规则
- 变量的内存管理语义
- arc如何清理实例变量
- 覆写内存管理方法
- 用“僵尸对象”调试内存管理问题
- 不要使用retain count

在dealloc方法中只释放引用并解除监听
编写"异常安全代码"时注意内存管理问题
以弱引用避免保留环
以"自动释放池块"降低内存峰值

《Objective-C高级编程》
>Apple 在Objective-C中采用ARC机制,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或release代码,这在降低程序崩溃,内存泄漏等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清楚目标对象,并能立刻释放那些不再被使用的对象。如此依赖,应用程序将具有可预测性,且能流程运行,速度也将大幅提升。

- 自己生成的对象,自己持有
- 非自己生成的对象,自己也能持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放


- ######15.1.内存泄漏有哪些情况
   - 循环引用
      - Delegate
我们在使用代理设计模式的时候,一定要注意将 delegate 变量声明为 weak 类型,像这样 @property (nonatomic, weak) id delegate;
      - Block
__weak typeof(self)weakSelf = self;
      - NSTimer
在一个ViewController里创建了一个定时器,并且repeats:值为YES,一定记得在 pop/dismiss当前ViewController将timer设为invalidate,否则会造成循环引用,这里要特别需要注意的一点是:我们不要在ViewController的dealloc方法里调用[timer invalidate]; 因为从来不会调到这里,我们应该在viewWillDisappear里边调用

   - performSelector延时调用导致的内存泄露
假设你延时10s触发某个方法,但是在3s的时候,用户点击了back,这时对象不能马上被回收,而是要等到10s后才有可能被回收。所以在项目中如果要延时触发方法,我不会选择该方法,而是使用GCD
         __weak typeof(self) weakSelf = self;
         double delayInSeconds = 10.0;
         dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds *NSEC_PER_SEC);
         dispatch_after(popTime, dispatch_get_main_queue(), ^{
             [weakSelf dodd];
         });

   - 代理未清空引起野指针
iOS的一些API,发现delegate都是assign的,这样就会引起野指针的问题,可能会引起一些莫名其妙的crash。那么这是怎么引起的,当一个对象被回收时,对应的delegate实体也就被回收,但是delegate的指针确没有被nil,从而就变成了游荡的野指针了。所以在delloc方法中要将对应的assign代理设置为nil
一般自己写的一些delegate,我们会用weak,而不是assign,weak的好处是当对应的对象被回收时,指针也会自动被设置为nil。

- ######15.2 什么时候用@autoreleasepool
   - 写基于命令行的的程序时,就是没有UI框架,如AppKit等Cocoa框架时。
   - 写循环,循环里面包含了大量临时创建的对象。(500000次循环,每次循环创建一个NSNumber实例和两个NSString实例)
   - 创建了新的线程。(非Cocoa程序创建线程时才需要)
   - 长时间在后台运行的任务。

- ######15.3. 页面使用过多内存过多闪退
- ######15.4. 1000张图片内存
- ######15.5.界面交互优化腾讯 banner100页怎么办

######16. 详细描述一下KVO的实现机制,代理 通知和kvo区别 代理和block区别 block 访问外部变量原理


######17. runtime的理解
- runtime作用
     - 发送消息
消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现
     - 交换方法
开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能。
方式一:继承系统的类,重写方法.
方式二:使用runtime,交换方法.
           // 获取imageWithName方法地址
           Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
           // 获取imageWithName方法地址
           Method imageName = class_getClassMethod(self, @selector(imageNamed:));
           // 交换方法地址,相当于交换实现方式
           method_exchangeImplementations(imageWithName, imageName);
     - 动态添加方法
开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法。
简单使用
           @implementation Person
           // void(*)()
           // 默认方法都有两个隐式参数,
           void eat(id self,SEL sel)
           {
               NSLog(@"%@ %@",self,NSStringFromSelector(sel));
           }
           // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
           // 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
           + (BOOL)resolveInstanceMethod:(SEL)sel
           {
               if (sel == @selector(eat)) {
                   // 动态添加eat方法
                   // 第一个参数:给哪个类添加方法
                   // 第二个参数:添加方法的方法编号
                   // 第三个参数:添加方法的函数实现(函数地址)
                   // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
                   class_addMethod(self, @selector(eat), eat, "v@:");
               }
               return [super resolveInstanceMethod:sel];
           }
           @end
     - 给分类添加属性
原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
           static const char *key = "name";
           @implementation NSObject (Property)
           - (NSString *)name
           {
               // 根据关联的key,获取关联的值。
               return objc_getAssociatedObject(self, key);
           }
           - (void)setName:(NSString *)name
           {
               // 第一个参数:给哪个对象添加关联
               // 第二个参数:关联的key,通过这个key获取
                // 第三个参数:关联的value
               // 第四个参数:关联的策略
               objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
           }
           @end
     - 字典转模型
- runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
- category中能不能使用声明属性?为什么?如果能,怎么实现?
给分类(Category)添加属性利用Runtime实现getter/setter 方法

@interface ClassName (CategoryName)
@property (nonatomic, strong) NSString *str;
@end

//实现文件

import "ClassName + CategoryName.h"

import <objc/runtime.h>

static void *strKey = &strKey;
@implementation ClassName (CategoryName)

-(void)setStr:(NSString *)str
{
objc_setAssociatedObject(self, & strKey, str, OBJC_ASSOCIATION_COPY);
}

-(NSString *)str
{
return objc_getAssociatedObject(self, &strKey);
}

@end

- 什么时候会报unrecognized selector的异常?
简单来说:当该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过“消息转发”进行解决。
简单的流程如下:objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:
`Method resolution`
objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回 YES,那运行时系统就会重新启动一次消息发送的过程,如果 resolve 方法返回 NO ,运行时就会移到下一步,消息转发(Message Forwarding)。
`Fast forwarding`
如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
`Normal forwarding`
这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。


######18. runloop的理解
- RunLoop 的概念
如果我们需要一个机制,让线程能随时处理事件但并不退出,这种模型通常被称作 Event Loop。Event Loop 在很多系统和框架里都有实现,比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。
- RunLoop 与线程的关系
苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。
线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。




######19. contentoffset contentinset contentsize
######20. 说说知道哪些设计模式,说说简单工厂和抽象工厂
######21.第三方分享的调用接口
######22.动画的实现方式
######23. 错误提示sympol
######24. 使用定时器注意问题
######25. 页面滑动丢帧的原因有多少种
######26.推送的原理具体实现
######27. 如果不用第三方库实现图片缓存设计一种方法
######28. 获得App闪退log怎么做
######29. 完整发布流程

- 如何重写带 copy 关键字的 setter?
重写copy的setter方法时候,一定要调用一下传入的对象的copy方法,然后在赋值给该setter的方法对应的成员变量

- @synthesize和@dynamic分别有什么作用?
   - @property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;
   - @synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
   - @dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var =someVar,由于缺setter方法会导致程序崩溃;或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
- BAD_ACCESS在什么情况下出现,如何调试?
   1.  死循环了
   2.  访问一个僵尸对象
设置全局断点快速定位问题代码所在行

- 谈谈instancetype和id的异同
1、相同点
都可以作为方法的返回类型
2、不同点
①instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;②instancetype只能作为返回值,不能像id那样作为参数

- @protocol 和 category 中如何使用 @property
1)在protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
2)category 使用 @property 也是只会生成setter和getter方法的声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数:
①objc_setAssociatedObject
②objc_getAssociatedObject



上一篇下一篇

猜你喜欢

热点阅读