美文网首页iOS那些坑
iOS面试题汇总(二)

iOS面试题汇总(二)

作者: zgsddzwj | 来源:发表于2017-11-02 15:39 被阅读13次

    13.简单介绍下iOS中消息调用的过程

    • objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:

      • Method resolution
      • objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回 YES,那运行时系统就会重新启动一次消息发送的过程,如果 resolve 方法返回 NO ,运行时就会移到下一步,消息转发(Message Forwarding)。
      • Fast forwarding
      • 如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
      • Normal forwarding
      • 这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
    • 参考: 深入浅出Cocoa之消息

    14.Runloop和线程有什么关系?关于Runloop你了解多少?

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

    • runloop 和线程的关系:
      线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
      主线程的run loop默认是启动的。
      iOS的应用程序里面,程序启动后会有一个如下的main()函数
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    
    • 重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。
    • 对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。
    • 在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。
    • NSRunLoop *runloop = [NSRunLoop currentRunLoop];

    参考文档:深入理解Runloop

    15.你了解source0、source1么??

    • RunLoop 对外的接口
      在 CoreFoundation 里面关于 RunLoop 有5个类:
    CFRunLoopRef
    CFRunLoopModeRef
    CFRunLoopSourceRef
    CFRunLoopTimerRef
    CFRunLoopObserverRef
    
    • 一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

    • CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。

    • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

    • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。

    具体原理参考上个题目的链接~~~

    16.Runtime你了解么?实际使用中用到了

    具体使用场景:
    1.利用关联对象给category添加成员属性。
    2.在load做方法替换,添加打点信息。
    3.消息拦截、消息转发。

    下面这篇文章讲解的够详细的,直接转载了
    [iOS] runtime 的使用场景--实战篇

    17.如何扩大button的点击区域
    -首先添加一个UIButton的category来重写hitTest,然后通过为分类添加的一个属性来扩大按钮的响应区域

    UIButton+EnlargeHitArea.h
    @interface UIButton (Extensions)
    @property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;
    @end
    
    UIButton+EnlargeHitArea.m
    
    #import "UIButton+EnlargeHitArea.h"
    #import <objc/runtime.h>
    
    @implementation UIButton (EnlargeHitArea)
    
    @dynamic hitTestEdgeInsets;
    
    static const NSString *KEY_HIT_TEST_EDGE_INSETS = @"HitTestEdgeInsets";
    
    -(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets
     {
        NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
        objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    -(UIEdgeInsets)hitTestEdgeInsets
    {
        NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
        if(value)
        {
            UIEdgeInsets edgeInsets;
            [value getValue:&edgeInsets]; 
            return edgeInsets;
        }
        else  
        {
            return UIEdgeInsetsZero;
        }
    }
    
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
     {
        if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) 
        {
            return [super pointInside:point withEvent:event];
        }
    
        CGRect relativeFrame = self.bounds;
        CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    
        return CGRectContainsPoint(hitFrame, point);
    }
    
    @end
    

    通过设置按钮分类的hitTestEdgeInsets属性扩大按钮的响应范围

    #import "UIButton+EnlargeHitArea.h"
    [button setHitTestEdgeInsets:UIEdgeInsetsMake(-10, -10, -10, -10)];
    

    18.Extension和Category区别,及对应的用法。

    • Category的小括号中有名字,而Extension没有;
    • Category只能扩充方法,不能扩充成员变量和属性;
    • 如果Category声明了声明了一个属性,那么Category只会生成这个属性的set,get方法的声明,也就不是会实现.

    延伸:
    1)、在类的+load方法调用的时候,我们可以调用category中声明的方法么?
    2)、这么些个+load方法,调用顺序是咋样的呢?
    答案是:
    1)、可以调用,因为附加category到类的工作会先于+load方法的执行
    2)、+load的执行顺序是先类,后category,而category的+load执行顺序是根据编译顺序决定的。

    参考:
    类别(Category)与类扩展 (Extension)的区别
    深入理解Objective-C:Category

    19.[UIApplication sharedApplication].keyWindow 添加视图无效,解决方案。(keyWindow和delegate.window区别)

    在rootViewController中的viewDidLoad:方法中调用[[UIApplication sharedApplication].keyWindow addSubview:]
    发现无效
    调试发现[UIApplication sharedApplication].keyWindow 为nil
    因为这个时候appdelegate中的keywindow还没有创建成功
    我们可以用[[[UIApplication sharedApplication] delegate] window]
    代替[UIApplication sharedApplication].keyWindow
    亲测可以
    这个问题在iOS7中很常见,iOS8中苹果就解决了这个弊端

    UIWindow *window = [[[UIApplication sharedApplication] delegate] window]
    [window addSubview:]
    

    keyWindow与delegate中的window其实是一样的,keyWindow的存在的意义,其实就是为了说明当前的window接管了这个控制器的view而已,你可以在keyWindow上加载你自己的建立的view了。
    参考:http://www.itcto.com.cn/post/2017/02/01/keywindow-delegate-window-1612161901139.aspx

    20.LayoutSubViews和drawrect分别在什么时候调用

    • layoutSubviews

    这个方法,默认没有做任何事情,需要子类进行重写 。 系统在很多时候会去调用这个方法:

    1.初始化不会触发layoutSubviews,但是如果设置了不为CGRectZero的frame的时候就会触发。
    2.addSubview会触发layoutSubviews
    3.设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
    4.滚动一个UIScrollView会触发layoutSubviews
    5.旋转Screen会触发父UIView上的layoutSubviews事件
    6.改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件

    • drawRect

    这个方法是用来重绘的。

    drawRect在以下情况下会被调用:

    1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect调用是在Controller->loadView, Controller->viewDidLoad 两方法之后掉用的.所以不用担心在控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量值).
    2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
    3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
    4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。
    以上1,2推荐;而3,4不提倡

    drawRect方法使用注意点:

    1、若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate的ref并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或者 setNeedsDisplayInRect,让系统自动调该方法。2、若使用calayer绘图,只能在drawInContext: 中(类似于drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法
    3、若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕

    本人QQ:297959735 邮箱:zgsddzwj@163.com,欢迎提意见。

    相关文章

      网友评论

        本文标题:iOS面试题汇总(二)

        本文链接:https://www.haomeiwen.com/subject/elnopxtx.html