iOS 面试 完整版

作者: 门前的那颗樱桃树 | 来源:发表于2019-03-29 10:58 被阅读4次

    1、字符串常用方法

    字符串截取

    NSString *strSub = [str substringFormIndex:2];

    NSString *strSubT = [str substringToIndex:2];

    NSString *strSubR = [str substringWithRange:range];

    字符串替换

    NSString *newStr = [str stringByReplacingOccurencesOfString:@"ll" withString:@"al"];

    编码,把UTF8编码的字符串编码成URL中可用的字符串

    url_cn = [url_cn stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

    字符串转数组

    NSArray *array = [str componentsSeparatedByString:@","];--分隔符

    数组转字符串

    NSString *str = [array componentsJoinedByString:@","];--分隔符

    字符串转字典

    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
    
    NSError *err;
    
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
    
                                  options:NSJSONReadingMutableContainers
    
                                                             error:&err];复制代码
    

    字典转字符串

    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&parseError];复制代码
    

    2、数组

    数组替换

    [_dataSource replaceObjectAtIndex:1 withObject:recentArray];

    数组倒序

    NSArray *array = @[@1, @20, @3, @10, @2];//排序后到新数组里
    NSArray *sortedArray = [array sortedArrayUsingComparator:^NSComparisonResult(NSNumber* obj1, NSNumber* obj2) {
     if ([obj1 intValue] > [obj2 intValue]) {
         return NSOrderedDescending;
     } else { 
         return NSOrderedAscending;
     } 
    }];复制代码
    

    3、浅谈iOS开发中方法延迟执行的集中方式

    1》performSelector方法

    - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;复制代码
    

    2》NSTimer定时器

    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;复制代码
    

    3》NSThread线程的sleep

    + (void)sleepForTimeInterval:(NSTimeInterval)ti;复制代码
    

    4》GCD

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    });复制代码
    

    4、写一个完整的代理,包括生命、实现

    //创建
    @protocol MyDelegate
    @required
    -(void)eat:(NSString *)food;
    @optional
    -(void)run;
    @end
    //声明.h
    @interface person:NSObject<MyDelegate>
    @end
    //实现.m
    @implementation person
    -(void)eat:(NSString *)food{
    }
    -(void)run{
    }
    @end复制代码
    

    5、BAD_ACCESS在什么情况下出现

    这种问题在开发时经常遇到,原因是访问了野指针,比如访问已经释放的对象的成员变量或者发消息、死循环等。

    6、lldb(gdb)常用的控制台调试命令

    1》p 输出基本类型,是打印命令,需要指定类型,是print的简写

    p (int)[[[self view] subviews] count]复制代码
    

    2》po 打印对象,会调用description方法,是print-object的简写

    po [self view]复制代码
    

    3》expr 可以在调试时动态执行制定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。

    4》bt 打印调用堆栈,是thread backtrace的简写,加all可打印所有的thread的堆栈

    5》br l 是breakpoint list的简写

    7、你一般是怎么用Instruments的?

    Instruments里面工具很多,常用的有:

    1》Time Profiler:性能分析

    2》Zoombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能

    3》Allocations:用来检查内存,写算法的那批人也用这个来检查

    4》Leaks:检查内存,看是否有内存泄漏

    8、iOS常用的数据存储方式有哪些?

    数据存储有四种方案:NSUserDefault、KeyChain、file、DB。 
    其中File有三种方式:plist、Archive(归档)
    DB包括:SQLite、FMDB、CoreData复制代码
    

    9、Runtime可以做什么事情

    1》获取类里面的所有成员变量

    2》为类动态添加成员变量

    3》动态改变类的方法实现

    4》为类动态添加新的方法。

    10、WKWebView与UIWebView的比较替换

    相比于UIWebView的优势

    1》在性能、稳定性、占用内存方面有很大提升

    2》允许JavaScript的Nitro库加载并使用(UIWebView中限制)

    3》增加加载进度实行:estimatedProgress,不用再自己写进度条了

    4》支持了更多的HTML属性。

    11、iOS常见加密方式

    1》base64加密:基本原理是原本是8个bit一组表示数据,改为6个bit一组表示数据,不足的部分补零,每两个0用一个 = 表示;用base64编码之后,数据长度会变大,增加了大约1/3左右;可进行反向解密;编码有个非常显著的特点,末尾有个 = 号

    2》MD5加密:把任意一个长度的字符串换成一定长度的十六进制的大整数

    3》AES加密

    4》RSA加密

    12、drawRect

    1》我们只能在继承了UIView的子类中通过重写drawRect方法来绘制图形

    2》如果需要绘制图形的子类直接继承自UIView,则子类的drawRect中不需要调用父类方法[super drawRect:rect];。如果子类继承自其它继承UIView的view类,则drawRect方法中需要调用父类方法[super drawRect:rect];

    3》drawRect方法不能手动直接调用,我们可以通过调用其它方法来实现drawRect方法的调用。如:在子类初始化时调用- (instancetype)initWithFrame:(CGRect)frame方法,且frame不为CGRectZero时。

    4》我们可以调用setNeedsDisplay()方法或setNeedsDisplayInRect方法,但是该方法不会自己调用drawRect方法,而是会标记视图,并在下一次循环更新的时候让视图通过drawRect来进行重绘,前提是rect不为CGRectZero。

    13、iOS开发中的锁

    1》临界区:指的是一块对公共资源进行访问的代码,并非是一种机制或者算法。

    2》互斥锁:是一种用于多线程编程中,防止两条线程同时对同一个公共资源进行读写的机制,该目的通过将代码切片成一个一个的临界区而达成。

    3》自旋锁:是用于多线程同步的一种锁,线程会反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显示释放自旋锁。自旋锁避免了进城上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。

    4》读写锁:是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁,用于解决多线程对公共资源的读写问题。读操作可并发重入,写操作是互斥的。读写锁通常用互斥锁、条件变量、信号量实现。

    5》信号量:是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。

    6》条件锁:就是条件变量,当进城的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进城继续运行。

    14、系统对象的 copy 与 mutableCopy 方法

    不管是集合类对象(NSArray、NSDictionary、NSSet ... 之类的对象),还是非集合类对象(NSString, NSNumber ... 之类的对象),接收到copy和mutableCopy消息时,都遵循以下准则:
    1\. copy 返回的是不可变对象(immutableObject);如果用copy返回值调用mutable对象的方法就会crash。
    2\. mutableCopy 返回的是可变对象(mutableObject)。
    
    一、非集合类对象的copy与mutableCopy
      在非集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;
      对可变对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:
        NSString *str = @"hello word!";
        NSString *strCopy = [str copy] // 指针复制,strCopy与str的地址一样
        NSMutableString *strMCopy = [str mutableCopy] // 内容复制,strMCopy与str的地址不一样
    
        NSMutableString *mutableStr = [NSMutableString stringWithString: @"hello word!"];
        NSString *strCopy = [mutableStr copy] // 内容复制
        NSMutableString *strMCopy = [mutableStr mutableCopy] // 内容复制
    
    二、集合类对象的copy与mutableCopy (同上)
      在集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;
      对可变对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对集合内的对象元素仍然是指针复制。(即单层内容复制)
        NSArray *arr = @[@[@"a", @"b"], @[@"c", @"d"];
        NSArray *copyArr = [arr copy]; // 指针复制
        NSMutableArray *mCopyArr = [arr mutableCopy]; //单层内容复制
    
        NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
        NSArray *copyArr = [mutableArr copy]; // 单层内容复制
        NSMutableArray *mCopyArr = [mutableArr mutableCopy]; // 单层内容复制
    
    【总结一句话】:
        只有对不可变对象进行copy操作是指针复制(浅复制),其它情况都是内容复制(深复制)!复制代码
    

    15、写一个 setter 方法用于完成 @property (nonatomic, retain) NSString *name,写一个 setter 方法用于完成 @property (nonatomic, copy) NSString *name

    答:
    // retain
    - (void)setName:(NSString *)str {
      [str retain];
      [_name release];
      _name = str;
    }
    // copy
    - (void)setName:(NSString *)str {
      id t = [str copy];
      [_name release];
      _name = t;
    }复制代码
    

    16、KVC的底层实现?

    当一个对象调用setValue方法时,方法内部会做以下操作:
    1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
    2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
    3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
    4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
    这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。复制代码
    

    17、你是否接触过OC中的反射机制?简单聊一下概念和使用

    1). class反射
        通过类名的字符串形式实例化对象。
            Class class = NSClassFromString(@"student"); 
            Student *stu = [[class alloc] init];
        将类名变为字符串。
            Class class =[Student class];
            NSString *className = NSStringFromClass(class);
    2). SEL的反射
        通过方法的字符串形式实例化方法。
            SEL selector = NSSelectorFromString(@"setName");  
            [stu performSelector:selector withObject:@"Mike"];
        将方法变成字符串。
            NSStringFromSelector(@selector*(setName:));复制代码
    

    18、调用方法有两种方式

    1). 直接通过方法名来调用。[person show];
    2). 间接的通过SEL数据来调用 SEL aaa = @selector(show); [person performSelector:aaa];  复制代码
    

    19、iOS的沙盒目录结构是怎样的?

    沙盒结构:
    1). Application:存放程序源文件,上架前经过数字签名,上架后不可修改。
    2). Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)
    3). Library:
            Caches:存放体积大又不需要备份的数据。(常用的缓存路径)
            Preference:设置目录,iCloud会备份设置信息。
    4). tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。复制代码
    

    20、什么是 TCP / UDP ?

    TCP:传输控制协议。
    UDP:用户数据协议。
    
    TCP 是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议。
    UDP 是面向无连接的,数据传输是不可靠的,它只管发,不管收不收得到。
    简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般。复制代码
    

    21、通信底层原理(OSI七层模型)

    OSI采用了分层的结构化技术,共分七层:
        物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。复制代码
    

    22、block的实质是什么?有几种block?分别是怎么产生的?

    block本质上是一个OC对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。

    一共有三种block,分别是全局的、栈上的、堆上的

    NSGlobalBlock直到程序结束才会被回收

    NSStackBlock类型block存放在栈中,我们直到栈中的内存由系统自动分配和释放,作用域执行完毕之后就会被立即释放

    NSMallocBlock是在平时编码过程中最常用到的。存放在堆中需要我们自己进行内存管理。

    image

    __block用于解决block内部不能修改auto变量值的问题,__block不能修饰静态变量(static) 和全局变量

    详解

    23、不借用第三个变量,如何交换两个变量的值?要求手动写出交换过程

    方法一:
    a = a + b;
    b = a - b;      // b = (a +b)-b,即 b = a
    a = a - b;      // a =  (a+b)-a
    方法二:
    a = a - b;
    b = a + b;     // b = (a-b)+b,即b=a
    a = b - a;      // a = a - (a-b)复制代码
    

    24、设计模式有哪些

    1》观察者模式:KVO是典型的观察者模式,观察某个属性的改变,改变时会通知观察者

    2》委托模式:代理+协议的组合,实现1对1的反向传值操作

    3》单利模式:通过static关键词,声明全局变量,在整个进程运行期间只会被赋值一次

    25、@property的本质是什么,有哪些属性关键字

    1》@property的本质是实例变量+存取方法。

    2》原子性与非原子性

    读写权限

    内存管理语义 assign strong weak copy

    方法名 getter setter

    不常用的 nonnull nullable等

    26、什么时候使用weak关键字,相比assign有什么区别

    1》在ARC中,有可能出现循环引用的时候使用weak,比如代理,block;自身已经对他进行一次强引用,没有必要再强引用一次,比如IBOutlot,因为父控件的subviews数组已经对他有一个强引用

    2》weak表明该属性定义了一种“非拥有关系”。在该属性所指的对象销毁时,属性值会自动清空(nil)。

    3》assign可以用于非OC对象,weak必须用于OC对象。

    27、怎么使用copy关键字

    1》NSString,NSArray,NSDictionary等经常使用copy关键字,因为他们有对应的可变类型。如果使用strong关键字,那么这个属性就有可能指向一个可变对象,如果这个可变对象修改了,那么会影响该属性。如果可变对象使用了copy关键字,那么这个可变对象做增删改的时候,系统会因为找不到对应的程序而崩溃。因为copy复制的是一个不可变对象,不可变对象是不能进行增删改的操作的。

    2》block也经常使用copy关键字。block使用copy关键字是从MRC留下来的传统,方法内部的block是在栈区的,使用copy可以把他放到堆区。在ARC中写不写都行,使用copy或者strong效果都是一样的。

    28、如何让自己的类用copy修饰符?如何重写带copy关键字的setter方法?

    1》该类需要遵从NSCopying协议

    2》实现NSCopying的协议: - (id)copyWithZone:(NSZone *)zone;

    29、@synthesize 和 @dynamic 分别有什么作用?

    如果@synthesize和@dynamic都没写,那么默认的就是@synthesize var = _var ;@synthesize的语义是如果你没有手动实现setter和getter方法,那么编译器会自动为你加上这两个方法。@dynamic告诉编译器,属性的setter和getter方法油用户自己实现,不自动生成。

    30、常见的OC的数据类型有哪些,和C的基本数据类型有什么区别?如:NSInteger和int

    OC的数据类型有NSString,NSArray,NSData等等,这些都是class,创建后便是对象,而C语言的基本数据类型是int,只是有一定字节的内存空间,用于存放数值。NSinteger是基本数据类型,不是NSNumber的子类,当然也不是NSObject的子类。NSInteger是基本数据类型int或者long的别名。他的区别在于,NSInteger会根据系统是32位还是64位来决定本身是int还是long。

    31、OC如何对内存管理的,说说你的看法和解决方案?

    OC的内存管理模式主要有三种:ARC(自动内存计数)、手动内存计数、内存池

    1》自动内存计数(ARC):由Xcode自动在APP编译阶段,在代码中添加内存管理代码

    2》手动内存计数(MRC):遵循内存谁申请,谁释放;谁添加,谁释放的原则

    3》内存释放池:把需要释放的内存统一放在一个池子里面,当池子被抽干后,池子中所有的内存空间也会被自动释放。内存池的释放操作分为自动和手动,自动释放受runloop机制影响。

    32、weak和strong的区别

    1》strong指针能够保持对象的生命,一个对象只要有strong指针指向他,那么他就不会被释放,相反的,如果没有strong指针指向他,那么他就会被自动释放。默认的局部变量都是强指针,存放在堆里面

    2》weak型的指针变量仍然可以是一个对象,但是不属于对象的拥有者。即当对象被销毁的时候,这个weak指针也就自动指向nil。

    33、block在ARC和MRC中的区别

    如何判断当前文件是MRC还是ARC:dealloc方法中能否调用super,只有MRC才能调用super;能否用retain、release,如果可以就是MRC。

    MRC没有strong、weak,局部变量对象就是相当于基本数据类型;MRC给成员变量赋值一定要用set方法,不能直接访问下划线成员属性赋值。

    总之,只要block不引用外部变量,block放在全局区。

    MRC 管理block:只要block引用外部变量,block放在栈区,block只能使用copy不能使用retain,用retain,block还是在栈里面

    ARC管理block:只要block引用外部变量,block就放在堆区,block使用copy,尽量不要使用strong。

    34、KVO、NSNotifaction、delegate、block的区别

    1》KVO是观察者模式,一般搭配KVC使用,通过KVO可以监测一个值得变化,是一对多的关系,一个值的变化会通知所有的观察者。

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

    3》delegate是代理,就是把自己不想做的事情交给别人去做,不需要关心中间需要做的事情,只要调用delegate就可以了,由其他类完成所需要的动作,所以是一对一的。

    4》block是delegate的另一种形式,是函数式编程的一种形式,使用场景和delegate一样,相比delegate更为灵活,而且实现也更直观。

    5》KVO一般的使用场景是数据,需求是数据变化。delegate一般的使用场景是行为,需要别人帮忙做一件事情。NSNotifaction一般是进行全局通知,只需要发出通知就可以,不关心你有没有接受到通知。delegate是强关联,就是委托和代理双方都知道。

    35、谈谈UITablebView的优化

    1》正确的复用cell

    2》设计统一规格的cell

    3》提前计算并缓存好高度,因为heightForRowAtIndexPath是调用最频繁的方法

    4》异步绘制,遇到复杂页面,遇到性能瓶颈时,可能就是突破口

    5》滑动时按需加载,这个在大量图片展示,网络加载的时候很管用

    6》减少子视图的层级关系

    7》不要动态的add或者remove子控件,最好在初始化的时候就添加完,然后通过hidden来控制是否显示。

    36、OC中堆和栈的区别

    管理方式:栈是编译器自动管理,堆的释放工作由程序员空

    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先设计好的。在windows 下,栈的大小是2M(也有的说是1M),如果申请的空间超过栈的剩余空间是,将提示overFlow,因此,能从栈获得的空间较小。

    堆:堆是向高笛子扩展的数据结构,是不连续的内存区域。堆得大小受限于计算机系统中有效的虚拟内存,由此可见,堆获得空间比较灵活,也比较大。

    碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出分配方式:堆都是动态分配的,没有静态分配的堆。

    栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的。

    37、分类和扩展

    分类里面有类名,方法列表,协议列表,但是没有属性列表,所以原则上来说,分类只能添加方法,不能添加属性的。当然,添加属性也可以,只要不去调用他。因为添加的属性没有get和set方法,虽然我们可以用runtime去动态的添加set和get方法,但是调用属性的时候还是不会通过的,因为他没有实例变量。

    分类里边添加的方法,即使不实现也是不会报错的,因为分类是在运行时添加到类里边去的。但是扩展添加的方法必须要实现,因为扩展是在编译时添加进去的。

    分类里边添加的方法如果和原有类中的方法重名,则会优先调用分类中的方法,因此尽量不要覆盖原有类中的方法。

    扩展添加的属性默认是私有的,扩展没有独立的实现部分,也就是说,扩展中所声明的方法必须依托对应的类的实现部分来实现。

    38、atomic的实现机制:为什么不能保证绝对的线程安全

    用atomic生成的set和get方法会进行加锁操作,这个锁仅仅保证了存取方法的线程安全,并非真正意义上的线程安全,因为线程安全还有除了读写之外的其他操作(比如:当一个线程正在进行读取方法的操作时,同时又有一个线程在进行release操作,可能会直接出现crash)

    atomic 更耗费资源,速度要慢,如果没有多线程之间的通讯,尽量还是使用nonatomic。

    举例:当几个线层同时调用同一属性的读取方法时,会get到一个完整的值,但是get的值不可控

    线程1 调用get

    线程2 调用set

    线程3 调用set

    这3个线程同时执行,线程1 会get到一个值,但是get到的值不可控,有可能是线程2 线程3 之前的原始值,也有可能是线程2 线层3 set之后的值

    nonatomic 生成的读取方法没有加锁,线程不安全,但是更快,当同一个线程同时访问同一个属性时,会出现无法预料的结果。

    39、被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sidetable吗?里面的结构可以画出来吗

    被weak修饰的对象在释放时会被置为nil,不同于assign。

    runtime 维护了一个weak 表,用于存储指向某个对象的所有weak 指针。weak表其实是一个hash 表,key是所指对象的地址,value是weak指针的地址数组。

    1》初始化时,runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址

    2》添加引用时,objc_initWeak函数会调用objc_storeWeak函数,objc_storeWeak的作用是更显指针指向,创建对应的弱引用表。

    3》释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据置为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

    sideTable

    struct SideTable {
        // 保证原子操作的自旋锁
        spinlock_t slock;
        // 引用计数的 hash 表
        RefcountMap refcnts;
        // weak 引用全局 hash 表
        weak_table_t weak_table;
    }
    
    struct weak_table_t {
        // 保存了所有指向指定对象的 weak 指针
        weak_entry_t *weak_entries;
        // 存储空间
        size_t    num_entries;
        // 参与判断引用计数辅助量
        uintptr_t mask;
        // hash key 最大偏移值
        uintptr_t max_hash_displacement;
    };
    复制代码
    

    40、关联对象有什么应用,系统如何关联对象?其被释放的指针需要手动将所有的关联对象的指针置空吗?

    AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。

    在obj dealloc 的时候会调用object_dispose,检查有无关联对象,有的话就_object_remove_assocations删除

    www.jianshu.com/p/e9582e7df…

    41、KVO的底层实现?如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?

    当观察某对象A时,KVO机制动态创建一个对象A当前的子类,并未这个新的子类重写了被观察属性keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。

    Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。

    使用方法,可实现取消系统kvo,自己触发,也就可控。

    +(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
        if ([key isEqualToString:@"name"]) {
            return NO;
        }else{
            return [super automaticallyNotifiesObserversForKey:key];
        }
    }
    -(void)setName:(NSString *)name{
    
        if (_name!=name) {
    
            [self willChangeValueForKey:@"name"];
            _name=name;
            [self didChangeValueForKey:@"name"];
        }
    
    }复制代码
    

    42、class_ro_t和class_rw_t的区别

    objc类中的属性、方法还有遵循的协议等消息都保存在class_rw_t中;

    其中还有一个指向常量的的指针ro,其中存储到了当前类在编译器就已经确定的属性、方法以及遵从的协议。

    43、iOS中内省的几个方法?class方法和object_getClass有什么区别?

    内省方法:

    判断对象类型:

    -(BOOL) isKindOfClass:判断是否是这个类或者这个类的子类的实例

    -(BOOL) isMemberOfClass:判断是否是这个类的实例

    判断对象or类是否有这个方法

    -(BOOL) respondsToSelector:判断实例是否有这样方法

    +(BOOL) instancesRespondToSelector: 判断类是否有这个方法

    object_getClass:获得的是isa的指向

    self.class:当self是实例对象的时候,返回的是类对象,否则返回自身。类方法class,返回的是self,所以当查找meta class,需要类对象调用object_getClass方法。

    44、一个int变量被_block修饰与否的区别

    没有修饰,被block捕获,是值拷贝。

    使用_block修饰,会生成一个结构体,复制int的引用地址,达到修改数据。

    45、为什么block在外部使用_weak修饰的同时需要在内部使用__strong修饰

    涉及到捕获的时候该变量是存在的,在执行block的时候可能被捕获对象释放了。

    46、Runloop的作用是什么?他的内部工作机制了解吗

    Runloop的作用是用来管理线程的,当线程的Runloop开启后,线程就会在执行任务后,处于休眠状态,随时等待接受新的任务,而不是退出。

    只有主线程的Runloop是默认开启的,所以线程在开启后,才会一直运行,不会退出。其他线程的Runloop如果需要开启,就需要手动开启。

    在Runloop内部有一个判断循环的条件,如果满足条件,就一直循环,线程得到唤醒事件被唤醒,事件处理完毕以后,回到睡眠状态,等待下次唤醒。

    47、哪些场景可以触发离屏渲染

    设置了一下属性时,都会触发离屏渲染:

    1》shouldRasterize(光栅化)

    2》masks(遮罩)

    3》shadows(阴影)

    4》edge antialiasing(抗锯齿)

    5》group opacity(不透明)

    6》复杂形状设置圆角等
    7》渐变

    48、block

    • 当没有外部变量时,block为__NSMallocBlock,它由开发者创建,存储在堆内存上。* 当有__weak修饰时block为__NSStackBlock,存储在栈区。* 当block有参数时(捕获了外部变量时)block为__NSGlobalBlock,存储在全局区。* block的本质是一个结构体。* 在block内部使用外部指针且会造循环引用情况下,需要使用__weak修饰外部指针* 在block内部如果调用了延时函数还是用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。* 如果需要在block内部改变外部栈区变量的话,需要用__block修饰外部变量。

    49、delegate和block的使用比较

    1》共同点:block和delegate的方法都可以理解成回调函数,当某件事情发生的时候执行一段代码片段。

    2》block优点:是一种轻量级的回调,能够直接访问上下文,使用块的地方和块的实现地方在同一个地方,使得代码组织更加连贯。

    3》delegate:相对来说是重量级的回调,因为方法的生命和实现分离开来,代码的连贯性不是很好,代理很多时候都需要存储一些临时数据。代理的回调函数可以是一组多个函数,在不同的时机调用不同的回调函数。

    4》怎么选择:当回调函数多余3个的时候,采用代理比较好;使用代码块容易造成循环引用,代理不会出现该问题;其他情况下优先考虑代码块。

    50、UIViewController的生命周期

    1》[ViewController initWithCoder:][ViewController initWithNibName:Bundle]:首先从归档文件中加载UIViewController对象。即使是纯代码,也会把nil作为参数传给后者。

    2》[UIView awakeFromNib]:作为第一个方法的助手,方法处理一些额外的设置。

    3》[ViewController loadView]:创建或加载一个view并把它赋值给UIViewControllerview属性。

    -[ViewController viewDidLoad]:此时整个视图层次(view hierarchy)已经放到内存中,可以移除一些视图,修改约束,加载数据等。

    4》[ViewController viewWillAppear:]:视图加载完成,并即将显示在屏幕上。还没设置动画,可以改变当前屏幕方向或状态栏的风格等。

    5》[ViewController viewWillLayoutSubviews]即将开始子视图位置布局

    6》[ViewController viewDidLayoutSubviews]用于通知视图的位置布局已经完成

    7》[ViewController viewDidAppear:]:视图已经展示在屏幕上,可以对视图做一些关于展示效果方面的修改。

    8》[ViewController viewWillDisappear:]:视图即将消失

    9》[ViewController viewDidDisappear:]:视图已经消失

    10》[ViewController dealloc:]:视图销毁的时候调用

    51、AppDelegate的几个方法

    1》当程序第一次运行并且将要显示窗口的时候执行该方法

    • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

    2》程序进入后台的时候需要先执行程序取消活跃的方法

    • (void)applicationWillResignActive:(UIApplication *)application

    3》当程序进入后台的时候

    • (void)applicationDidEnterBackground:(UIApplication *)application

    4》当程序将要进入前台的时候

    • (void)applicationWillEnterForeground:(UIApplication *)application

    5》当程序变得活跃的时候

    • (void)applicationDidBecomeActive:(UIApplication *)application

    6》当程序将要退出的时候

    • (void)applicationWillTerminate:(UIApplication *)application

    如果程序在后台可以运行,则上面的方法会被替换成applicationDidEnterBackground

    52、反射是什么?可以举出几个应用场景吗

    在计算机科学中,反射是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且改变自己的而行为。

    要注意术语“反射”和“内省”的关系:内省机制仅指程序在运行时对自身信息的检测;反射机制不仅包括要能在运行时对程序自身信息进行检测,还要求程序能进一步根据这些信息改变程序状态或结构。

    一个重点是改变,一个重点是检测。

    比如通过类名,生成类 Class * tempClass = NSClassFromString(str);
    为类增加方法等。

    53、有哪些场景是NSOperation比GCD更容易实现的?(或者是NSOperation优于GCD的几点)

    GCD是基于C的底层API,NSOperation属于object-c类。

    相对于GCD:

    1》NSOperation拥有更多的函数可用

    2》在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。

    3》有KVO,可以检测NSOperation是否正在执行,是否结束,是否取消

    4》NSOperationQueue可以方便的管理开发

    54、APP启动优化策略?最好结合启动流程来说(main函数的执行前后都分别说一下)

    www.jianshu.com/p/a51fcabc9…

    55、OC中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?

    // 创建线程的方法
    - [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]
    - [self performSelectorInBackground:nil withObject:nil];
    - [[NSThread alloc] initWithTarget:nil selector:nil object:nil];
    - dispatch_async(dispatch_get_global_queue(0, 0), ^{});
    - [[NSOperationQueue new] addOperation:nil];
    
    // 主线程中执行代码的方法
    - [self performSelectorOnMainThread:nil withObject:nil waitUntilDone:YES];
    - dispatch_async(dispatch_get_main_queue(), ^{});
    - [[NSOperationQueue mainQueue] addOperation:nil];
    复制代码
    

    56、用伪代码写一个线程安全的单例模式

    static id _instance;
    + (id)allocWithZone:(struct _NSZone *)zone {
       static dispatch_once_t onceToken;
       dispatch_once(&onceToken, ^{
           _instance = [super allocWithZone:zone];
       });
       return _instance;
    }
    
    + (instancetype)sharedData {
       static dispatch_once_t onceToken;
       dispatch_once(&onceToken, ^{
           _instance = [[self alloc] init];
       });
       return _instance;
    }
    
    - (id)copyWithZone:(NSZone *)zone {
       return _instance;
    }复制代码
    

    57、AFNetworking 底层原理分析

    AFNetworking主要是对NSURLSession和NSURLConnection(iOS9.0废弃)的封装,其中主要有以下类:
    1). AFHTTPRequestOperationManager:内部封装的是 NSURLConnection, 负责发送网络请求, 使用最多的一个类。(3.0废弃)
    2). AFHTTPSessionManager:内部封装是 NSURLSession, 负责发送网络请求,使用最多的一个类。
    3). AFNetworkReachabilityManager:实时监测网络状态的工具类。当前的网络环境发生改变之后,这个工具类就可以检测到。
    4). AFSecurityPolicy:网络安全的工具类, 主要是针对 HTTPS 服务。
    
    5). AFURLRequestSerialization:序列化工具类,基类。上传的数据转换成JSON格式
        (AFJSONRequestSerializer).使用不多。
    6). AFURLResponseSerialization:反序列化工具类;基类.使用比较多:
    7). AFJSONResponseSerializer; JSON解析器,默认的解析器.
    8). AFHTTPResponseSerializer; 万能解析器; JSON和XML之外的数据类型,直接返回二进
    制数据.对服务器返回的数据不做任何处理.
    9). AFXMLParserResponseSerializer; XML解析器;复制代码
    

    58、描述下SDWebImage里面给UIImageView加载图片的逻辑

    SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。
    
    加载图片的过程大致如下:
        1.首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
        2.如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
        3.如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
        4.下载后的图片会加入缓存中,并写入磁盘中
        5.整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
    
    SDWebImage原理:
    调用类别的方法:
        1\. 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。
        2\. 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。
        3\. 从网络上获取,使用,缓存到内存,缓存到沙盒。复制代码
    

    59、HTTPS和HTTP的区别

    1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
    2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
    3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
    4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

    60、iOS中imageNamed 和 imageWithContentOfFile的区别

    使用imageNamed:加载图片

    1. 加载到内存中后,会一直停留在内存中,不会随着对象销毁而销毁
    2. 加载进图片后,占用的内存归系统管理,我们无法管理
    3. 相同的图片,图片不会重新加载
    4. 加载到内存中后,占据内存空间较大

    使用 imageWithContentOfFile:加载图片

    1. 加载到内存中后,占据内存空间比较小
    2. 相同的图片会被重复加载到内存中
    3. 对象销毁的时候,加载到内存中得图片会被一起销毁

    结论:如果图片较小,频繁使用的图片,使用imageNamed来加载图片(如按钮图片、主页图片、展位图)

    如果图片较大,使用次数少,建议使用imageWithContentOfFile来加载图片(相册、版本新特性)

    61、为什么assign不用用于修饰对象?

    首先我们需要明确,对象的内存一般被分配到堆上,基本数据类型和oc数据类型的内存一般被分配在栈上。

    如果用assign修饰对象,当对象被释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,从而造成了野指针。因为对象是分配在堆上的,堆上的内存由程序员分配释放。而因为指针没有被置为nil,如果后续的内存分配中,刚好分配到了这块内存,就会造成崩溃。

    而assign修饰基本数据类型或oc数据类型,因为基本数据类型是分配在栈上的,由系统分配和释放,所以不会造成野指针。

    62、id类型的指针为什么可以指向任意类型?

    id是一个比较灵活的对象指针,并且是一个指向任何一个继承自Object(或者NSObject)类的对象,而在cocoa的开发环境里,NSObject是所有类的根类,所以id可以指向任何一个cocoa的合法对象。

    typedef struct objc_object {
     Class isa;
    } *id;复制代码
    

    id和NSObject的区别:

    NSObject是一个静态数据类型,id是一个动态数据类型,默认情况下所有的数据类型都是静态数据类型。

    63、load和initalize

    + (void)load;
    1.对于加入运行期系统的类以及分类,必定会调用此方法,且仅调用一次。
    2.iOS会在应用程序启动的时候调用load方法,在main函数之前调用
    3.执行子类的load方法前,会先执行所有超类的load方法,顺序为父类->子类->分类
    4.load方法不遵从继承规则,如果类本身没有实现load方法,那么系统就不会调用,不管父类有没有实现
    5.尽可能的精简load方法,因为整个应用程序在执行load方法时会阻塞,即,程序会阻塞知道所有的load方法执行完毕,才会继续
    7.load方法中最常用的就是方法交换 method swizzling复制代码
    
    + (void)initialize;
    1.在首次使用该类之前有运行期系统(非人为)调用,且仅调用一次
    2.惰性调用,只有当程序使用相关类时,才会调用
    3.如果类未实现initialize方法,而其超类实现了,那么会运行超类的实现代码,且会运行两次,且第一次打印出来是父类,第二次打印出来是子类
    4.initialize遵循继承规则
    5.初始化子类的时候会优先初始化父类,然后调用父类的initialize方法,而子类没有覆写initialize方法,因此会再次调用父类方法
    复制代码
    

    64、深入解构objc_msgSend函数的实现

    通常情况下每个OC对象的最开始处都有一个隐藏的数据成员isa,isa保存有类的描述信息(包含方法数组列表和缓存)

    struct objc_class {    
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__    
    
    Class _Nullable super_class                              
    OBJC2_UNAVAILABLE;    
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;    
    long version                                             OBJC2_UNAVAILABLE;    
    long info                                                OBJC2_UNAVAILABLE;    
    long instance_size                                       OBJC2_UNAVAILABLE;    
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;    
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;    
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;    
    struct objc_protocol_list * _Nullable protocols          
    OBJC2_UNAVAILABLE;#endif
    } OBJC2_UNAVAILABLE;复制代码
    

    通过isa指针,去objc_cache里面查找是否有缓存的方法,如果有,则直接调用,如果没有则去objc_method_list里面去寻找对应的方法的实现,如果再找不到,就进入到消息转发的阶段了。

    65、AFNetworking3.0后为什么不再需要常驻线程?

    AF2.x为什么需要常驻线程:

    AF2.x 首先需要在子线程去start connection,请求发送成功后,所在的子线程需要保活以保证正常接收到NSURLConnectionDelegate回调方法。如果每来一个请求就开辟一条线程,并且保活线程,这样开销就太大了。所以只需要保活一条固定的线程,在这个线程里发起请求,接收回调。

    AF3.x为什么不需要常驻线程?

    NSURLSession发起的请求,不再需要在当前线程进行代理方法的回调,可以指定回调的delegateQueue,这样我们就不用为了等待代理回调方法而苦苦保活线程了。

    原文链接:iOS 面试 完整版

    相关文章

      网友评论

        本文标题:iOS 面试 完整版

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