美文网首页
iOS面试的常见问题清单

iOS面试的常见问题清单

作者: 刘是丑 | 来源:发表于2016-12-27 17:51 被阅读68次

    Q1: UIView常用的一些方法小记之setNeedsDisplay和setNeedsLayout


    首先两个方法都是异步执行的。
    setNeedsDisplay会自动调用drawRect方法,这样可以拿到 UIGraphicsGetCurrentContext,可以实现绘图;
    setNeedsLayout会默认调用layoutSubViews,就可以处理子视图中的一些数据。

    layoutSubviews在以下情况下会被调用:
    1、init初始化不会触发layoutSubviews。
    2、addSubview会触发layoutSubviews。
    3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
    4、滚动一个UIScrollView会触发layoutSubviews。
    5、旋转Screen会触发父UIView上的layoutSubviews事件。
    6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
    7、直接调用setLayoutSubviews。

    drawRect在以下情况下会被调用:
    1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect调用是在Controller->loadView, Controller->viewDidLoad 两方法之后调用的,所以不用担心在控制器中,这些View的drawRect就开始画了,这样可以在控制器中设置一些值给View。
    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并且不能用于画图。
    2、drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或者 setNeedsDisplayInRect,让系统自动调该方法。
    3、若使用CALayer绘图,只能在drawInContext: 中(类似于drawRect)绘制,或者在delegate中的相应方法绘制,同样也是调用setNeedDisplay等间接调用以上方法。
    4、若要实时画图,不能使用GestureRecognizer,只能使用touchbegan等方法来调用setNeedsDisplay实时刷新屏幕。

    Q2:RunLoop在什么情况下会用到?


    • 定义一个NSTimer来隔一会调用某个方法 ,但这时你在拖动TextVIew不放手 ,主线程就被占用了,timer的监听方法就不调用 直到你松手,这时把NSTimer加到RunLoop里 ,就相当于告诉主循环腾出点时间来给timer ,再拖动TextView就不会因主线程被占用而不调用了。
    • 例如AFNetWorking和SDWebImage的源码中都是子类化NSOperation,在子线程中发起NSURLConnetion,connetion的代理就是这个operation,然后CFRunloopRun(),开启Runloop,Fail或者Finish的时候CFRunloopStop(CFRunloopGetCurrent())关闭Runloop。如果不写CFRunloopRun(),根本不会执行NSURLConnection的代理方法的,因为该线程没开启Runloop,马上就完了。RunLoop相当于子线程的循环,可以灵活控制子线程的生命周期。

    Q3:Runtime在什么情况下会用到?


    Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的。
    我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething));

    • 获取列表
      我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)
    unsigned int count;
        //获取属性列表
        objc_property_t *propertyList = class_copyPropertyList([self class], &count);
        for (unsigned int i=0; i<count; i++) {
            const char *propertyName = property_getName(propertyList[i]);
            NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
        }
    
        //获取方法列表
        Method *methodList = class_copyMethodList([self class], &count);
        for (unsigned int i; i<count; i++) {
            Method method = methodList[i];
            NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
        }
    
        //获取成员变量列表
        Ivar *ivarList = class_copyIvarList([self class], &count);
        for (unsigned int i; i<count; i++) {
            Ivar myIvar = ivarList[i];
            const char *ivarName = ivar_getName(myIvar);
            NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
        }
    
        //获取协议列表
        __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
        for (unsigned int i; i<count; i++) {
            Protocol *myProtocal = protocolList[i];
            const char *protocolName = protocol_getName(myProtocal);
            NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
        }
    
    • 方法调用

      • 重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了;
      • 如果想调用已经重写过的方法的父类的实现,只需使用super这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。
    • 动态添加方法

    • 关联对象
      现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。 这种情况的一般解决办法就是继承。 但是,只增加一个属性,就去继承一个类,总是觉得太麻烦。

    //首先定义一个全局变量,用它的地址作为关联对象的key
    static char associatedObjectKey;
    //设置关联对象
    objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象
    NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
    NSLog(@"AssociatedObject = %@", string);
    
    • objc_setAssociatedObject的四个参数:
      id object给谁设置关联对象。
      const void *key关联对象唯一的key,获取时会用到。
      id value关联对象。
      objc_AssociationPolicy关联策略。

    • objc_getAssociatedObject的两个参数。
      id object获取谁的关联对象。
      const void *key根据这个唯一的key获取关联对象。

    • 方法交换

    #import "UIViewController+swizzling.h"
    #import <objc/runtime.h>
    
    @implementation UIViewController (swizzling)
    //load方法会在类第一次加载的时候被调用
    //调用的时间比较靠前,适合在这个方法里做方法交换
    + (void)load{
        //方法交换应该被保证,在程序中只会执行一次
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
    
            //获得viewController的生命周期方法的selector
            SEL systemSel = @selector(viewWillAppear:);
            //自己实现的将要被交换的方法的selector
            SEL swizzSel = @selector(swiz_viewWillAppear:);
            //两个方法的Method
            Method systemMethod = class_getInstanceMethod([self class], systemSel);
            Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
    
            //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
            BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
            if (isAdd) {
                //如果成功,说明类中不存在这个方法的实现
                //将被交换方法的实现替换到这个并不存在的实现
                class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
            }else{
                //否则,交换两个方法的实现
                method_exchangeImplementations(systemMethod, swizzMethod);
            }
    
        });
    }
    
    - (void)swiz_viewWillAppear:(BOOL)animated{
        //这时候调用自己,看起来像是死循环
        //但是其实自己的实现已经被替换了
        [self swiz_viewWillAppear:animated];
        NSLog(@"swizzle");
    }
    
    @end
    

    在一个自己定义的viewController中重写viewWillAppear:

    - (void)viewWillAppear:(BOOL)animated{
        [super viewWillAppear:animated];
        NSLog(@"viewWillAppear");
    }
    

    Q4: 线程创建的几种方式


    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
        
        //创建线程的第一种方式
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"universe"];
        [thread start];
        [thread release];
        
        
        //创建线程的第二种方式,NSThread类方法
        [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"yuzhou"];
        
        
        //创建线程的第三种方法  NSObject方法
        [self performSelectorInBackground:@selector(run:) withObject:@"nsobject thread"];
        
        //创建线程的第四种方式
        NSOperationQueue *oprationQueue = [[NSOperationQueue alloc] init];
        [oprationQueue addOperationWithBlock:^{
            //这个block语句块在子线程中执行
            NSLog(@"oprationQueue");
        }];
        [oprationQueue release];
        
        //第五种创建线程的方式
        NSOperationQueue *oprationQueue1 = [[NSOperationQueue alloc] init];
        oprationQueue1.maxConcurrentOperationCount = 1;//指定池子的并发数
        
        //NSOperation 相当于java中的runnable接口,继承自它的就相当一个任务
        NSInvocationOperation *invation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"invation"];
        [oprationQueue1 addOperation:invation];//将任务添加到池子里面,可以给池子添加多个任务,并且指定它的并发数
        [invation release];
        
        //第二个任务
        NSInvocationOperation *invation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2:) object:@"invocation2"];
        invation2.queuePriority = NSOperationQueuePriorityHigh;//设置线程优先级
        [oprationQueue1 addOperation:invation2];
        [invation2 release];
        
        [oprationQueue1 release];
        
        //调用主线程,用来子线程和主线程交互,最后面的那个boolean参数,如果为yes就是等这个方法执行完了在执行后面的代码;如果为no的话,就是不管这个方法执行完了没有,接着往下走
        [self performSelectorOnMainThread:@selector(onMain) withObject:self waitUntilDone:YES];
        
        //---------------------GCD----------------------支持多核,高效率的多线程技术
        //创建多线程第六种方式
        dispatch_queue_t queue = dispatch_queue_create("name", NULL);
        //创建一个子线程
        dispatch_async(queue, ^{
            // 子线程code... ..
            NSLog(@"GCD多线程");
            
            //回到主线程
            dispatch_sync(dispatch_get_main_queue(), ^{//其实这个也是在子线程中执行的,只是把它放到了主线程的队列中
                Boolean isMain = [NSThread isMainThread];
                if (isMain) {
                    NSLog(@"GCD主线程");
                }
            });
        });
        
        
        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        return YES;
    }
    
    - (void)onMain
    {
        Boolean b = [NSThread isMainThread];
        if (b) {
            NSLog(@"onMain;;%d",b);
        }
    }
    
    
    - (void) run:(NSString*)str
    {
        NSLog(@"多线程运行:::%@",str);
    }
    
    - (void) run2:(NSString*)str
    {
        NSLog(@"多线程运行:::%@",str);
    }
    

    Q5: ____block 与 __ __weak的区别理解


    • __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
    • __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
    • ____block对象可以在block中被重新赋值,__ __weak不可以。

    PS:____unsafe_unretained修饰符可以被视为iOS SDK 4.3以前版本的__weak的替代品,不过不会被自动置空为nil,所以尽可能不要使用这个修饰符。

    Q6: KVO,NSNotification,delegate及block区别


    解释:

    • KVO就是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以监测一个值的变化,比如View的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。

    • NSNotification是通知,也是一对多的使用场景。在某些情况下,KVO和NSNotification是一样的,都是状态变化之后告知对方。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。

    • delegate 是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate(代理人)就可以了,由其他类完成所需要的操作。所以delegate是一对一关系。

    • block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观。

    区别:

    • KVO一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用KVO(观察者模式)。
    • delegate一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate。
    • Notification一般是进行全局通知,比如利好消息一出,通知大家去买入。
    • delegate是强关联,就是委托和代理双方互相知道,你委托别人买股票你就需要知道经纪人,经纪人也需要知道自己的顾客。Notification是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息。

    Q6: iOS是否有私有方法和私有变量?


    私有方法

    • 如果一个方法不在头文件中声明,那么这个方法在编译期,通过[receiver MethodName]的形式向对象发送消息,编译器会有警告,告诉你未找到该方法,但是实际运行时,依然可以正常运行;
    • 因为在编译时,即使这个方法不在头文件中声明,编译器仍然会将方法的签名编译进类的方法列表中, 发送消息时,会自动查找消息列表,如果找到同名消息,则会被触发。
    @interface ABC : NSObject
    @end
    
    @implementation ABC
    - (void)abc {
        NSLog(@"abc");
    }
    @end
    
     ABC *abc = [ABC new];
     [abc performSelector:@selector(abc)];
    

    私有变量

    • OC中所有的方法调用都是通过消息传递,即使你使用了obj.prop 这种点语法去为对象属性赋值,其编译后的代码仍然是转换为消息的调用。obj.prop 其实是向对象发送了一个setProp方法,等价于[obj setProp] 。 如果你用[obj setProp] 去向这个所谓的"私有属性"赋值,仍然可以赋值成功,并且可以正常使用。

    结论

    • OC中其实并无真正意义上的的私有方法和私有属性。但是在实际使用中,我们应遵守规则,不调用不能调用的方法。

    Q67 iOS中id和instancetype的区别?


    • 相同点
    • 都可以作为方法的返回类型

    • 不同点
      • instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;
      • instancetype只能作为返回值,不能像id那样作为参数。
    //err,expected a type  
    - (void)setValue:(instancetype)value {  
        //do something  
    } 
    

    应该写成如下形式:

    - (void)setValue:(id)value {  
        //do something  
    }  
    

    相关文章

      网友评论

          本文标题:iOS面试的常见问题清单

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