iOS 面试 百题基础训练-机会是给有准备的人

iOS 面试 百题基础训练-机会是给有准备的人1 字符串常用方法 NSString strSub str substringFor 2 NSString strSubT str substringToI 2 NSString strSubR

大家好,欢迎来到IT知识分享网。

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

》我们只能在继承了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开发中的锁

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

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是在平时编码过程中最常用到的。存放在堆中需要我们自己进行内存管理。 
iOS 面试 百题基础训练-机会是给有准备的人

__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的区别

》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删除

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(不透明)

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并把它赋值给UIViewController的view属性。

[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)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且改变自己的而行为。

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

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

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函数的执行前后都分别说一下)

mach-O

哪些名词指的是Mach-O

  • Executable 可执行文件
  • Dylib 动态库
  • Bundle 无法被连接的动态库,只能通过dlopen()加载
  • Image 指的是Executable,Dylib或者Bundle的一种,文中会多次使用Image这个名词。
  • Framework 动态库和对应的头文件和资源文件的集合
  • Apple出品的操作系统的可执行文件格式几乎都是mach-O,iOS当然也不例外。mach-o可以大致分为三部分:
iOS 面试 百题基础训练-机会是给有准备的人

Header头部,包含可以执行的CPU架构,比如x86,arm64

  • Load commands 加载命令,包含文件的组织架构和在虚拟内存中的布局方式
  • Data,数据,包含load commands中需要的各个段(segment)的数据,每个Segment的大小都是Page的整数倍。
  • 们用MachOView打开Demo工程的可以执行文件,来验证下mach-o的文件布局:

    • __TEXT代码段,只读,包含函数,和只读的字符串,上图中类似__TEXT,__text的都是代码段
    • __Data数据段,读写,包括可读写的全局变量等,__DATA,__data都是数据段
    • __LINKEDIT包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。
      关于mach-o更多细节,可以看看文档:《Mac OS X ABI Mach-O File Format Reference》
    iOS 面试 百题基础训练-机会是给有准备的人

    启动过程

    使用dyld2启动应用的过程如图:

    iOS 面试 百题基础训练-机会是给有准备的人

    大致的过程如下:

    加载dyld到App进程 加载动态库(包括所依赖的所有动态库) Rebase Bind 初始化Objective C Runtime 其它的初始化代码

    加载动态库

     192:Desktop Leo$ otool -L demo demo: @rpath/PullToRefreshKit.framework/PullToRefreshKit (compatibility version 1.0.0, current version 1.0.0) /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1444.12.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) @rpath/libswiftCore.dylib (compatibility version 1.0.0, current version 900.0.65) @rpath/libswiftCoreAudio.dylib (compatibility version 1.0.0, current version 900.0.65) //...

    启动时间

    冷启动 VS 热启动

    如果你刚刚启动过App,这时候App的启动所需要的数据仍然在缓存中,再次启动的时候称为热启动。如果设备刚刚重启,然后启动App,这时候称为冷启动。

    启动时间在小于400ms是最佳的,因为从点击图标到显示Launch Screen,到Launch Screen消失这段时间是400ms。启动时间不可以大于20s,否则会被系统杀掉。

    在Xcode中,可以通过设置环境变量来查看App的启动时间,DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS。

    iOS 面试 百题基础训练-机会是给有准备的人

    Total pre-main time: 43.00 milliseconds (100.0%) dylib loading time: 19.01 milliseconds (44.2%) rebase/binding time: 1.77 milliseconds (4.1%) ObjC setup time: 3.98 milliseconds (9.2%) initializer time: 18.17 milliseconds (42.2%) slowest intializers : libSystem.B.dylib : 2.56 milliseconds (5.9%) libBacktraceRecording.dylib : 3.00 milliseconds (6.9%) libMainThreadChecker.dylib : 8.26 milliseconds (19.2%) ModelIO : 1.37 milliseconds (3.1%)Total pre-main time: 43.00 milliseconds (100.0%) dylib loading time: 19.01 milliseconds (44.2%) rebase/binding time: 1.77 milliseconds (4.1%) ObjC setup time: 3.98 milliseconds (9.2%) initializer time: 18.17 milliseconds (42.2%) slowest intializers : libSystem.B.dylib : 2.56 milliseconds (5.9%) libBacktraceRecording.dylib : 3.00 milliseconds (6.9%) libMainThreadChecker.dylib : 8.26 milliseconds (19.2%) ModelIO : 1.37 milliseconds (3.1%)

    对于这个libMainThreadChecker.dylib估计很多同学会有点陌生,这是XCode 9新增的动态库,用来做主线检查的。

    优化启动时间

    启动时间这个名词,不同的人有不同的定义。在我看来,

    启动时间是用户点击App图标,到第一个界面展示的时间。

    以main函数作为分水岭,启动时间其实包括了两部分:main函数之前和main函数到第一个界面的viewDidAppear:。所以,优化也是从两个方面进行的,个人建议优先优化后者,因为绝大多数App的瓶颈在自己的代码里。

    Main函数之后

    我们首先来分析下,从main函数开始执行,到你的第一个界面显示,这期间一般会做哪些事情。

    • 执行AppDelegate的代理方法,主要是didFinishLaunchingWithOptions
    • 初始化Window,初始化基础的ViewController结构(一般是UINavigationController+UITabViewController)
    • 获取数据(Local DB/Network),展示给用户。

    UIViewController

    延迟初始化那些不必要的UIViewController。
    在启动的时候只需要初始化首页的头条页面即可。像“要闻”,“我的”等页面,则延迟加载,即启动的时候只是一个UIViewController作为占位符给TabController,等到用户点击了再去进行真正的数据和视图的初始化工作。

    AppDelegate

    通常我们会在AppDelegate的代理方法里进行初始化工作,主要包括了两个方法:

    • didFinishLaunchingWithOptions
    • applicationDidBecomeActive

    优化这些初始化的核心思想就是:

    能延迟初始化的尽量延迟初始化,不能延迟初始化的尽量放到后台初始化。

    这些工作主要可以分为几类:

    • 三方SDK初始化,比如Crash统计; 像分享之类的,可以等到第一次调用再出初始化。
    • 初始化某些基础服务,比如WatchDog,远程参数。
    • 启动相关日志,日志往往涉及到DB操作,一定要放到后台去做
    • 业务方初始化,这个交由每个业务自己去控制初始化时间。

    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,这样我们就不用为了等待代理回调方法而苦苦保活线程了。

    掘金-风筝整理-搞定技术拦路虎

    免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/164237.html

    (0)
    上一篇 2024-12-24 14:33
    下一篇 2024-12-24 15:00

    相关推荐

    发表回复

    您的邮箱地址不会被公开。 必填项已用 * 标注

    关注微信