可能会用到iOS 面试汇总iOS面试宝典

IOS 基础面试一

2016-05-21  本文已影响371人  John_LS

1.1 谈一谈GCD和NSOperation的区别?

1.2 谈谈多线程的应用

通常耗时的操作都放在子线程处理,然后到主线程更新UI,如

2. 线程之间是如何通信的?

通过主线程和子线程切换的时候传递参数

1、-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array; 
2、-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait; 
3、-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array    
4、-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait 
5、-(void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg  

方法1和方法2提供了子线程到主线程的单向通信
方法3和方法4提供了当前线程到任意线程之间的通信,比较灵活
方法5其实是创建了一个子线程来接受selecter

3. 网络图片处理问题怎么解决图片重复下载问题?(SDWebImage大概实现原理)

1501122-e719042afe2fa99e.png.jpeg

4. 多线程安全的几种解决方法?

5. 原子属性

6. 代理的作用、block

7. 谈谈你对runTime运行时机制的了解(注意哦,这个很重要的)

runtime是一套比较底层的纯C语言API,属于一个C语言库,包含了很多底层的C语言的API

OC--> [[WPFPerson alloc] init]
runtime-->objc_msgSend(objc_msgSend("WPFPerson", "alloc"), "init")
  1. 在程序运行过程中,动态创建一个类(比如KVO底层实现:检测isa指针,发现是新建了一个类,当然Xcode7.0以前的版本才可以监听到isa指针)
  2. 遍历一个类的所有成员变量、方法,访问私有变量(先通过runtime的class_getInstanceVariable获取成员变量,再通过class_getIvar获取它的值)
  3. 在程序运行过程中,动态为某个类添加属性\方法,修改属性值\方法,比如产品经理需要跟踪记录APP中按钮的点击次数和频率等数据,可以通过继承按钮或者类别实现,但是带来的问题比如别人不一定去实例化你写的子类,或者其他类别也实现了点击方法导致不确定会调用哪一个,runtime可以这样解决:在按钮的分类里面,重写load方法,利用dispatch_once保证只执行一次,新建监控按钮点击的方法,先用class_addMethod方法,判断其返回的bool值,如果添加成功,就用class_replaceMethod将原来的方法移除,如果添加失败,就用method_exchangeImplementations方法进行替换
  4. 拦截并替换方法,比如由于某种原因,我们要改变这个方法的实现,但是又不能动它的源码(比如一些开源库出现问题的时候,这时候runtime就可以出场了)-->先增加一个tool类,然后写一个我们自己实现的方法-change,通过runtime的class_getInstanceMethod获取两个方法,在用class_replaceMethod方法进行替换。防止数组越界的方法:数组越界的时候报错的方法是add_object,做一个逻辑判断,越界的时候通过class_replaceMethod交换掉add_object(相当于重写了这个方法)

1.NSCoding(归档和解档),如果一个模型有很多个属性,那么需要对每个属性都实现一遍encodeObject和decodeObjectForKey方法,十分麻烦,但是如果使用class_copyIvarList获取所有属性,然后循环遍历,使用[ivarName substringFromIndex:1]去掉成员变量下划线

  1. 字典转模型:像几个出名的开源库JSONModel、MJExtension等都是通过这种方式实现的(利用runtimeclass_copyIvarList获取属性数组,遍历模型对象的所有成员属性,根据属性名找到字典中key值进行赋值,当然这种方法只能解决NSString、NSNumber等,如果含有NSArray或NSDictionary,还要进行第二步转换,如果是字典数组,需要遍历数组中的字典,利用objectWithDict方法将字典转化为模型,在将模型放到数组中,最后把这个模型数组赋值给之前的字典数组)
[objc_getClass("__NSArrayM") swizzleSelector:@selector(addObject:)
withSwizzledSelector:@selector(hyb_safeAddObject:)];

使用场景:addObject方法添加的值为nil的时候会崩溃。调用objectAtIndex:时出现崩溃提示empty数组问题

8. 谈谈你对Run Loop的理解

8.1 如何处理事件
  • 界面刷新: 当UI改变( Frame变化、 UIView/CALayer 的继承结构变化等)时,或手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理。 苹果注册了一个用来监听BeforeWaiting和Exit的Observer,在它的回调函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。
  • 手势识别: 如果上一步的 _UIApplicationHandleEventQueue() 识别到是一个guesture手势,会调用Cancel方法将当前的touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。 苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,其回调函数为 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。 当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。
8.2 应用
  • 滑动与图片刷新:当tableView的cell上有需要从网络获取的图片的时候,滚动tableView,异步线程回去加载图片,加载完成后主线程会设置cell的图片,但是会造成卡顿。可以设置图片的任务在CFRunloopDefaultMode下进行,当滚动tableView的时候,Runloop切换到UITrackingRunLoopMode,不去设置图片,而是当停止的时候,再去设置图片。(在viewDidLoad中调用self.imageView performSelector@selector(setImage) withObject:...afterDelay:...inModes@[NSDefayltRunLoopMode]
  • 常驻子线程,保持子线程一直处理事件 为了保证线程长期运转,可以在子线程中加入RunLoop,并且给Runloop设置item,防止Runloop自动退出

9. SQLite常用的SQL语句

10. 关于Socket,谈谈TCP/IP 和 UDP的理解

  • 长连接是为了复用,长连接指的是TCP连接,也就是为了复用TCP连接,也就是说多个HTTP请求可以复用一个TCP连接,节省了很多TCP连接建立和断开的消耗
  • 比如请求了一个网页,这个网页肯定还包含了CSS、JS等一系列资源,如果是短连接的话,每次打开一个网页,基本要建立几个甚至几十个TCP连接,浪费了大量资源
  • 长连接不是永久连接,如果一段时间内,具体的时间长短,是可以在header当中进行设置的,也就是所谓的超时时间,这个连接没有HTTP请求发出的话,那么这个长连接就会被断掉

11. 谈一谈内存管理

12. 常见的数据持久化有哪些

  • NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,需要先转化为前面的类型,才能用NSUserDefault存储
  • 偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据
  • 偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。
//1.获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中写入内容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.读取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
  • 归档及时将内存中的对象写入到磁盘文件中,归档也叫序列化,解档就是讲磁盘中文件中的对象读取出来
  • 必须遵循NSCoding协议,只要遵循了NSCoding协议的对象都可以通过它实现序列化,两个协议方法必须实现
// 反归档
 (id)initWithCoder:(NSCoder *)aDecoder {
    if ([super init]) { 
        self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
        self.name = [aDecoder decodeObjectForKey:@"name"]; 
        self.age = [aDecoder decodeIntegerForKey:@"age"];
    }
    return self;
}

// 归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.avatar forKey:@"avatar"];
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
}
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];
  • 反归档
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
        self.avatarView.image = person.avatar;
        self.nameField.text = person.name;
        self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
}
这五种持久化操作不同点

13. KVC 和 KVO

  • 给私有变量(该变量不对外开放)赋值:[Person setValue: @"19" ForKeyPath:@"age"]
  • 字典转模型:setValuesForKeyWithDictionary
  • 取出私有变量:[Person valueForKey:@"age"]
  • 没有找到对应的key会崩溃:重写setValueForUndefinedKey
  • 给监听的属性设置一个观察者:
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
  • 当person的name的值发生改变时,就会执行该方法
(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ 
do something....
}
  • 当一个类的属性被观察的时候,系统会通过runtime动态的创建一个该类的派生类,并且会在这个类中重写基类被观察的属性的setter方法,而且系统将这个类的isa指针指向了派生类(NSNotifying_类名),从而实现了给监听的属性赋值时调用的是派生类的setter方法。重写的setter方法会在调用原setter方法前后,通知观察对象值得改变。

14. @synthesize和@dynamic区别是什么

15. 谈谈时间响应链的一般顺序

16. post和get方式的区别

什么情况下用POST:
什么情况下用GET:

17. 深复制和浅复制

18. 关于项目中动画的使用

CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position.y"]; 
anim.fromValue toValue repeatCount [btn.layer addAnimation]
[UIView transitionWithView: duration: options: animations:^{ } completion:nil];
  • springBounciness[0,20]越大振幅越大。
  • springSpeed速度

19. 谈谈你对MVVM的认识

  • 楼市餐厅需要充餐卡,默认是100元,在模型层中,如果想保存这个值 --> let balanece = 100
  • 但是展示给用户的时候,我们想呈现出“您的账户余额为:¥100”,在mvc中,这种代码只能放到视图控制器中,显得很臃肿,如果放在模型当中,会更丑,因为有许多进行格式化的代码挤在其中
    如果添加视图模型,仅仅需要映射一下原始数据
struct AccountViewModel {
        let dispalyBalance: String
        init(mode: cardAccount) { 
            let formattedBalance = model.balance.currencyValue;
            displayBalance = "Your balance is \(formatterBalance)"
        }
}
  • 通过这种方式,视图模型实际上会读取数据模型,然后将其中的信息进行格式化,从而准备展现在视图当中,很容易测试,直接把带有账户信息的模型放进去,然后测试显示就可以了,在之前是特别复杂的
  • 大概意思就是每一帧都是静态值,可以通过改变任务手部抬起的距离,或者任务头部倾斜的距离,来对字符进行编码。每一帧都是静态的,但是当把他们放在一起,然后一直看向一个中心的话,那么始终都有新的数据出现,这样就可以得到一个而美丽的、生动的动画(类比Tom猫)
  • 我们可以使用相同的方式来实现值类型,视图控制器会跟随zoetrope的最后一个帧图像--也就是最新的一块活跃数据,然后将其展示给用户。只要您的模型发生了更新,视图就会根据最新的信息进行更改了
var viewModel = ViewModel(model: Account)

20.0 关于Swift与OC的不同

其实是个面试官八个不懂Swift,而且一般不懂得就问个Swift与OC的不同。题主也只是在自学Swift,等到3.0出了之后再深入研究,而且项目中可能也要开始从混编逐渐向Swift靠拢了。

protocol SwitchWithTextCellProtocol {
      var title: String { get } 
      var titleFont: UIFont { get } 
      var titleColor: UIColor { get }
      var switchOn: Bool { get }
      var switchColor: UIColor { get } 
      func onSwitchTogglenOn(onL Bool)
}
// 这个方法只需要一个参数,相比于之前的多个参数简便了很多
class SwitchWithTextTableViewCell: UITableViewCell { 
    func configure(withDelegate delagate: SwitchWithTextCellProtocol) { 
    // 在这里配置方法 
    }
}
struct MinionModeViewModel: SwitchWithTextCellProtocol { 
    var title = "excellent!!" 
    var switchOn = true 
    var switchColor: UIColor {
         return .yellowColor()
     }
    func onSwitchToggleOn(on: Bool) {
         if on { 
                 print("The Minions are here to stay!")
         } else { 
                 print("The Minions went out to play!") 
        }
   }
}
let cell = tableView.dequeueReuseableCellWithIdentifier("SwitchWithTextTableViewCell", forIndexPath: indexPath) as! SwitchWithTextTableViewCell
cell.configure(withDelegate: MinionModeViewModel())
return cell
protocol SwitchWithTextCellDataSource { 
    var title: String { get } 
    var switchOn: Bool { get }
}
protocol SwitchWithTextCellDelegate { 
    func onSwitchTogglenOn(on: Bool) 
    var switchColor: UIColor { get }
    var textColor: UIColor { get } 
    var font: UIFont { get }
}
// SwitchWithTextTableViewCellfunc 
configure(withDataSource dataSource: SwitchWithTextCellDataSource, delegate: SwitchWithTextCellDelegate?) {
 // 在这里配置视图
}
struct MinionModeViewModel: SwiftWithTextCellDataSource { 
    var title = "Minion Mode!!"
    var switchOn = true
}
extension MinionModeViewModel: SwitchWithTextCellDelegate { 
    var switchColor: UIColor {
        return .yellowColor() 
    }
    func onSwitchToggleOn(on: Bool) { 
        if on {
               print("The Minions are here to stay!")
        } else { 
              print("The Minions went out to play!") 
        } 
    }
}
// SettingViewControllerlet 
viewModel = MinionModeViewModel()
cell.configure(withDataSource:viewModel, delegate: viewModel)
return cell

20.1 Swift2.0中的 MinxinTrait

// 一看这个对象的类型,就知道他有哪些功能,而不是一个个去查找她的实现
class ZapMonster: GameObject, GunTraint, HealthTraint, MovementTraint { ...}
protocol TextPresentable { 
    var text: String { get } 
    var textColor: UIColor { get } 
    var font: UIFont { get }
}
protocol SwitchPresentable { 
    var switchOn: Bool { get } 
    var switchColor: UIColor { get } 
    func onSwitchToggleOn(on: Bool)
}
class SwitchWithTextTableViewCell<T where T: TextPresentable, T: SwitchPresentable>: UITableViewCell { 
    private var delegate: T? // T是视图模型 
    func configure(withDelegate delegate: T) { 
        // 在这里配置视图 
   }
}
extension MinionModeViewModel: TextPresentable { 
    var text: String { 
        return "Minion Mode" 
    } 
    var textColor: UIColor {
        return .blackColor() 
    } 
    var font: UIFont { 
        return .systemFontOfsize(17.0) 
    }
}

20.2 Swift2.2随着iOS9.3一同登场,讲讲什么新调整?

  • 使用var, 让你在函数内部修改参数
  • 使用inout,可以让你的改变延续到函数结束后

21. 优化tableViewCell高度

  • 设置估算高度以后,tableViewcontentSize.height是根据cell高度预估值和cell的个数来计算的,导致导航条处于很不稳定的状态,因为contentSize.height会逐渐由预估高度变为实际高度,很多情况下肉眼是可以看到导航条跳跃的
  • 如果是设计不好的上拉加载或下拉刷新,有可能使表格滑动跳跃
  • 估算高度设计初衷是好的,让加载速度更快,但是损失了流畅性,与其损失流畅性,我宁愿让用户加载界面的时候多等那零点几秒
  • self.tableView.estimatedRowHeight = 213;self.tableView.rowHeight = UITableViewAutomaticDimension;如果不加上估算高度的设置,自动算高就失效了
  • 这个自动算高在 push 到下一个页面或者转屏时会出现高度特别诡异的情况,不过现在的版本修复了。
  • dequeueReusableCellWithIdentifier:forIndexPath:相比不带“forIndexPath” 的版本会多调用一次高度计算
  • iOS7 计算高度后有”缓存“机制,不会重复计算;而 iOS8 不论何时都会重新计算cell高度
  • 当用户正在滑动 UIScrollView 时,RunLoop将切换到UITrackingRunLoopMode 接受滑动手势和处理滑动事件(包括减速和弹簧效果),此时,其他 Mode (除 NSRunLoopCommonModes 这个组合 Mode)下的事件将全部暂停执行,来保证滑动事件的优先处理,这也是 iOS 滑动顺畅的重要原因
  • 注册 RunLoopObserver 可以观测当前 RunLoop的运行状态,并在状态机切换时收到通知:RunLoop开始
    • RunLoop即将处理Timer
    • RunLoop即将处理Source
    • RunLoop即将进入休眠状态
    • RunLoop即将从休眠状态被事件唤醒
    • RunLoop退出
  • 分解成多个RunLoop Source任务,假设列表有 20 个 cell,加载后展示了前 5 个,那么开启估算后 table view只计算了这 5 个的高度,此时剩下 15 个就是“预缓存”的任务,而我们并不希望这 15 个计算任务在同一个 RunLoop 迭代中同步执行,这样会卡顿 UI,所以应该把它们分别分解到 15 个 RunLoop 迭代中执行,这时就需要手动向RunLoop中添加Source 任务(由应用发起和处理的是Source 0 任务)

22. 列举一下你常用的第三方库

  • Alcatraz:Xcode 插件管理工具
  • ColorSense-for-Xcode:代码生成颜色预览,可视化编辑
  • KSImageNamed-Xcode:引入图片自动提示,预览
  • VVDocumenter-Xcode:规范化注释
  • AFNetworking:网络库,通常会在AFN上面再封装一层,主要封装接口逻辑
  • SDWebImage:下载网络图片,定时清除缓存
  • Reachability:网络状态判断,AFN已经有这个功能
  • WebViewJavaScriptBridge: Webview和cocoa之间消息传递
  • fmdb:SQLite的封装,简单易用
  • DTCoreText:CoreText库,支持HTML
  • KissXML:XML解析,支持读取和修改,基于libxml
  • ZXingObjC:二维码,支持编码解码
  • GTMBase64:base64编解码
  • GPIImage:图像处理
  • JSONKit:json解析,性能最好
  • MansonrySnapkit(Swift)辅助自动布局
  • MJRefresh:上拉加载,下拉刷新
  • MBProgressHUDSVProgressHUD进度图,加载效果提示

23. 为什么AFN显示图片不如SDWebImage流畅?同样是从网络上下载图片而不是从缓存取图片?

24. 关于新特性

iOS7新特性
iOS8新特性

####### iOS9新特性

  • 临时调出的滑动覆盖:Slide Over
  • 视频播放的画中画模式(Picture in Picture) (AVPlayerViewController默认支持。MPMoviePlayerViewControllerdeprecated掉了,不支持)
  • iPad真正同时使用两个App

25. 我是怎样用两个imageView实现了无线轮播!

  1. 建立一个scrollView,设置contentsize3*kWidthcontentOffSetkWidth
  • 定义一个block属性暴露给外界void(^imageClickBlock)(NSInteger index)
  • 设置currImageViewuserInteractionEnabled为YES
  • currImageView添加一个点击的手势
    -在手势方法里调用block,并传入图片索引
  • scheduledTimerWithTimeInterval是创建一个定时器,并加入到当前运行循环[NSRunLoop currentRunLoop]
  • 其他两个([NSTimer timerWithTimeInterval:3 target:self selector:@selector(doSomeThing1) userInfo:nil repeats:YES];[[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:5] interval:3 target:self selector:@selector(doSomeThing2) userInfo:nil repeats:YES];)只是创建定时器,并未添加到当前运行循环中,所以如果是其他两种方式创建的定时器则需要手动添加到currentRunLoop

26. tableView的优化

iOS平台因为UIKit本身的特性,需要将所有的UI操作都放在主线程执行,所以有时候就习惯将一些线程安全性不确定的逻辑,以及它线程结束后的汇总工作等等放到了主线程,所以主线程包含大量计算、IO、绘制都有可能造成卡顿。

27. 谈谈内存的优化和注意事项(使用Instrument工具的CoreAnimation、GPU Driver、I/O操作,检查fps数值)

  • 数组:有序的一组值,使用索引查询起来很快,使用值查询的很慢,插入/删除 很慢
  • 字典:存储键值对对,用键查找比较快
  • 集合:无序的一组值,用值来查找很快,插入/删除很快
上一篇 下一篇

猜你喜欢

热点阅读