iOS面试题总结

1. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的

  • property本质
    @property = ivar(实例变量) + getter + setter(存取方
    法)
    struct property_t {
      const char *name;
      const char *attributes;
    }
    
  • ivar、getter、setter 是如何生成并添加到这个类中的?
    “自动合成”( autosynthesis)
    由于@property只是对getter和setter方法进行了声明,其他的什么也没有干,所以完成属性的定义以后,编译器自动在编译阶段根据@synthesize 去实现getter和setter和ivar,默认是@synthesize ivar = _ivar
  • 底层操作
    每次在增加一个属性,系统都会在 类中的ivar_list 中添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转。

2. @synthesize和@dynamic分别有什么作用?

  1. @property与@synthesize@property与@dynamic,是一一对应并且成对出现的,@property只起到getter和setter方法声明的作用,而实现则是由@synthesize来完成。如果 @synthesize@dynamic都没写,那么编译器默认添加的就是@syntheszie var = _var
  2. @synthesize语义是如果没有手动生成getter 和setter,那么编译器自动生成。并且它自动合成的一共有三项_ivarsettergetter,合成规则:要么合成三项,要么都不合成。
  3. @dynamic语义是告诉编译器不要为我生成_ivar变量、和getter 和setter方法,我要自己实现
  • 什么情况下自动合成@synthesize会失效?
    • 当我们想要自己手动管理的时候
    1. 同时重写了 setter 和 getter 时
    2. 重写了只读属性的 getter 时
    3. 使用了 @dynamic 时
    4. 在 @protocol 中定义的所有属性
    5. 在 category 中定义的所有属性
    6. 重载的属性
  • @synthesize失效的例子
    // 打开第14行和第17行中任意一行,就可编译成功
    
    @import Foundation;
    
    @interface CYLObject : NSObject
    @property (nonatomic, copy) NSString *title;
    @end
    
    @implementation CYLObject {
       //    NSString *_title;
    }
    
    //@synthesize title = _title;
    
    - (instancetype)init
    {
       self = [super init];
       if (self) {
           _title = @"微博@iOS程序犭袁";
       }
       return self;
    }
    
    - (NSString *)title {
       return _title;
    }
    
    - (void)setTitle:(NSString *)title {
       _title = [title copy];
    }
    
    @end
    
    iOS面试题总结_第1张图片
  • @synthesize使用方法
    // 声明自动合成get和set方法,@property默认就是这种情况
    @synthesize mArray = _mArray;// 默认
    
    @synthesize mArray;// 生成的名称是 mArray
    
    @synthesize mArray = _abcd;// 生成的名称是 _abcd
    

3. runtime 如何实现 weak 属性

  • weak属性的特点
    • 表示一种非拥有的属性关系,setter方法赋值时既不保留新值,也不释放旧值,因为若引用不会使对象的引用计数器+1
  • runtime怎么实现weak置nil
    • runtime会对当前程序所注册的类进行布局,并且将所有weak对象放到hash表中。在运行时,调用weak对象的setter方法时,会将weak属性(假设weak属性为name)所赋值对象地址当作key,name的地址当作值存入哈希表中,当name所持有的对象释放时查找hash表,将name置nil

4. @property中有哪些属性关键字

  1. 原子性:nonatomicatomic(默认)
    在默认情况下,由编译器合成的方法(set和get方法)会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用自旋锁。
  2. 读写:readwrite(读写)readonly (只读)
  3. 内存语义:assign、strong、 weak、unsafe_unretained、copy、retain
  4. 方法名:getter=setter=
    @property (nonatomic, getter=isOn) BOOL on;

5. weak属性需要在dealloc中置nil吗

不需要:ARC下不需要在dealloc中置nil,ARC会自动置nil。

6. ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些

  • 基本数据类型
    • atomic、readwrite、assign
  • 对象类型
    • atomic、readwrite、strong

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

  • 非集合类对象copy - NSString、NSMutableString
     [immutableObject copy] // 浅复制
     [immutableObject mutableCopy] //深复制
     [mutableObject copy] //深复制
     [mutableObject mutableCopy] //深复制
    
  • 集合类对象
    // 单层的意思就是集合内部对象仍然是浅复制
    [immutableObject copy] // 浅复制
    [immutableObject mutableCopy] //单层深复制
    [mutableObject copy] //单层深复制
    [mutableObject mutableCopy] //单层深复制
    
    如果想要对集合对象进行深层次的复制调用如下方法
    // 方法含义是复制self.mutableCopy数组,并且向里面的对象都发送copy消息
    // 这种方法也不能完全完成深层复制,该方法只会对mutableCopy对象内的对象发送一次copy,如果对象是多层嵌套的,就不能继续复制了
    NSArray *a = [[NSArray alloc] initWithArray:self.mutableCopy copyItems:YES];
    

7. 一个objc对象如何进行内存布局?(考虑有父类的情况)

  1. 内部存储所有父类的成员变量和自己的成员变量
  2. isa指针
  • 内存布局


    iOS面试题总结_第2张图片
  • 对象继承关系


    iOS面试题总结_第3张图片

8. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放吗

无论在MRC下还是ARC下均不需要。

  • 对象内存销毁时间表
    根据以下时间表可以看出,在主对象销毁以后(1步)调用dealloc方法,如果主对象没有实现dealloc方法(ARC环境下)那么逐层向上调用直到在NSObjcet类中找到dealloc方法,这个dealloc方法内部调用 Objective-C runtime 中的 object_dispose()方法去完成下面第4步。如果是MRC下,主对象必须实现dealloc方法去释放自己的实例变量(iVars),即使主对象重写了dealloc方法,那么在这个方法中也会调用[super dealloc]去掉用NSObject中的dealloc去调用object_dispose()
    // 对象的内存销毁时间表
    // 根据 WWDC 2011, Session 322 (36分22秒)中发布的内存销毁时间表 
    // 
     1. 调用 -release :引用计数变为零
         * 对象正在被销毁,生命周期即将结束.
         * 不能再有新的 __weak 弱引用, 否则将指向 nil.
         * 调用 [self dealloc] 
     2. 子类 调用 -dealloc
         * 继承关系中最底层的子类 在调用 -dealloc
         * 如果是 MRC 代码 则会手动释放实例变量们(iVars)
         * 继承关系中每一层的父类 都在调用 -dealloc
     3. NSObject 调 -dealloc
         * 只做一件事:调用 Objective-C runtime 中的   object_dispose() 方法
     4. 调用 object_dispose()
         * 为 C++ 的实例变量们(iVars)调用 destructors 
         * 为 ARC 状态下的 实例变量们(iVars) 调用 -release 
         * 解除所有使用 runtime Associate方法关联的对象
         * 解除所有 __weak 引用
         * 调用 free()
    

9. 直接_objc_msgForward调用它将会发生什么

  • _objc_msgForward是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
  • 当调用[receiver message]编译器将之转化成 objc_msgSend(receiver, @selector(message)),objc_msgSend方法内部实现逻辑就是,receiver为nil时返回nil,否则去receiver->isa中的cache和方法列表中查找@selector(message)的实现IMP,如果找到把找到的方法复制给IMP,没找到就把_objc_msgForward函数指针复制给IMP,然后执行IMP
  • 如何调用_objc_msgForward
    • typedef void (*voidIMP)(id, SEL, ...)
      1. id(方法所属对象)
      2. SEL(方法名)
      3. 可变参数类型(可变参数)
    • 一旦调用_objc_msgForward方法则会跳过正常的查找self的SEL的IMP的过程,直接进入消息转发(转发3步),即使self已经实现了SEL那么也会告诉objc_msgSend,“我没有在这个对象里找到这个方法的实现”
  • 使用场景
    • 最常见的场景是:你想获取某方法所对应的NSInvocation对象
    • JSPatch 就是直接调用_objc_msgForward来实现其核心功能的:

      JSPatch 以小巧的体积做到了让JS调用/替换任意OC方法,让iOS APP具备热更新的能力。

10. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

  • 不能向编译后得到的类中增加实例变量

    • 因为编译后的类已经注册在 runtime 中(已经把内存分配给了类),类结构体中的 objc_ivar_list 实例变量的链表 和instance_size 实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayoutclass_setWeakIvarLayout 来处理 strong和weak 引用。所以不能向存在的类中添加实例变量
  • 能向运行时创建的类中添加实例变量运调用 class_addIvar 函数

    运行时创建类过程分3步如下:

    1. 为"class pair"分配空间(objc_allocateClassPair)
    2. 为创建的类添加方法和成员(class_addMethodclass_addIvar,注添加成员变量和方法必须在1分配之后,2注册之前)
    3. 注册你创建的这个类(objc_registerClassPair)
  • objc_allocateClassPair里面的pair指的是什么呢?

    • objc_allocateClassPair只返回一个创建好的class,那么pair的另一半就是元类(meta-class)也就是说objc_allocateClassPair会创建两个类,一个类对象,一个元类对象

11. runloop的mode作用是什么?

  • model 主要是用来指定事件在运行循环中的优先级的,分为:
    • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
    • UITrackingRunLoopMode:ScrollView滑动时
    • UIInitializationRunLoopMode:启动时
    • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合包括默认和滑动模式

12. dispatch_barrier_async的作用是什么?

  • 保证任务按照一定的顺序执行,在dispatch_barrier_async之前的任务全部执行完毕以后在执行队列后面的任务
    注意:使用 dispatch_barrier_async ,该函数只能搭配自定义并行队列 dispatch_queue_t使用。不能使用: dispatch_get_global_queue ,否则 dispatch_barrier_async 的作用会和 dispatch_async 的作用一模一样。
    NSLog(@"begin");
      
      // 使用 dispatch_barrier_async函数,队列一定是手动创建的并发队列,不能是系统的全局并发队列
      dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
      
      dispatch_async(queue, ^{
          NSLog(@"----1-----%@", [NSThread currentThread]);
      });
      dispatch_async(queue, ^{
          NSLog(@"----2-----%@", [NSThread currentThread]);
          sleep(3);
      });
      
      NSLog(@"--------------------");
      
      // 执行完1,2在执行barrier里面的block,然后执行3,4
      dispatch_barrier_async(queue, ^{
          NSLog(@"----barrier-----%@", [NSThread currentThread]);
      });
      
      dispatch_async(queue, ^{
          NSLog(@"----3-----%@", [NSThread currentThread]);
      });
      dispatch_async(queue, ^{
          NSLog(@"----4-----%@", [NSThread currentThread]);
      });
      
      NSLog(@"end");
    

12. 以下代码运行结果如何?

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}

以上代码执行后只输出1。因为主线程发生了死锁

原因:在主队列中以同步(sync)的方式派发一个任务会出现一下两种情况:这样就发生“你等我我等你的状况”谁也不执行(挂起状态),所以死锁

  1. 当前任务需要等待主队列中的任务执行完成以后才能执行(因为是穿行队列)
  2. 而当前执行的派发任务(dispatch_sync)要等block中的任务执行完成以后才能执行

13. addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?

// 添加键值观察
/*
1 观察者,负责处理监听事件的对象
2 观察的属性
3 观察的选项
4 上下文
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
  • self需要实现以下方法
// 所有的 kvo 监听到事件,都会调用此方法
/*
 1. 观察的属性
 2. 观察的对象
 3. change 属性变化字典(新/旧)
 4. 上下文,与监听的时候传递的一致
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
  • NSKeyValueObservingOptions的种类
    1. NSKeyValueObservingOptionNew:提供更改前的值(存储在change中)
    2. NSKeyValueObservingOptionOld:提供更改后的值(存储在change中)
    3. NSKeyValueObservingOptionInitial:观察最初的值(在注册观察服务时会调用一次触发方法)
    4. NSKeyValueObservingOptionPrior:分别在值修改前后触发方法(即一次修改有两次触发)

14. 如何手动通知KVO

  • 手动通知kvo:在设置kvo情况下,_student里面的age没有被改变的时候,也可以在观察者中调用observeValueForKeyPath(自己控制回调时机)
      // 在控制器中,控制器是观察者
      _student = [ManualKVO new];
      [_student addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
      NSLog(@"1");
      
      // 自己控制回调时机,而“回调的调用时机”就是在你调用 didChangeValueForKey: 方法时。即使self.age没有被改变也会回调
      [_student willChangeValueForKey:@"age"]; // “手动触发self.now的KVO”,必写。
      NSLog(@"2");
      
      // 在didChangeValueForKey方法里面执行observeValueForKeyPath回调
      [_student didChangeValueForKey:@"age"]; // “手动触发self.now的KVO”,必写。
      NSLog(@"4");
    
      // 在观察者类:self
      - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:  (void *)context {
      NSLog(@"3");
      //NSLog(@"%@", change);
    }
    
  • kvo实现原理:
    • 只要设置kvo的对象实现了setter方法,就能实现kvo
    • property属性自动支持kvo
    • 键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后, observeValueForKey:ofObject:change:context: 会被调用,继而 didChangeValueForKey: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。
     [_student addObserver:self
                 forKeyPath:@"age"   // 实例变量
                    options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                    context:nil];
    // 执行完上面的方法,系统做了如下几件事
    // 1. 生成student的子类NSNotification_Student,并重写student的setter(setAge:)方法
    // 2. 将_student对象的isa指针内容改成只想NSNotification_Student类
    // 3. 当调用_student的setAge方法的时候就会调用子类重写的setAge方法,并在该方法中添加通知observer的逻辑
     NSNotification_Student内setter方法主要逻辑:
     - (void)setNow:(NSDate *)aDate {
       [self willChangeValueForKey:@"now"];
       [super setValue:aDate forKey:@"now"];
       [self didChangeValueForKey:@"now"];
    }
    
  • 禁止kvo的自动监听功能以后还能怎么实现观察(应用到手动通知kvo)
//  Student.h

#import 

@interface Student : NSObject
{
    NSString  *_age;
}
- (void)setAge:(NSString *)age;
- (NSString *)age;

@property (nonatomic, strong) NSString  *name;

@end
//  Student.m

#import "Student.h"

@implementation Student

@synthesize name = _name;
- (void)setName:(NSString *)name
{
    _name = name;
}
- (NSString *)name
{
    return _name;
}


// 手动设定KVO
- (void)setAge:(NSString *)age
{
    // 最重要的就是下面两个方法(手动通知kvo)
    [self willChangeValueForKey:@"age"];
    _age = age;
    // 在didChangeValueForKey中调用观察者的observerValueForKey
    [self didChangeValueForKey:@"age"];
}
- (NSString *)age
{
    return _age;
}
// 在注册kvo的时候(调用addObserver:)会调用下面方法,返回NO则禁用传入的key的kvo
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    // 如果监测到键值为age,则指定为非自动监听对象
    if ([key isEqualToString:@"age"])
    {
        return NO;
    }
    
    return [super automaticallyNotifiesObserversForKey:key];
}
@end

15. IBOutlet连出来的视图属性为什么可以被设置成weak?

因为既然有外链那么视图在xib或者storyboard中肯定存在,视图已经对它有一个强引用了。
参考stackoverflow
不过这个回答漏了个重要知识,使用storyboard(xib不行)创建的vc,会有一个叫_topLevelObjectsToKeepAliveFromStoryboard的私有数组强引用所有top level的对象,所以这时即便outlet声明成weak也没关系

16. oc中protocol、category和继承的区别

  • protocol的作用是为一些列类仅仅提供一套公用的接口,而完全没 有办法也没可能去提供具体的一些实现情况
    @protocol ProcessDataDelegate 
    @required //必须实现的方法(默认)
    - (void) processSuccessful: (BOOL)success;
    
    @optional
    - (id) submitOrder: (NSNumber *) orderid;
    @end
    
  • category则是为一个已有的类提供一些额外的接口和具体实现,并且可以有声明,没实现,只要不调用就好了
    // 声明
    #import "SomeClass.h"
    
    @interface SomeClass (Hello)
    -(void)hello;
    @end
    // 实现
    #import "SomeClass+Hello.h"
    @implementationSomeClass (Hello)
    -(void)hello{
        NSLog (@"name:%@ ", @"Jacky");
    }
    @end
    
  • 而继承则基于两者之间,既可以想 protocol一样提供只是纯粹提供接口,也可以像Category一样提供完整的实现,而且继承还能对类以后的功能进行改写

16. Category和Protocol的使用场景,使用时应注意什么

  • 使用场景
    • Category:
      1. 当你在定义类的时候,在某些情况下(例如需求变更),你可能想要为其中的某个或几个类中添加方法。
      2. 一个类中包含了许多不同的方法需要实现,而这些方法需要不同团队的成员实现
      3. 当你在使用基础类库中的类时,你可能希望这些类实现一些你需要的方法。
    • Protocol:
      1. 最常用的就是委托代理模式,Cocoa框架中大量采用了这种模式实现数据和UI的分离。例如UIView产生的所有事件,都是通过委托的方式交给Controller完成。
  • 注意事项

17. 如何检测僵尸对象


iOS面试题总结_第5张图片

18. gcd和NSOperation区别

  1. GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择(GCD更轻量,Operation更多元)
  2. NSOperation能方便的取消已经放入队列中的任务、设置任务依赖、设置NSOperation的priority优先级即:使同一个并发队列中的任务区分先后的执行,而在GCD中职能区分不同队列的优先级。而gcd要完成这些则需要编写大量代码。
  3. 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;
  4. 我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。

总的来说当我们的需求能够以更简单的底层代码完成的时候,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。

19. __block和__weak区别

  1. __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型,__block修饰基本数据类型的作用就是扩大变量的生命周期(将变量的内存搬到了堆中)
  2. __weak只能在ARC模式下使用,并且只能修饰对象,不能修饰基本数据类型
  3. __block能够在MRC下解决循环引用问题,并且__block修饰的变量能够在block中修改,并且出了block访问外部变量,此时这个外部变量的值是修改过的值。__weak是在ARC下解决循环引用的问题(__weak是__unsafe_unretained的替代品,__unsafe_unretained不会自动置空)
  4. __weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
    __weak MyObject *weakObj = obj;
     
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    __strong MyObject *strongObj = weakObj;
        if (strongObj) {
            // do something ...
      }
     });
    

20. mrc和arc下block什么时候会持有外部对象

  • mrc
    • 栈中的block不会持有外部对象,堆中的会持有
  • arc
    • 栈中(匿名block作为参数传递时在栈中)和堆中的block都会持有外部对象

21. oc调用js,js调用oc

  • oc调用js
     // 获取页面document的title属性,那么只需要:
     NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"]];
     // 调用页面的一个叫test的函数
     [webview stringByEvaluatingJavaScriptFromString:@"test()"];
    
  • js调用oc
    // iOS里面加载一个网页用的是UIWebView,而关于页面加载的情况是通过UIWebView的一个Delegate:[UIWebViewDelegate](https://developer.apple.com/library/ios/documentation/uikit/reference/UIWebViewDelegate_Protocol/Reference/Reference.html)来通知 对应的webview的。而每次点击页面上的链接(或者是加载本页面的地址时)都会在加载前调用UIWebViewDelegate的一个方法:
    // 如果这个方法的返回值是YES的话就继续加载这个请求,如果是NO的话就不加载了。 所以Javascript调用Objective C代码的秘诀就在这个方法里面了。
      - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request   navigationType:   (UIWebViewNavigationType)navigationType
       {
         if (request.URL.absoluteString match urlSchemePattern) {
            [self executeSomeObjectiveCCode];
            return NO;
          } else {
            return YES;
         }
        }
    request.URL.absoluteString match urlSchemePattern    这里就是如果页面的url的格式是满足某种特定格式的话就不加载那个请求,而是执行Objective C的代码。
    

你可能感兴趣的:(iOS面试题总结)