2016年1月TX电面题,记得多少写多少了【答案待更】

  • 问题
  • 答案
    • 3.线程和RunLoop的关系
      • 3.1 一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)
    • 4. NSTimer有什么需注意的以及和RunLoop的关系?
    • 5. NSString copy 和 NSString mutableCopy 的区别
    • 12.runtime如何实现weak变量的自动置nil?
    • 13.AFNetworking的内部实现原理?
    • 14.block循环引用了如何解决?
      • 使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
    • 15.如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
    • 16.UIKit的框架结构?

问题

  1. 如何绘制一个三角形?
    1.1 如何绘制大量三角形?
    1.2 一定要重写drawRect吗?
    1.3 如何刷新View界面?
    1.4 Layer好在哪?
  2. assign和weak的区别
  3. 线程和RunLoop的关系
    3.1 一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)
  4. NSTimer有什么需注意的以及和RunLoop的关系?
  5. NSString copy 和 NSString mutableCopy 的区别
  6. 线程加锁原理(信号量,临界区,自选锁)
  7. iOS7 - iOS9的区别
  8. GCD指向了野指针了怎么办
  9. 用HTTP传数据,丢包严重怎么办
  10. iOS中广播的种类
  11. app slying
  12. runtime如何实现weak变量的自动置nil?
  13. AFNetworking的内部实现原理?
  14. block循环引用了如何解决?
  15. 如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
  16. UIKit的框架结构?

答案

3.线程和RunLoop的关系

总的说来,Run loop,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分, Cocoa 和 CoreFundation 都提供了 run loop 对象方便配置和管理线程的 run loop (以下都以 Cocoa 为例)。每个线程,包括程序的主线程( main thread )都有与之相应的 run loop 对象。

runloop 和线程的关系:

  • 1.主线程的run loop默认是启动的。

iOS的应用程序里面,程序启动后会有一个如下的main()函数

int main(int argc, char * argv[]) {
    @autoreleasepool {    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。

  • 2.对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。

  • 3.在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。

NSRunLoop *runloop = [NSRunLoop currentRunLoop];

猜想runloop内部是如何实现的?

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑 是这样的:

function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

或使用伪代码来展示下:

// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
int main(int argc, char * argv[]) {
 //程序一直运行状态
 while (AppIsRunning) {
      //睡眠状态,等待唤醒事件
      id whoWakesMe = SleepForWakingUp();
      //得到唤醒事件
      id event = GetEvent(whoWakesMe);
      //开始处理事件
      HandleEvent(event);
 }
 return 0;
}

根据苹果在文档里的说明,RunLoop 内部的逻辑大致如下:

这里写图片描述

(答案参考:《招聘一个靠谱的iOS》面试题参考答案(下)第28、29、30、31问)
(拓展学习:《Objective-C之run loop详解》《深入理解RunLoop》《CFRunLoop》)

3.1 一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)

分两种情况:手动干预释放时机、系统自动去释放。

手动干预释放时机–指定autoreleasepool 就是所谓的:当前作用域大括号结束时释放。
系统自动去释放–不手动指定autoreleasepool
Autorelease对象会在当前的 runloop 迭代结束时释放。
如果在一个vc的viewDidLoad中创建一个 Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了。

(答案参考:《招聘一个靠谱的iOS》面试题参考答案(下)第34问)
(拓展学习:《黑幕背后的Autorelease》)


4. NSTimer有什么需注意的以及和RunLoop的关系?

  1. 不管是重复性的timer还是一次性的timer都会对它的方法的接收者进行retain,这两种timer的区别在于“一次性的timer在完成调用以后会自动将自己invalidate,而重复的timer则将永生,直到你显式的invalidate它为止”。

  2. timer不是一种实时的机制,会存在延迟,而且延迟的程度跟当前线程的执行情况有关。

  3. 必须得把timer添加到runloop中,它才会生效。

  4. 要让timer生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配。

(答案参考:《NSTimer你真的会用了吗》)


5. NSString copy 和 NSString mutableCopy 的区别

类似的问题还有:用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

1)因为父类指针可以指向子类对象,使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.

2)如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.

copy此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为NSString时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个NSMutableString类的实例。这个类是NSString的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

为了理解这种做法,首先要知道,对非集合类对象的copy操作:

在非集合类对象中:对immutable对象进行copy操作,是指针复制,mutableCopy操作时内容复制;对mutable对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:

  • [immutableObject copy] // 浅复制
  • [immutableObject mutableCopy] //深复制
  • [mutableObject copy] //深复制
  • [mutableObject mutableCopy] //深复制

在集合类对象中,对immutable对象进行copy,是指针复制,mutableCopy是内容复制;对mutable对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:

  • [immutableObject copy] // 浅复制
  • [immutableObject mutableCopy] //单层深复制
  • [mutableObject copy] //单层深复制
  • [mutableObject mutableCopy] //单层深复制

比如以下代码:

NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];

查看内存,会发现 string、stringCopy 内存地址都不一样,说明此时都是做内容拷贝、深拷贝。即使你进行如下操作:

[string appendString:@"origion!"];

stringCopy的值也不会因此改变,但是如果不使用copy,stringCopy的值就会被改变。 集合类对象以此类推。 所以,

用@property声明 NSString、NSArray、NSDictionary 经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

(答案参考:《招聘一个靠谱的iOS》面试题参考答案(上)第13问)
(拓展学习:《iOS 集合的深复制与浅复制》)


12.runtime如何实现weak变量的自动置nil?

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

在第8问《runtime 如何实现 weak 属性》有论述。(注:在第23问《使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?》里给出的“对象的内存销毁时间表”也提到__weak引用的解除时间。)

我们可以设计一个函数(伪代码)来表示上述机制:

objc_storeWeak(&a, b)函数:

objc_storeWeak函数把第二个参数–赋值对象(b)的内存地址作为键值key,将第一个参数–weak修饰的属性变量(a)的内存地址(&a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,

你可以把objc_storeWeak(&a, b)理解为:objc_storeWeak(value, key),并且当key变nil,将value置nil。

在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。

而如果a是由assign修饰的,则: 在b非nil时,a和b指向同一个内存地址,在b变nil时,a还是指向该内存地址,变野指针。此时向a发送消息极易崩溃。

下面我们将基于objc_storeWeak(&a, b)函数,使用伪代码模拟“runtime如何实现weak属性”:

// 使用伪代码模拟:runtime如何实现weak属性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

 id obj1;
 objc_initWeak(&obj1, obj);
/*obj引用计数变为0,变量作用域结束*/
 objc_destroyWeak(&obj1);

下面对用到的两个方法objc_initWeak和objc_destroyWeak做下解释:

总体说来,作用是: 通过objc_initWeak函数初始化“附有weak修饰符的变量(obj1)”,在变量作用域结束时通过objc_destoryWeak函数释放该变量(obj1)。

下面分别介绍下方法的内部实现:

objc_initWeak函数的实现是这样的:在将“附有weak修饰符的变量(obj1)”初始化为0(nil)后,会将“赋值对象”(obj)作为参数,调用objc_storeWeak函数。

obj1 = 0;
obj_storeWeak(&obj1, obj);

也就是说:

weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)

然后obj_destroyWeak函数将0(nil)作为参数,调用objc_storeWeak函数。

objc_storeWeak(&obj1, 0);

前面的源代码与下列源代码相同。

// 使用伪代码模拟:runtime如何实现weak属性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* ... obj的引用计数变为0,被置nil ... */
objc_storeWeak(&obj1, 0);

objc_storeWeak函数把第二个参数–赋值对象(obj)的内存地址作为键值,将第一个参数–weak修饰的属性变量(obj1)的内存地址注册到 weak 表中。如果第二个参数(obj)为0(nil),那么把变量(obj1)的地址从weak表中删除。

(答案参考:《招聘一个靠谱的iOS》面试题参考答案(下)第26问)
(拓展学习:《理解 Objective-C Runtime》)


13.AFNetworking的内部实现原理?

使用到了RunLoop

使用NSOperation+NSURLConnection并发模型都会面临NSURLConnection下载完成前线程退出导致NSOperation对象接收不到回调的问题。AFNetWorking解决这个问题的方法是按照官方的guide上写的NSURLConnection的delegate方法需要在connection发起的线程runloop中调用,于是AFNetWorking直接借鉴了Apple自己的一个Demo的实现方法单独起一个global thread,内置一个runloop,所有的connection都由这个runloop发起,回调也是它接收,不占用主线程,也不耗CPU资源。

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
     @autoreleasepool {
          [[NSThread currentThread] setName:@"AFNetworking"];

          NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
          [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
          [runLoop run];
     }
}

+ (NSThread *)networkRequestThread {
     static NSThread *_networkRequestThread = nil;
     static dispatch_once_t oncePredicate;
     dispatch_once(&oncePredicate, ^{
          _networkRequestThread =
          [[NSThread alloc] initWithTarget:self
               selector:@selector(networkRequestThreadEntryPoint:)
               object:nil];
          [_networkRequestThread start];
     });

     return _networkRequestThread;
}

(答案参考:《CFRunLoop》——使用RunLoop的案例)


14.block循环引用了如何解决?

一个对象中强引用了block,在block中又使用了该对象,就会发生循环引用。 解决方法是将该对象使用__weak或者__block修饰符修饰之后再在block中使用。

  1. id weak weakSelf = self;
    或者
    weak __typeof(&*self)weakSelf = self
    该方法可以设置宏

  2. id __block weakSelf = self;

使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?

系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api 需要考虑:

所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:

[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }]; 
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }]; 
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" 
                                                  object:nil 
                           queue:[NSOperationQueue mainQueue]                                              usingBlock:^(NSNotification * notification) {
                                                    self.someProperty = xyz; }];

这些情况不需要考虑“引用循环”。

但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:

__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
    __typeof__(self) strongSelf = weakSelf;
    [strongSelf doSomething];
    [strongSelf doSomethingElse];
} );

类似的:

__weak __typeof__(self) weakSelf = self;
  _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                                object:nil
                                                                 queue:nil
                                                            usingBlock:^(NSNotification *note) {
      __typeof__(self) strongSelf = weakSelf;
      [strongSelf dismissModalViewControllerAnimated:YES];
  }];

self –> _observer –> block –> self 显然这也是一个循环引用。

(答案参考:《招聘一个靠谱的iOS》面试题参考答案(下)第37、39问)


15.如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 合并图片
});

(答案参考:《招聘一个靠谱的iOS》面试题参考答案(下)第41问)

16.UIKit的框架结构?

先贴一个UIKit类的结构图,明确一下继承关系

这里写图片描述

注意一下,UIResponder这个类,它是UIApplicationUIView的超类,UIResponder类定义了对象相应和控制事件的接口,它的实例通常被称为应答对象。
这个类中主要的触摸方法法是:touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent: , 和 touchesCancelled:withEvent:
其实例方法包括:
becomeFirstResponder :通告接受者对象称为当前的第一响应者对象(- (BOOL)becomeFirstResponder )

主要的时间控制方法:

touchesBegan:withEvent:通知调用者当有一根或者多根手指触摸到了视图或者窗口
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event: 通知接收者当系统发出取消事件的时候(比如低内存消耗的警告框)
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event :当一个触摸事件结束时发出的UITouch实例对象
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event :告诉接收者一个或者多个手指在视图或者窗口上触发移动事件。默认不允许多点触摸。

如果要接收多点触摸事件你必须发setMultipleTouchEnabled: 这个消息给接收的视图实例对象并传递YES参数。

继承自UIControl类的子类都可以通过addTarget添加事件,如果不是继承自它的控件也要与用户交互怎么办,可以设置它的userInteractionEnabled属性为yes
给这个控件添加tap或其他手势与用户交互,所有继承自UIResponder这个类的控件都可以添加

另外注意一下UIBarButtonItem和UITapBarItem,不要把这两个控件搞混,UIBarButtonItem是导航栏上的按钮,UITapBarItem是底部工具栏的按钮

(答案参考:《OC之UIKit类的继承结构图》)


/**
 *
 * ━━━━━━神兽出没,文章待更━━━━━━
 *    ┏┓   ┏┓
 *   ┏┛┻━━━┛┻┓
 *   ┃       ┃
 *   ┃   ━   ┃
 *   ┃ ┳┛ ┗┳ ┃
 *   ┃       ┃
 *   ┃   ┻   ┃
 *   ┃       ┃
 *   ┗━┓   ┏━┛  没过不开心 ̄へ ̄
 *     ┃   ┃    
 *     ┃   ┃...没想到2月又接到复面电话了!_(:з」∠)_
 *     ┃   ┗━━━┓
 *     ┃       ┣┓
 *     ┃       ┏┛
 *     ┗┓┓┏━┳┓┏┛
 *      ┃┫┫ ┃┫┫
 *      ┗┻┛ ┗┻┛
 *
 * ━━━━━━神兽保佑,代码无bug━━━━━━
 */

你可能感兴趣的:(*,我的面经,面试题)