基础篇
1 . 一个字节多少位?一个汉字多少位?一个字母多少位
一个汉字等于两个英文字母等于两个字节、一个字节是八位
-
写一个标准的宏MIN,输入两个参数并返回较小的一个
define MIN (A,B) ( (A) <= (B) ? (A) : (B) )
- define定义和const定义的常量的区别
const定义的常量,是在程序运行时存放在常量表中的,系统会为它自动分配内存,而且在编译时进行类型检查
- static
1).作用于变量:
用static声明局部变量时,则改变变量的存储方式(生命期),使变量成为静态的局部变量,即编译时就为变量分配内存,直到程序退出才释放存储单元。这样,使得该局部变量有记忆功能,可以记忆上次的数据,不过由于仍是局部变量,因而只能在代码块内部使用(作用域不变)。
用static声明外部变量-------外部变量指在所有代码块{}之外定义的变量,它缺省为静态变量,编译时分配内存,程序结束时释放内存单元。同时 其作用域很广,整个文件都有效甚至别的文件也能引用它。为了限制某些外部变量的作用域,使其只在本文件中有效,而不能被其他文件引用,可以用static 关键字对其作出声明
2).作用于函数:
使用static用于函数定义时,对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。
如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字extern,表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外部函数的文件中,使用extern声明要用的外部函数即可。
- 一个函数中有异步的网络请求如何返回一个值
1. GCD信号量的方法
+ (NSString *)httpNet
{
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
__block NSString *objectID;
// 模拟block异步
[UIView animateWithDuration:3 animations:^{
objectID = @"222";
dispatch_semaphore_signal(signal);
}];
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
return objectID;
2. 直接给函数定义一个block返回
+ (NSString *)httpNetBlock:(void(^)(UIImage *Image))block
-
将一个函数在主线程执行的4种方法
•GCD方法,通过向主线程队列发送一个block块,使block里的方法可以在主线程中执行
dispatch_async(dispatch_get_main_queue(), ^{
//需要执行的方法
});
•NSOperation 方法
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; //主队列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//需要执行的方法
}];
[mainQueue addOperation:operation];
•NSThread 方法
[self performSelector:@selector(method) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES modes:nil];
[self performSelectorOnMainThread:@selector(method) withObject:nil waitUntilDone:YES];
[[NSThread mainThread] performSelector:@selector(method) withObject:nil];
•RunLoop方法
[[NSRunLoop mainRunLoop] performSelector:@selector(method) withObject:nil];
-
NSTimer创建后,会在哪个线程运行
•用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程
•自己创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程 -
NSNotification接受通知是在那个线程,接收通知是异步的还是同步的
•接收通知的线程,和发送通知所处的线程是同一个线程
•NSNotification是同步的 ,因为 NSNotificationCenter 会一直等待所有的 接收者 (observer)都收到并且处理了通知才会返回到poster - id和NSObject*的区别
typedef struct objc_object *id
•id可以理解为指向对象的指针。所有oc的对象 id都可以指向,编译器不会做类型检查,id调用任何存在的方法都不会在编译阶段报错,当然如果这个id指向的对象没有这个方法,该崩溃还是会崩溃的。
•NSObject *指向的必须是NSObject的子类,调用的也只能是NSObjec里面的方法否则就要做强制类型转换。
•不是所有的OC对象都是NSObject的子类,还有一些继承自NSProxy。NSObject *可指向的类型是id的子集。
-
NSProxy & NSObject
NSObjetct:
•NSObject协议组对所有的Object -C下的objects都生效。如果objects遵从该协议,就会波看作是first -class
objects (- 级类)。另外, 遵从该协议的objects的retain, release, autorelease等 方法也服从objects的管理和在
Foundation中定义的释放方法。- -些容器中的对象也可以管理这些
•objects,比如说NSArray和NSDictionary定义的对象。Cocoa的根类也遵循该协议,所以所有继承NSObjects的
objects都有遵循该协议的特性。
•NSProXY:NSProxy是一个虚基类,它为一些表现的像是其它对象替身或者并不存在的对象定义一套API。 -般
的,发送给代理的消息被转发给一个真实的对象或者代理本身load(或者将本身转换成)一个真实的对象。
•NSProxy的基类可以被用来透明的转发消息或者耗费巨大的对象的lazy初始化
-
id和instancetype的区别
•instancetype是clang 3.5开始,clang提供的一个关键字,表示某个方法返回的未知类型的Objective-C对象。
一、关联返回类型(related result types)
根据Cocoa的命名规则,满足下述规则的方法:
1、类方法中,以alloc或new开头
2、实例方法中,以autorelease,init,retain或self开头
会返回一个方法所在类类型的对象,这些方法就被称为是关联返回类型的方法
NSArray *array = [[NSArray alloc] init];
二、instancetype作用
• 就是使那些非关联返回类型的方法返回所在类的类型!
• 能够确定对象的类型,能够帮助编译器更好的为我们定位代码书写问题,比如:
[[[NSArray alloc] init] mediaPlaybackAllowsAirPlay]; // "No visible @interface for `NSArray` declares the selector `mediaPlaybackAllowsAirPlay`"
[[NSArray array] mediaPlaybackAllowsAirPlay]; // (No error)
上例中第一行代码,由于[[NSArray alloc]init]的结果是NSArray*,这样编译器就能够根据返回的数据类型检测出NSArray是否实现mediaPlaybackAllowsAirPlay方法。有利于开发者在编译阶段发现错误。
第二行代码,由于array不属于关联返回类型方法,[NSArray array]返回的是id类型,编译器不知道id类型的对象是否实现了mediaPlaybackAllowsAirPlay方法,也就不能够替开发者及时发现错误
三、instancetype和id的异同
1、相同点
都可以作为方法的返回类型
2、不同点
(1)instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;
(2)instancetype只能作为返回值,不能像id那样作为参数
- KVO,NSNotification,delegate及block区别
•KVO就是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以监测一个值的变化,比如View的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。
•NSNotification是通知,也是一对多的使用场景。在某些情况下,KVO和NSNotification是一样的,都是状态变化之后告知对方。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。
•delegate 是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate(代理人)就可以了,由其他类完成所需要的操作。所以delegate是一对一关系。
•block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观。
•KVO一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用KVO(观察者模式)。delegate一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate。
Notification一般是进行全局通知,比如利好消息一出,通知大家去买入。delegate是强关联,就是委托和代理双方互相知道,你委托别人买股票你就需要知道经纪人,经纪人也不要知道自己的顾客。Notification是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息
-
synthesize & denamic
• 通过@sythesize
指令告诉编译器在编译期间产生getter/setter方法。
• 通过@dynamic指令,自己实现方法。
有些存取是在运行时动态创建的,如在CoreData的NSManagedObject类使 -
imageName和mageWithContextOfFile的区别?哪个性能高
• 用imageNamed的方式加载时,图片使用完毕后缓存到内存中,内存消耗多,加载速度快。即使生成的对象被autoReleasePool释放了,这份缓存也不释放,如果图像比较大,或者图像比较多,用这种方式会消耗很大的内存,可能会存在内存溢出的情况。
imageNamed采用了缓存机制,如果缓存中已加载了图片,直接从缓存读就行了,每次就不用再去读文件了,效率会更高。
• ImageWithContextOfile加载, 图片是不会缓存的,加载速度慢。
• 大量使用imageNamed方式会在不需要缓存的地方额外增加开销CPU的时间当应用程字需要加载- -张比较大的图片并且使用一次性,那么其实是没有必要去缓存这个图片的,用imageWithContentsOfile是 最为经济的方式,这样不会因为Ullmage元素较多情况下,CPU会被逐个分散在不必要缓存上浪费过多时间 -
项目开发中遇到的内存溢出
• 滑动列表的时候,内存出现莫名的增长,原因可能有如下可能:
• 没有使用UITableView的reuse机制; 导致每显示一个cell都用autorelease的方式重新alloc一次; 导致cell的内存不断的增加;
• 每个cell会显示一个单独的UIView, 在UIView发生内存泄漏,导致cell的内存不断增长;
• 频繁访问图片的时候,内存莫名的增长;
频繁的访问网络图片,导致iOS内部API,会不断的分配autorelease方式的buffer来处理图片的解码与显示; 利用图片cache可以缓解一下此问题;
• 频繁打开和关闭SQLite,导致内存不断的增长
•非ARC模式下, release忘了.
• 少用[UIImage imageNamed]
• 后台线程的时候,忘了加AutoReleasePool -
项目开发中遇到的内存泄露
• 第三方框架的不正当使用
• block循环引用(正确使用copy)
• delegate的循环引用(正确使用weak)
• NSTimer的循环引用(NSTimer的二次封装,或者repeats为NO是正确的释放)
• 非OC对象的内存处理(CF类CG类)
• 地图类处理(代理以及大头针之类)
• 大数量次数的循环内存暴涨(for循环在结束时才会释放,所有把需要循环的代码逻辑放在)
17.NSCache & NSDcitionary
NSCache与可变集合有几点不同:
•NSCache类结合了各种自动删除策略,以确保不会占用过多的系统内存。如果其它应用需要内存时,系统自动执行这些策略。当调用这些策略时,会从缓存中删除一些对象,以最大限度减少内存的占用。
•NSCache是线程安全的,我们可以在不同的线程中添加、删除和查询缓存中的对象,而不需要锁定缓存区域。
•不像NSMutableDictionary对象,- 一个缓存对象不会拷贝key对象。
•NSCache和NSDictionary类似,不同的是系统回收内存的时候它会自动删掉它的内容。
(1)可以存储(当然是使用内存
(2)保持强应用,无视垃圾回收. =>这-点同NSMutableDictionary(3)有固定客户.
18.UILayer & UiView
•UlView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它本身完全是由CoreAnimation来实现的(Mac下似乎不是这样)。它真正的绘图部分,是由一个叫CAL ayer (Core Animation Layer)的类来管理。UIView本身,更像是-个CAL ayer的管理器,访问它的跟绘图和跟坐标有关的属性,例如frame, bounds等等,实际 上内部都是在访问它所包含的CAL ayer的相关属性。
•UIView有个重要属性layer,可以返回它的主CAL ayer实例。
•UIView的CAL ayer类似UIView的子View树形结构,也可以向它的layer上添加子layer,来完成某些特殊的表示。即CALayer层是可以嵌套的。
•UIView的ayer树形在系统内部,被维护着三份copy。分别是逻辑树,这里是代码可以操纵的;动画树,是一个中间层,系统就在这一层上更改属性,进行各种渲染操作;显示树,其内容就是当前正被显示在屏幕上得内容。
- iOS Category 和 Protocol 中的 Property会自动生成setter/getter方法吗
- 对象提前释放。野指针,空指针, 僵尸对象
- runtime的内存模型(isa、对象、类、metaclass、结构体的存储信息等
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
}
类对象 第一个元素是一个隐藏元素(来自于父类),该属性即为isa,其指向元类;
第二个元素是Classsuperclass,指向其父类;
第三个元素是cache_t cache,类的相关缓存;
第四个元素是class_data_bits_t bits,返回类存储的相关信息;
Class superclass 同样是一个指针,占用8字节; Class isa 是一个指针,占用8字节;
cache_t 是一个结构体,其中第一个元素是一个指针占8字节,第二、三个元素都是int32各占4字节,总共占16字节
类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中
class_ro_t,其中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。
-
为什么设计metalclass
可以从 objc_object的结构中看出最终的基类(NSObject)的元类对象isa指向的是自己本身,从而形成一个闭环。
元类(Meta Class):是一个类对象的类,即:Class的类,这里保存了类方法等相关信息
metaclass代表的是类对象的对象,它存储了类的类方法,它的目的是将实例和类的相关方法列表以及构建信息区分开来,方便各司其职,符合单一职责设计原则。
全面的解释请移步这里 -
class_copyIvarList & class_copyPropertyList&objc_method_list区别
class_copyIvarList:获得的是class_copyPropertyList修饰的以及在m文件的中@implementation内定义的变量
class_copyPropertyList:获得的是由@property修饰过的变量,
objc_method_list:存储了类的方法列表,可以通过class_copyMethodList获取 - 消息转发机制,消息转发机制和其他语言的消息机制优劣对比
// 第一阶段 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveClassMethod:(SEL)selector //处理的是类方法
// 第二阶段:第三者的处理
- (id)forwardingTargetForSelector:(SEL)selector
// 第三阶段: 标准消息转发流程
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)invocation
// 第四阶段:报错
- (void)doesNotRecognizeSelector:(SEL)aSelector{}
-
IMP、SEL、Method的区别和使用场景
Method = SEL + IMP.
IMP:是方法的实现,只想函数实现的结构体指针.
SEL:是方法名
Method:是objc_method类型指针,它是一个结构体. -
weak 和 SideTable的认识
weak:其实是一个hash表结构,其中的key是所指对象的地址,value是weak的指针数组,weak表示的是弱引用,不会对对象引用计数+1,当引用的对象被释放的时候,其值被自动设置为nil,一般用于解决循环引用的。
weak的实现原理
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数, 调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
struct SideTable {
// 保证原子操作的自旋锁
spinlock_t slock;
// 引用计数的 hash 表
RefcountMap refcnts;
// weak 引用全局 hash 表
weak_table_t weak_table;
}
-
保证线程顺序执行的方法
1 dispatch_group_t + dispatch_group_notify
- (void)runDispatchTest:(NSArray *)images{
// 需要上传的数据
// 准备保存结果的数组,元素个数与上传的图片个数相同,先用 NSNull 占位
NSMutableArray* result = [NSMutableArray array];
for (UIImage* image in images) {
[result addObject:[NSNull null]];
}
dispatch_group_t group = dispatch_group_create();
for (NSInteger i = 0; i < images.count; i++){
dispatch_group_enter(group);//通知 group,下个任务要放入 group 中执行了
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// NSLog(@"队列组:有一个耗时操作完成!");
[uploadImgae startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest * _Nonnull request) {
NSLog(@"第 %d 张图片上传成功: %@", (int)i + 1, request.responseObject);
@synchronized (result)
{ // NSMutableArray 是线程不安全的,所以加个同步锁
result[i] = request.responseObject;
}
dispatch_group_leave(group);//通知 group,任务成功完成,要移除,与 enter成对出现
} failure:^(__kindof YTKBaseRequest * _Nonnull request) {
NSLog(@"第 %d 张图片上传失败: %@", (int)i + 1, request.error);
}];
});
}
/**只要任务全部完成了,就会在最后调用*/
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"队列组:前面的耗时操作都完成了,回到主线程进行相关操作");
for (id response in result) {
NSLog(@"上传完成的数据%@", response);
}
});
}
2 dispatch_group_t + dispatch_group_notify
提交一个栅栏函数在执行中,它会等待栅栏函数执行完再去执行下一行代码(注意是下一行代码),同步栅栏函数是在主线程中执行的
dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t blcok);
提交一个栅栏函数在异步执行中,它会立马返回开始执行下一行代码(不用等待任务执行完毕)
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t blcok);
1、dispatch_barrier_sync需要等待自己的任务(barrier)结束之后,才会继续添加并执行写在barrier后面的任务(4、5、6),然后执行后面的任务
2、dispatch_barrier_async将自己的任务(barrier)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(4、5、6)插入到queue,然后执行任务。
3 NSOperationQueueu 添加依赖或者等待
- (void)operationQueueMethod {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 2;
// 第一张图片
NSBlockOperation *op_one = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"正在下载第一张图片");
}];
// 第二张图片
NSBlockOperation *op_two = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"正在下载第二张图片");
}];
/*
// 添加到队列执行
[queue addOperation:op1];
[queue addOperation:op2];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
UIGraphicsBeginImageContext(CGSizeMake(300, 400));
[self.imageOne drawInRect:CGRectMake(0, 0, 150, 400)];
[self.imageTwo drawInRect:CGRectMake(150, 0, 150, 400)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
});
}];
[operation addDependency: op1];
[operation addDependency: op2];
[queue addOperation:operation];/// 不会阻塞主线程
*/
[queue addOperations:@[op_one, op_two] waitUntilFinished:true];// 会阻塞主线程
[queue addOperationWithBlock:^{
UIGraphicsBeginImageContext(CGSizeMake(300, 300));
[self.imageOne drawInRect:CGRectMake(0, 100, 150, 300)];
[self.imageTwo drawInRect:CGRectMake(180, 100, 150, 300)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *imageView = [[UIImageView alloc] initWithImage:newImage];
[self.view addSubview:imageView];
self.textLabel.text = @"图片合成";
});
}];
NSLog(@"主线程会等到队列执行完,即会阻塞主线程");
}
中极篇
- 寻找两个UIView的最近的公共父类
这个问的其实是数据结构中的二叉树,查找一个普通二叉树中两个节点最近的公共祖先问题
假设两个视图为UIViewA、UIViewC,其中UIViewA继承于UIViewB,UIViewB继承于UIViewD,UIViewC也继承于UIViewD;即A->B->D,C->D
我们将一个路径中的所有点先放进NSSet中.因为NSSet的内部实现是一个hash表,所以查询元素的时间的复杂度变成O(1),我们一共有N个节点,所以总时间复杂度优化到了O(N)
- (void)viewDidLoad {
[super viewDidLoad];
Class commonClass1 = [self commonClass1:[ViewA class] andClass:[ViewC class]];
NSLog(@"%@",commonClass1);
// 输出:2018-03-22 17:36:01.868966+0800 两个UIView的最近公共父类[84288:2458900] ViewD
}
// 获取所有父类
- (NSArray *)superClasses:(Class)class {
if (class == nil) {
return @[];
}
NSMutableArray *result = [NSMutableArray array];
while (class != nil) {
[result addObject:class];
class = [class superclass];
}
return [result copy];
}
- (Class)commonClass1:(Class)classA andClass:(Class)classB {
NSArray *arr1 = [self superClasses:classA];
NSArray *arr2 = [self superClasses:classB];
NSSet *set = [NSSet setWithArray:arr2];
for (NSUInteger i =0; i<arr1.count; ++i) {
Class targetClass = arr1[i];
if ([set containsObject:targetClass]) {
return targetClass;
}
}
return nil;
}
我的理解如果有错漏请一定指出,非常感谢!
网友评论