iOS面试

作者: 小人物_b | 来源:发表于2019-03-05 18:00 被阅读0次

iOS面试点

1、以下三个指针指向的地址是否相同

NSArray *a1 = [NSArray alloc];
NSArray *a2 = [NSArray alloc];
NSArray *a3 = [NSArray alloc];

答:相同,其中包含工厂设计模式的思想,NSArray是一个类簇

2、image初始化的几种方法和区别

+ (nullable UIImage *)imageNamed:(NSString *)name;      // load from main bundle
#if __has_include(<UIKit/UITraitCollection.h>)
+ (nullable UIImage *)imageNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection NS_AVAILABLE_IOS(8_0);
#endif
+ (nullable UIImage *)imageWithContentsOfFile:(NSString *)path;
+ (nullable UIImage *)imageWithData:(NSData *)data;

1、第一二种方式能自动识别@2x,@3x等不同分辨率的图片,第三种不能
2、第一种方式会将加载过的图片放入缓存,因此如果图片过多会导致内存溢出问题。第二种方法不占用系统内存,大大节省了内存,起到了性能优化,缺点如果资源素材多,会增加包的体积。
3、第三种方式不为图像提供缓存,从二进制数据创建,利用NSData方式加载时,图像会被系统以数据方式加载到程序。当你不需要重用该图像,或者你需要将图像以数据方式存储到数据库,又或者你要通过网络下载一个很大的图像时,请尽量使用imageWithData的方式加载图像。

扩展:

  1. Assets.xcassets,我们一般使用这个比较方便,资源文件不太大、不是很多的情况下并不会造成很大的性能问题。但是量大会增加系统内存,导致APP吃内存较多。
  2. bundle ,这种适合大图片,量大的资源素材。我们使用imageWithContentsOfFile去加载图片,既能避免占用大量内存,同时bundle又起到对资源的封闭存储。缺点会增加包的体积,这点有时候对于我们并不是很大的影响
  3. 创建文件管理,这类直接把资源素材添加到一个开放文件中,可以使用imageWithContentsOfFile不占用内存,但容易丢失。

3、线程通信

GCD方式

//创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.queue.bang", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger i =0; i<10; i++) {
        //在队列中创建线程处理任务
        //async会创建新线程,sync不会创建
        //DISPATCH_QUEUE_CONCURRENT 创建的是并发队列,可以创建新线程,不必等前一个线程里的任务完成;  DISPATCH_QUEUE_SERIAL创建的是串行队列,只会创建一个线程,新的任务必须要等到上一个任务完成才能执行,
        dispatch_async(queue, ^{
            [self download];
        });
    }
- (void)download
{
    NSLog(@"%@",[NSThread currentThread]);
    [NSThread sleepForTimeInterval:arc4random()%3];
    dispatch_async(dispatch_get_main_queue(), ^{
        //回到主线程做刷新UI等操作
    });    
}

NSObject中的对象线程访问模式

//回到主线程执行任务
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
//后台任务
[self performSelectorInBackground:@selector(download) withObject:nil];
//指定线程执行任务
NSThread *thread1 = [[NSThread alloc]initWithBlock:^{
   //线程常驻方法,AFN现在就是用这种方法达到常驻线程的效果
    @autoreleasepool {
            NSRunLoop *loop = [NSRunLoop currentRunLoop];
            [loop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
            [loop run];
        }
    }];
[thread1 start];
//线程在执行完任务后会被销毁,找不到指定线程执行任务导致崩溃!
[self performSelector:@selector(download) onThread:thread1 withObject:nil waitUntilDone:YES];

- (void)download
{
    NSURL *url = [NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1206121028,2489642848&fm=27&gp=0.jpg"];
    UIImage *img= [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
    dispatch_async(dispatch_get_main_queue(), ^{
        UIImageView *imgView = [[UIImageView alloc]initWithImage:img];
        imgView.frame = CGRectMake(0, 40, img.size.width, img.size.height);
        [self.view addSubview:imgView];
    });
}

4、代理一对多

思路:借助一个中间层,中间层成为代理对象,需要代理的对象
核心代码:

//定义一个协议类,里面有一个协议和一个协议方法
@protocol ColorChangeDelegate <NSObject>

- (void)changeColor;

@end
    //初始化中间层
    _middleObj = [[MiddleObject alloc]init];
    float width = [UIScreen mainScreen].bounds.size.width;
    //初始化4个view。实现效果:点击第四个view,让其他三个view 变色
    FirstView *view1 = [[FirstView alloc]initWithFrame:CGRectMake(0, 90, width, 60)];
    [self.view addSubview:view1];
    SecondView *view2 = [[SecondView alloc]initWithFrame:CGRectMake(0, 160, width, 60)];
    [self.view addSubview:view2];
    ThirdView *view3 = [[ThirdView alloc]initWithFrame:CGRectMake(0, 230, width, 60)];
    [self.view addSubview:view3];
    DelegateView *deView = [[DelegateView alloc]initWithFrame:CGRectMake(0, 300, width, 60)];
    //将代理对象设置成中间层
    deView.colorDelegate = _middleObj;
    [self.view addSubview:deView];
    //将想要的代理对象存入到中间层里
    [_middleObj.viewTables addObject:view1];
    [_middleObj.viewTables addObject:view2];
    [_middleObj.viewTables addObject:view3];
//初始化方法
- (instancetype)init
{
    self = [super init];
    if (self) {
        //viewTables 是NSHashTable的对象,持有需要执行代理方法的类,这里是初始化方法。设置NSPointerFunctionsWeakMemory这个option不会将加入这个table中的对象引用计数+1,相当于加入其中的对象是weak属性。
        self.viewTables = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
    }
    return self;
}

//协议方法,这里就可以使多个view同时变色,实现一对多效果
- (void)changeColor
{
    for (UIView *view in self.viewTables) {
        float r1 = arc4random()%255/255.0;
        float r2 = arc4random()%255/255.0;
        float r3 = arc4random()%255/255.0;
        view.backgroundColor = [UIColor colorWithRed:r1 green:r2 blue:r3 alpha:1];
    }
}

5、load和initialize总结

+load:
调用时间
main函数之前调用,无论是否引用都会执行
调用规则:
先执行父类的load,再执行子类的load,直到将所有的普通类中的load方法执行完毕后,最后才执行category中的load方法(这里与普通方法的执行顺序不同,普通方法是先执行category中的同名方法再执行类中的同名方法),因此没有必要在子类中调用[super load]方法。如果一个类没有实现load方法,那么它的父类不会自动调用load方法。
使用场景:
load方法中的类有可能还没有加载到内存中,使用对象时无法初始化,会很不安全,应当减少其中的逻辑。load方法是线程安全的,它内部使用了锁,所以我们应该避免线程阻塞在load方法中。一般来说,除了Method Swizzle,别的逻辑都不应该放在load方法中实现。

+initialize
调用时间:
第一次给某个类发消息的时候调用,类似于懒加载
调用规则:
与load类似,子类会自动调用父类的initialize方法。但是即使子类没有实现该方法,父类也会自动调用,这样父类会调用多次,可以加上如下的判断

// In Parent.m
+ (void)initialize {
    if (self == [Parent class]) {
        NSLog(@"Initialize Parent, caller Class %@", [self class]);
    }
}

使用场景:
对象初始化、静态变量、属性赋值等。

load和initialize总结:
1、load和initialize方法都会在实例化对象之前调用,以main函数为分水岭,前者在main函数之前调用,后者在之后调用。这两个方法会被自动调用,不能手动调用它们。
2、load和initialize方法都不用显式的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。
3、load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
4、load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。

引用:细说OC中的load和initialize方法

6、main()函数之前经历了什么?

经过编译阶段生成可执行文件。读取可执行文件,获取dyld路径,运行dyld,初始化运行环境,开启缓存策略。加载程序的依赖库,并对这些依赖库进行链接,最后初始化依赖库,同时runtime也会被初始化。依赖库初始化完成后,项目文件开始初始化,这时候runtime会对所有的类类结构进行初始化,调用load方法。最后dyld返回main函数地址,main函数被调用。
引用:iOS程序启动->dyld加载->runtime初始化 过程

7、object_getClass()和 [objc class]区别

object_getClass返回时参数的isa指针指向
[objc class] 返回的是当前类的类对象

//OC中所有的类都是对象,实例对象、类对象、元类、根类
    //平时常用的类例如NSString,NSDictionary,NSObject等都是类对象
    //此时obj1是实例对象
    NSObject *obj1 = [[NSObject alloc]init];
    Class cls1 = object_getClass(obj1);//cls1是类对象
    Class cls2 = [obj1 class];//cls2也是类对象
    BOOL aa1 = class_isMetaClass(cls1);
    BOOL aa2 = class_isMetaClass(cls1);
    NSLog(@"%p,%p",cls1,cls2);
    NSLog(@"%d,%d",aa1,aa2);
    //NSObject是类对象
    cls2 = [NSObject class];//cls2这里是类对象
    Class metaCls = object_getClass(cls2);//返回的metaCls已经是元类了
    aa1 = class_isMetaClass(cls2);
    aa2 = class_isMetaClass(metaCls);
    NSLog(@"%p,%p",cls2,metaCls);
    NSLog(@"%d,%d",aa1,aa2);
    //metaCls 是元类对象
    cls2 = [metaCls class]; //这里cls2是元类
    Class cls3 = object_getClass(cls2);//cls3既是元类,又是根类
    aa1 = class_isMetaClass(cls2);
    aa2 = class_isMetaClass(cls3);
    NSLog(@"%p,%p",cls2,cls3);
    NSLog(@"%d,%d",aa1,aa2);

打印结果

2019-03-06 12:24:02.160485+0800 Demo2[77239:1612888] 0x1023c8f38,0x1023c8f38
2019-03-06 12:24:02.160578+0800 Demo2[77239:1612888] 0,0
2019-03-06 12:24:02.160654+0800 Demo2[77239:1612888] 0x1023c8f38,0x1023c8ee8
2019-03-06 12:24:02.160721+0800 Demo2[77239:1612888] 0,1
2019-03-06 12:24:02.160782+0800 Demo2[77239:1612888] 0x1023c8ee8,0x1023c8ee8
2019-03-06 12:24:02.160837+0800 Demo2[77239:1612888] 1,1

其他面试点

相关文章

网友评论

      本文标题:iOS面试

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