iOS objc 面试


CocoaPods是什么?工作原理是什么?
答案:
CocoaPods是用来iOS项目中第三方框架的工具,重要通过建立podfile文件,文件中指定项目所需要的第三方框架,然后使用podinstall 安装框架
其重要的原理是:pods项目中的第三方框架最终会编译成一个名为libpods.a的文件,主项目依赖这个.a文件即可;cocoapods通过一个pods.xcconfig的文件在编译时设置所有的依赖和参数。对于资源文件, cocoapods提供了一个名为pods-resources.sh的bash脚本,该脚本在每次项目编译的时候都会执行,将第三方的各种资源文件复制到目标目录中;



如果一个对象释放钱被加到了NotificationCenter中,不在NotificationCenter中remove对象可能会怎么样?
答案:
只要添加对象到消息中心进行注册,之后就一定要其remove进行通知注销,将对象添加到消息中心后,消息中心只是保存该对象的地址,消息中心到时候地址发送通知给该对象,但没有取得该对象的强引用,对象的引用计数不会加1,如果对象释放后没有从消息中心remove,也就是通知中心还保存着那个指针,而那个指针的对象可能已经释放销毁了,那么那个指针就成为了一个野指针,当通知发生时,会向这个野指针发送消息导致程序崩溃。



沙盒机制?
答案:
用来保存应用的资源盒数据。应用程序只能访问自己的沙盒目录,而应用程序之间禁止数据的访问和共享。
1.Document 目录主要用于存储非常大的文件或需要非常频繁更新的数据
2.library目录
在library目录下分别有preferences和caches目录。
preferencesmulu主要用于存放应用程序的设置数据,通常保存应用的设置信息。
而caches目录下主要方数据缓存文件,适合存储体积大,不要要备份的非重要数据。
3.temp目录 是应用程序的临时目录,里面的文件随时可能被系统清除。



iOS应用的生命周期?
答案:
参考链接



kvo和kvc
答案:
参考链接



imagenamed的错误用法
答案:
参考链接



tableview的优化?
答案:
参考链接



block为什么要用copy?
答案:
Block在没有使用外部变量时,内存存在全局区,然而,当Block在使用外部变量的时候,内存是存在于栈区,当Block copy之后,是存在堆区的。存在于栈区的特点是对象随时有可能被销毁,一旦销毁在调用的时候,就会造成系统的崩溃。所以Block要用copy关键字。



你在开发过程中常用到那些定时器,会有误差吗,如果有为什么?
答案:iOS常用的NSTimer, CADisplaylink, GCD定时器
其中NSTimer, CADisplaylink基于runloop实现,故存在误差,
gcdtimer是依赖系统内核,相对比前两者是比较准确的。
误差的原因是: runloop的机制有关,因为runloop每完成一次循环,才去检查当前的累计的时间是否已经到达设置的时间间隔,如果未到达,则进入下一轮新的任务。
而等新一轮的任务结束,可能已经超出定时器的时间间隔。所以存在误差



@proprety的本质是什么?
答案:@property = ivar(实例变量)+getter + setter(存取方法)
“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)


如何给 Category 添加属性?
Objective-C 中的 Category它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。在 Category 中增加属性的目的主要为了解耦,简化框架调用,在很多第三方框架中会使用。

分类特点:
  • 分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性 ;
  • 分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量,会编译通过,但是引用变量会报错;
  • 如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法,同名方法调用的优先级为 分类 > 本类 > 父类;
    OC runtime动态绑定变量的方法:
objc_getAssociatedObject(<#T##object: Any##Any#>, <#T##key: UnsafeRawPointer##UnsafeRawPointer#>)
 objc_setAssociatedObject(<#T##object: Any##Any#>, <#T##key: UnsafeRawPointer##UnsafeRawPointer#>, <#T##value: Any?##Any?#>, <#T##policy: objc_AssociationPolicy##objc_AssociationPolicy#>)

*事件传递以及响应机制?
事件响应:

  1. 如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
  2. 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
  3. 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
  4. 如果UIApplication也不能处理该事件或消息,则将其丢弃


    image.png

    事件传递:

  5. 比如点击一个view,当发生触摸事件之后,系统会利用runloop将事件添加到UIApplication管理的队列中(即首先受到事件的是UIApplication)
  6. UIApplication会从事件队列中取出最前面的触摸事件分发给应用程序的主窗口keyWindow
  7. keyWinow会在视图层次结构中找到一个最合适的视图来处理触摸事件
    如何找到最合适的视图来出时间:
 override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        super.hitTest(point, with: event)
    }
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        super.point(inside: point, with: event)
    }

UIApplication事件队列->[UIWindow hitTest:withEvent:]->返回更合适的view->[子控件 hitTest:withEvent:]->返回最合适的view
pointInside:withEvent:方法判断点在不在当前view上(方法调用者的坐标系上)如果返回YES,代表点在方法调用者的坐标系上;返回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能处理事件。
事件的传递和响应的区别:
事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。
参考链接


autoreleasepool 什么时候释放?
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是 -2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 autoreleasepool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 pool 了。


iOS编译都做了什么?
两者都需要通过编译器(Clang + LLVM)把代码编译器生成机器码。


推送的原理?

image.png

  1. app向iOS设备发送注册的通知,用户需要同意发送推送
  2. iOS向apns远程推送服务器发送bundleid和设备的udid
  3. apns根据设备的udid和bundleid生成devicetoken返回给app
  4. app再将自己的deviceToken发送自己的服务器,由服务器保存在数据库中
  5. 当需要推送的时候,自己的服务器再根据devicetoken发送给apns
  6. apns在根据devicetoken发送给对应的app

0C的id对应swift any 还是anyobject?
答案:是anyobject
any可以代表任何类型的实例,包含函数类型(包括可选类型),协议
anyobject 可以代表任何类型的实例,对象类型


_objc_msgForward函数是做什么的,直接调用它将会发生什么?
_objc_msgForward是IMP类型,用于消息转发的:当一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward 会尝试做消息转发。
详解: _objc_msgForward 在进行消息转发的过程中会涉及以下这几个方法:

  1. resolveInstanceMethod: f方法(或 resolveClassMethod:)。
  2. forwardingTargetForSelector:方法
  3. methodSignatureForSelector:方法
  4. forwardInvocation:方法
  5. doesNotRecognizeSelector:方法

runtime如何实现weak变量的自动置为nil?知道SideTable吗?
runtime如何实现weak属性具体流程大致可以分为3步:

  1. weak的原理在于底层维护了一张weak_table_t结构的hash表,key是所指对象的地址,value是weak指针的地址数组。
  2. 初始化的时: runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址
  3. 添加引用时: objc_initWeak 函数会调用objc_storeWeak() 函数, objc_stroeWeak()的作用是更新指针指向,创建对应的弱引用表。
  4. weak 关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放的时候自动被设置为 nil。
  5. 对象释放时,调用clearDeallocating函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
    SideTable结构体是负责管理类的引用计数和weak表。
    weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)。

15. Compile Error/ Runtime Crash/ NSLog... ?
下面的代码会? Compile Error/ Runtime Crash/ NSLog.. ?

@interface NSObject (Sark)
+ (void)foo;
- (void)foo;
@end

@implementation NSObject (Sark)
- (void)foo {
    NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end

// 测试代码
[NSObject foo];
[[NSObject new] performSelector:@selector(foo)];
IMP: -[NSObject(Sark) foo] ,全都正常输出,编译和运行都没有问题。

这道题和上一到题很相似,第二个调用肯定没有问题,第一个调用后会从元类中查找方法,然而方法并不在元类中,所以找元类的superclass。方法定义是在NSObject的Category,由于NSObject的对象模型比较特殊,元类的superclass是类对象,所以从类对象中找到了对象并调用。


load和initialize的区别
load:
当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用无关,每个类的load函数只会自动调用一次。由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。
load 函数的执行顺序:

    1. 当父类和子类都实现load函数时,父类的load方法执行顺序要优于子类。
    1. 当子类未实现load方法时,不会调用父类的load方法。
    1. 类中的load方法执行顺序要优于类别
  • 4.当有多个类别都实现了load方法,这几个load方法都会执行,但执行的顺序不确定(其执行顺序与类别在Compile Sources 中出现的顺序一致)
    1. 当然当有多个不同的类的时候,每个类的load执行顺序与其在Compile Sources 出现的顺序一致
      load使用:
      由于调用load方法时的环境很不安全,我们应该尽量减少load方法的逻辑。另一个原因是load方法是线程安全的,它的内部使用了锁,所以我们应该避免线程阻塞在load方法中。
      load很常见的一个使用场景,交换两个方法的实现。
      initialize:
      在这个类接受第一条消息之前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于这是系统调用,也不需要显式的调用父类的initialize,否则父类的initialize会被多次执行,假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。
      initalize:
      initialize initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值。比如NSMutableArray这种类型的实例化依赖于runtime的消息发送,所以显然无法在编译器初始化:
  • 1.父类的initailize方法会比子类优先执行
  • 2.当子类不实现initialize方法,会把父类的实现继承过来调用一遍
    1. 当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category的initalize方法)
      参考链接

什么情况下使用weak关键字,相比assign有什么不同?

  1. 在ARC中,在有可能出现循环引用的时候,往往要通过让其中的一端weak来解决
  2. assign 可以用于非OC对象,而weak必须用于OC对象
    追加问题:如果用assign修饰一个OC对象会怎么样?
    答:assign 和 weak 都是弱引用,对象的内存是存在堆上,而地址指针时存在于栈上的。对于assign修饰的基本数据类型,内存空间存在栈上面由系统统一管理。不需要程序员去管理,而存在于堆上的空间需要程序员去手动管理的,当我们将对象销毁的时候,对象的内存空间释放,存在于栈的指针也会nil,就不会产生野指针了。回到上面问题,如果assign修饰一个对象后,当对象被释放的时候,存在栈上的指针还是存在的。假如此时使用次指针,它就是一个野指针,就容易造成程序崩溃,如果用weak修饰的对象,则不会产生上面的情况,因为对象销毁的时候,系统会将指针置为nil,也就不会产生野指针了。

怎么用 copy 关键字?
1.NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
1.为啥要copy
下面代码示例

@property (nonatomic, copy) NSString *name;
   NSMutableString *mtaString = [[NSMutableString alloc] initWithString:@"张三"];
    self.name = mtaString;
    [mtaString appendString: @"丰"];
    NSLog(@"name=%@ mtaString: %@", self.name, mtaString);

输出是

name=张三丰 mtaString: 张三丰

这时候自己的name在自己没有修改情况因为赋值的NSMutableString的改变而修改,造成一些不必要的影响。
注意的是:下面的代码如果name赋值的NSMutableString则是深copy,如果是NSString这是浅copy。

- (void)setName:(NSString *)name
{
    _name = [name copy];
}

附加问题 @property (nonatomic, copy) NSMutableArray *mutableArray; 这样用copy会有什么问题?
1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象

@property (nonatomic, copy) NSMutableArray *mutableArray;
NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];
self.mutableArray = array;
[self.mutableArray removeObjectAtIndex:0];

接下来就会崩溃:

-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460

如何让自己的类用copy功能?

  1. 需声明该类遵从 NSCopying 协议
  2. 实现 NSCopying 协议。该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;

Block相关?
1.使用block时什么情况会发生循环引用,如何解决?
一个对象中强引用了block,在block中又强引用了该对象,就会发生循环引用。

  1. 在block内如何修改block外部变量?
    参考链接

apple 用什么方式实现对一个对象的KVO?如何手动触发kvo
Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
如何手动触发kvo?

//  .m文件
//  Created by https://github.com/ChenYilong
//  微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/).
//  手动触发 value 的KVO,最后两行代码缺一不可。

//@property (nonatomic, strong) NSDate *now;
- (void)viewDidLoad {
   [super viewDidLoad];
   _now = [NSDate date];
   [self addObserver:self forKeyPath:@"now" options:NSKeyValueObservingOptionNew context:nil];
   NSLog(@"1");
   [self willChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。
   NSLog(@"2");
   [self didChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。
   NSLog(@"4");
}

autoreleasepool的原理和使用场景?

  • 若干个autoreleasepoolpage组成的双向链表的栈结构, objc_autorelasepoolpush, objc_autoreleasepoolpop,objc_autorelease
  • 使用场景:多次创建临时变量导致内存上涨时,需要延迟释放。
  • autoreleasepoolpage的内存结构:4k储存大小
    9778944-e12b88657c490f33.jpg

离屏渲染?
如果有时因为一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。也就是GPU需要在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。流程如图:

173313b3aebc4252.png

  1. OpenGL中,GPU屏幕渲染有以下两种方式当前屏幕渲染(On-Screen Rendering):正常情况下,我们在屏幕上显示都是GPU读取帧缓冲区(Frame Buffer)渲染好的的数据,然后显示在屏幕上。流程如图:
    1732f3543028519f.png

你可能感兴趣的:(iOS objc 面试)