iOS 开发每天分享优质文章将来跳槽用iOS面试资料搜集

二十二道 ~ 自我总结面试题

2019-07-12  本文已影响102人  iOS_小久

朋友面试帮他准备了22道面试题,分享一下希望对你们也有帮助

1. 常见的object-c的数据类型有那些, 和C的基本数据类型有什么区别?如:NSInteger和int

答:object-c的数据类型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,这些都是class,创建后便是对象,而C语言的基本数据类型int,只是一定字节的内存空间,用于存放数值;NSInteger是基本数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。NSInteger是基本数据类型Int或者Long的别名(NSInteger的定义typedef long NSInteger),它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是Long。

2. 这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray arr;

问题:添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。
//如:-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
// copy后返回的是不可变对象(即 arr 是 NSArray 类型,NSArray 类型对象不能调用 NSMutableArray 类型对象的方法)
原因:是因为 copy 就是复制一个不可变 NSArray 的对象,不能对 NSArray 对象进行添加/修改。

3. 写一个 setter 方法用于完成 @property (nonatomic, retain) NSString name,写一个 setter 方法用于完成 @property (nonatomic, copy) NSString name

// retain - (void)setName:(NSString *)str {
      [str retain];
      [_name release];
      _name = str;
    } // copy - (void)setName:(NSString *)str {
      id t = [str copy];
      [_name release];
      _name = t;
    }

4. Objective-C 中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?

答:线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:

5. KVC的底层实现?

当一个对象调用setValue方法时,方法内部会做以下操作:
1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。

6. isa指针问题

isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调 用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

7. 写一个完整的代理,包括声明、实现

// 创建 @protocol MyDelagate @required -(void)eat:(NSString *)foodName; 
    @optional -(void)run;
    @end //  声明 .h @interface person: NSObject

    @end //  实现 .m @implementation person - (void)eat:(NSString *)foodName { NSLog(@"吃:%@!", foodName);
    } 
    - (void)run { NSLog(@"run!");
    }

    @end

8. block的注意点

1). 在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针:
__weak typeof(self) weakSelf = self;
2). 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。
__strong typeof(self) strongSelf = weakSelf;
3). 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量。

9. iOS的沙盒目录结构是怎样的?

沙盒结构:
1. AppName.app 目录:这是应用程序的程序包目录,包含应用程序的本身。由于应用程序必须经过签名,所以您在运行时不能对这个目录中的内容进行修改,否则可能会使应用程序无法启动。
2. Documents:您应该将所有的应用程序数据文件写入到这个目录下。这个目录用于存储用户数据。iCloud备份目录。(这里不能存缓存文件,否则上架不被通过)
3. Library 目录:这个目录下有两个子目录:
Preferences 目录:包含应用程序的偏好设置文件。您不应该直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的偏好.
Caches 目录:用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息。
可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份。
4. tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。

10. Runtime实现的机制是什么,怎么用,一般用于干嘛?

1). 使用时需要导入的头文件
2). Runtime 运行时机制,它是一套C语言库。
3). 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
比如:
类转成了 Runtime 库里面的结构体等数据类型,
方法转成了 Runtime 库里面的C语言函数,
平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)
// OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
// [stu show]; 在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));
4). 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。

有了Runtime库,能做什么事情呢?
Runtime库里面包含了跟类、成员变量、方法相关的API。
比如:
(1)获取类里面的所有成员变量。
(2)为类动态添加成员变量。
(3)为类动态添加新的方法。
(4)动态改变类的方法实现等。(Method Swizzling)

11. 什么是 Method Swizzle(黑魔法),什么情况下会使用?

1). 在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法 Method Swizzle。
2). Method Swizzle 指的是改变一个已存在的选择器对应的实现的过程。OC中方法的调用能够在运行时通过改变,通过改变类的调度表中选择器到最终函数间的映射关系。
3). 在OC中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用OC的动态特性,可以实现在运行时偷换selector对应的方法实现。
4). 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的方法实现。
5). 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP。
6). 我们可以利用 class_replaceMethod 来修改类。
7). 我们可以利用 method_setImplementation 来直接设置某个方法的IMP。
8). 归根结底,都是偷换了selector的IMP。

12. 你是怎么封装一个view的

1). 可以通过纯代码或者xib的方式来封装子控件 2). 建立一个跟view相关的模型,然后将模型数据传给view,通过模型上的数据给view的子控件赋值

     *  纯代码初始化控件时一定会走这个方法
     */ - (instancetype)initWithFrame:(CGRect)frame { if(self = [super initWithFrame:frame]) {
            [self setupUI];
        } return self;
    } /**

通过xib初始化控件时一定会走这个方法

     */ - (id)initWithCoder:(NSCoder *)aDecoder { if(self = [super initWithCoder:aDecoder]) {
            [self setupUI];
        } return self;
    }

    - (void)setupUI { // 初始化代码 }

13. ios开发逆向传值的几种方法整理

第一种:代理传值

第二个控制器: @protocol WJSecondViewControllerDelegate  - (void)changeText:(NSString*)text; @end @property(nonatomic,assign)iddelegate;

  - (IBAction)buttonClick:(UIButton*)sender {
  _str = sender.titleLabel.text;
  [self.delegate changeText:sender.titleLabel.text];
  [self.navigationController popViewControllerAnimated:YES];
  }

  第一个控制器:

  - (IBAction)pushToSecond:(id)sender {
  WJSecondViewController *svc = [[WJSecondViewController alloc]initWithNibName:@"WJSecondViewController" bundle:nil];
  svc.delegate = self;
  svc.str = self.navigationItem.title;
  [self.navigationController pushViewController:svc animated:YES];
  [svc release];
  }
  - (void)changeText:(NSString *)text{ self.navigationItem.title = text;
  }

第二种:通知传值

第一个控制器: //注册监听通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(limitDataForModel:) name:@"NOV" object:nil];
  - (void)limitDataForModel:(NSNotification *)noti{ self.gamesInfoArray = noti.object;
  }

  第二个控制器: //发送通知 [[NSNotificationCenter defaultCenter]     postNotificationName:@"NOV" object:gameArray];

第三种:单例传值

Single是一个单例类,并且有一个字符串类型的属性titleName
  在第二个控制器:

  - (IBAction)buttonClick:(UIButton*)sender {
  Single *single = [Single sharedSingle];
  single.titleName = sender.titleLabel.text;
  [self.navigationController popViewControllerAnimated:YES];
  }

  第一个控制器:

  - (void)viewWillAppear:(BOOL)animated{
  [super viewWillAppear:animated];
  Single *single = [Single sharedSingle]; self.navigationItem.title = single.titleName;
  }

第四种:block传值

第二个控制器: @property (nonatomic,copy) void (^changeText_block)(NSString*);
  - (IBAction)buttonClick:(UIButton*)sender {
  _str = sender.titleLabel.text; self.changeText_block(sender.titleLabel.text);
  [self.navigationController popViewControllerAnimated:YES];
  }

  第一个控制器:

  - (IBAction)pushToSecond:(id)sender {
  WJSecondViewController *svc = [[WJSecondViewController alloc]initWithNibName:@"WJSecondViewController" bundle:nil];
  svc.str = self.navigationItem.title;
  [svc setChangeText_block:^(NSString *str) {
      >self.navigationItem.title = str;
  }];
  [self.navigationController pushViewController:svc animated:YES];
  }

第五种:extern传值

第二个控制器: extern NSString *btn;
  - (IBAction)buttonClick:(UIButton*)sender {
  btn = sender.titleLabel.text;
  [self.navigationController popViewControllerAnimated:YES];
  }

  第一个控制器: NSString *btn = nil;
  - (void)viewWillAppear:(BOOL)animated{
  [super viewWillAppear:animated]; self.navigationItem.title = btn;
  }

第六种:KVO传值

第一个控制器:

  - (void)viewDidLoad {
  [super viewDidLoad];
   _vc =[[SecondViewController alloc]init]; //self监听vc里的textValue属性 [_vc addObserver:self forKeyPath:@"textValue" options:0 context:nil];   
  }

  第二个控制器:

  - (IBAction)buttonClicked:(id)sender { self.textValue = self.textField.text;
  [self.navigationController popViewControllerAnimated:YES];
  }

14. NSPersistentStoreCoordinator , NSManaged0bjectContext 和NSManaged0bject中的那些需要在线程中创建或者传递

答:NSPersistentStoreCoordinator是持久化存储协调者,主要用于协调托管对象上下文和持久化存储区之间的关系。NSManagedObjectContext使用协调者的托管对象模型将数据保存到数据库,或查询数据。

15. 你使用过Objective-C的运行时编程(Runtime Programming)么?如果使用过,你用它做了什么?你还能记得你所使用的相关的头文件或者某些方法的名称吗?

答:Objecitve-C的重要特性是Runtime(运行时),在#import 下能看到相关的方法,用过objc_getClass()和class_copyMethodList()获取过私有API;使用
Method method1 = class_getInstanceMethod(cls, sel1);
Method method2 = class_getInstanceMethod(cls, sel2);
method_exchangeImplementations(method1, method2);

代码交换两个方法,在写unit test时使用到。

16. NSNotification和KVO的区别和用法是什么?什么时候应该使用通知,什么时候应该使用KVO,它们的实现上有什么区别吗?如果用protocol和delegate(或者delegate的Array)来实现类似的功能可能吗?如果可能,会有什么潜在的问题?如果不能,为什么?(虽然protocol和delegate这种东西面试已经面烂了…)

答:NSNotification是通知模式在iOS的实现,KVO的全称是键值观察(Key-value observing),其是基于KVC(key-value coding)的,KVC是一个通过属性名访问属性变量的机制。例如将Module层的变化,通知到多个Controller对象时,可以使用NSNotification;如果是只需要观察某个对象的某个属性,可以使用KVO。
对于委托模式,在设计模式中是对象适配器模式,其是delegate是指向某个对象的,这是一对一的关系,而在通知模式中,往往是一对多的关系。委托模式,从技术上可以现在改变delegate指向的对象,但不建议这样做,会让人迷惑,如果一个delegate对象不断改变,指向不同的对象。

17. 不用中间变量,用两种方法交换A和B的值**

1.中间变量 void swap(int a, int b) { int temp = a; a = b; b = temp; }
2.加法 void swap(int a, int b) { a = a + b; b = a - b; a = a - b; }
3.异或(相同为0,不同为1. 可以理解为不进位加法) void swap(int a, int b) { a = a ^ b; b = a ^ b; a = a ^ b; }

18. 求最大公约数

1.直接遍历法 */ int maxCommonDivisor(int a, int b) { int max = 0; for (int i = 1; i <=b; i++) { if (a % i == 0 && b % i == 0) { max = I; } } return max; }

2.辗转相除法 */ int maxCommonDivisor(int a, int b) { int r; while(a % b > 0) { r = a % b; a = b; b = r; } return b; } // 扩展:最小公倍数 = (a * b)/最大公约数

19. 排序算法

选择排序、冒泡排序、插入排序三种排序算法可以总结为如下:

都将数组分为已排序部分和未排序部分。 1. 选择排序将已排序部分定义在左端,然后选择未排序部分的最小元素和未排序部分的第一个元素交换。 2. 冒泡排序将已排序部分定义在右端,在遍历未排序部分的过程执行交换,将最大元素交换到最右端。 3. 插入排序将已排序部分定义在左端,将未排序部分元的第一个元素插入到已排序部分合适的位置。

选择排序 /** * 【选择排序】:最值出现在起始端 * * 第1趟:在n个数中找到最小(大)数与第一个数交换位置 * 第2趟:在剩下n-1个数中找到最小(大)数与第二个数交换位置 * 重复这样的操作...依次与第三个、第四个...数交换位置 * 第n-1趟,最终可实现数据的升序(降序)排列。 * / void selectSort(int arr, int length) { for (int i = 0; i < length - 1; i++) { //趟数 for (int j = i + 1; j < length; j++) { //比较次数 if (arr[i] > arr[j]) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } } * 冒泡排序 / * 【冒泡排序】:相邻元素两两比较,比较完一趟,最值出现在末尾 * 第1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n个元素位置 * 第2趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n-1个元素位置 * …… …… * 第n-1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第2个元素位置 */ void bublleSort(int *arr, int length) { for(int i = 0; i < length - 1; i++) { //趟数 for(int j = 0; j < length - i - 1; j++) { //比较次数 if(arr[j] > arr[j+1]) { int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } }

20. iOS 中的多线程

iOS中的多线程,是Cocoa框架下的多线程,通过Cocoa的封装,可以让我们更为方便的使用线程,做过C++的同学可能会对线程有更多的理解,比如线程的创立,信号量、共享变量有认识,Cocoa框架下会方便很多,它对线程做了封装,有些封装,可以让我们创建的对象,本身便拥有线程,也就是线程的对象化抽象,从而减少我们的工程,提供程序的健壮性。

GCD是(Grand Central Dispatch)的缩写 ,从系统级别提供的一个易用地多线程类库,具有运行时的特点,能充分利用多核心硬件。GCD的API接口为C语言的函数,函数参数中多数有Block,关于Block的使用参看这里,为我们提供强大的“接口”,对于GCD的使用参见本文

NSOperation与Queue

NSOperation是一个抽象类,它封装了线程的细节实现,我们可以通过子类化该对象,加上NSQueue来同面向对象的思维,管理多线程程序。具体可参看这里:一个基于NSOperation的多线程网络访问的项目。

NSThread

NSThread是一个控制线程执行的对象,它不如NSOperation抽象,通过它我们可以方便的得到一个线程,并控制它。但NSThread的线程之间的并发控制,是需要我们自己来控制的,可以通过NSCondition实现。

21. 将一个函数在主线程执行的4种方法

  • GCD方法,通过向主线程队列发送一个block块,使block里的方法可以在主线程中执行。 dispatch_async(dispatch_get_main_queue(), ^{ //需要执行的方法 });
  • NSOperation 方法 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; //主队列 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ //需要执行的方法 }];
    [mainQueue addOperation:operation];
  • NSThread 方法
    [self performSelector:@selector(method) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES modes:nil];
    [self performSelectorOnMainThread:@selector(method) withObject:nil waitUntilDone:YES];
    [[NSThread mainThread] performSelector:@selector(method) withObject:nil];
  • RunLoop方法
    [[NSRunLoop mainRunLoop] performSelector:@selector(method) withObject:nil];

22. 使用 Swift 语言编程的优缺点

总的来说,我认为使用 Swift 来作为编程语言的优点还是要远远大于缺点的,而且很多缺点苹果也在逐渐改善。

扫码进交流群 有技术的来闲聊 没技术的来学习

691040931
上一篇 下一篇

猜你喜欢

热点阅读