BEGAN
1、如何正确使用const,static, extern?
作用:
const:限制类型(1、仅仅用来修饰右边的变量;2、被 const 修饰的变量只能是只读的)
static:
1、修饰局部变量:
1)延长局部变量的生命周期,程序结束才会销毁;
2)局部变量只能生成一份内存,只会初始化一次;
3)改变局部变量的作用域;
2、修饰全局变量:
1)只能在本文件中访问,修改全局变量的作用域,生命周期不会改;
2)避免重复定义全局变量
extern:用来获取全局变量的值(包括静态的全局变量)
延伸:
1、const 与宏的区别?
1)编译时刻:宏是预编译,const 是编译阶段
2)编译检查:宏只是替换,在编译时不会报错,不检查,const 会编译检查,会报错
3)宏的好处:宏能定义一些函数、方法,const 不能
4)宏的坏处:大量的宏会导致编译时间变长,每次都要重新替换
2、const 与 static 配合使用
在多个文件中使用同一个字符串常量,可以使用 const 与 static 组合;
const 与 static 组合:在每个文件都需要定义一份静态全局变量
extern 与 static 组合:只需要定义一次全局变量,多个文件共享
2、@property 关键字详解,assign 与weak、 __block 与 __weak、strong 与copy的区别?
1、assign 与 weak 区别:
assign 适用于基本数据类型,基础数据类型一般分配在栈上,栈的内存用系统自动处理。
weak 适用于修饰 NSObject 对象,是一个弱引用。一般修饰代理和 IB 控件。
2、strong 与copy的区别:
strong 与 copy 都会使引用计数加1,但是 strong 是两个指针指向统一个内存地址,copy 会在内存里拷贝一份对象,两个指针指向不用的内存地址。
3、__weak与__block的区别:
1)使用 __block修饰的变量在block代码块中会被retain(ARC下会retain,MRC下不会retain)
2)使用__weak修饰的变量不会在block代码块中被retain
要避免block出现循环引用 __weak __typedof(&*self)weakSelf = self;
4、block变量定义时为什么用copy?block 是放在哪里的?
block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,可能被随时回收,而不是在堆(heap)上。通过copy可以把block拷贝(copy)到堆,保证block的声明域外使用
3、一个按钮被一个半透明的View部分遮挡,需要点击到按钮的时候,按钮始终响应
// 一个View超出了父视图的范围,需要点击超出范围的View也有响应
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
//当触摸点在按钮上的时候,才让按钮去响应事件.
//把当前点转换成按钮坐标系上的点.
CGPoint btnP = [self convertPoint:point toView:self.btn];
if ( [self.btn pointInside:btnP withEvent:event]) {
return self.btn;
}else{
return [super hitTest:point withEvent:event];
}
}
4、iOS 的沙盒目录结构是怎样的?
Application:存放程序源文件,上架前经过数字签名,上架后不可修改
Documents:常用目录,iCloud备份目录,存放数据,这里不能存缓存文件,否则上架不被通过
Library :
Caches:存放体积大又不需要备份的数据,SDWebImage缓存路径就是这个
Preference:设置目录,iCloud会备份设置信息
tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能
5、+load 和 +initialize 的区别是什么?
+(void)load;
当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息 load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子类, 子类优先于分类
load 方法不会被类自动继承
+(void)initialize;
也是在第一次使用这个类的时候会调用这个方法
6、如何让 Category 支持属性? runtime实现
头文件
@interface NSObject (test)
@property (nonatomic, copy) NSString *name;
@end
.m文件
@implementation NSObject (test)
// 定义关联的key
static const char *key = "name";
- (NSString *)name {
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name {
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
7、strong / weak / unsafe_unretained 的区别?
• weak只能修饰OC对象,使用weak不会使计数器加1,对象销毁时修饰的对象会指向nil
• strong等价与retain,能使计数器加1,且不能用来修饰数据类型
• unsafe_unretained等价与assign,可以用来修饰数据类型和OC对象,但是不会使计数器加1,且对象销毁时也不会将对象指向nil,容易造成野指针错误
8、frame 和 bounds 的区别是什么?
(1)frame相对于父视图,是父视图坐标系下的位置和大小。bounds相对于自身,是自身坐标系下的位置和大小。
(2)frame以父控件的左上角为坐标原点,bounds以自身的左上角为坐标原点
9、KVO与Notification的异同
KVO和Notification本质都是观察者模式。
KVO是被观察者直接发消息(-willChange和-didChange),耦合性较强,适合某些绑定,比如说界面上的进度条显示;
Notification是被观察者发消息给NotificationCenter,再由NotificationCenter转发出去,耦合性较低,适合登录、等级变化、监听全局的某个属性变化;
10、Objective-C消息机制的原理
Objective-C的类结构
@interface NSObject {
Class isa OBJC_ISA_AVAILABILITY;
}
typedef struct objc_class *Class;
objc_msgSend方法:objc_msgSend含两个必要参数:receiver、方法名(selector)
[receiver message];将被转换为:objc_msgSend(receiver, selector);
带参数的情况是:objc_msgSend(receiver, selector, arg1, arg2, …);
当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的原来类,然后在类的方法列表中查找selector;
如果查找不到,通过Class super_class指针找到父类,并在父类的方法列表查找,直到NSObject类;
11、数据的持久化
ios中存储数据基本上就是plist、sqlite和CoreData (NSUserDefault其实也是plist)
常见的持久化实现:
1)实现NSCoding,配合runtime读取属性,再用NSKeyedArchiver存储到文件中;
2)实现NSCoding,存储到NSUserDefault;
3)数据库,使用SQLitePersistentObjects写入db;
4)使用CoreData;
12、聊天室中UITableView的优化
每一条消息是单独的UITableViewCell,通过富文本显示聊天消息,耗时操作是:富文本拼接、高度计算、滚动显示;
业务方向:
(1)下发房间配置文件,房间分普通、热闹、火爆等状态,某些情况下省略不必要的消息,再进行发言等级控制等;
(2)消息合并,对同类型的消息进行合并;
代码方向:
(1)根据帧率动态加载消息数量,当进行消息追赶的时候,多条消息调用一次insert,用CADisplayLink保证添加速率和帧率一致;
(2)代码创建cell
(3)图像预加载,程序在启动的时候会进行礼物版本同步,把礼物图片预先下载好,在显示直接通过富文本进行图片拼接;(为了避免锯齿,图像大小和显示使用整数)
(4)富文本根据消息内容进行拼接后缓存;
13、TCP/IP
(1)3次握手-建立连接
1、A发送sync报文;seq=x Sync=1
2、B回复ack报文;seq=y Sync=1 ack=x+1
3、A回复ack报文;seq=x+1 Sync=1 ack=y+1
(2)4次握手-断开连接
1、A端发送FIN,停止发送报文;A进入FIN-WAIT
2、B端发送ACK,表示收到,继续发送报文; A收到报文进入FIN2-WAIT
3、B端发送FIN,停止发送报文;B进入CLOSE_WAIT
4、A端收到FIN,发送ACK报文,A进入TIME_WAIT状态
14、HTTP协议
http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式
http请求由三部分组成,分别是:请求行、消息报头、请求正文。
常见状态码:
200 成功
400 请求的语法错误
403 Forbidden
404 not found 服务器找不到请求的资源
408 Request Time out
500 服务器内部错误
请求头
GET 请求方法、地址、协议版本
GET /foo.php?first_name=John&last_name=Doe&action=Submit HTTP/1.1
请求体(POST请求有)
form-data
HTTP响应
HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文。
15、线程安全问题
线程之间的资源共享,本质是对同一对象、变量、文件等进行修改和访问,主要有以下同步方式:
加锁;
原子操作;
sync代码块;
@synchronized( 同一对象){
// 线程执行代码;
}
NSOperationQueue 可以停止队列还没执行
suspended
但是不能终止当前操作。
16、NSOperation 相比 GCD 有哪些优势
(1)更容易的添加依赖关系;
(2)提供了任务的状态:isExecuteing、isFinished;
(3)可以很方便的取消一个NSOperation的执行。
17、如何为 Class 定义一个对外只读对内可读写的属性?
在头文件申明属性的时候用 readonly, 在.m 文件重新申明属性的时候用 readwrite
18、UIView 和 CALayer 之间的关系?
(1)UIView 继承自 UIResponder,可以响应事件,CALayer不可以响应用户事件;
(2)UIView本身,更像是一个CALayer的管理器,访问它的根绘图和坐标有关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性。
19、创建一个单例
+ (XYHundle *)shareHaudle {
static XYHundle *shareHaudle = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareHaudle = [[XYHundle alloc] init];
});
return shareHaudle;
}
20、冒泡排序(或其他算法)
NSMutableArray *array = [NSMutableArray arrayWithObjects:@"10", @"2", @"22", @"14", @"18", nil];
NSLog(@"打印前的数组:%@", array);
for (int i = 0; i < array.count - 1; i++) {
for (int j = 0; j < array.count - 1 - i; j++) {
if ([array[j + 1] intValue] < [array[j] intValue]) {
int temp = [array[i] intValue];
temp = [array[j] intValue];
array[j] = array[j + 1];
array[j + 1] = [NSString stringWithFormat:@"%d",temp];
}
}
}
NSLog(@"打印后的数组:%@", array);
21、哪些途径可以让 ViewController 瘦下来?(MVVM 思想)
(1)把 Data Source 和其他 Protocols 分离出来(将UITableView或者UICollectionView的代码提取出来放在其他类中)
(2)将业务逻辑移到 Model 中(和模型有关的逻辑全部在model中写)
(3)把网络请求逻辑移到 Model 层(网络请求依靠模型)
(4)把 View 代码移到 View 层(自定义View)
22、有哪些常见的 Crash 场景?
(1)访问了僵尸对象
(2)访问不存在的方法
(3)数组越界
(4)在定时器下一次回调前将定时器释放,会Crash
23、如果一个函数10次中有7次正确,3次错误,问题可能出现在哪里?
1.首先既然有正确有错误,那么这个bug肯定是不一定会出错的,先看函数条件是否有漏写;
2.然后再检查函数是否会存在空的情况;
3.反复操作以上步骤去查明每个调用的函数结果都是正确的。
24、iOS开发中Debug和Release的区别
Debug : 调试版本,主要是让程序员使用,在调试的过程中调用 Debug 会启动更多的服务来监控错误,运行速度相对较慢,而且比较耗能.
Release : 发布版本,主要是让用户使用, 在使用的过程中会去掉那些繁琐的监控服务,运行速度相对较快,而且比较节约内存.
25、请写一个”标准"宏MIN ,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
26、请分别说明@public、@protected、@private的含义与作用
@public:对象的实例变量的作用域在任意地方都可以被访问 ;
@protected:对象的实例变量作用域在本类和子类都可以被访问 ;
@private:实例变量的作用域只能在本类(自身)中访问 。
27、@synthesize、@dynamic的区别
@synthesize是系统自动生成getter和setter属性声明;
@synthesize的意思是,除非开发人员已经做了,否则由编译器生成相应的代码,以满足属性声明;
@dynamic是开发者自已提供相应的属性声明;
@dynamic意思是由开发人员提供相应的代码:对于只读属性需要提供getter,对于读写属性需要提供 setter 和getter。
28、block使用时的注意点?
1.在block内部使用外部指针且会造成循环引用情况下,需要用weak修饰外部指针weak typeof(self) weakSelf = self;
2.在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下__strong typeof(self) strongSelf = weakSelf;
3.如果需要在block内部改变外部变量的话,需要在用__block修饰外部变量
举个
// 创建一个 Student 类
#import
@interface Student : NSObject
@property(nonatomic, strong) NSString *name;
@end
// Student.m
#import "Student.h"
@implementation Student
@end
随便在一个函数中调用,例如下面的函数中:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Student *student = [[Student alloc] init];
student.name = @"Tom";
__weak typeof(Student) *weakSelf = student;
void(^block)()=^{
NSLog(@"Student Name = %@",weakSelf.name);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Student Name(Delay) = %@",weakSelf.name);
});
};
block();
}
打印出来的信息:
2017-02-23 10:55:03.876 TestBlock[1904:336958] Student Name = Tom
2017-02-23 10:55:05.876 TestBlock[1904:337002] Student Name(Delay) = (null)
dispatch_after函数在viewDidLoad函数结束后执行,此时student对象已经被销毁,所以weakSelf所引用的内容已经不存在,所以取得不到Student Name。
因此对于Block内部的延时函数,为了保证延时之后Block所引用的对象还存在,需要用__strongSelf引用。上面的代码修改为:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Student *student = [[Student alloc] init];
student.name = @"Tom";
__weak typeof(Student) *weakSelf = student;
void(^block)()=^{
// 需要强引用下
__strong typeof(Student) *strongSelf = weakSelf;
NSLog(@"Student Name = %@",strongSelf.name);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Student Name(Delay) = %@",strongSelf.name);
});
};
block();
}
29、[@property (nonatomic, copy) NSString *name; 重写 setter 方法]
- (void)setName:(NSString *)name {
_name = [name copy];
}
[@property (nonatomic, retain) NSString *name; 重写 setter 方法]
– (void) setName:(NSString*) str {
[str retain];
[name release];
name = str;
}
——————————底层实现————————————
1、字典转模型之MJExtension底层实现
//返回一个创建好的模型
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
//创建一个模型
id objc = [[self alloc] init];
int count = 0;
/*
方法:获取成员变量列表
参数一:class获取哪个类成员变量列表
参数二:count成员变量总数
*/
// 成员变量数组 指向数组第0个元素
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍历所有成员变量
for (int i = 0; i < count; i++) {
// 获取成员变量
Ivar ivar = ivarList[i];
// 获取成员变量名称(将c转为oc)
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 成员变量名称转换key(将成员变量前边的"_"截取掉)
NSString *key = [ivarName substringFromIndex:1];
// 从字典中取出对应value
id value = dict[key];
// 给模型中属性赋值(底层会去找对应的属性和值)
[objc setValue:value forKey:key];
}
return objc;
}
2、无限轮播实现原理
UIScrollView
1、准备好一个UIScrollView,上面依次放上去三个UIImageView,简称为A,B,C。
2、假设我们要放4张图片,那么首先我们在A B C上依次放的是:A -> 最后一张图片, B -> 第一张图片, C -> 第二张图片。首先让ScrollView显示B也就是第一张图片。
(1)当我们左滑显示第二张图片也就是C,当页面显示滑动停止以后,我们将A B C重新显示图片为:A -> 第一张图片, B -> 第二张图片, C -> 第三张图片,然后让ScrollView再次显示B。所以界面显示的图片虽然变了,但一直显示的还是中间那个UIImageView。这样就造成了无限循环滑动。右滑原理一样。
(2)如果我们右滑显示最后一张图片也就是A,当页面显示滑动停止以后,我们将A B C重新显示图片为:A -> 倒数第二张图片, B -> 最后一张图片, C -> 第一张图片,然后让ScrollView再次显示B。这样子就是一个无限循环轮播。
func reloadImage() {
let currentIndex = pageView.currentPage
let nextIndex = (currentIndex + 1) % 4
let preIndex = (currentIndex + 3) % 4
(scrollView.subviews[0] as! UIImageView).image = UIImage(named: "\(preIndex).png")
(scrollView.subviews[1] as! UIImageView).image = UIImage(named: "\(currentIndex).png")
(scrollView.subviews[2] as! UIImageView).image = UIImage(named: "\(nextIndex).png")
}
UICollectionView
1、设置UICollectionView的cell为两倍image数量,然后循环对cell从第一张图片到最后一张图片赋值,首先显示第二部分的第一张图片。
2、当左滑显示到第二部分的最后一张图片的时候,也就是最后一个cell,当图片显示完成以后,我们将scrollView设置为显示第一部分的最后一张图片,这样子就可以继续右滑,就是无限循环轮播的效果。
3、当右滑显示到第一部分的第一张图片的时候,也就是第一个cell,当图片显示完成以后,我们将scrollView设置为显示第二部分的第一张图片,这样子就可以继续左滑,就是无限循环轮播的效果。
func reloadImage() {
guard let currentIndexPath = currentIndexPath else {
return
}
if currentIndexPath.item == images.count * 2 - 1 { //如果是最后一个图片,回到第一部分的最后一张图片
let newIndexPath = IndexPath(item: images.count - 1, section: 0)
self.currentIndexPath = newIndexPath
collectionView.scrollToItem(at: newIndexPath, at: .centeredHorizontally, animated: false)
} else if currentIndexPath.item == 0 { //如果是第一个图片,就回到第二部分的第一张图片
let newIndexPath = IndexPath(item: images.count, section: 0)
self.currentIndexPath = newIndexPath
collectionView.scrollToItem(at: newIndexPath, at: .centeredHorizontally, animated: false)
}
}
后续持续更新~
END