目录
- 1、block的本质
- 2、可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?
- 3、关于nonatomic和atomic
- 4、objc在向一个对象发送消息时,发生了什么?
- 5、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
- 6 为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
- 7 有没有用过运行时,用它都能做什么?
- 8、frame和bounds有什么不同,如果修改其中的值,会有什么变化?
- 9、load和initialize的区别
- 10、怎么交换两个变量的值?不用临时变量呢?
- 11、简单介绍一下KVC和KVO,他们都可以应用在哪些场景?
- 12、浅复制和深复制的区别
- 13、__block和__weak修饰符的区别是什么?
- 14、a,b任务并发执行,然后执行c任务怎么做,有哪些方法实现
- 15、objc_msgSend(消息传递)
- 16、这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr
- 17、KVC的底层实现
- 18、KVO
- 19、下面的代码输出什么?(关于self和super)
- 20、isKindOfClass、isMemberOfClass、selector作用分别是什么
- 21、如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图(如何用GCD同步若干个异步调用)
- 22、以下代码运行结果如何?(多线程死锁)
- 23、谈谈 UITableView 的优化
- 24、如何实行cell的动态的行高
- 25、runtime中,SEL、Method 和 IMP有什么区别,使用场景?
- 26、常见的内存泄漏有哪些情况?如何排查和避免?
- 27、struct和class的区别
- 28、self和super的区别
- 29、id 和 instanceType 有什么区别?
- 30、nil、NIL、NSNULL 有什么区别?
- 31、Category 的实现原理?
- 32、如何优化 ‘APP’ 的电量?
- 33、说一下调用方法在runtime时是怎么样的?([obj makeText])
- 34、layoutSubviews方法什么时候调用?
- 35、定义一个NSString类型属性时,为什么用copy不用strong?
- 36、子视图超出父视图的部分能看到么?超出的部分有什么影响?
- 37、一个objc对象如何进行内存布局?(考虑有父类的情况)
- 38、SDWebImage缓存机制
- 39、property相关面试题
- 40、OC 如何实现多重继承?
- 41、runtime 如何实现 weak 属性
- 42、iOS的消息机制和消息转发
最近开始找工作,日常记录一些遇到的面试题,可能会有重复发,持续更新、修改,如果那里不对、不准确,欢迎留言,共同进步
1、block的本质
1.1 block的本质
block的本质就是一个object-c对象。
block:存储位置,可能分为3个地方:代码去,堆区、栈区(ARC情况下会自动拷贝到堆区,因此ARC下只能有两个地方:代码去、堆区)
代码区:不访问栈区的变量(如局部变量),且不访问堆区的变量(alloc创建的对象),此时block存放在代码去。
堆区:访问了处于栈区的变量,或者堆区的变量,此时block存放在堆区。–需要注意实际是放在栈区,在ARC情况下会自动拷贝到堆区,如果不是ARC则存放在栈区,所在函数执行完毕就回释放,想再外面调用需要用copy指向它,这样就拷贝到了堆区,strong属性不会拷贝、会造成野指针错区。
1.2 为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?
默认情况下,block里面的变量,拷贝进去的是变量的值,而不是指向变量的内存的指针。
当使用__block修饰后的变量,拷贝到block里面的就是指向变量的指针,所以我们就可以修改变量的值。
1.3 block反向传值
在前一个控制器的touchesBegan:方法内实现如下代码。
// OneViewController.m
TwoViewController *twoVC = [[TwoViewController alloc] init];
twoVC.valueBlcok = ^(NSString *str) {
NSLog(@"OneViewController拿到值:%@", str);
};
[self presentViewController:twoVC animated:YES completion:nil];
// TwoViewController.h (在.h文件中声明一个block属性)
@property (nonatomic ,strong) void(^valueBlcok)(NSString *str);
// TwoViewController.m (在.m文件中实现方法)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 传值:调用block
if (_valueBlcok) {
_valueBlcok(@"123456");
}
}
2、可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?
可变集合类使用copy表示深拷贝,不可变集合类使用copy的时候是浅拷贝。
可变集合类、不可边类型使用mutablecopy表示深拷贝
当是浅拷贝的时候,容器的内容是没有复制的。如果是深拷贝的话,容器的内容都会收到一条copy消息,拷贝出新的内容,从新组成新的容器返回。
3、关于nonatomic和atomic
3.1 区别
nonatomic:表示非原子,不安全,但是效率高。
atomic:表示原子行,安全,但是效率低。
nonatomic的内存管理语义是非原子性的,非原子性的操作本来就是线程不安全的,而atomic的操作是原子性的,但是并不意味着它是线程安全的,它会增加正确的几率,能够更好的避免线程的错误,但是它仍然是线程不安全的。
3.2 atomic是绝对的线程安全么?为什么?
当使用nonatomic的时候,属性的setter,getter操作是非原子性的,所以当多个线程同时对某一属性读和写操作时,属性的最终结果是不能预测的。
当使用atomic时,虽然对属性的读和写是原子性的,但是仍然可能出现线程错误:当线程A进行写操作,这时其他线程的读或者写操作会因为该操作而等待。当A线程的写操作结束后,B线程进行写操作,然后当A线程需要读操作时,却获得了在B线程中的值,这就破坏了线程安全,如果有线程C在A线程读操作前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。
也就是要注意:atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。如下列所示:
比如:@property(atomic,strong)NSMutableArray *arr;
如果一个线程循环的读数据,一个线程循环写数据,那么肯定会产生内存问题,因为这和setter、getter没有关系。如使用[self.arr objectAtIndex:index]就不是线程安全的。
据说,atomic要比nonatomic慢大约20倍
3.3 如果不是,那应该如何实现?
好的解决方案就是加锁
具体请见保证线程安全的方式
4、objc在向一个对象发送消息时,发生了什么?
根据对象的isa指针找到类对象id,在查询类对象里面的methodLists方法函数列表,如果没有在好到,在沿着superClass,寻找父类,再在父类methodLists方法列表里面查询,最终找到SEL,根据id和SEL确认IMP(指针函数),在发送消息;
5、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
1.不能向编译后得到的类增加实例变量
编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或class_setWeaklvarLayout来处理strong weak引用.所以不能向存在的类中添加实例变量
2.能向运行时创建的类中添加实例变量
运行时创建的类是可以添加实例变量,调用class_addIvar函数.但是的在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.
6 为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
nstime对象是在 NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用nstime的消息,我们可以把nsrunloop的模式更改为NSRunLoopCommonModes
7 有没有用过运行时,用它都能做什么?
7.1 交换方式
一般写在类的+(void)load方法里面
/** 获取原始setBackgroundColor方法 */
Method originalM = class_getInstanceMethod([self class], @selector(setBackgroundColor:));
/** 获取自定义的pb_setBackgroundColor方法 */
Method exchangeM = class_getInstanceMethod([self class], @selector(pb_setBackgroundColor:));
/** 交换方法 */
method_exchangeImplementations(originalM, exchangeM);
7.2 创建类
Class MyClass = objc_allocateClassPair([NSObject class], "Person", 0);
7.3 添加方法
/**参数一、类名参数
二、SEL 添加的方法名字参数
三、IMP指针 (IMP就是Implementation的缩写,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP)
参数四、其中types参数为"i@:@“,按顺序分别表示:具体类型可参照[官方文档](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)i 返回值类型int,若是v则表示void@ 参数id(self): SEL(_cmd)@ id(str)
V@:表示返回值是void 带有SEL参数 (An object (whether statically typed or typed id))
*/
class_addMethod(Person, @selector(addMethodForMyClass:), (IMP)addMethodForMyClass, "V@:");
7.4 添加实例变量
/**参数一、类名参数
二、属性名称参数
三、开辟字节长度参数
四、对其方式参数
五、参数类型 “@” 官方解释 An object (whether statically typed or typed id) (对象 静态类型或者id类型) 具体类型可参照[官方文档](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)return: BOOL 是否添加成功
*/
BOOL isSuccess = class_addIvar(Person, "name", sizeof(NSString *), 0, "@");
isSuccess?NSLog(@"添加变量成功"):NSLog(@"添加变量失败");
8、frame和bounds有什么不同,如果修改其中的值,会有什么变化?
9、load和initialize的区别
10、怎么交换两个变量的值?不用临时变量呢?
方法1
int a,b;
a = 10;b = 12;
a = b - a; // a=2;b=12
b = b - a; // a=2;b=10
a = b + a; // a=10;b=10
方法2
int a=10,b=12; // a=1010^b=1100;
a = a^b; // a=0110^b=1100;
b = a^b; // a=0110^b=1010;
a = a^b; // a=1100=12;b=1010;
11、简单介绍一下KVC和KVO,他们都可以应用在哪些场景?
KVO:键值监听,观察某一属性的方法
KVC:键值编码,是一种间接访问对象的属性
12、浅复制和深复制的区别
在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误!
13、__block和__weak修饰符的区别是什么?
1,在MRC时代,__block修饰,可以避免循环引用;ARC时代,__block修饰,同样会引起循环引用问题;
2,__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型;
3,__weak只能在ARC模式下使用,也只能修饰对象,不能修饰基本数据类型;
4,__block对象可以在block中被重新赋值,__weak不可以;
14、a,b任务并发执行,然后执行c任务怎么做,有哪些方法实现
- dispatch_group_async(A),dispatch_group_async(B),dispatch_group_notify©
- operation设置任务C依赖A,依赖B
- 对C加栅栏
-(void)testBarrier{
dispatch_queue_t osConcurrent = dispatch_queue_create("dyConcurrent1", DISPATCH_QUEUE_CONCURRENT); //自己创建并行队列
dispatch_async(osConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任务1");
}
});
dispatch_async(osConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任务2");
}
});
dispatch_barrier_async(osConcurrent, ^{
//这边一定要是自己创建的并行队列,系统global无效
NSLog(@"追加追加追加");
});
dispatch_async(osConcurrent, ^{
for (int i = 0; i< 3; i++) {
NSLog(@"任务3");
}
});
}
输出:
任务1、2并发执行,执行完执行追加追加,再执行任务3
一个dispatch_barrier允许你在一个并行队列中创建一个同步点。当在队列中遇到这个barrier时,这个barrier block便会延迟执行(同时所有在其后的block都会延迟),直至所有在barrier之前的block执行完成。这时,这个barrier block便会执行。之后队列便恢复正常执行。
15、objc_msgSend(消息传递)
objc_msgSend叫做消息传递,消息有名称或选择子,可以接受参数
Runtime时执行的流程是这样的:
一个对象的方法像这样[obj foo],编译器转成消息发送objc_msgSend(obj, foo),Runtime时执行的流程是这样的:
首先,通过obj的isa指针找到它的 class ;
在 class 的 method list 找 foo ;
如果 class 中没到 foo,继续往它的 superclass 中找 ;
一旦找到 foo 这个函数,就去执行它的实现IMP 。
16、这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr
问题:添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。
//如:-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
// copy后返回的是不可变对象(即 arr 是 NSArray 类型,NSArray 类型对象不能调用 NSMutableArray 类型对象的方法)
原因:是因为 copy 就是复制一个不可变 NSArray 的对象,不能对 NSArray 对象进行添加/修改。
17、KVC的底层实现
当一个对象调用setValue方法时,方法内部会做以下操作:
1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。
18、KVO
KVO是通过isa-swizzling技术实现的(这句话是整个KVO实现的重点)。
在运行时根据原类创建一个中间类NSKVONotifying_***,这个中间类是原类的子类,并动态修改当前对象的isa指向中间类。
并且将class方法重写,返回原类的Class。
19、下面的代码输出什么?(关于self和super)
@implementation Son : Father
- (id)init {
if (self = [super init]) {
NSLog(@"%@", NSStringFromClass([self class])); // Son
NSLog(@"%@", NSStringFromClass([super class])); // Son
}
return self;
}
@end
self 是类的隐藏参数,指向当前调用方法的这个类的实例。
super是一个Magic Keyword,它本质是一个编译器标示符,和self是指向的同一个消息接收者。
不同的是:super会告诉编译器,调用class这个方法时,要去父类的方法,而不是本类里的。
上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *obj 这个对象。
20、isKindOfClass、isMemberOfClass、selector作用分别是什么
isKindOfClass:作用是某个对象属于某个类型或者继承自某类型。
isMemberOfClass:某个对象确切属于某个类型。
selector:通过方法名,获取在内存中的函数的入口地址。
21、如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图(如何用GCD同步若干个异步调用)
// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
/*加载图片1 */
});
dispatch_group_async(group, queue, ^{
/*加载图片2 */
});
dispatch_group_async(group, queue, ^{
/*加载图片3 */
});
// 当并发队列组中的任务执行完毕后才会执行这里的代码
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合并图片
});
22、以下代码运行结果如何?(多线程死锁)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
// 只输出:1。(主线程死锁)
原因是 viewDidLoad 和 dispatch_sync(dispatch_get_main_queue() 之间存在队列等待, viewDidLoad 方法是在串行队列优先执行完,而GCD的闭包要等到 viewDidLoad 执行完才能执行完,而 NSLog(@“3”); 要执行要先等GCD的闭包 执行完,相互等待,死锁
23、谈谈 UITableView 的优化
1). 正确的复用cell;
2). 设计统一规格的Cell;
3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
5). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
6). 减少子视图的层级关系;
7). 尽量使所有的视图不透明化以及做切圆操作;
8). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示;
9). 使用调试工具分析问题。
24、如何实行cell的动态的行高
如果希望每条数据显示自身的行高,必须设置两个属性,1.预估行高,2.自定义行高。
设置预估行高 tableView.estimatedRowHeight = 200。
设置定义行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。
如果要让自定义行高有效,必须让容器视图有一个自下而上的约束。
25、runtime中,SEL、Method 和 IMP有什么区别,使用场景?
它们之间的关系可以这么解释:一个类(Class)持有一个分发表,在运行期分发消息,表中的每一个实体代表一个方法(Method),它的名字叫做选择子(SEL),对应着一种方法实现(IMP)。
具体的分析如下
- SEL
定义:
typedef struct objc_selector *SEL
代表方法的名称。仅以名字来识别。翻译成中文叫做选择子或者选择器,选择子代表方法在 Runtime 期间的标识符。为 SEL 类型,虽然 SEL 是 objc_selector 结构体指针,但实际上它只是一个 C 字符串。在类加载的时候,编译器会生成与方法相对应的选择子,并注册到 Objective-C 的 Runtime 运行系统。不论两个类是否存在依存关系,只要他们拥有相同的方法名,那么他们的SEL都是相同的。比如,有n个viewcontroller页面,每个页面都有一个viewdidload,每个页面的载入,肯定都是不尽相同的。但是我们可以通过打印,观察发现,这些viewdidload的SEL都是同一个
SEL sel = @selector(methodName); // 方法名字
NSLog(@"address = %p",sel);// log输出为 address = 0x1df807e29
因此类方法定义时,尽量不要用相同的名字,就算是变量类型不同也不行。否则会引起重复,例如:
-(void)setWidth:(int)width;
-(void)setWidth:(double)width;
- IMP
定义:
typedef id (*IMP)(id, SEL, ...)
代表函数指针,即函数执行的入口。该函数使用标准的 C 调用。第一个参数指向 self(它代表当前类实例的地址,如果是类则指向的是它的元类),作为消息的接受者;第二个参数代表方法的选择子;… 代表可选参数,前面的 id 代表返回值。
- Method:
定义:
typedef struct objc_method *Method
Method 对开发者来说是一种不透明的类型,被隐藏在我们平时书写的类或对象的方法背后。它是一个 objc_method 结构体指针,我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。 objc_method 的定义为:
/// Method
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
- 方法名 method_name 类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。 方法类型
- method_types 是个 char 指针,其实存储着方法的参数类型和返回值类型,即是 Type Encoding 编码。
- method_imp 指向方法的实现,本质上是一个函数的指针,就是前面讲到的 Implementation。
26、常见的内存泄漏有哪些情况?如何排查和避免?
内存泄漏原理:程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
26.1、常见的内存泄漏情况
1、对象之间的循环引用问题
循环引用的实质:多个对象相互之间有强引用,不能施放让系统回收。
解决办法:使用 weak 打破对象之间的相互强引用
2、block的循环引用 block在copy时都会对block内部用到的对象进行强引用的。
解决办法使用:使用__weak打破循环的方法只在 ARC 下才有效,在 MRC 下应该使用__block
__weak typeof(self) weakSelf = self; self.myBlock = ^() {
// 除了下面的还有 调用 self的一些属性等等 [weakSelf doSomething]
};
3、 delegate 的循环引用 delegate是委托模式.
委托模式是将一件属于委托者做的事情,交给另外一个被委托者来处理,在这里我们可能会出现委托者和被委托人之间的相互强引用问题;
解决办法:在声明 delegate 属性的时候 用weak 进行弱引用 或者 通过中间对象(代理对象)的方式来解决(效率更加高的中间对象NSProxy:不需要进行发送消息和再动态解析,直接进行消息转发)
@property(nonatomic, weak) id delegate;
4、CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用;
解决办法:NSTimer 有一个block的方法,我们可以利用block的弱指针来解决__weak typeof(self) weakSelf = self; 传 weakSelf 进去
5、通知的循环引用 iOS9 以后,一般的通知,都不再需要手动移除观察者,系统会自动在dealloc 的时候调用 [[NSNotificationCenter defaultCenter] removeObserver: self]。iOS9 以前的需要手动进行移除。
原因是:iOS9 以前观察者注册时,通知中心并不会对观察者对象做 retain 操作,而是进行了 unsafe_unretained 引用,所以在观察者被回收的时候,如果不对通知进行手动移除,那么指针指向被回收的内存区域就会成为野指针,这时再发送通知,便会造成程序崩溃。从 iOS9 开始通知中心会对观察者进行 weak 弱引用,这时即使不对通知进行手动移除,指针也会在观察者被回收后自动置空,这时再发送通知,向空指针发送消息是不会有问题的。建议最好加上移除通知的操作:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self.observer name:@"name" object:nil];
}
6、WKWebView 造成的内存泄漏
总的来说,WKWebView 不管是性能还是功能,都要比 UIWebView 强大很多,本身也不存在内存泄漏问题,但是,如果开发者使用不当,还是会造成内存泄漏。请看下面这段代码:
@property (nonatomic, strong) WKWebView *wkWebView;
- (void)webviewMemoryLeak {
WKWebViewConfiguration config = [[WKWebViewConfiguration alloc] init];
config.userContentController = [[WKUserContentController alloc] init];
[config.userContentController addScriptMessageHandler:self name:@"WKWebViewHandler"];
_wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
_wkWebView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:_wkWebView]; NSURLRequest requset = [NSURLRequest requestWithURL:
[NSURL URLWithString:@"https://www.baidu.com"]];
[_wkWebView loadRequest:requset];
}
这样看起来没有问题,但是其实 “addScriptMessageHandler” 这个操作,导致了 wkWebView 对 self 进行了强引用,然后 “addSubview”这个操作,也让 self 对 wkWebView 进行了强引用,这就造成了循环引用。解决方法就是在合适的机会里对 “MessageHandler” 进行移除操作:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"WKWebViewHandler"];
}
26.2、内存泄漏的查询
1、Analyze 静态分析 (command + shift + b)也就是编译,主要分析以下四种问题:
逻辑错误:访问空指针或未初始化的变量等;
内存管理错误:如内存泄漏等;
声明错误:从未使用过的变量;
Api调用错误:未包含使用的库和框架。
2、Instruments中的Leak动态分析内存泄漏,product->profile ->leaks 打开工具主窗口
3、Facebook早已开源了一款检测内存问题的三方库FBRetainCycleDetector
27、struct和class的区别
类: 引用类型(位于栈上面的指针(引用)和位于堆上的实体对象)
结构:值类型(实例直接位于栈中)
28、self和super的区别
- self调用自己方法,super调用父类方法
- self是类,super是预编译指令
- [self class] 和 [super class] 输出是一样的
- self和super底层实现原理
1.当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;
而当使用 super 时,则从父类的方法列表中开始找,然后调用父类的这个方法
2.当使用 self 调用时,会使用 objc_msgSend 函数:
id objc_msgSend(id theReceiver, SEL theSelector, ...)
第一个参数是消息接收者,第二个参数是调用的具体类方法的 selector,后面是 selector 方法的可变参数。以 [self setName:] 为例,编译器会替换成调用 objc_msgSend 的函数调用,其中 theReceiver 是 self,theSelector 是 @selector(setName:),这个 selector 是从当前 self 的 class 的方法列表开始找的 setName,当找到后把对应的 selector 传递过去。
3.当使用 super 调用时,会使用 objc_msgSendSuper 函数:
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一个参数是个objc_super的结构体,第二个参数还是类似上面的类方法的selector
struct objc_super {
id receiver;
Class superClass;
};
29、id 和 instanceType 有什么区别?
相同点
instancetype 和 id 都是万能指针,指向对象。
不同点:
1.id 在编译的时候不能判断对象的真实类型,instancetype 在编译的时候可以判断对象的真实类型。
2.id 可以用来定义变量,可以作为返回值类型,可以作为形参类型;instancetype 只能作为返回值类型。
30、nil、NIL、NSNULL 有什么区别?
- nil、NIL 可以说是等价的,都代表内存中一块空地址
- NSNULL 代表一个指向 nil 的对象。
31、Category 的实现原理?
- Category 实际上是 Category_t的结构体,在运行时,新添加的方法,都被以倒序插入到原有方法列表的最前面,所以不同的Category,添加了同一个方法,执行的实际上是最后一个。
- Category 在刚刚编译完的时候,和原来的类是分开的,只有在程序运行起来后,通过 Runtime ,Category 和原来的类才会合并到一起。
32、如何优化 ‘APP’ 的电量?
1、程序的耗电主要在以下四个方面:
CPU 处理
定位
网络
图像
2、优化的途径主要体现在以下几个方面:
尽可能降低 CPU、GPU 的功耗。
尽量少用 定时器。
优化 I/O 操作。
3、不要频繁写入小数据,而是积攒到一定数量再写入
读写大量的数据可以使用 Dispatch_io ,GCD 内部已经做了优化。
数据量比较大时,建议使用数据库
4、网络方面的优化
减少压缩网络数据 (XML -> JSON -> ProtoBuf),如果可能建议使用 ProtoBuf。
如果请求的返回数据相同,可以使用 NSCache 进行缓存
使用断点续传,避免因网络失败后要重新下载。
网络不可用的时候,不尝试进行网络请求
长时间的网络请求,要提供可以取消的操作
采取批量传输。下载视频流的时候,尽量一大块一大块的进行下载,广告可以一次下载多个
5、定位层面的优化
如果只是需要快速确定用户位置,最好用 CLLocationManager 的 requestLocation 方法。定位完成后,会自动让定位硬件断电
如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务
尽量降低定位精度,比如尽量不要使用精度最高的 kCLLocationAccuracyBest
需要后台定位时,尽量设置 pausesLocationUpdatesAutomatically 为 YES,如果用户不太可能移动的时候系统会自动暂停位置更新
尽量不要使用 startMonitoringSignificantLocationChanges,优先考虑 startMonitoringForRegion:
6、硬件检测优化
用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件
33、说一下调用方法在runtime时是怎么样的?([obj makeText])
[obj makeText];
其中obj是一个对象,makeText是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成
objc_msgSend(obj,@selector(makeText));
在objc_msgSend函数中,首先通过obj的isa指针找到obj对应的class。
在Class中先去cache中 通过SEL查找对应函数method(猜测cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),
若 cache中未找到,再去methodList中查找,
若methodlist中未找到,则取superClass中查找。
若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
34、layoutSubviews方法什么时候调用?
1、init方法不会调用
2、addSubview方法等时候会调用
3、bounds改变的时候调用
4、scrollView滚动的时候会调用scrollView的layoutSubviews方法(所以不建议在scrollView的layoutSubviews方法中做复杂逻辑)
5、旋转设备的时候调用
6、子视图被移除的时候调用
35、定义一个NSString类型属性时,为什么用copy不用strong?
NSString类型属性是一个指针,定义成copy,操作是拷贝一份等同的对象,这个指针指向新生成的拷贝对象。
当使用copy时,这个拷贝的对象无论是拷贝自NSString还是NSMutableString结果都是不可变的NSString。
而如果用Strong,则指向一个字符串对象,若指向的是一个NSMutableString,则当指向的对象改变时,属性值也会发生相应改变,导致错误,因为是一个不可变字符串
36、子视图超出父视图的部分能看到么?超出的部分有什么影响?
子视图超出父视图的部分能看到。但是超出的部分不能响应事件
想让超出的部分响应事件,就该写父视图的hitTest方法。判断触碰区域是否在子视图内,如果在子视图内,则返回子视图。让子视图去响应事件。
37、一个objc对象如何进行内存布局?(考虑有父类的情况)
所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中.
每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的
- 对象方法列表(对象能够接收的消息列表,保存在它所对应的类对象中)
- 成员变量的列表
- 属性列表
它内部也有一个isa指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向他的父类对象。
- 根对象就是NSobject,它的superclass指针指向nil。
- 类对象既然称为对象,那它也是一个实例。类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表,根元类的isa指针指向自己,superclass指针指向NSObject类。
38、SDWebImage缓存机制
1.先显示placeholderimage(占位图片),再根据URL来在本地查找图片
2.从缓存中根据cachekey来查找图片是否已经在缓存当中
3.如果缓存中已经有图片缓存,进行回调并将图片展示
4.如果内存中没有该图片的缓存,那么生成NSInvocationOperation添加到队列,从硬盘中查找图片是否已经被下载。
5.根据URLkey在硬盘缓存目录下尝试读取图片文件,这一步是在NSOperation在操作,所以要回到主线程进行结果回调。
6.如果上一步操作从硬盘中读取到了图片,那么将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存),然后进行回调展示图片
7.如果从硬盘缓存目录读取不到图片,那就说明所有的缓存中都不存在该图片,那么需要下载图片
8.共享或者创建一个下载器,开始下载图片。
9.图片下载由NSURLConnection来做,实现相关的delegate来判断图片的下载状态,下载中、下载完成、下载失败。
10利用imageIO做按图片下载进度加载效果
11.数据下载完成后交给SDWebImageDecoder做图片解码处理。
12.图片解码处理在一个NSOperationQueue
完成,不会拖慢主线程的UI,所有如果有需要对下载的图片进行二次处理时,最哈也在这里完成,效率会好很多。
13.当主线程中宣告解码按成 之后,回调给下载器,下载器再回调给回调给 SDWebImageManager,告知图片下载完成。
14.通知所有的下载器下载完成,回调给所需要的地方来进行图片的展示。
15.将图片保存到到SDImageCache中,内存缓存和硬盘缓存都要保存。
16.写文件到硬盘在单独的NSInvocationOperation中进行,避免拖慢主线程。
39、property相关面试题
39.1、strong与retain
相同点:strong和retain都是针对对象类型进行内存管理。如果去修饰基本数据类型,Xcode会直接报错,当给对象类型使用此修饰符时,setter方法先将旧的对象属性release掉,再将新的对象赋值给属性并对该对象进行一次retain操作,两者都会增加对象的引用计数。
不同点:strong一般用于ARC,retain一般用于MRC环境。
39.2、assgin与weak
相同点:assgin和weak不会牵扯到内存管理,不会增加引用计数
不同点:assign可修饰基本数据类型,也可修饰OC对象,但如果修饰对象类型指向的是一个强指针,当它指向的这个指针释放后,他仍指向这块内存,必须手动给置为nil,否则就会产生野指针,如果还通过此指针操作那块内存,便会导致EXC_BAD_ACCESS错误,调用了已经释放的内存空间;而weak只能修饰OC对象,且相比assign比较安全,如果指向的对象消失了,那么他会自动置为nil,不会产生野指针。
39.3、strong与copy
在不可变对象之间进行转换,strong与copy作用是一样的,但是如果在不可变与可变之间进行操作,那么楼主比较推荐copy,这也就是为什么很多地方用copy,而不是strong修饰NSString,NSArray等存在可变不可变之分的类对象了,避免出现意外的数据操作.
39.4、修饰block为什么要用copy修饰?
(1)block内部没有调用外部局部变量时存放在全局区(ARC和MRC下均是)
(2)block使用了外部局部变量,这种情况也正是我们平时所常用的方式。MRC:Block的内存地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.所以在使用Block属性时使用copy修饰。但是ARC中的Block都会在堆上的,系统会默认对Block进行copy操作
(3)用copy,strong修饰block在ARC和MRC都是可以的,都是在堆区
40、OC 如何实现多重继承?
Object-c的类没有多继承,只支持单继承,如果要实现多继承的话,可使用如下几种方式间接实现
- 通过组合实现
A和B组合,作为C类的组件 - 通过协议实现
C类实现A和B类的协议方法 - 消息转发实现
forwardInvocation:方法
41、runtime 如何实现 weak 属性
weak 此特质表明该属性定义了一种「非拥有关系」(nonowning relationship)。为这种属性设置新值时,设置方法既不持有新值(新指向的对象),也不释放旧值(原来指向的对象)。
runtime 对注册的类,会进行内存布局,从一个粗粒度的概念上来讲,这时候会有一个 hash 表,这是一个全局表,表中是用 weak 指向的对象内存地址作为 key,用所有指向该对象的 weak 指针表作为 value。当此对象的引用计数为 0 的时候会 dealloc,假如该对象内存地址是 a,那么就会以 a 为 key,在这个 weak 表中搜索,找到所有以 a 为键的 weak 对象,从而设置为 nil。
runtime 如何实现 weak 属性具体流程大致分为 3 步:
- 初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
- 添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。在这个过程中,为了防止多线程中竞争冲突,会有一些锁的操作。
- 释放时:调用 clearDeallocating 函数,clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录。
42、iOS的消息机制和消息转发
42.1、消息机制
RunTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。
对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 )。编译完成之后直接顺序执行,无任何二义性。
OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
例如:
[obj foo];
请看问题15
42.2、消息转发
当向someObject发送某消息,但runtime system在当前类和父类中都找不到对应方法的实现时,runtime system并不会立即报错使程序崩溃,而是依次执行下列步骤:
- 动态方法解析:向当前类发送 resolveInstanceMethod: 信号,检查是否动态向该类添加了方法
- 快速消息转发:检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法。若该方法返回值对象非nil或非self,则向该返回对象重新发送消息。
- 标准消息转发:runtime发送methodSignatureForSelector:消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。