@@@ 饭前小菜 @@@
1. 面向过程与面向对象的本质区别
面向过程: 分析出解决问题所需要的步骤,然后用函数(方法)把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了
面向对象: 是把构成问题或事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
2. 面向对象的弊端
1. 找不到对象(不厚道的笑了😁😁😁)
2. 不知道面向对象原本要解决的问题是什么
3. 过度封装
一:@@@《基础篇》@@@
二:@@@《进阶篇》@@@
1.为什么说Objective-C是一门动态的语言?
答:数据类型的确定由编译时推迟到运行时!
友情扩展:
1.1 静态、动态语言是相对的。 Objective-C 可以通过Runtime 这个运行时机制,在运行时动态的添加和修改变量、方法、类等,所以说Objective-C 是一门动态的语言。
2. #import 跟 #include 有什么区别,@class呢,#import<> 跟 #import"" 有什么区别?
答:
1. #import 是Objective-C导入头文件的关键字,#include 是C/C++导入头文件的关键字,使用 #import 头文件会自动只导入一次,不会重复导入。
2. @class 是告诉编译器声明某个类,当执行时,才去查看类的实现文件,可以解决头文件的相互包含。
3. #import<> 用来包含系统的头文件,#import"" 用来包含用户头文件。
3.frame 和 bounds 有什么不同?
frame 指的是:该view在父view坐标系统中的位置(X,Y)和大小(W,H)。(参照点是父view的坐标系统)
bounds 指的是:该view在本身坐标系统中的位置(X,Y)和(W,H)大小。(参照点是本身坐标系统,所以X Y 永远都是 0 )
4. @property (属性) 的本质是什么?
@property = ivar(实例变量)+ getter + setter(存取方法);
5. @property (属性) 中有哪些属性关键字?各个作用是什么?
属性可以拥有的特质属性分为四类:
1.原子性: nonatomic 和 atomic
2.读/写权限: readwrite(读写) 和 readonly (只读)
3.内存管理语义: assign、weak、strong 和 copy
4.方法名:getter=<name> 和 setter=<name>
5.不常用的:nonnull,null_resettable,nullable
各个属性关键字的作用:
1.nonatomic: 非原子操作,编译器生成的setter和getter方法都不会加锁,禁止多线程,提高性能。atomic: 原子操作,自动加锁(创建lock),锁定变量,确保多线程安全。虽然系统默认是atomic, 但是实际开发中一般使用nonatomic,效率高。
2.readwrite: 可读可写,会 getter方法 和 setter方法。readonly: 只读,只会生成getter方法,不会生成setter方法,不希望属性在类外改变。
3.assign: 是赋值特性,用于基本数据类型(Bool Int Float Double Struct (结构体) )。weak:弱引用, 一般用于 delegate 和 block 等引用类型。strong: 强引用,对象不易释放掉。copy: 表示拷贝特性,setter方法将传入对象复制一份,生成不可变对象。
6. weak 和 assign 有什么不同?什么情况下使用 weak 关键字?
不同点:
assign 可以用非 OC 对象(基本数据类型),而 weak 必须用于 OC 对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)。
使用 weak 关键字的情况
1.用 delegate(代理属性) 进行一对一操作的时候。
2.自身已经对它进行一次强引用,没有必要再强引用一次,也使用 weak 。
所以XIB的IBOutlet连出来的视图属性被设置成weak,因为父控件的subViews数组已经对它有一个强引用。
7. Objective-C 的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?
答:Objective-C 的类不可以多重继承;可以实现多个接口( @protocol 协议)!Category 是类别;一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。
8. 什么情况下使用 copy 关键字?如果改用strong关键字,可能造成什么问题?
copy 使用情景:
1. NSString、NSArray、NSDictionary 等经常使用 copy 关键字修饰。
2. block 也经常使用 copy 关键字。
使用 strong 的话,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的)。为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份(所以使用copy)。
一句话:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。
9. 浅拷贝(浅复制) 和 深拷贝(深复制) 的区别?
答:
浅拷贝:只复制指向对象的指针,而不复制引用对象本身。
深拷贝:复制引用对象本身。
10. 使用系统对象的 copy 与 mutableCopy 方法
数据的可变对象和不可变对象
不管是集合类对象(NSArray、NSDictionary、NSSet ... 之类的对象),还是非集合类对象(NSString, NSNumber ... 之类的对象),接收到 copy 和 mutableCopy 消息时,都遵循以下准则:
1. copy 返回的是不可变对象(immutableObject);如果用 copy 返回值调用可变对象(mutable)的方法就会 crash(奔溃) 。
2. mutableCopy 返回的是可变对象(mutableObject)。
数据的浅复制和深复制
在非集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;对可变对象进行copy和mutableCopy都是内容复制。
在集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;对可变对象进行copy 和 mutableCopy 都是内容复制。但是:集合对象的内容复制仅限于对象本身,对集合内的对象元素仍然是指针复制。(即单层内容复制)
总结一句话: 只有对不可变对象进行 copy 操作是指针复制(浅复制),其它情况都是内容复制(深复制)!
11. 这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr;
出现的问题:添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。
//如:-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
原因:copy 后返回的是不可变对象(即 arr 是 NSArray 类型,NSArray 类型对象不能调用 NSMutableArray 类型对象的方法),不能对 NSArray 对象进行 添加/修改/删除 等。
12. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
具体步骤:
1. 需声明该类遵从 NSCopying 协议
2. 实现 NSCopying 协议的方法。
// 该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;
// 注意:使用 copy 修饰符,调用的是copy方法,其实真正需要实现的是 “copyWithZone”方法。
13. @synthesize 和 @dynamic 分别有什么作用?
@synthesize : 编译器自动生成属性的 setter方法 和 getter方法 。
@dynamic : 属性的 setter 与 getter方法 由用户自己实现,不自动生成。
系统默认的就是 @synthesize
14. 常见的 Objective-C 的数据类型有那些,和C的基本数据类型有什么区别?如:NSInteger和int
答:
Objective-C 的数据类型有 NSString,NSNumber,NSArray,NSMutableArray,NSData 等等,这些都是class,创建后便是对象。而C语言的基本数据类型只是一定字节的内存空间,用于存放数值;NSInteger是基本数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。NSInteger是基本数据类型Int或者Long的别名,它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是long。
15. id 声明的对象有什么特性?
答:id 是指向任意类型的 Objcetive-C 的对象,声明的对象具有运行时的特性。
16. Objective-C 如何对内存管理的,说说你的看法和解决方法?
答:Objective-C的内存管理主要有三种方式 ARC(自动内存计数)、手动内存计数、内存池。
1. 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。
2. 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。
3. 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。
17. Category(类别)、 Extension(扩展)和继承的区别
区别:
1. 分类(Category)有名字。类扩展(Extension)没有名字,类扩展是一种特殊的分类。
2. 分类只能扩展方法(属性仅仅是声明,并没真正实现)。类扩展可以扩展属性、成员变量和方法。
3. 继承可以增加,修改或者删除方法,并且可以增加属性。
18. 为什么常见的delegate属性都用是 week 而不是 strong ?
答:是为了防止 delegate 两端产生不必要的循环引用。
@property (nonatomic, weak) id<UITableViewDelegate> delegate;
19. delegate 和 Notification 的区别?
delegate: 一对一反向传值和通知,但是需要遵守协议(@protocol)。
Notification: 一对多的反向传值和通知,需要提前注册通知,只负责消息发送出去,并不关心谁是接收者。
20. 方法和选择器有何不同?
selector 只是一个方法的名字。方法却是一个组合体,包含了名字和实现。
21. 你是否接触过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:));
22. 什么是block?用来做什么?使用的时候应该注意什么?
block: 闭包(代码块),获取其它函数局部变量的匿名函数。
作用: 用来反向传值的。
使用注意点:
1. 在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针:
__weak typeof(self) weakSelf = self;
2. 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。
__strong typeof(self) strongSelf = weakSelf;
3. 如果需要在block内部改变外部栈区变量的话,需要在用 __block 修饰外部变量。
23. BAD_ACCESS在什么情况下出现?
答:访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等。
24. 如何访问并修改一个类的私有属性?
1. 通过KVC获取和修改。
2. 通过runtime访问并修改私有属性。
25. 一个objc对象的isa的指针指向什么?有什么作用?
指向他的类对象,可以找到对象上的方法。
26. 什么是懒加载?
答:懒加载就是只在用到的时候才去初始化。也可以理解成延时加载。
最简单的一个例子就是tableView中图片的加载显示了, 一个延时加载, 避免内存过高,一个异步加载,避免线程堵塞提高用户体验。
27. APP启动的顺序
1.先执行main()函数
2.mian()函数中执行UIApplicationMain()函数
3.加载info.plist文件
4.创建RunLoop()回调函数
5.创建UIApplication对象,设置其代理UIApplicationDelegate
6.程序加载完毕后调用delegate对象的application:didFinishLaunchingWithOptions:方法
7.在上诉6.方法中创建UIWindow(窗口)和设置rootViewController(根视图控制器)
8.最终设置显示窗口(makeKeyAndVisible)
9.最后程序变成活跃状态(BecomeActive),Runloop 时刻监听各种事件的发生
28. View的加载顺序
1.initWithCoder(如果没有storyboard就会调用initWithFrame,这里两种方法视为一种)
2.awakeFromNib
3.layoutSubviews
4.drawRect
29. ViewController生命周期
按照执行顺序排列:
1. initWithCoder:通过nib文件初始化时触发。
2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每对象。
3. loadView:开始加载视图控制器自带的view。
4. viewDidLoad:视图控制器的view被加载完成。
5. viewWillAppear:视图控制器的view将要显示在window上。
6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9. viewDidAppear:视图控制器的view已经展示到window上。
10. viewWillDisappear:视图控制器的view将要从window上消失。
11. viewDidDisappear:视图控制器的view已经从window上消失。
30. 如何对App进行性能测试
答: Profile-> Instruments ->Time Profiler
31. 如何优化App的性能
一.入门级
1. 用ARC管理内存 (避免忘记释放内存所造成内存泄露)
2. 正确的使用重用标识符 reuseIdentifier (给TableViewCell CollectionViewCell HeaderFooterView等添加重用标志)
3. 尽量把view设置为完全不透明
4. 避免庞大的XIB
5. 不要阻塞主线程(耗时操作放在异步中执行,再回到主线程)
6. 在ImageView中直接设置图片大小(运行中缩放图片是很耗费资源的,尤其是嵌套在UIScrollView中的情况下)
7. 选择正确的Collection
8. 打开gzip压缩
二.中级
1. 重用和延迟加载(懒加载)View
2. 重要数据使用Cache(缓存)
3. 权衡渲染方法
4. 处理内存警告
5. 重用大开销的对象
6. 使用Sprite Sheets
7. 避免反复处理数据
8. 选择正确的数据格式(一般用JSON,因为解析JSON会比XML更快一些,JSON也通常更小更便于传输)
9. 正确地设定Background Images
10. 减少使用Web特性
11. 设定Shadow Path
12. 优化你的TableView
13. 选择正确的数据存储选项
三.高级
1. 加速启动时间
2. 使用Autorelease Pool
3. 选择是否缓存图片
4. 尽量避免日期格式转换
32. 如何优化UITableView
1.注册重用标识符(reuseIdentifier),给UITableViewCell 和 HeaderFooterView
2.缓存行高
3.尽量少在cellForRowAtIndexPath中设置数据,可以在willDisplayCell里进行数据的设置
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//不要去设置cell的数据
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
//当cell要显示时才去设置需要显示的cell对应的数据
}
4.避免主线程阻塞(获取数据、数据处理等耗时操作,放在子线程异步处理)
5.避免在Cell中频繁的创建对象
6.减少对象的属性赋值操作(UIView的frame/bounds等属性的赋值操作,会产生比较大的CPU消耗)
7.异步绘制
8.简化视图结构,减少Subviews的数量(减少Cell中控件的数量)
9.减少离屏渲染
10.尽量使用rowHeight,sectionFooterHeight 和 sectionHeaderHeight来设定固定的高,不要请求delegate
11.尽量设置Cell中的view的颜色为完全不透明
12.尽量少设置颜色渐变,图片的缩放
13.如果cell内现实的内容来自web,使用异步加载,缓存请求结果
14.使用正确的数据结构来存储数据
33. 内存泄漏和内存溢出。 如何检查内存泄漏?
内存泄漏: 申请的内存空间使用完毕之后未回收。
一次内存泄露危害可以忽略,但若一直泄漏,无论有多少内存,迟早都会被占用光,最终导致程序crash。(因此,开发中我们要尽量避免内存泄漏)
内存溢出: 程序在申请内存时,没有足够的内存空间供其使用(就是内存不够用了),导致机器重启或者程序crash。
检查内存泄露方法:
1. 静态分析 analyze。
2. instruments 工具里面有个 leak 可以动态分析。
34. 导致内存泄漏的原因
ARC环境下导致内存泄漏的根本原因是: 存在循环引用。导致一些内存无法释放,最终dealloc()方法无法被调用。
情况如下:
1. VC(控制器)中存在NSTimer(定时器) , 没有及时设置其为失效([timer invalidate])和置空(self.timer=nil)
2. VC(控制器)中的delegate使用了Strong修饰,没有使用Weak修饰
3. VC(控制器)中的Block被当前VC(self)持有,又在block内部调用VC中的属性或者方法。解决方法: 在block外部弱化VC(self)。
__weak typeof(self) weakSelf = self; // block外部弱化
[self.operationQueue addOperationWithBlock:^{
__strong typeof(weakSelf) strongSelf = weakSelf; // 子线程中再次强引用获取self
}];
4.大次数循环,内存暴涨
for (int i = 0; i < 1000000; i++) {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
解决方法:在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。
for (int i = 0; i < 1000000; i++) {
@autoreleasepool {
NSString *string = @"Abc";
string = [string lowercaseString];
tring = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
}
35. iOS中常用的数据存储方式有哪些?
1. Plist存储。 (是一种XML格式的属性列表文件,不能存储自定义对象,只能存储NSString,NSArray,NSNumber,Bool等类型,通过NSBundle获取)
2. NSUserDefaults(偏好设置存储)。不需要关心文件名,可以快速进行键值对存储,能够直接存储基本数据类型。
// 通过key(fieldName)存储
NSUserDefaults *defaus=[NSUserDefaults standardUserDefaults];
[defaus setObject:value forKey:fieldName];
[defaus synchronize]; // 即时写入
// 通过key(fieldName)获取
NSUserDefaults *defaus=[NSUserDefaults standardUserDefaults];
return [defaus objectForKey:fieldName];
3. NSKeyedArchiver 归档 (专门用来做自定义对象归档)。 需要遵守NSCoding协议中的两个方法。encodeWithCoder:(归档对象时,要存储哪些对象的哪些属性) initWithCoder:(解析文件中的数据)
4. SQLite及FMDB(中小型数据库)。写SQL语句建库、建表、建约束...
在FMDB中,除查询以外的所有操作,都称为"更新", create、drop、insert、update、delete等,使用executeUpdate:方法执行更新。
FMDB有三个主要的类:1.FMDataBase:代表一个单独的DataBase数据库。 2.FMResultSet:执行查询后的结果集。3.FMDataBaseQueue: 用于多线程执行多个查询或者更新,它是线程安全的。
5. CoreData (对象-关系映射,能够将OC对象转化成数据,也能够将数据库中的数据还原成OC对象。是对SQLite3的封装,更加面向对象,效率没有SQLite3高,在此数据操作期间,不需要编写任何SQL语句)。
36. iOS中的沙盒目录结构是怎样的?
沙盒结构:
1. Application:存放程序源文件,上架前经过数字签名,上架后不可修改。
2. Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)
3. Library:
Caches:存放体积大又不需要备份的数据。(常用的缓存路径)
Preference:设置目录,iCloud会备份设置信息。
4. tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。
37. 你一般是怎么用Instruments的?
Instruments里面工具很多,常用:
1. Time Profiler: 性能分析
2. Leaks:检查内存,看是否有内存泄露
3. Allocations:用来检查内存,写算法的那批人也用这个来检查
4. Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。
38. isKindOfClass isSubclassOfClass 和 isMemberOfClass 的区别
1. isKindOfClass
// 添加测试函数
-(void)addTestFun{
// 父类
TestModel *modelClass=[[TestModel alloc]init];
// 子类(继承TestModel类)
TestModelSonClass *sonClass=[[TestModelSonClass alloc]init];
// 使用 isKindOfClass 判断
BOOL isSuperClass=[modelClass isKindOfClass:[TestModel class]];
BOOL isSonClass=[sonClass isKinkdOfClass:[TestModel class]];
NSLog(@"One: %d Two: %d",isSuperClass,isSonClass);
}
最终运行效果:
TestModel[87850:13575604] One: 1 Two: 1
2. isSubclassOfClass
// 添加测试函数
-(void)addTestFun{
// 父类
TestModel *modelClass=[[TestModel alloc]init];
// 子类(继承TestModel类)
TestModelSonClass *sonClass=[[TestModelSonClass alloc]init];
// 使用 isSubclassOfClass 判断
BOOL isSuperClass=[[modelClass class] isSubclassOfClass:[TestModel class]];
BOOL isSonClass=[[sonClass class] isSubclassOfClass:[TestModel class]];
NSLog(@"One: %d Two: %d",isSuperClass,isSonClass);
}
最终运行效果:
TestModel[87850:13575604] One: 1 Two: 1
3. isMemberOfClass
// 添加测试函数
-(void)addTestFun{
// 父类
TestModel *modelClass=[[TestModel alloc]init];
// 子类(继承TestModel类)
TestModelSonClass *sonClass=[[TestModelSonClass alloc]init];
// 使用 isMemberOfClass 判断
BOOL isSuperClass=[modelClass isMemberOfClass:[TestModel class]];
BOOL isSonClass=[sonClass isMemberOfClass:[TestModel class]];
NSLog(@"One: %d Two: %d",isSuperClass,isSonClass);
}
最终运行效果:
TestModel[87850:13575604] One: 1 Two: 0
4. 最后总结
isKindOfClass 和 isSubclassOfClass 的作用是: 用来判断是否是某个类 或其 子类 的实例。
isMemberOfClass的作用是: 用来判断是否是某个类的实例(要完全匹配)。
39. respondsToSelector 和 conformsToProtocol 的区别
respondsToSelector : 用来判断是否有以某个名字命名的方法
conformsToProtocol : 用来判断对象是否实现了指定协议类的方法
网友评论