作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发公众号:编程大鑫,不管你是大牛还是小白都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)
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是在平时编码过程中最常用到的。存放在堆中需要我们自己进行内存管理。
__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删除
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
1》当没有外部变量时,block为__NSMallocBlock,它由开发者创建,存储在堆内存上。
2》当有__weak
修饰时block为__NSStackBlock,存储在栈区。
3》当block有参数时(捕获了外部变量时)block为__NSGlobalBlock,存储在全局区。
4》block的本质是一个结构体。
5》在block内部使用外部指针且会造循环引用情况下,需要使用__weak修饰外部指针
6》在block内部如果调用了延时函数还是用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。
7》如果需要在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)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且改变自己的而行为。
要注意术语“反射”和“内省”的关系:内省机制仅指程序在运行时对自身信息的检测;反射机制不仅包括要能在运行时对程序自身信息进行检测,还要求程序能进一步根据这些信息改变程序状态或结构。
一个重点是改变,一个重点是检测。
比如通过类名,生成类 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函数的执行前后都分别说一下)
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:加载图片
- 加载到内存中后,会一直停留在内存中,不会随着对象销毁而销毁
- 加载进图片后,占用的内存归系统管理,我们无法管理
- 相同的图片,图片不会重新加载
- 加载到内存中后,占据内存空间较大
使用 imageWithContentOfFile:加载图片
- 加载到内存中后,占据内存空间比较小
- 相同的图片会被重复加载到内存中
- 对象销毁的时候,加载到内存中得图片会被一起销毁
结论:如果图片较小,频繁使用的图片,使用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开发交流群:130595548,不管你是大牛还是小白都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)