欢迎大家来到IT世界,在知识的湖畔探索吧!
1、字符串常用方法
NSString *strSub = [str substringFormIndex:2];
NSString *strSubT = [str substringToIndex:2];
NSString *strSubR = [str substringWithRange:range];
欢迎大家来到IT世界,在知识的湖畔探索吧!
字符串替换
欢迎大家来到IT世界,在知识的湖畔探索吧!NSString *newStr = [str stringByReplacingOccurencesOfString:@"ll" withString:@"al"];
编码,把UTF8编码的字符串编码成URL中可用的字符串
url_cn = [url_cn stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
字符串转数组
欢迎大家来到IT世界,在知识的湖畔探索吧!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是在平时编码过程中最常用到的。存放在堆中需要我们自己进行内存管理。
__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(不透明)
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并把它赋值给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函数的执行前后都分别说一下)
mach-O
哪些名词指的是Mach-O
- Executable 可执行文件
- Dylib 动态库
- Bundle 无法被连接的动态库,只能通过dlopen()加载
- Image 指的是Executable,Dylib或者Bundle的一种,文中会多次使用Image这个名词。
- Framework 动态库和对应的头文件和资源文件的集合
- Apple出品的操作系统的可执行文件格式几乎都是mach-O,iOS当然也不例外。mach-o可以大致分为三部分:
Header头部,包含可以执行的CPU架构,比如x86,arm64
们用MachOView打开Demo工程的可以执行文件,来验证下mach-o的文件布局:
中分析的mach-o文件来源于PullToRefreshKit。这是一个纯Swift的编写的工程。
那么Data部分又包含那些segment呢?绝大多数mach-o包括以下三个阶段(支持用户自定义Segment,但是很少使用)
- __TEXT代码段,只读,包含函数,和只读的字符串,上图中类似__TEXT,__text的都是代码段
- __Data数据段,读写,包括可读写的全局变量等,__DATA,__data都是数据段
- __LINKEDIT包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。
关于mach-o更多细节,可以看看文档:《Mac OS X ABI Mach-O File Format Reference》
启动过程
使用dyld2启动应用的过程如图:
大致的过程如下:
加载dyld到App进程
加载动态库(包括所依赖的所有动态库)
Rebase
Bind
初始化Objective C Runtime
其它的初始化代码
加载动态库
dyld会首先读取mach-o文件的Header和load commands。
接着就知道了这个可执行文件依赖的动态库。例如加载动态库A到内存,接着检查A所依赖的动态库,就这样的递归加载,直到所有的动态库加载完毕。通常一个App所依赖的动态库在100-400个左右,其中大多数都是系统的动态库,它们会被缓存到dyld shared cache,这样读取的效率会很高。
dyld会首先读取mach-o文件的Header和load commands。
接着就知道了这个可执行文件依赖的动态库。例如加载动态库A到内存,接着检查A所依赖的动态库,就这样的递归加载,直到所有的动态库加载完毕。通常一个App所依赖的动态库在100-400个左右,其中大多数都是系统的动态库,它们会被缓存到dyld shared cache,这样读取的效率会很高。
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。
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:加载图片
- 加载到内存中后,会一直停留在内存中,不会随着对象销毁而销毁
- 加载进图片后,占用的内存归系统管理,我们无法管理
- 相同的图片,图片不会重新加载
- 加载到内存中后,占据内存空间较大
使用 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,这样我们就不用为了等待代理回调方法而苦苦保活线程了。
掘金-风筝整理-搞定技术拦路虎
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/49878.html