过段时间打算跳槽,找了一些面试题来作,在这里作个总结方便review,但愿能对要面试的童鞋有帮助。web
如下为面试题:面试
NSString *str1 = @"str1"; NSString *str2 = [NSString stringWithFormat:@"str1"]; NSString *str3 = @"str1"; NSLog(@"str1 == str2 --- %d", str1 == str2); NSLog(@"str1 == str3 --- %d", str1 == str3); NSLog(@"str1 isEqualToString str2 --- %d", [str1 isEqualToString:str2]); NSLog(@"str1 isEqualToString str3 --- %d", [str1 isEqualToString:str3]);
第一眼看这道题,只能肯定使用isEqualToString:来比较字符串是比较每个字符,因此isEqualToString确定是true,而在OC里使用==号用于判断是否指向同一个地址,那么问题就来了,使用字面量建立的字符串与调用方法建立的有什么区别呢?算法
实践出真知,老老实实敲代码,打上断点来一探究竟数组
能够看到使用字面量建立的字符串为常量字符串,而用方法建立的则是指针字符串。常量字符串会在app销毁后释放,在app存在期间会一直存在,且相同的常量字符串都指向同一个地址。缓存
运行结果就以下了安全
[NSTimer scheduledTimerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) { //建立一个timer而且在当前的runloop中执行。 }]; [NSTimer scheduledTimerWithTimeInterval:1.f repeats:NO block:^(NSTimer * _Nonnull timer) { }];
//timer加入到runloop才能fire成功 [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:2.f] interval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) { }];
//建立timer必须加入到run loop才能执行 [NSTimer timerWithTimeInterval:1.f target:self selector:@selector(performTimer:) userInfo:nil repeats:YES]
我以前对timer的理解就是指定timer启动的时间、是否重复执行,是否马上执行或手动fire。再深刻一点就是能够将timer加入到不一样的runloop,这样就能在scrollview滑动的时候也不影响timer执行了。查资料发现,NSTimer在repeats为YES的状态下会对target强引用,而且在没有invalidate的状况下是不会释放的,所以使用timer的时候就可能会出现循环引用的状况。例如,控制器A强引用timer,同时timer的target为A,这就产生了循环引用,当控制器被pop后,该控制器也不会销毁,就会形成内存泄漏。因此在使用timer的时候就须要在适当的时机来释放timer。仅仅是invalidate依然会形成循环引用,只能把timer置为nil才能够。拿以前的例子来讲,就须要在控制器的生命周期来作这件事,viewWillAppear建立timer,在viewWillDisappear将timer置为nil。app
能够利用富文本NSAttributeString与NSTextAttachment来实现,先找到要插入图片的位置,而后利用NSTextAttachment来包装图片,最后用NSTextAttachment来生成NSAttributeString便可。异步
UIWebView原本就有内存泄漏的问题,只能经过一些手段来减小内存泄漏,并不能彻底的解决,要解决的办法就是使用WKWebView。优化的方法以下:oop
(1) 收到内存警告时清除缓存优化
- (void)applicationDidReceiveMemoryWarning:(UIApplication*)application { [[NSURLCache sharedURLCache] removeAllCachedResponses]; }
(2)释放webView时
self.webView.delegate = nil; [self.webView loadHTMLString:@"" baseURL:nil]; [self.webView stopLoading]; [self.webView removeFromSuperview]; [[NSURLCache sharedURLCache] removeAllCachedResponses]; [self.webView release];
(3) webViewDidFinishLoad时
[[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"WebKitCacheModelPreferenceKey"]; [[NSUserDefaults standardUserDefaults] synchronize];
一般状况下线程在执行完代码后就会销毁,RunLoop其实就是事件处理循环,只有当接受到退出事件时才会退出。在iOS开发中,RunLoop与线程是一一对应的关系,一个RunLoop对应一个线程。须要注意的是只有主线程会在默认状态下建立RunLoop,其余的辅助线程须要本身显示的调用
[NSRunLoop currentRunLoop]来得到与当前线程绑定的RunLoop(ps:若是RunLoop不存在则建立一个新的)。建立出来的RunLoop须要在其中添加有Timer/Source/Observer或者march port,否则RunLoop会销毁。
不仅是ARC,MRC也是利用引用计数来进行内存管理的,只是ARC管理就不须要本身来Release和Retain。系统会在自动释放池结束时,对没有强引用的对象统一进行释放。
由于block会对全部在block中使用到的对象进行强引用(capture),因此当block被一个对象持有,同时这个对象又在Block中被使用时就会出现强引用。解决方式就是在Block中只用弱引用,代码以下
__weak typeof(self)weakSelf = self; void (^block)(void) = ^{ NSLog(@"%@", weakSelf); }
还有一种方式是主动打破循环引用,将调用的block置为nil
@synthesize的做用:为Property指定生成要生成的成员变量名,并生成getter和setter方法。用途:对于只读属性,若是同时从新setter和getter方法,就须要使用synthesize来手动合成成员变量,代码以下
@interface Person : NSObject @property (nonatomic, assign, readonly) NSInteger age; @end @implementation Person @synthesize age = _age; - (void)setAge:(NSInteger)age { _age = age; } - (NSInteger)age { return _age; } @end
@dynamic的做用:告诉编译器,不用为指定的Property生成getter和setter。使用方式:当咱们在分类中使用Property为类扩展属性时,编译器默认不会为此property生成getter和setter,这时就须要用dynamic告诉编译器,本身合成了,代码以下
@interface Person (Extension) @property (strong, nonatomic) NSString *name; @end @implementation Person (Extension) @dynamic name; - (void)test { NSLog(@"%@", self.name); } @end
9. runtime如何经过Selector找到对应的IMP地址?(分别考虑类方法和实例方法)
这就要从类的结构来讲了,先来看下面这张官方给出的
。
initialize会在Class第一次收到消息时调用,父类会比子类先调用,若是子类没有从新实现initialize方法,此方法会在子类接受消息时被屡次调用。load方法会在类被加载到运行时环境中时调用,在整个运行时期间都只会调用一次。
申明私有变量总的来讲有3种方式,一种是在@interface中利用@private 关键字来申明,第二种方式是在@implementation声明。私有方法声明的话,就只可以在@implementation中声明了。访问私有变量能够经过提供getter和setter方法,KVO中key使用成员变量名也能够访问,调用私有方法只能经过暴露方法,或者是利用runtime,以及NSObject提供的方法performSelector系列的方法。
@interface Student : Person @end @implementation Student - (instancetype)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
运行结果都为Student,缘由是self表示此方法从本身的方法表开始找,super则表示方法先从父类的方法表里找,由于class方法两个类都没有实现,最终方法的都是会NSObject中找到,因此结果都是调用的类名Student。
readwrite表示此属性能够读写,会自动生成getter与setter
readonly表示此属性只可读,外部只能方法getter方法
assign在MRC中用于表示引用计数不用加一,以及用于除类以外的声明。在ARC中用于除了类以外的声明
retain在MRC中表示引用计数加一,在ARC中表示强引用
copy在MRC中不会影响调用copy方法的对象的引用计数,在ARC中表示在setter方法中会调用传入对象的copy方法,经常使用于须要不可变对象的属性NSString、NSArray等
atomic表示属性读写的原子性,然而并不能保证线程安全
nonatomic则不保证线程安全
init->viewDidLoad->ViewWillAppear->ViewDidappear->viewWillDisappear->viewDidDisappear->dealloc
利用dispatch_once,其余的实现方式能够重写allocWithZone方法
+ (instancetype)sharedInstance { static Student *instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; }
崩溃率这个就不谈了,开发过程当中遇到的崩溃问题主要查看崩溃的栈信息,利用异常断点来解决。线上崩溃的话,就是利用dSYMS文件来符号化苹果的崩溃日志来解决了
讲响应者链条前,须要知道iOS中事件响应是基于UIResponder对象的及子类,包括UIResponder的子类UIView、UIViewController、UIWindow、UIApplication,当iOS App接收到触摸事件时,UIKit会自动的找到最合适的第一响应者。没有处理的事件会沿着当前激活状态的响应者链条传递下去。
以下图所示,这是app中默认的事件响应链条,若是在UILabel上触发了为处理的事件,那么这个事件会传递给label的父视图UIView,而后是UIWindow对象。对于根视图而言,事件会先传递给UIViewController,而后才是window。若是UIWindow也没有处理,那就会传递给UIApplication,application也未处理的话,若是application的代理对象是UIResponder子类而且没有出如今以前的响应者链条中。
当用户触摸屏幕时,UIKit会根据默认规则来找到第一事件响应者,此规则是基于hit-testing来决定的。UIKit会在touch发生的view的视图层级中比较touch location与View 的bounds。 hitTest:withEvent:方法会遍历整个视图层级找到最深层次的包含这次触摸的子视图,这个子视图就会是第一事件响应者。
RunLoop是一个事件处理的循环,这个循环会不停的从一个地方收到事件,收到事件就作相应的处理,只有收到退出事件时,这个循环才会退出。
在iOS的开发中,主线程的RunLoop会自动建立,辅助线程的RunLoop只有在主动获取时才会被建立。
RunLoop由RunLoopMode构成,RunLoopMode又由Timer/Source/Observer构成。RunLoop同时只可以运行在一个Mode下,app主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。当TableView处于滑动过程当中时,RunLoop的mode为UITrackingRunLoopMode,其他时间Mode为:kCFRunLoopDefaultMode。
在app开发中默认用到RunLoop的地方有:NSTimer、NSObject提供的performSelectorOnMainThread:withObject:waitUntilDone:方法。当咱们使用NSTimer在指定时间执行时,实际上是在RunLoop中添加这个timer,并在到达指定的时间点后执行回调。NSObject执行perform等方法的时候一样是在指定时间来执行那个回调,只是在执行perform方法是当前线程必需要存在RunLoop才行,否则无效。
系统中使用RunLoop的地方有AutoReleasePool,RunLoop会在每一次进入RunLoop时建立自动释放池,而后在RunLoop进入waiting状态时释放旧的释放池并从新建立自动释放池,最后在ExitRunLoop时释放自动释放池。
在平常的iOS开发中,默认建立的Timer是添加在kCFRunLoopDefaultMode模式下的,因此在滑动TableView时,RunLoop会切换为UITrackingRunLoopMode,timer会被暂停。要想Timer可以在UITrackingRunLoopMode下正常运行有两种办法,一是将Timer分别添加到以上两种Mode中,二是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到全部具备”Common”属性的 Mode 里去。
总的来讲,进程是程序分配资源的最小单元,线程是程序运行的最小单元,进程能够有多个线程组成。
优化1. 利用UITableView重用cell的机制
优化2. 分批次异步加载数据
优化3. 缓存高度
优化4. 将耗时操做放在异步线程来作
faceBook的pop动画、
oc不支持多重继承,只支持多层继承。要变相的实现多重继承,能够利用protocol来实现
int findBalancePoint(int a[], int n) { if (n == 1) { return -1; } int leftSum = 0; int rightSum = 0; for (int i = 0, j = n - 1; ; i++, j--) { leftSum += a[i]; rightSum += a[j]; if (i < j) {//继续加 continue; }else{ if (leftSum == rightSum) {//相等 if (i == j) {//奇数个 return i + 1; }else{//偶数个 return n; } }else{//不等 return -1; } } } return -1; } int main(int argc, const char * argv[]) { @autoreleasepool { int a[] = {-7, 1, 5, 2, -4, 3, 0}; int b[] = {1, 2, 3, 3, 2, 1}; int found1 = findBalancePoint(a, 7); int found2 = findBalancePoint(b, 6); NSLog(@"%i %i", found1, found2); } return 0; }