11、Objective-C与C、C++之间的联系和区别?
OC
- OC是C的超集,扩展了C语言使它具备面向对象设计的能力。例如类、消息、继承;同时在OC的代码中可以有C和C++语句,它可以调用C的函数,也可以通过C++对象访问方法;
- OC不同于C++,尽管都有面向对象的能力,但他们分属不同的学派,OC属于SmallTalk学派,C++属于Simula 67学派(一种早期的面向对象语言)。
- OC可以底层系统编程,另一方面可以支持利用动态架构进行开发。
OC于C++的比较
两者都是面向对象设计语言,有很多相似之处,但属于不同的学派,也有不同之处:
- 继承:OC不支持多继承,C++支持多继承
- 函数调用:OC通过传递消息实现函数调用,而C++直接进行函数调用
- 定型:OC是动态类型,所以它的类库比C++容易操作。OC在运行时可以允许根据字符串名字访问方法和类,还可以动态链接和添加库。而C++,对象的静态类型决定你是否可以发消息给它。
- 接口:OC采用协议(正式和非正式)的形式定义接口,而C++采用虚函数的形式定义接口
- 方法重载:OC不支持方法重载,C++支持方法重载。方法重载就是方法名相同,参数个数相同,但是参数类型不同或者返回值不同。
两者的主要差别是因为OC即支持动态类型也支持静态类型。对于id类型的变量,变量只是一个容器,本身是没有类型的,或者只是属于最基本的类型,所以也不需要强制类型转换。因为编译器不会检查变量类型是否正确,只是运行时如果类型不正确才会产生异常。
而C++是静态语言,编译时会检查类型,所以必须要加上强制类型转换,否则编译器就会报错。
12、UICollectionView自定义layout如何实现?
- 创建UICollectionViewLayout子类
- 创建UICollectionViewLayoutAttributes子类,用于描述每个cell的size,frame,transform属性。
- 重写prepare方法,并在方法内部实现各个cell的size、center等的计算。
- 重写layoutAttributesForElements和layoutAttributesForItem方法,方法内部返回UICollectionViewLayoutAttributes实例用于对cell进行布局。
- 如果有自定义attribute属性,可以放在UICollectionViewLayoutAttributes类中,并在cell实现类的applyLayoutAttributes方法中为每个cell进行属性赋值。
13、进程和线程的区别?同步异步的区别?并行和并发的区别?
进程和线程
进程:一个正在运行的程序可以看做一个进程。(例如:正在运行的QQ就是一个进程),进程拥有独立运行所需的全部资源。
线程:程序中独立运行的代码段。(例如:接收QQ消息的代码)
一个进程是由一或多个线程组成。进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。同步和异步
同步是指:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1,程序1才继续执行下去。
异步是指:当程序1调用程序2时,程序1径直继续自己的下一个动作,不受程序2的影响。
或者
同步是指:发送方发出数据后,等接受方发挥响应以后才发下一个数据包的通信方式
异步是指:发送方发出数据后,不等接收方发回响应,接着发送下一个数据包的通讯方式。-
并行和并发
并行(parallel):同一时刻可以互不干扰的同时做几件事
并发(concurrency):统一时间段做几件事
并发的解决方案:
- 队列和缓冲区
- 争抢锁资源
- 预处理
- 并行
- 提速
- 消息中间件(RabbitMQ)
参考链接
https://www.jianshu.com/p/c334f8198f9b
14、线程间通信?
线程间通信的体现:
一个线程传递数据给另一个线程
在一个线程中执行玩特定任务后,转到另一个线程继续执行任务
常用的线程间通信的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;作者:Smallwolf_JS
15、GCD的一些常用的函数?(group,barrier,信号量,线程同步)
GCD的使用很简单:
- 创建一个队列(串行队列和并行队列)
- 将任务追加到等待队列中,然后系统就会根据任务的类型执行(同步执行和异步执行)
dispatch_queue_create
dispatch_barrier_async
dispatch_after
dispatch_once
dispatch_apply
dispatch_group
dispatch_group_notify
dispatch_group_wait
dispatch_group_enter
dispatch_group_leave
信号量的作用在于处理多线程任务访问资源限制的问题,有时候也用于上锁和解锁的问题。
dispatch_semaphore_create
dispatch_semaphore_signal
增加信号量
dispatch_semaphore_wait
减少信号量
/**
* 线程安全:使用 semaphore 加锁
* 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
*/
- (void)initTicketStatusSave {
NSLog(@"currentThread---%@", [NSThread currentThread]);
NSLog(@"semaphore---begin");
self.semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testqueue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testqueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
/**
* 售卖火车票(线程安全)
*/
- (void)saleTicketSafe {
while (1) {
//相当于加锁
dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) {
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%zd 窗口: %@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else {
NSLog(@"所有火车票已售完");
dispatch_semaphore_signal(self.semaphoreLock);
break;
}
//相当于解锁
dispatch_semaphore_signal(self.semaphoreLock);
}
}
参考链接:
https://www.jianshu.com/p/2d57c72016c6
https://www.jianshu.com/p/189a09d669de
16、如何访问并修改一个类的私有属性?
访问以及修改私有属性的两种方式:
KVC
runtime
objc_msgSend() 通过私有属性的setter和getter方法实现
KVC
setValue:forKey:
valueForKey:
-(void)way1{
PrivateVariablesClass *classA = [[PrivateVariablesClass alloc] init];
[classA setValue:@(4) forKey:@"_priviteNum"];
[classA setValue:self.view forKey:@"_priviteView"];
[classA showPropertyPrivateVariablesClass];
}
runtime
-(void)way2{
PrivateVariablesClass *classA = [[PrivateVariablesClass alloc] init];
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([PrivateVariablesClass class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
const char *ivarName = ivar_getName(ivar);
//这里要注意ARC下, 这个会报错
/**
在修改NSInteger型变量的时候,ARC下,编译器不允许你将NSInteger类型的值赋值给id,在buildsetting中将Objective-C Automatic Reference Counting修改为No即可。但是这样工程就会变成MRC,所以,如果是非对象类型就不建议用object_setIvar这样的方法去修改了。
*/
int a = strcmp(ivarName, "_priviteNum");
if (strcmp(ivarName, "_priviteNum") == 0) {
//这种方式传值int类型会报错,不能传入
object_setIvar(classA, ivar, 22);
}
if (strcmp(ivarName, "_priviteView") == 0) {
object_setIvar(classA, ivar, self.view);
}
}
[classA showPropertyPrivateVariablesClass];
}
使用ivar_getName()
获取属性名并使用object_setIvar()
修改属性值,使用获取的属性名通过valueForKey:
修改属性值
objc_msgSend()
-(void)way3{
PrivateVariablesClass *classA = [[PrivateVariablesClass alloc] init];
((void (*)(id, SEL, int))(void *) objc_msgSend)((id)classA, @selector(setPriviteNum:) , 33);
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)classA, @selector(setPriviteView:) , self.view);
[classA showPropertyPrivateVariablesClass];
}
17、数据持久化的几个方案(fmdb用没用过)
- plist文件(属性列表)
- preference(偏好设置)
- NSKeyedArchiver(归档)
- SQLite 3
- CoreData
FMDB的三个主要的类:
- FMDatabase
一个FMDatabase对象就代表一个单独的SQLite数据库,用来执行SQL语句 - FMResultSet
使用FMResultSet执行查询后的结果集 - FMDatabaseQueue
用于在多线程中执行多个查询和更新,它是线程安全的
CoreData
iOS10以后使用NSPersistentContainer来管理数据库对象。NSPersistentContainer中包括NSManagedObjectContext、NSManagedObjectModel、NSPersistentStoreCoordinator。
不是太复杂的模型修改,CoreData都可以自动迁移数据,我们只需要重新生成table映射的model文件即可。
数据库文件默认存放在Library/Application Support文件夹中
参考链接
https://www.jianshu.com/p/7616cbd72845
https://www.jianshu.com/p/6e048f7c5812
18、说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?
- 应用启动,并进行初始化时调用:
aaplication:didFimnishLanuchingWithOptions:
- 应用进入前台并处于活动状态时候调用:
applicationDidBecomeActive:
- 应用从活动状态进入到非活动状态:
applicationWillResignActive:
- 应用进入到后台时候调用的方法:
applicationDidEnterBackground:
- 应用进入到前台时候调用的方法:
appplicationWillEnterForeground:
- 应用被终止的状态:
applicationWillTeminate:
从后台到前台调用了哪些方法?
appplicationWillEnterForeground:
applicationDidBecomeActive:
第一次启动调用了哪些方法?
aaplication:didFimnishLanuchingWithOptions:
applicationDidBecomeActive:
从前台到后台调用了哪些方法?
applicationWillResignActive:
applicationDidEnterBackground:
19、NSCache优于NSDictionary的几点?
- NSCache具有自动删除的功能,以减少系统占用的内存
- NSCache是线程安全的,不需要加线程锁;
- 键对象不会像NSMutableDictionary中那样被复制。(NSCache的key只是对对象的strong引用,对象不需要实现NSCopying协议,NSCache也不会像NSDictionary一样复制对象)。
20、知不知道Designated Initializer?使用它的时候有什么需要注意的问题?
指定初始化函数 vs 便利初始化函数
-
子类如果有指定初始化函数,那么指定初始化函数实现时必须调用它的直接父类的指定初始化函数。
-
子类如果有指定初始化函数,那么便利初始化函数必须调用自己的其他初始化函数(包括指定初始化函数以及其他的便利初始化函数),不能调用super的初始化函数。
-
如果子类提供了指定初始化函数,那么一定要实现所有父类的指定初始化函数。
当 initWithCoder: 遇到 NS_DESIGNATED_INITIALIZER
- 如果父类没有实现NSCoding协议,那么应该调用父类的指定初始化函数
- 如果父类实现了NSCoding协议,那么子类的initWithCoder:的实现中需要调用父类的initWithCoder:方法
实现NSCoding协议的时候,我们可以显示的声明 initWithCoder: 为指定初始化函数(一个类可以有多个指定初始化函数,比如UIViewController)即可完美解决问题,既满足了指定初始化函数的三个规则,又满足了NSCoding协议的三条原则。
总计归纳
- 便利初始化函数只能调用自己类中的其他初始化方法
- 指定初始化函数才有资格调用父类的指定初始化函数
参考文章
https://www.cnblogs.com/smileEvday/p/designated_initializer.html
21、实现description方法能取到什么效果?
NSLog(@"%@",object);
调试的时候方便打印对自己有用的信息
22、objc使用什么机制管理对象内存?
ARC自动引用计数