iOS面试题-2
如何对iOS设备进行性能测试?
product-Profile-> Instruments ->Time Profiler
1.Time Profiler:性能分析
2.Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能
3.Allocations:用来检查内存,写算法的那批人也用这个来检查
4.Leaks:检查内存,看是否有内存泄露
常见单词的正确写法 左边是正确的
iPhone -> IPHONE IPhone
Xcode -> XCode xcode
Objective-C -> Object-C
JSON -> Json
HTTP -> Http
推送
推送通知分为两种,一个是本地推送,一个是远程推送
本地推送:不需要联网也可以推送,是开发人员在APP内设定特定的时间来提醒用户干什么
远程推送:需要联网,用户的设备和苹果APNS服务器形成一个长连接,用户设备会发送uuid和Bundle identifier给苹果服务器,苹果服务器会加密生成一个deviceToken给用户设备,然后设备会将deviceToken发送给APP的服务器,服务器会将deviceToken存进他们的数据库,这时候如果有人发送消息给我,服务器端就会去查询我的deviceToken,
cocoapods原理
CocoaPods注意点:CocoaPods在pod install以后会生成一个Podfile.lock的文件,这个文件在多人协作开发的时候就不能加入在.gitignore中,因为这个文件会锁定当前各依赖库的版本,就算之后再pod install也不会更改版本,不提交上去的话就可以防止第三方库升级后造成大家各自的第三方库版本不同
CocoaPods原理:
1.Pods项目最终会编译成一个名为libPods.a的文件,主项目只需要依赖这个.a文件即可
2.对于资源文件,CocoaPods提供了一个名为Pods-resources.sh的bash脚本,该脚本在每次项目编译的时候都会执行,将第三方的各种资源文件复制到目标目录中
3.CocoaPods通过一个名为Pods.xcconfig的文件在编译时设置所有的依赖和参数
常用git命令行
创建一个仓库 git -init
提交到暂缓区 git add .
查看文件状态 git status(如果看到文件颜色是绿色,表示可以提交)
查看项目日志 git log
提交项目到版本控制 git commit "初始化"
命令使用记录 git reflog
回退版本: git reset --hard HEAD^ 回到当前版本
git reset --hard HEAD^ + 版本号
删除项目中的文件 git rm + 文件名
git branch 查看分支
git check v1(分支名称) 切换分支
git branch v1 查看制定分支/创建分支
git branch -d v1 删除V1分支
ViewController的didReceiveMemoryWarning是在什么时候调用的?默认的操作是什么?
当应用程序接收到系统的内容警告时,就有可能调用控制器的didRece…Warning方法
它的默认做法是:
当控制器的view不在窗口上显示时,就会直接销毁,并且调用viewDidUnload方法
怎么理解MVC,在Cocoa中MVC是怎么实现的?
1> M:Model,模型,封装数据
2> V:View,视图界面,负责展示数据
3> C:Controller,控制器,负责提供数据(Model)给界面(View)
- self.跟self->什么区别?
1> self.是调用get方法或者set方法
2> self是当前本身,是一个指向当前对象的指针
3> self->是直接访问成员变量
- id、nil代表什么?
1> id类型的指针可以指向任何OC对象
2> nil代表空值(空指针的值, 0)
+(void)load; +(void)initialize;有什么用处?
+(void)load;
当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息。
load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子类, 子类优先于分类。
由于 load 方法会在类被 import 时调用一次,而这时往往是改变类的行为的最佳时机,在这里可以使用例如 method swizlling 来修改原有的方法。
load 方法不会被类自动继承。
+(void)initialize;
也是在第一次使用这个类的时候会调用这个方法,也就是说 initialize 也是懒加载
总结:
在 Objective-C 中,runtime 会自动调用每个类的这两个方法
1.+load 会在类初始加载时调用
2.+initialize 会在第一次调用类的类方法或实例方法之前被调用
这两个方法是可选的,且只有在实现了它们时才会被调用
两者的共同点:两个方法都只会被调用一次
这段代码有什么问题吗?
-(void)setAge:(int)newAge{
self.age = newAge;
}
在age属性的setter方法中,不能通过点语法给该属性赋值。
会造成setter方法的循环调用。因为self.age = newAge;
本质上是在调用 [self setAge:newAge]; 方法。
解决循环调用的方法是方法体修改为 _age = newAge;
另外 变量名称不能使用new开头!
以下代码运行结果如何?
-(void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
答:
只输出:1。(主线程死锁)
分析上面代码:
viewDidLoad 在主线程中, 及在
dispatch_get_main_queue() 中,执行到sync 时 向
dispatch_get_main_queue()插入 同步 threed1.
sync 会等到 后面block 执行完成才返回, sync 又再 dispatch_get_main_queue() 队列中,
它是串行队列,sync 是后加入的,前一个是主线程,
所以 sync 想执行 block 必须等待主线程执行完成,主线程等待 sync 返回,去执行后续内容。
照成死锁,sync 等待mainThread 执行完成, mianThread 等待sync 函数返回。
下面输出结果
@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 这个对象。
如何实现单例,单例会有什么弊端?
1.概念:单例可以保证某个类的实例在程序中是唯一的,便于进行资源和数据的共享
2.优点:
1)一个类只被实例化一次,可以全局共享数据
3.缺点:
1)由于单例模式没有抽象层,因此单例模式的扩展有很大的困难
2)一直占着这块内存,不会被释放
4.特别注意:线程安全性保护
可能单例的某个属性被多个对象同时读取,从而造成数据混乱
解决方法:使用互斥锁
@synchronized(锁对象) { 需要锁定的代码 }
被锁定的代码在多线程中会顺序执行。
互斥锁使用前提:多条线程抢夺同一块资源,互斥锁就是使用了线程同步技术
互斥锁的优缺点:
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
使用OC原子性实现互斥锁
atomic:原子性,为setter方法加锁
nonatomic:非原子属性,不会为setter方法加锁
总结:鉴于数据安全和代码执行效率考虑,在共享的单例对象中,只读的属性建议选择非原子性,可读写的属性则必须为原子性。
代码:
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}