ios基础知识iOS基础知识点iOS开发-高级汇总

iOS知识点总结

2016-03-14  本文已影响3919人  Kk太阳

内容均转自标哥的技术博客 只是按照自己的习惯进行简单的整理

1、对数组中的元素去重复

NSArray *array = @[@"12-11", @"12-11", @"12-11", @"12-12", @"12-13", @"12-14"];

NSMutableArray *resultArray = [[NSMutableArray alloc] initWithCapacity:array.count];
// 外层一个循环
for (NSString *item in array) {

   // 调用-containsObject:本质也是要循环去判断,因此本质上是双层遍历
   // 时间复杂度为O ( n^2 )而不是O (n)
    if (![resultArray containsObject:item]) {
      [resultArray addObject:item];
    }
}
NSLog(@"resultArray: %@", resultArray);

//补充:原来集合操作可以通过valueForKeyPath来实现的,去重可以一行代码实现:
array = [array valueForKeyPath:@"@distinctUnionOfObjects.self"];
NSLog(@"%@", array);

但是返回的结果是无序的,与原来的顺序不同。大家可以阅读:Collection Operators

NSMutableDictionary *resultDict = [[NSMutableDictionary alloc] initWithCapacity:array.count];
for (NSString *item in array) {
    [resultDict setObject:item forKey:item];
}
NSArray *resultArray = resultDict.allValues;
NSLog(@"%@", resultArray);
//需要结果有序的话  就用快速枚举排序
NSSet *set = [NSSet setWithArray:array];
NSArray *resultArray = [set allObjects];
NSLog(@"%@", resultArray);

//使用过有序集合
NSOrderedSet *set = [NSOrderedSet orderedSetWithArray:array];
NSLog(@"%@", set.array);

2、说说以下元素的特性和作用

NSArray NSSet NSDictionary与NSMutableArray NSMutableSet NSMutableDictionary

特性:

3、简单描述一下XIB与Storyboards,说一下他们的优缺点。

参考答案:

笔者倾向于纯代码开发,所以所提供的参考答案可能具有一定的个人感情,不过还是给大家说说笔者的想法。

优点:

缺点:

4、请把字符串2015-04-10格式化日期转为NSDate类型

参考答案:

NSString *timeStr = @"2015-04-10";
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
formatter.timeZone = [NSTimeZone defaultTimeZone];
NSDate *date = [formatter dateFromString:timeStr];
// 2015-04-09 16:00:00 +0000
NSLog(@"%@", date);

5、在App中混合HTML5开发App如何实现的。在App中使用HTML5的优缺点是什么?

参考答案:

在iOS中,通常是通常UIWebView来实现,当然在iOS8以后可以使用WKWebView来实现.有以下几种实现方法:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
 

优缺点:

6、请描述一下同步和异步,说说它们之间的区别。

参考答案:

首先,我们要明确一点,同步和异步都是在线程中使用的。在iOS开发中,比如网络请求数据时,若使用同步请求,则只有请求成功或者请求失败得到响应返回后,才能继续往下走,也就是才能访问其它资源(会阻塞了线程)。网络请求数据异步请求时,不会阻塞线程,在调用请求后,可以继续往下执行,而不用等请求有结果才能继续。

区别:

7、请简单描述一下队列和多线程的使用原理。

参考答案:

在iOS中队列分为以下几种:

dispatch_queue_t q = dispatch_queue_create("...", DISPATCH_QUEUE_SERIAL);
 
dispatch_queue_t q = dispatch_queue_create("......", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t q = dispatch_get_main_queue();

上面这四种是针对GCD来讲的,串行队列中的任务只能一个个地执行,在前一个没有执行完毕之前,下一个只能等待。并行队列可以并发地执行任务,因此多个任务之间执行的顺序不能确定,当添加一个新的任务时,交由GCD来判断是否要创建新的新的线程。

大家可以阅读图片多线程,也许更明了:

8、描述一下iOS的内存管理,在开发中对于内存的使用和优化包含哪些方面。我们在开发中应该注意哪些问题。

参考答案:

内存管理准则:谁强引用过,谁就在不再使用时使引用计数减一。

对于内存的使用和优化常见的有以下方面:

9、plist文件是用来做什么的。一般用它来处理一些什么方面的问题。

参考答案:

plist是iOS系统中特有的文件格式。我们常用的NSUserDefaults偏好设置实质上就是plist文件操作。plist文件是用来持久化存储数据的。

我们通常使用它来存储偏好设置,以及那些少量的、数组结构比较复杂的不适合存储数据库的数据。比如,我们要存储全国城市名称和id,那么我们要优先选择plist直接持久化存储,因为更简单。

10、iOS中缓存一定量的数据以便下次可以快速执行,那么数据会存储在什么地方,有多少种存储方式?

参考答案:

11、请简单写出增、删、改、查的SQL语句。

参考答案:

数据库的简单操作,还是会的,大学可没白学。

增:

insert into tb_blogs(name, url) values('aaa','http://www.baidu.com');

删:

delete from tb_blogs where blogid = 1;

改:

update tb_blogs set url = 'www.baidu.com' where blogid = 1;

查:

select name, url from tb_blogs where blogid = 1;

12、在提交苹果审核时,遇到哪些问题被拒绝,对于被拒绝的问题是如何处理的。

参考答案:
·····

13、请写出有多少有方法给UIImageView添加圆角?

1.最直接的方法就是使用如下属性设置:

imgView.layer.cornerRadius = 10;
// 这一行代码是很消耗性能的
imgView.clipsToBounds = YES;

好处是使用简单,操作方便。坏处是离屏渲染(off-screen-rendering)需要消耗性能。对于图片比较多的视图上,不建议使用这种方法来设置圆角。通常来说,计算机系统中CPU、GPU、显示器是协同工作的。CPU计算好显示内容提交到GPU,GPU渲染完成后将渲染结果放入帧缓冲区。

简单来说,离屏渲染,导致本该GPU干的活,结果交给了CPU来干,而CPU又不擅长GPU干的活,于是拖慢了UI层的FPS(数据帧率),并且离屏需要创建新的缓冲区和上下文切换,因此消耗较大的性能。

2.给UIImage添加生成圆角图片的扩展API:

- (UIImage *)hyb_imageWithCornerRadius:(CGFloat)radius {
  CGRect rect = (CGRect){0.f, 0.f, self.size};
  
  UIGraphicsBeginImageContextWithOptions(self.size, NO, UIScreen.mainScreen.scale);
  CGContextAddPath(UIGraphicsGetCurrentContext(),
                   [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath);
  CGContextClip(UIGraphicsGetCurrentContext());
  
  [self drawInRect:rect];
  UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  
  UIGraphicsEndImageContext();
  
  return image;
}
 
//然后调用时就直接传一个圆角来处理:
gView.image = [[UIImage imageNamed:@"test"] hyb_imageWithCornerRadius:4];

这么做就是on-screen-rendering了,通过模拟器->debug->Color Off-screen-rendering看到没有离屏渲染了!(黄色的小圆角没有显示了,说明这个不是离屏渲染了)

3.在画之前先通过UIBezierPath添加裁剪,但是这种不实用:

- (void)drawRect:(CGRect)rect {
  CGRect bounds = self.bounds;
  [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.0] addClip];
 
  [self.image drawInRect:bounds];
}

通过mask遮罩实现
这里不细说了,个人感觉不如第二种好用、通用

14.请描述事件响应者链的工作原理

参考答案:

iOS使用hit-testing寻找触摸的view。 Hit-Testing通过检查触摸点是否在关联的view边界内,如果在,则递归地检查该view的所有子view。在层级上处于lowest(就是离用户最近的view)且边界范围包含触摸点的view成为hit-test view。确定hit-test view后,它传递触摸事件给该view。

官方小例子事件响应者链如下图所示:

Hit-test view是处理触摸事件的第一选择,如果hit-test view不能处理事件,该事件将从事件响应链中寻找响应器,直到系统找到一个处理事件的对象。若不能处理,则就有事件传递链了,继续看下面的事件传递链。

事件传递链如下图所示:


左半图:

右半图:

15如何避免使用block时发生循环引用

参考答案:

关于block循环引用问题是非常常见的,但是很多人没有深入研究过,xcode没有提示警告就以为没有形成循环引用了。笔者也见过很多高级iOS开发工程师的同事,使用block并不会分析是否形成循环引用。

推荐阅读iOS Block循环引用精讲

16、请比较GCD与NSOperation的异同

参考答案:

17、请写出NSTimer使用时的注意事项(两项即可)

说到NSTimer这个定时器类,要使用好它,还得了解Run Loop,因为在不同的run loop mode下,定时器不都会回调的。

mode主要是用来指定事件在运行循环中的优先级的,分为:

如果想更深入地了解RunLoop,请参考iOS之Run Loop详解

如果想要销毁timer,则必须先将timer置为失效,否则timer就一直占用内存而不会释放。造成逻辑上的内存泄漏。该泄漏不能用xcode及instruments测出来。 另外对于要求必须销毁timer的逻辑处理,未将timer置为失效,若每次都创建一次,则之前的不能得到释放,则会同时存在多个timer的实例在内存中。

参考答案:

注意timer添加到runloop时应该设置为什么mode
注意timer在不需要时,一定要调用invalidate方法使定时器失效,否则得不到释放

18、说说Core Animation是如何开始和结束动画的

19、线程和进程的区别不正确的是

参考答案:B

这是学习操作系统知识的时候经常会考试的内容,但是在工作中经常会遇到多线程处理问题。通常来说,一个进程就代表着一个应用程序,而操作系统为了更好的利用资源,提供了线程用于处理并发。线程之间没有有单独的地址空间,处理完成之后还得回到主线程,所以,一个线程死掉就等于整个进程死掉。进程和线程都是操作系统的基本单元,只是分工不同,是两种不同的资源管理方式。线程所需要的资源都来自于进程,它没有自己独立的资源,也就没有自己的堆栈和局部变量。

修正:这里参考答案与描述不符合的问题。

20、堆和栈的区别正确的是

参考答案:D

栈是由编译器管理的,不是我们手动控制,但是栈所能分配的内存是比较少的,如果要处理大数据,则需要在堆上分配,因此在栈上比较容易出现Memory Leak;
对于堆,需要我们自己申请内存,同时也需要我们自己手动释放,否则会造成内存泄露;对于堆,如果过多地申请内存空间,会导致内存空间不连接,从而造成内存碎片,使程序效率降低。

21、下列回调机制的理解不正确的是

参考答案:B

对于Target-Action机制,要求两个对象之间有比较紧密的联系,比如在控制器与cell之间,可通过设置target为控制器对象,而action则为控制器中的某个回调方法;
对于Delegator机制,它是苹果提供的标准回调机制,通常会提供一个标准的协议,然后由代理类遵守协议,最常用的用法是反向传值,比如打开蓝牙后要反馈给前一个界面蓝牙的开关状态;
对于通知,通常是多对多的关系,它并不关心是谁要处理消息,任意对象都可以注册通知到通知中心,当发送通知时,所有注册了该通知的对象都可以收到消息。最常用的场景是跨模块,比如登录模块与其它模块有着非常紧密的联系,但是登录成功后各个地方可能需要做一些处理,因此通常会在登录成功或者登出成功后发送通知,以便各个需要处理的模块得到正确的处理;
对于Block是相当简单的,它只适用于一对一的关系,比如在做某个操作成功或者失败后回调。

22、对于runloop的理解不正确的是

参考答案:C

说到RunLoop,它可是多线程的法宝。通常来说,一个线程一次只能执行一个任务,执行完任务后就会退出线程。但是,对于主线程是不能退出的,因此我们需要让主线程即时任务执行完毕,也可以继续等待接收事件而不退出,那么RunLoop就是关键法宝了。但是非主线程通常来说就是为了执行某一任务的,执行完毕就需要归还资源,因此默认是不运行RunLoop的。

每一个线程都有其对应的RunLoop的,只是默认只有主线程的RunLoop是启动的,其它子线程的RunLoop默认是不启动的,若要启动则需要手动启动。
在一个单独的线程中,如果需要在处理完某个任务后不退出,继续等待接收事件,则需要启用RunLoop。
NSRunLoop提供了一个添加NSTimer的方法,可以指定Mode,如果要让任何情况下都回调,则需要设置Mode为Common模式。
实质上,对于子线程的runloop默认是不存在的,因为苹果采用了懒加载的方式。如果我们没有手动调用[NSRunLoop currentRunLoop],就不会去查询是否存在当前线程的RunLoop,也就不会去加载,更不会创建。


// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
  t = pthread_main_thread_np();
}
 
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
  __CFSpinUnlock(&loopsLock);
  CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
  CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
  CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
  if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
    CFRelease(dict);
  }
  CFRelease(mainLoop);
  __CFSpinLock(&loopsLock);
}
 
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
  CFRunLoopRef newLoop = __CFRunLoopCreate(t);
  __CFSpinLock(&loopsLock);
  loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  if (!loop) {
    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
    loop = newLoop;
  }
  // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
  __CFSpinUnlock(&loopsLock);
  CFRelease(newLoop);
}
 
if (pthread_equal(t, pthread_self())) {
  _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
  if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
    _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
  }
}
 
return loop;

关键加载过程如下:
1.检查全局字典里是否存在该线程的runLoop,如果有则退出,否则
2.创建一个新的runLoop放到全局字典中

23、Apple用什么方式实现对一个对象的KVO?

Apple 的文档对 KVO 实现的描述:

Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …

从Apple 的文档可以看出:Apple 并不希望过多暴露 KVO 的实现细节。不过,要是借助 runtime 提供的方法去深入挖掘,所有被掩盖的细节都会原形毕露:

当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。我画了一张示意图,如下所示:

KVO 确实有点黑魔法:

Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。

下面做下详细解释:

键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就会记录旧的值。而当改变发生后, didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。可以手动实现这些调用,但很少有人这么做。一般我们只在希望能控制回调的调用时机时才会这么做。大部分情况下,改变通知会自动调用。

比如调用 setNow: 时,系统还会以某种方式在中间插入 wilChangeValueForKey: 、 didChangeValueForKey: 和 observeValueForKeyPath:ofObject:change:context: 的调用。大家可能以为这是因为 setNow: 是合成方法,有时候我们也能看到人们这么写代码:


- (void)setNow:(NSDate *)aDate {
    [self willChangeValueForKey:@"now"]; // 没有必要
    _now = aDate;
    [self didChangeValueForKey:@"now"];// 没有必要
}

这是完全没有必要的代码,不要这么做,这样的话,KVO代码会被调用两次。KVO在调用存取方法之前总是调用 willChangeValueForKey: ,之后总是调用 didChangeValueForkey: 。怎么做到的呢?答案是通过 isa 混写(isa-swizzling)。第一次对一个对象调用 addObserver:forKeyPath:options:context: 时,框架会创建这个类的新的 KVO 子类,并将被观察对象转换为新子类的对象。在这个 KVO 特殊子类中, Cocoa 创建观察属性的 setter ,大致工作原理如下:

- (void)setNow:(NSDate *)aDate {
    [self willChangeValueForKey:@"now"];
    [super setValue:aDate forKey:@"now"];
    [self didChangeValueForKey:@"now"];
}

这种继承和方法注入是在运行时而不是编译时实现的。这就是正确命名如此重要的原因。只有在使用KVC命名约定时,KVO才能做到这一点。

KVO 在实现中通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。这在Apple 的文档可以得到印证:

Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …

然而 KVO 在实现中使用了 isa 混写( isa-swizzling) ,这个的确不是很容易发现:Apple 还重写、覆盖了 -class 方法并返回原来的类。 企图欺骗我们:这个类没有变,就是原本那个类。。。

但是,假设“被监听的对象”的类对象是 MYClass ,有时候我们能看到对 NSKVONotifying_MYClass 的引用而不是对 MYClass 的引用。借此我们得以知道 Apple 使用了 isa 混写(isa-swizzling)。具体探究过程可参考这篇博文

上一篇 下一篇

猜你喜欢

热点阅读