1. iOS单例的实现方式?
之前总是这样写:
static Singleton *shareSingleton = nil;
(instancetype)shareSingleton {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareSingleton = [[self alloc] init];
});
return shareSingleton;
}
可能创建多次的原因:当使用alloc init创建对象时,在调用alloc方法时,oc内部会调用allocWithZone这个方法来申请内存,为
了避免allocWithZone申请新的内存,可以重写allocWithZone方法,在该方法中调用shareSingleton方法返回单例对象。拷贝对
象也是同样的道理。
可以这样写:
static Singleton *shareSingleton = nil;
(instancetype)shareSingleton {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareSingleton = [[super allocWithZone:NULL] init];
});
return shareSingleton;
}
(instancetype)allocWithZone:(struct _NSZone *)zone {
return [Singleton shareSingleton];
}
- (id)copyWithZone:(struct _NSZone *)zone {
return [Singleton shareSingleton];
}
2. 哪些类不适合使用单例模式?即使他们在周期中只会出现一次?
工具类,不需要存储数据的 例如判断邮箱 电话 字符串是否为纯数字等 可以写成方法
3. timer是否精准, 怎么使用精准的定时器?
正常情况下是精准的,但遇到一下情况就会不准确:
RunLoop的影响:
定时器被添加在主线程中,由于定时器在一个RunLoop中被检测一次,所以如果在这一次的RunLoop中做了耗时的操作,当前
RunLoop持续的时间超过了定时器的间隔时间,那么下一次定时就被延后。
解决办法:
1、在子线程中创建timer,在主线程进行定时任务的操作
2、在子线程中创建timer,在子线程中进行定时任务的操作,需要UI操作时切换回主线程进行操作
RunLoop模式的影响:
为了验证,我们在当前页面上添加一个tableview,在定时器运行时,我们对tableview进行滑动操作,可以发现,定时器并不
会触发下一次的定时任务。
原因分析:
主线程有两种预设模式,分别是RunLoopDefaultModel 和TrackingRunLoopModel 当定时器添加到主线程且没有执行的运行模
式时,一般会 默认添加到RunLoopDefaultModel 一般情况下定时器会正常触发定时任务 但当用户进行滑动操作时,主线程就
会切换成TrackingRunLoopModel 在此模式下定时器不会被触发
解决方法:
添加定时器到主线程的CommonMode中或者子线程中
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
其他方式的Timer:
使用mach_absolute_time()来实现更高精度的定时器:
iPhone上有这么一个均匀变化的东西来提供给我们作为时间参考,就是CPU的时钟周期数(ticks)。
通过mach_absolute_time()获取CPU已运行的tick数量。将tick数经过转换变成秒或者纳秒,从而实现时间的计算。
GCD定时器:
RunLoop是dispatch_source_t实现的timer,所以理论上来说,GCD定时器的精度比NSTimer只高不低
NSTimeInterval interval = 1.0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_walltime(NULL,0), interval *NSEC_PER_SEC,0);
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"GCD timer test");
});
dispatch_resume(_timer);
4. -id、instancetype、NSObject *和id *的区别?
首先需要知道,在cocoa的开发环境里,NSObject是所有类的根类
id:
从定义来看,id就是一个isa指针,可以指向任何一个继承了Object(或者NSObject)类的对象
id可以简单理解为一个万能指针
id是动态数据类型,编译时编译器不会检查id对象的类型,只有在运行时动态检查后会报错。
id 可用于定义变量 定义方法返回值 定义方法参数。
instancetype:
instancetype意思为实例化,instancetype与和id一样,都可以指向一个继承了Object(或者NSObject)类的对象
区别在于:instancetype只能作为方法返回值,需要返回该方法所在的类的实例化对象,所以instancetype也被称为关联返回类型。
使用instancetype会在编译时进行类型检查,有利于开发者在编译阶段发现错误。
关联返回类型的方法:
以 alloc 或 new 开头的类方法
以 autorelease、init、retain 或 self 开头的实例方法。
NSObject*:
NSObject* 就是指向NSObject类型的指针了,可以指向任何一个继承了NSObject的对象,感觉和上面的id、instancetype也没有区别。
区别在于NSObject* 是静态数据类型,编译时会进行类型检查。
NSObject*的作用与id一致。
id<NSObject> *
id<NSObject> *就是指该对象的类型可以是任何一个NSObject或继承了NSObject的子类,但该对象必须要遵循<NSObject>协议(奇葩的命名:协议名与类名一样)。简单来说就是它不关心对象是什么类型,只要遵循<NSObject>协议即可。
在定义delegate时常用。
5. iOS的签名机制?
因为苹果的安全策略,通过签名机制保证手机上的每个App都是经过苹果认证的。
通过App Store安装
开发者可以通过Xcode安装
Ad-Hoc 测试证书打包的App,数量限制100
In-House 企业版证书打包App,信任企业证书后可以使用
由苹果生成一对公私钥,公钥内置与iOS设备中,私钥由苹果保管。
开发者上传App给苹果审核后,苹果用私钥对App数据进行签名,发布至App Store。
iOS设备下载App后,用公钥进行验证,若正确,则证明App是由苹果认证过的。
6. +load和+initilaze在分类,父类,子类和main函数的调用顺序?
+load加载顺序:父类,子类,分类。如果多个分类会按照PBXSourcesBuildPhase中顺序逐个调用。
+initialize加载顺序:首先有分类时,最后被load的分类会覆盖类的该方法。然后先父类,再子类,直到第一次被调用的类。
7. drawRect方法?
- (void)drawRect:(CGRect)rect;中去绘制一些我们所需要的图形,如虚线、圆形、方形以及曲线等等图形。
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。
8. main()之前的过程有哪些?
Pre-mian 大概过程主要分为:Load dylibs、Rebase、Bind、Objc、Initializers 这几个步骤
1. dyld 开始将程序二进制文件初始化
2. dyld 首先会读取镜像文件,然后递归的查找动态库,利用 ImageLoader 来将其加载到内存中
3. 交由ImageLoader 读取 image,其中包含了我们的类,方法等各种符号
4. 接下来再调用 load_image,遍历调用类的 load 方法、调用C++的构造函数属性函数、创建非基本类型的C++静态全局变量等等。
9. 简述消息转发机制?
从全局来看,消息转发机制共分为3大步骤:
第一步:Method resolution 方法解析处理阶段:
对象在收到无法解读的消息后,首先会调用+(BOOL)resolveInstanceMethod:(SEL)sel或者+ (BOOL)resolveClassMethod:
(SEL)sel, 询问是否有动态添加方法来进行处理,
+(BOOL)resolveInstanceMethod:(SEL)sel(
if(sel==@selector(speak)){
class_addMethod([selfclass],sel,(IMP)speak,"V@:");
returnYES;
}return[
superresolveInstanceMethod:sel];
}
如果在上一步的2个方法内返回的为YES则能接受消息 NO不能接受消息
第二步: 在-forwardingTargetForSelector:进行判是否转发给有现实这个方法的对象或者类.
-(id)forwardingTargetForSelector:(SEL)aSelector{
Helper*helper=[[Helper alloc]init];
if([helper respondsToSelector:aSelector]){
returnhelper;}
else{returnnil;
}
}
第三步: 实现 -methodSignatureForSelector: 通过正确的类型方法方法签名
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{
return[NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
事不过三,何况操过三次了.处理基本都已经结束了.
实现:-doesNotRecognizeSelector: 在此方法中抛出错误! 然后奔溃
10. objc中向一个nil对象发送消息将会发生什么?
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类
方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。
那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
11. oc的反射机制?
Class对象其实本质上就是一个结构体,这个结构体中的成员变量还是自己,这种设计方式非常像链表的数据结构。
可以直接用一个实例对象或类对象,直接调用Class方法,都可以获取Class对象。
// SEL和字符串转换
FOUNDATION_EXPORT NSString*NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SELNSSelectorFromString(NSString*aSelectorName);
// Class和字符串转换
FOUNDATION_EXPORT NSString*NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class __nullableNSClassFromString(NSString*aClassName);
// Protocol和字符串转换FOUNDATION_EXPORT NSString*NSStringFromProtocol(Protocol*proto)NS_AVAILABLE(10_5,2_0);
FOUNDATION_EXPORT Protocol*__nullableNSProtocolFromString(NSString*namestr)NS_AVAILABLE(10_5,2_0);
// 当前对象是否这个类或其子类的实例
-(BOOL)isKindOfClass:(Class)aClass;
// 当前对象是否是这个类的实例
-(BOOL)isMemberOfClass:(Class)aClass;
// 当前对象是否遵守这个协议
-(BOOL)conformsToProtocol:(Protocol*)aProtocol;
// 当前对象是否实现这个方法
-(BOOL)respondsToSelector:(SEL)aSelector;
可以解决的实际问题:
假设有一天公司产品要实现一个需求:根据后台推送过来的数据,进行动态页面跳转,跳转到页面后根据返回到数据执行对应的操作。
12. 静态库的原理是什么?你有没有自己写过静态编译库,遇到了哪些问题?
静态库是闭源库,不公开源代码,都是编译后的二进制文件,不暴露具体实现。静态库 一般都是以 .a 或者 .framework 形式存
在。
静态库编译的文件比较大,因为整个函数库的数据都会被整合到代码中,这样的好处就是编译后的程序不需要外部的函数库支
持,不好的一点就是如果改变静态函数库,就需要程序重新编译。多次使用就有多份冗余拷贝。
使用静态库的好处:模块化分工合作、可重用、避免少量改动导致大量的重复编译链接。
framework中用到了NSClassFromString,但是转换出来的class 一直为nil。
解决方法:在主工程的【Other Linker Flags】需要添加参数【-ObjC]即可。
如果Xcode找不到框架的头文件,你可能是忘记将它们声明为public了。
解决方法:进入target的Build Phases页,展开Copy Headers项,把需要public的头文件从Project或Private部分拖拽到Public部分。
尽量不要用 xib 。由于静态框架采用静态链接,
新建项目,选择Cocoa Touch Static Library 定义一个类方法+ (void)test;,在.h文件暴露出来
然后选择Build phases 清空copy Files 下面的Subpath
适配最低版本 设置支持多个架构的的静态库 Build Active Architecture Only 设置为NO
分别在真机和模拟器下编译 Debug 和 Release
合并.framework 静态库,合成的是二进制文件而不是framework,最后合成的二进制文件替代之前的二进制文件即可
cd 到 Products目录,输入命令(如果是Debug模式,将Release替换成Debug即可)lipo-create Release-iphoneos/MXFrameworkTool.framework/MXFrameworkTool Release-iphonesimulator/MXFrameworkTool.framework/MXFrameworkTool-output MXFrameworkTool
13.ViewController生命周期?
nitWithCoder:(如果连接了串联图storyBoard会走这个方法)或initWithNibName:Bundle:(非storyBoard(Xib或纯代码)都会走这个方法)
init
初始化对象 纯代码和Xib都会走这个方法,storyBoard不会
awakeFromNib
此方法被调用时,所有视图的outlet和action已经连接,但还没有被确定。这个方法可以算作是和视图控制器的实例化配合在一
起使用的,因为有些需要根据用户喜好来进行设置的内容,无法存在storyboard中,所以可以在awakeFromNib方法中被加载进
来。
loadView
对Controller的View进行初始化
viewDidLoad
视图加载完成,但是还没有从屏幕上显示出来,可以重写这个方法,做一些其它的初始化操作,比如移除一些视图,修改约
束,加载数据等操作
viewWillAppear
在视图即将显示到屏幕上的时候调用,可以在这个方法里改变当前屏幕的方向或状态栏风格
iewWillLayoutSubViews
这个方法会在控制器将要布局View的子控件时调用,每当视图的bounds改变时,view将调整其子控件的位置
viewDidLayoutSubviews
此方法会在控制器已经布局子控件的时候调用
viewDidAppear
此方法在视图已经显示在屏幕上的时候调用,可以做一些对视图展示效果的改变
viewWillDisAppear
视图即将消失,被覆盖或被隐藏的时候调用
viewDidDisAppear
视图已经消失,被覆盖或被隐藏的时候调用
didReceiveMemoryWarning
当系统发出内存警告时,会自动清理视图,同时系统会调用此方法来通知视图控制器
可以在此方法内释放一些资源,但通常不需要,因为比较占资源的view已经被清除了
14. 轮播图项目实现细节?
第一种:基于collectionView进行的封装(推荐)
使用UICollectionView的复用特性,复制 多份 图片作为数据源,从中间开始显示
在 第一张图片前加 上最后一张图片,在最后一张图片前加上第一张图片
注意:
记得设置scrollView.isPagingEnabled = true
控制UIScrollView回滚的时候 不要使用动画,否则会调用代理方法
第二种:基于scrollView的无限轮播(首尾各多创建一个展示图片的ImageView)
第三种:同样是基于scrollView的无限轮播(总共就创建三个ImageView)
15. 单行多个Label,中间可压缩,怎么添加约束?
[self.label1 setContentHuggingPriority:1forAxis:UILayoutConstraintAxisHorizontal];
[self.label1 setContentCompressionResistancePriority:2forAxis:UILayoutConstraintAxisHorizontal];