ios常见面试题

前言

本文主要收录一些Ios面试中常会问到的一些题目

1、frame和bounds的区别

    1、frame:该view在父视图坐标系中的位置和大小,参考坐标系为父视图。 2、bounds:该view在自己坐标系中位置和大小,参考坐标系为自己。3、要想知道自身的坐标,只需要知道其在父视图中的坐标即可。因此当一个父视图包含一个子视图时,可以通过修改父视图的bounds属性修改其在父视图中的相对位置。 因为bounds参考坐标系为自身,因此修改bounds的x,y坐标相当于修改其自身坐标原点的位置,从而修改了子视图参考父视图的位置。

2、面向协议编程(pop 即 protocol oriented programming)

    讲到pop现将oop(面向对象编程),oop具有如下优点。1、封装和权限控制,在.h文件中申明公有方法和属性,在.m文件中申明私有方法和属性。2、扩展性,在oc中可以通过分类的方式在不破坏原本封装性的同时增加新的方法。还可以通过协议(protocol)和代理(delegate)实现类的灵活扩展。3、继承和多态,将公有的属性和方法放在父类中,子类继承时根据自身需要扩展新的方法和属性,增加代码灵活性。在ios中,消息可能会转发给不同的对象,需要根据消息类型确定转发的对象,从而调用不同的类别的方法,这种称为运行时多态,也是动态多态。

    oop的缺点1、隐式共享,说动隐式共享就需要提到深拷贝和浅拷贝,在对变量向另一个变量赋值时,如果该变量只是一个值引用类型,那么赋值后的两个变量都会指向同一块内存区域。造成数据混乱。需要需要对数据进行区分,需要重新向内存区申请一块新的地址存放新的数据。

    pop相比oop的优势:1、更加灵活,比如当希望一个类需要具有某种能力时,可以使用protocol的方式,如果该类继承了该协议,并且实现了协议方法,就说明其具有处理这种场景的能力。这样的好处是不必去基类种增加方法。降低的冗余性。2、降低依赖,需要 这种能力的类就去实现这个协议,否则就不必实现,这样可以使得父类的只有通用的方法。

    考虑下面的场景,如果给一个button添加一个点击后带有抖动的效果,如何实现。1、实现一个自定义的Uibutton类,在其中添加shake方法。这种方式的代码缺乏复用性,如果其他地方需要这个能力,就必须重新实现一遍shake方法。2、写一个UIbutton类的分类,扩展其自身的能力。这种方式虽然可以解决问题,但是如果不同的场景需要不同shake方式,就需要增加条件判断,而且并不是所有人都知道存在这个分类方法。3、定义一个protocol协议,继承了该协议的类就需要实现shake方法。这种方法具有高度的定制划,需要什么类型的shake就自行定义。代码扩展能力很强。

3、IOS MVC和MVVM

    在公司中一般编程都是采用MVC的设计方式,即view和model进行交互,model的来源又由controller管理,同时controller持有view,当数据来源变更时会由view触发model的变化,model变化反过来影响view的展示。大致这样一个过程。在听说MVVM的设计方式后,感觉没有用过总是不知道是如何实现的。网上搜博客也是简单的给出两个图,一个是MVC,一个是MVVM,光看图还是难以理解两者之间的真正差别。此处就不在介绍MVC模式的方式,这种方式也比较简单上手,理解比较容易。

    https://www.jianshu.com/p/548980de876a 这篇博客中给出了一个demo比较简单的一个MVVM架构。可以做一定的参考。下面根据博客内容对MVVM模式做一个简单介绍。

    MVVM

    如图中所示:1、VC还是管理view的生命周期,但是view不在和model进行数据交互,而是和Viewmodel进行交互。2、model的数据更新不在告知view需要更新视图。而是通过viewmodel,viewmodel告知view需要更新视图。3、大致流程可以理解为,服务端数据来之后VC触发viewmodel更新,viewmodel更新引起model和View的更新。这个流程里面数据变化引起的视图变化主要工作交给了Viewmodel去处理。这样可以使VC中的代码量大大降低。

    文中demo也大致如下,当列表刷新时会触发tableView的reloaddata方法,从而调用cellforrow重新渲染数据,在cellforRow中viewModel通过indexpath去为每个cell创建一个model,但是这个model是由ViewModel持有的,看起来model和cell之间没有任何关系。(就是在model和cell之间加了一个屏障,这个屏障基本做了之前model和cell之间要做的事。)既然Viewmodel持有了model,那么所有model内的数据就可以轻松拿到了。

4、Ios method swizzling

    方法交换在很多app内都有运用,https://www.jianshu.com/p/ff19c04b34d0 ,这篇博客对其解释的比较详细,可以参考一下。其原理祖耀是利用oc的runtime时候的消息发送机制。使用method swizzling将两个method进行互换。

    我现在所在公司主要是做货运相关的,app内有很多的货源列表,试想这样一种场景,给所有列表增加一个页面时长统计的功能,1、我们可以给在每一个列表的viewWillappear中开始计时,然后在dealloc里面结束计时。这样的话工作量是比较大的。2、可以让所有的列表都有继承自同一个基类列表,这样只需要在基类列表中做处理即可。我们公司如今就是这样的处理方案。3、给viewcontrlloer增加一个分类,使用method swizzling,交换需要实现的方法和viewWillappear方法。

使用method swizzling主要有几个方法比较重要:

1
2
3
4
5
6
7
8
9
10
11
///获取一个实例方法method 第一个参数实该类的类名,第二个参数是方法的选择子
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
///获取一个类方法
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
/*
第一个参数是该类类名,第二个参数是要被添加的方法的选择子,第三个参数是添加的新方法的指针,第四个参数是新添加方法的参数列表
如果方法交换成功返回yes,否则no
*/
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
/// 进行原方法和目的方法的交换
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)

Method Swizzling类簇

    在项目开发中经常容易遇见NSArray数组越界或者NSDictionary取到的vlaue/key为nil导致crash,苹果的这种做法有些狠了。但是在使用method swizzling为数组越界做判断时,却发现不起作用。这是因为method swizzling对NSarray类簇不起作用,其内部有很多继承子当前类的子类,比如 objectAtIndex:这个方法会在内部创建不同的抽象类进行操作,所以我们进行method swizzling时实际操作的是NSArray的父类。 可以使用runtime方法获取其真正的类。

5、 UITabBarController && UINavigationController

    今天主要梳理了下tabBarcontroller和navigationController的运用。首先和和同事商量了一下app页面的组织形式。目前常见的app页面形式主要是首页底部一个tabbar,顶部一个navi,中间是用于展示的视图。也就是底部一个tabBarController持有了一个navigationcontroller,然后navi由持有了一个用于展示的视图。

    当前我公司大致也是这种设计思路。如果想操作tabBar,可以自行实现tabBar的相关协议。如果想修改Navi,可以通过uiViewController的navigationItem取到相关属性进行操作。

    如果想要设置navi上的背景色,可以如下使用,否则navi可能会看起来有一层蒙层一般。这种效果在app内一般设置在基类中,免得所有继承的子类都要重新设置一次。

1
2
3
4
5
6
//背景色
[nav.navigationBar setBarTintColor:[UIColor orangeColor]];
//元素颜色
[nav.navigationBar setTintColor:[UIColor whiteColor]];
[nav.navigationBar setTitleTextAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:19],
NSForegroundColorAttributeName:[UIColor redColor]}];

6、iOS开发中@property的属性nonatomic retain readonly strong等介绍

nonatomic和atomic

    默认是有这个atomic这个属性的,这是为了保证在多线程情况下编译器自动生成互斥锁代码防止读写不同步。如果该对象无需考虑多线程的场景,使用nonatomic修饰。

copy,assign,retain

    assgin:默认类型,setter方法直接赋值,不进行热河retain操作,不改变引用计数,一般用于处理基本的数据类型。(float,double,integer)。 retain:释放旧的对象,将旧对象的值赋值给新对象,再另新对象的引用计数为1.应该是拷贝一份原来的指针,赋值给新的对象,再将原来的对象释放。 copy:copy和retain处理流程比较类似,释放旧对象,copy新对象,但是会重新申请一块内存,使用copy需要保证对象符合NSCopying协议,如果是NSArray对象,copy只会是浅拷贝,即将原Array的地址赋值一份。

assign和retain

    assign主要是用于修饰一些基础数据类型,并且没有引用计数,retain会有引用计数,只有当一个属性的引用计数为0时才会销毁。

copy与retain

    retain 和copy的区别主要是是否重新创建一块新地址。如果是copy,会重新申请一块内存保存新的属性。如果是retain,赋值时引用计数加1.两者会指向同一块内存区。

strong和weak

    一般来讲使用weak是为了防止出现循环引用的问题,外层调用了内层的属性,内层将某些方法放到外层调用(delegate),使用strong会出现retain cycle。 声明weak的指针,在指针被释放时,本身会指向nil,防止野指针问题。 还有一遍类的内部属性使用strong,在外部时比如(delegate)为weak

7、iOS block原理浅析

   block底层是一个结构体,该结构体中有一个isa指针,本质是一个OC对象,block的变量捕获方式:局部变量通过值传递的形式,因此在block外部定义的局部变量,在block内部改变值大小时block内部是不会改变的,因为局部变量有可能在block执行时已经销毁。 局部静态变量的访问方式是指针传递,因为指针存放在堆区,在block运行时可以捕获到。全局变量:全局变量具有全局作用域,不需要block传值,内部可以直接访问修改。

    block类型,在以下几种情况下,编译器会自动将栈上的block复制到堆上。1、block作为函数返回值时。2、block使用strong修饰时,3、block作为gcdAPI的方法参数时。堆上的block内部持有强引用的变量时,会造成变量无法释放,造成内存泄漏。正确的做法是在block内部弱引用变量,消除循环。

    __block修饰符, __block可以用来解决block内部无法修改局部变量值的问题, __block不能修饰全局变量,静态变量。 __block作用是将变量使用一个结构体包装起来,其内部使用一个指针指向包装的结构体,使得block内部通过指针访问到变量修改值。

8.copy mutabelCopy

  对于常见的oc数据类型,包括string,array,dictionary都包含可变和不可变两种类型,对两者分类调用copy和mutableCopy时是否创建新的空间如下

1
2
3
4
5
6
7
8
9
                          copy是否产生新对象    新对象类型    mutableCopy是否产生新对象      新对象类型
NSString 否 是 NSMutableString
NSMutableString 是 NSString 是 NSMutableString

NSArray 否 是 NSMutableArray
NSMutableArray 是 NSArray 是 NSMutableArray

NSDictionary 否 是 NSMutableDictionary
NSMutableDictionary 是 NSDictionary 是 NSMutableDictionary