美文网首页
问题大全

问题大全

作者: 海牛骑士 | 来源:发表于2018-08-31 23:00 被阅读58次

1.说一下OC的反射机制

所有的oc 对象都是继承于nsobject nsobject 提供了如下反射方法
反射方法

//SEL和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
// Class和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class __nullable NSClassFromString(NSString *aClassName);
// Protocol和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) NS_AVAILABLE(10_5, 2_0);
FOUNDATION_EXPORT Protocol * __nullable NSProtocolFromString(NSString *namestr) NS_AVAILABLE(10_5, 2_0);

通过上面的方法,我们可以动态的去选择创建哪个实例,并选择调用哪个方法,我们可以根据服务器 定制的参数来动态实例化我们的对象。

我们可以使用反射来实现 工厂模式。

2、block的实质是什么?有几种block?分别是怎样产生的?

block的实质就是一个结构体的oc 对象,里面又一个isa指针指向自己的类

根据isa指针,block一共有3种类型的block

  • _NSConcreteGlobalBlock 全局静态
  • _NSConcreteStackBlock 保存在栈中,出函数作用域就销毁
  • _NSConcreteMallocBlock 保存在堆中,

3、__block修饰的变量为什么能在block里面能改变其值?

因为变量通过__block 修饰后 定义后的内存地址和block内部变量的内存地址 指向的是同一个,所以是同一个变量 所以在 block内部能改变值。

4.说一下线程之间的通信

nsthreed 
线程间通信常用方法
// waitUntilDone的含义:
//    如果传入的是YES: 那么会等到主线程中的方法执行完毕, 才会继续执行下面其他行的代码
/*
 *  回到主线程执行,执行self的showImage方法,参数是image
 */
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
/*
 *  回到xx线程执行aSelector方法,参数是arg
 */
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
GCD
异步下载 完成后回到主线程 执行
 dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 1.下载图片(耗时)
    dispatch_async(queue, ^{
        NSLog(@"%@", [NSThread currentThread]);
        // 1.创建URL
        NSURL *url = [NSURL  URLWithString:@"http://stimgcn1.s-msn.com/msnportal/ent/2015/08/04/7a59dbe7-3c18-4fae-bb56-305dab5e6951.jpg"];
        // 2.通过NSData下载图片
        NSData *data = [NSData dataWithContentsOfURL:url];
        // 3.将NSData转换为图片
        UIImage *image = [UIImage imageWithData:data];

        // 4.更新UI
//        self.imageView.image = image;
//        NSLog(@"更新UI完毕");
        // 如果是通过异步函数调用, 那么会先执行完所有的代码, 再更新UI
        // 如果是同步函数调用, 那么会先更新UI, 再执行其它代码
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"%@", [NSThread currentThread]);
            self.imageView.image = image;
            NSLog(@"更新UI完毕");
        });
        NSLog(@"Other");
    });
NSOperation

// 1.创建一个新的队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 2.添加任务(操作)
    [queue addOperationWithBlock:^{
       //完成操作
      ...................
      //回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = image;
        }];
    }];

2. 添加依赖  
NSOperation之间可以设置依赖来保证执行顺序,⽐如一定要让操作A执行完后,才能执行操作B,可以像下面这么写
[operationB addDependency:operationA]; // 操作B依赖于操作

5.说一下hash算法原理

散列表,它是基于高速存取的角度设计的,也是一种典型的“空间换时间”的做法。顾名思义,该数据结构能够理解为一个线性表,可是当中的元素不是紧密排列的,而是可能存在空隙。
散列表(Hash table,也叫哈希表),是依据关键码值(Key value)而直接进行訪问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来訪问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

6.说一下nsdictionary 的实现原理

NSDictionary(字典)是使用 hash表来实现key和value之间的映射和存储的, hash函数设计的好坏影响着数据的查找访问效率。数据在hash表中分布的越均匀,其访问效率越高。而在Objective-C中,通常都是利用NSString 来作为键值,其内部使用的hash函数也是通过使用 NSString对象作为键值来保证数据的各个节点在hash表中均匀分布。

9、遇到过BAD_ACCESS的错误吗?你是怎样调试的?

BAD_ACCESS 报错属于内存访问错误,会导致程序崩溃,错误的原因是访问了野指针(悬挂指针)。野指针指的是本来指针指向的对象已经释放了,但指向该对象的指针没有置 nil,指针指向随机的未知的内存
1.开启僵尸(NSZombieEnabled)对象诊断 快速找到问题代码所在
2.内存泄漏检测 (Analyze)

10、什么是指针常量和常量指针?

指针常量:指针本身是一个常量。
常量指针:指向的对象是一个常量。

11、不借用第三个变量,如何交换两个变量的值?要求手动写出交换过程。

int  a = 4; 
int b = 8;

方法一  .算数运算
a = a+b;
b = a - b;
a = a - b;

方法 二 位运算
   a = a^b;
   b = a^b;
   a = a^b;
     NSLog(@"%li---%li",a,b);

13、如何去设计一个方案去应对后端频繁更改的字段接口?
14、KVO、KVC的实现原理

 kvc: Key-Value Coding: 键值编码 (KVC)
KVC运用了一个isa-swizzling技术. isa-swizzling就是类型混合指针机制, 将2个对象的isa指针互相调换, 就是俗称的黑魔法.
KVC主要通过isa-swizzling, 来实现其内部查找定位的. 默认的实现方法由NSOject提供
[object setValue:@"134567" forKey:@"uid"];

就会被编译器处理成:
// 首先找到对应sel
SEL sel = sel_get_uid("setValue:forKey:");
// 根据object->isa找到sel对应的IMP实现指针
IMP method = objc_msg_lookup (object->isa,sel);
// 调用指针完成KVC赋值
method(object, sel, @"134567", @"uid");
Key-Value Observing 键值观察(KVO), KVO是观察者模式的一种应用
kvo :基于kvc 的基础上添加了通知的思想。
// 添加一个观察者
[self.object addObserver:self forKeyPath:@"uid" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];

// 观察者监听到之后回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
   NSLog(@"keyPath: %@", keyPath);
   NSLog(@"object: %@", object);
   NSLog(@"change: %@", change);
   NSLog(@"context: %@", context);
}

// 移除观察者, 类在销毁前需要销毁
[self.object removeObserver:self forKeyPath:@"uid"];

15、用递归算法求1到n的和

16、category为什么不能添加属性?

在分类里使用@property声明属性,只是将该属性添加到该类的属性列表,但是没有生成相应的成员变量,也没有实现setter和getter方法。

成员变量是一个类的东西,分类本身就不是一个类,分类本来就是OC里面通过运行时动态的为一个类添加的一些方法和属性等,不是一个真正的类,你怎么给他添加成员变量呢?所以必须得用到高大上的运行时了。

总结一下,其实分类中是可以为一个类添加属性的,但是一定做不到添加成员变量,不要混淆了成员变量和属性的概念

其实属性是可以添加的。只是说现在Xcode自动会给属性生成成员变量让大家对这个概念有点混淆。Property是Property,Ivar是Ivar。
分类里面不能添加Ivar是因为分类本身并不是一个真正的类,它并没有自己的ISA。有兴趣可以研究一下类是怎么被创建出来的,类最开始生成了很多基本属性,比如IvarList,MethodList,分类只会将自己的method attach到主类,并不会影响到主类的IvarList。这就是为什么分类里面不能增加成员变量的原因。

17、说一下runloop和线程的关系。

runloop 本质是一个线程死循环,和线程相辅相成的 是为线程而生的 ,为线程提供保活,监听消息唤起线程的执行。没事干就睡觉。

18、说一下autoreleasePool的实现原理。

 单个自动释放池的执行过程就是objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)。
void *
objc_autoreleasePoolPush(void)
{
    if (UseGC) return nil;
    return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;
    AutoreleasePoolPage::pop(ctxt);
}
看到这里,我们发现这两个函数的实现都是调用了AutoreleasePoolPage类中的方法。于是我们可以断定,AutoreleasePool的是通过AutoreleasePoolPage类来实现的

magic:用来校验 AutoreleasePoolPage 的结构是否完整;
next:指向栈顶,也就是最新入栈的autorelease对象的下一个位置;
thread:指向当前线程;
parent:指向父节点
child:指向子节点
depth:表示链表的深度,也就是链表节点的个数
hiwat:表示high water mark(最高水位标记

通AutoreleasePoolPage类属性,我们可以推断出,这是一个双向链表的节点,AutoreleasePool的内存结构就是一个双向链表。
autoreleasepool的内存结构是一个双向链表栈,会频繁的有入栈和出栈操作,栈中存放的对象也会有增有减,hiwat就记录了入栈对象最多时候对象的个数

19、说一下简单工厂模式,工厂模式以及抽象工厂模式?

简单工厂模式:工厂可以创建同一系列的产品,产品的接口一致,但工厂就要根据参数进行判断到底创建哪种产品

卖早饭的张婆婆:可以做茶叶蛋,包子,稀饭

工厂模式:可以有多种工厂,工厂有共同的接口,一个工厂只能产生一种产品,比起简单工厂,工厂方法就不需要判断,耦合度低了不少

刘老板:只卖包子的包子铺,只卖稀饭的稀饭庄

抽象工厂模式:可以产生多个系列的产品,有2个维度的产品

KFC老板:可乐系列产品、汉堡系列产品,每种系列产品又分大,中,小三种。

如果这样来看应该很容易就能区分他们之间的关系了。

抽象工厂模式的优点:

(1)分离接口和实现

客户端使用抽象工厂来创建需要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已。也就是说,客户端从具体的产品实现中解耦。

(2)使切换产品族变得容易

因为一个具体的工厂实现代表的是一个产品族,比如上面例子的从Intel系列到AMD系列只需要切换一下具体工厂。

抽象工厂模式的缺点:

不太容易扩展新的产品:如果需要给整个产品族添加一个新的产品,那么就需要修改抽象工厂,这样就会导致修改所有的工厂实现类。

总结
抽象工厂模式是一种极为常见的设计模式。它是最基本的,因为它可以涉及许多类型的对象创建。一系列相关的类,应该作为一种抽象,不为客户端所见。抽象工厂则可以方便的解决这个问题,而不暴露创建过程中任何不必要的细节或所创建对象的确切类型。

20、如何设计一个网络请求库?

21、说一下多线程,你平常是怎么用的?

22、说一下UITableViewCell的卡顿你是怎么优化的?
1.cell 重用机制。
2.提前创建我们的UI对象。
3.避免发生离屏渲染
4.图片异步加载缓存处理
5.对应的cell 高度缓存 避免重复计算。
6.尽量减少子视图创建的数量。

23、看过哪些三方库?说一下实现原理以及好在哪里?

24、说一下HTTP协议以及经常使用的code码的含义。

25、设计一套缓存策略。

26、设计一个检测主线和卡顿的方案。

27、说一下runtime,工作是如何使用的?看过runtime源码吗?
消息转发 objc_sendMessage()
IMP指针是指向实现函数的指针,通过SEL取得IMP,objc_msgSend来执行实现方法

28、说几个你在工作中使用到的线程安全的例子。

1.使用atomic多线程原子性控制,atomic的原理给setter加上锁,只能保证setter方法的完整性。getter不会加锁
2.单例 使用GCD dispatch_once 保证线程安全和初始化唯一
3.多次循环处理 添加 nslock锁 保证线程安全防止内存泄漏。

29、用过哪些锁?哪些锁的性能比较高?
1.NSLock
2.NSConditionLock;条件锁 --- 条件锁是一个互斥锁,可以通过特定值来锁住和解锁
3.NSRecursiveLock;递归锁 --- 对同一线程,可以多次获得(lock)而不会造成死锁

30、说一下HTTP和HTTPs的请求过程?

31、说一下TCP和UDP

TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于广播和细节控制交给应用的通信传输
UDP(User Datagram Protocol)

      UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。 即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。

TCP(Transmission Control Protocol)

     TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。

小结TCP与UDP的区别:
    1.基于连接与无连接;
    2.对系统资源的要求(TCP较多,UDP少);
    3.UDP程序结构较简单;
    4.流模式与数据报模式 ;
    5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

32、说一下静态库和动态库之间的区别
静态库:链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝。
动态库:链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用(如系统的UIKit.framework等),节省内存。

33、load和initialize方法分别在什么时候调用的?
load 方法是在程序编译加载的时候调用。
initialize 是在第一次使用的时候 也就是第一次收到消息的时候
详解: https://blog.csdn.net/runintolove/article/details/52350070

34、NSNotificationCenter是在哪个线程发送的通知?

35、用过swift吗?如果没有,平常有学习吗?

36、说一下你对架构的理解?
第一,你必须要有抽象的能力,抽象的能力最基本就是去重,去重在整个架构中体现在方方面面,从定义一个函数,到定义一个类,到提供的一个服务,以及模板,背后都是要去重提高可复用率。
第二, 分类能力。做软件需要做对象的解耦,要定义对象的属性和方法,做分布式系统的时候要做服务的拆分和模块化,要定义服务的接口和规范。
第三, 算法(性能),它的价值体现在提升系统的性能,所有性能的提升,最终都会落到CPU,内存,IO和网络这4大块上。

37、为什么一定要在主线程里面更新UI?

1、在子线程中是不能进行UI 更新的,而可以更新的结果只是一个幻像:因为子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈,这中间的时间非常的短,就让大家误以为分线程可以更新UI。如果子线程一直在运行,则子线程中的UI更新的函数栈 主线程无法获知,即无法更新

2、只有极少数的UI能,因为开辟线程时会获取当前环境,如点击某个按钮,这个按钮响应的方法是开辟一个子线程,在子线程中对该按钮进行UI 更新是能及时的,如换标题,换背景图,但这没有任何意义

相关文章

网友评论

      本文标题:问题大全

      本文链接:https://www.haomeiwen.com/subject/detebftx.html