iOS之OC面试题

本文转自(https://www.jianshu.com/p/2e1b3f54b4f3)、
(https://www.jianshu.com/p/5bbde2480680)、(http://www.cocoachina.com/ios/20180305/22453.html)、(http://www.cnblogs.com/zyy98877/p/8567128.html)。本文主要为自己复习基础知识点,整理笔记使用,温故而知新。

1、Objective-C反射机制

Objective-C反射机制:是类似于Java的反射机制,这种动态机制可以让oc语言更加的灵活。
oc反射机制有三个用途:

 1、获得Class
 2、检查继承关系
 3、动态的调用方法
获取Class对象
Class对象其实本质上就是一个结构体,这个结构体中的成员变量还是自己,
这种设计方式非常像链表的数据结构。

typedef struct objc_class *Class;
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;                                  
}

可以直接用一个实例对象或类对象,直接调用Class方法,都可以获取Class对象。我们调用下面方法,都可以获得Class对象。

// 在实例方法中通过self调用class实例方法获取类对象
[self class]
// 通过ViewController类直接调用class类方法获取类对象
[ViewController class]
// 在类方法中使用类对象调用class方法获取类对象
+ (Class)classMethod {
    return [self class];
}
NSLog(@"%p, %p, %p", [ViewController classMethod], 
[ViewController class], [self class]);

#import "Person.h"

 // 1.通过字符串来获得Class
    Class className = NSClassFromString(@"Person");
    NSLog(@"%@", className);

  // 2.直接使用Class来创建 一般情况之下我们都用这种方式创建对象
    Person *person = [[Person alloc] init];
    NSLog(@"%@", person);
  // 通过实例对象获得Class
    NSLog(@"%@", [person class]);

   // 3.通过类来获得Class
    NSLog(@"Person.Class = %@", [Person class])

通过打印,我们发现调用这三个方法,获取到的类对象是同一个类对象,内存地址也是一样的。
这是因为这三个方法调用class方法,打印的都是类对象的isa指针。

检查继承关系

NSObject类中为我们提供了一些基础方法,用来做一些判断操作,这些方法都是发生在运行时动态判断的。

// 当前对象是否这个类或其子类的实例
- (BOOL)isKindOfClass:(Class)aClass;
// 当前对象是否是这个类的实例
- (BOOL)isMemberOfClass:(Class)aClass;
// 当前对象是否遵守这个协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 当前对象是否实现这个方法
- (BOOL)respondsToSelector:(SEL)aSelector;
// NSObject提供的了一下几个方法可供一般(继承于NSObject)的对象直接调用

    // 通过对象来判断该对象的Class
    Person *person = [[Person alloc] init];
    NSLog(@"[person class] =  %@", [person class]);    

    // 判断对象是否为某个类的实例对象
    NSLog(@"person是否是Person的实例对象:%d",
 [person isMemberOfClass:Person.class]);

    // 判断实例对象是都为某个类及其子类的实例
    NSLog(@"[person isKindOfClass:[person class]] = %d ", 
[person isKindOfClass:[person class]]);

if ([self isKindOfClass:NSClassFromString(@"UIView")]) {
    NSLog(@"The Current Class is UIView Class");
}
    
    // 判断的实例对象是否实现了指定的协议
    NSLog(@"%d", 
[person conformsToProtocol:@protocol(UITableViewDelegate)]);
动态的调用方法

系统Foundation框架为我们提供了一些方法反射的API,我们可以通过这些API执行将字符串转为SEL等操作。由于OC语言的动态性,这些操作都是发生在运行时的。

// 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);

通过这些方法,我们可以在运行时选择创建那个实例,并动态选择调用哪个方法。这些操作甚至可以由服务器传回来的参数来控制,我们可以将服务器传回来的类名和方法名,实例为我们的对象。

// 假设从服务器获取JSON串,通过这个JSON串获取需要创建的类为ViewController,并且调用这个类的getDataList方法。
Class class = NSClassFromString(@"ViewController");
ViewController *vc = [[class alloc] init];
SEL selector = NSSelectorFromString(@"getDataList");
[vc performSelector:selector];

// 此处希望能动态的调用move方法

    // 使用 performSelector: withObject: 动态的调用move:方法
    [self performSelector:@selector(move:) withObject:@2];
    [self performSelector:NSSelectorFromString(@"move:") withObject:@10];

    // 使用objc_msgSend 进行实现方法
    objc_msgSend(self, @selector(move:), @4);
    objc_msgSend(self, @selector(test:andNum:), @1, @2);
2、设计模式是什么? 你知道哪些设计模式,并简要叙述?
设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情。
1). MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。
2). MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。
3). 单例模式:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。
4). 观察者模式:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。
5). 委托模式:代理+协议的组合。实现1对1的反向传值操作。
6). 工厂模式:通过一个类方法,批量的根据已有模板生产对象。
3、MVC 和 MVVM 的区别
1). MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。 MVVM是对胖模型进行的拆分,其本质是给控制器减负,将一些弱业务逻辑放到VM中去处理。
2). MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。MVC是一切设计的基础,所有新的设计模式都是基于MVC进行的改进。
4、#import跟 #include 有什么区别,@class呢,#import<> 跟 #import””有什么区别?
1). #import 是Objective-C导入头文件的关键字,#include是C/C++导入头文件的关键字,使用#import头文件会自动只导入一次,不会重复导入。
2). @class告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含。
3). #import<>用来包含系统的头文件,#import””用来包含用户头文件。
5、NSDictionary的实现原理

NSSArray 用于对象有序集合(NSObject对象)
NSSet 用于对象的无序,不重复集合
NSDictionary无序键值映射

NSDictionary(字典)是使用哈希表 Hash table(也叫散列表)来实现的。哈希表是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键(key)值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做哈希表。也就是说哈希表的本质是一个数组,数组中每一个元素其实就是NSDictionary键值对。

若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为哈希函数。

哈希冲突:如果关键字k不同,但是通过哈希函数f(k)得到的结果是一样的,这样就会出现哈希冲突,也就是说,得到的这个地址有可能已经存在键值对了。
比如我们存储70个元素,但我们可能为这70个元素申请了100个元素的空间。70/100=0.7,这个数字称为负载因子。我们之所以这样做,也是为了“快速存取”的目的。我们基于一种结果尽可能随机平均分布的固定函数H为每个元素安排存储位置,这样就可以避免遍历性质的线性搜索,以达到快速存取。但是由于此随机性,也必然导致一个问题就是冲突。所谓冲突,即两个元素通过散列函数H得到的地址相同,那么这两个元素称为“同义词”。

解决冲突是一个复杂问题。冲突主要取决于:
(1)散列函数,一个好的散列函数的值应尽可能平均分布。
(2)处理冲突方法。
(3)负载因子的大小。太大不一定就好,而且浪费空间严重,负载因子和散列函数是联动的。

解决冲突的办法:
(1)线性探查法:冲突后,线性向前试探,找到最近的一个空位置。缺点是会出现堆积现象。存取时,可能不是同义词的词也位于探查序列,影响效率。
(2)双散列函数法:在位置d冲突后,再次使用另一个散列函数产生一个与散列表桶容量m互质的数c,依次试探(d+n*c)%m,使探查序列跳跃式分布。

解决冲突:可以通过优化哈希函数来减少冲突的几率,如果冲突已经发生,可以通过开放寻址法或者拉链法解决冲突。
拉链法解决冲突:大概原理就是将同一个存储位置的所有元素保存在一个链表中。

6、线程和进程的区别?

进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

7、@property 的本质是什么?
@property 的本质是什么?
    @property = ivar + getter + setter;
“属性” (property)有两大概念:ivar(实例变量)、getter+setter(存取方法)

“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。
8、 什么情况使用 weak 关键字,相比 assign 有什么不同?
什么情况使用 weak 关键字?

在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性

自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;
当然,也可以使用strong。

因为当我们将控件拖到 Storyboard 上,相当于新创建了一个对象,而这个对象是加到视图控制器的view上,view有一个subViews属性,这个属性是一个数组,里面是这个view的所有子view,而我们加的控件就位于这个数组中,那么说明,实际上我们的控件对象是属于view的,也就是说view对加到它上面的控件是强引用。当我们使用Outlet属性的时候,我们是在viewController里面使用,而这个Outlet属性是有view来进行强引用的,我们在viewController里面仅仅是对其使用,并没有必要拥有它,所以是weak的。

如果将weak改为strong,也是没有问题的,并不会造成强引用循环。当viewController的指针指向其他对象或者为nil,这个viewController销毁,那么对控件就少了一个强引用指针。然后它的view也随之销毁,那么subViews也不存在了,那么控件就又少了一个强引用指针,如果没有其他强引用,那么这个控件也会随之销毁。

使用storyboard(xib不行)创建的vc,会有一个叫_topLevelObjectsToKeepAliveFromStoryboard的私有数组强引用所有top level的对象,所以这时即便outlet声明成weak也没关系。

不同点:
weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。

assign 可以用非 OC 对象,而 weak 必须用于 OC 对象
9、Objective-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?Category和Extension的区别?

为系统类进行拓展的时候,我们可以不用继承系统类,直接给系统类添加方法,最大程度的体现了Objective-C的动态语言特性。

  • Category的小括号内一定要有名字;故Class Extension亦被称之为匿名分类;
  • Category的方法不一定非要在@implement中实现,也可以在其他位置中实现,但是当调用Category的方法时,依据继承树没有找到该方法的实现,程序则会崩溃;并且,Category的方法优先级较高,可以覆盖原类的方法;
  • Category的方法中,不可以调用super方法;
  • 在实际开发中要注意的是,Category 方法可能会覆盖于同一个类class 的其它 category 中的方法。但也可能被覆盖,因为不法预知他们的加载优先顺序,出现这种情况通常会在编译时出错。如果在一个开发的SDK中使用了类别, 就最好保证类别名不同于使用者的类别名以及类别方法也不同于使用者的类别方法名, 通常通过加前缀来做到。
  • Category理论上不能添加成员变量,但是可以使用@dynamic(即运行时Runtime)来弥补这种不足.

Objective-C的类不可以多重继承;可以实现多个接口(协议);Category是类别;一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。
Category的小括号中有名字,而Extension没有;
Category只能扩充方法,不能扩充成员变量和属性;
如果Category声明了声明了一个属性,那么Category只会生成这个属性的set,get方法的声明,也就不是会实现。

10、苹果为什么要废弃dispatch_get_current_queue?
`dispatch_get_current_queue` 容易造成死锁
11、dispatch_barrier_async的作用是什么?
在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 `barrier` 来等待之前任务完成,避免数据竞争等问题。 `dispatch_barrier_async` 函数会等待追加到`Concurrent Dispatch Queue`并行队列中的操作全部执行完之后,然后再执行 `dispatch_barrier_async` 函数追加的处理,等 `dispatch_barrier_async` 追加的处理执行结束之后,`Concurrent Dispatch Queue`才恢复之前的动作继续执行。

打个比方:比如你们公司周末跟团旅游,高速休息站上,司机说:大家都去上厕所,速战速决,上完厕所就上高速。超大的公共厕所,大家同时去,程序猿很快就结束了,但程序媛就可能会慢一些,即使你第一个回来,司机也不会出发,司机要等待所有人都回来后,才能出发。 `dispatch_barrier_async` 函数追加的内容就如同 “上完厕所就上高速”这个动作。

(注意:使用 `dispatch_barrier_async` ,该函数只能搭配自定义并行队列 `dispatch_queue_t` 使用。不能使用: `dispatch_get_global_queue` ,否则 `dispatch_barrier_async` 的作用会和 `dispatch_async` 的作用一模一样。 )
12、@synthesize 和 @dynamic 分别有什么作用?
@property有两个对应的词,一个是@synthesize(合成实例变量),一个是@dynamic。
如果@synthesize和@dynamic都没有写,那么默认的就是 @synthesize var = _var;
// 在类的实现代码里通过 @synthesize 语法可以来指定实例变量的名字。(@synthesize var = _newVar;)
1. @synthesize 的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
2. @dynamic 告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成(如,@dynamic var)。
13、常见的 Objective-C 的数据类型有那些,和C的基本数据类型有什么区别?如:NSInteger和int
Objective-C的数据类型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,这些都是class,创建后便是对象,而C语言的基本数据类型int,只是一定字节的内存空间,用于存放数值;NSInteger是基本数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。NSInteger是基本数据类型Int或者Long的别名(NSInteger的定义typedef long NSInteger),它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是long。
14、 如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 合并图片
});
15、GCD的队列(dispatch_queue_t)分哪两种类型?
串行队列 Serial Dispatch Queue
并行队列 Concurrent Dispatch Queue
16、使用block时什么情况会发生引用循环,如何解决?
一个对象中强引用了block,在block中又强引用了该对象,就会发射循环引用。

解决方法是将该对象使用__weak或者__block修饰符修饰之后再在block中使用。

1.  id weak weakSelf = self; 或者 weak __typeof(&*self)weakSelf = self该方法可以设置宏
2.  id __block weakSelf = self;

或者将其中一方强制制空 `xxx = nil`。

检测代码中是否存在循环引用问题,可使用 Facebook 开源的一个检测工具  [FBRetainCycleDetector](https://github.com/facebook/FBRetainCycleDetector) 。

17、id 声明的对象有什么特性?
id 声明的对象具有运行时的特性,即可以指向任意类型的Objcetive-C的对象。
18、Objective-C 如何对内存管理的,说说你的看法和解决方法?
通过` retainCount` 的机制来决定对象是否需要释放。 每次 `runloop` 的时候,都会检查对象的` retainCount`,如果`retainCount` 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。

Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。
1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。
2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。
3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。
19、苹果是如何实现autoreleasepool的?
`autoreleasepool` 以一个队列数组的形式实现,主要通过下列三个函数完成.

objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_autorelease

看函数名就可以知道,对 autorelease 分别执行 push,和 pop 操作。销毁对象时执行release操作。
20、我们说的OC是动态运行时语言是什么意思?
主要是将数据类型的确定由编译时,推迟到了运行时。简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。
21、BAD_ACCESS在什么情况下出现?
访问了悬垂指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环
22、什么是 KVO 和 KVC?
1). KVC(Key-Value-Coding):键值编码 是一种通过字符串间接访问对象的方式(即给属性赋值)
    举例说明:
    stu.name = @"张三" // 点语法给属性赋值
    [stu setValue:@"张三" forKey:@"name"]; // 通过字符串使用KVC方式给属性赋值
    stu1.nameLabel.text = @"张三";
    [stu1 setValue:@"张三" forKey:@"nameLabel.text"]; // 跨层赋值
2). KVO(key-Value-Observing):键值观察机制 他提供了观察某一属性变化的方法,极大的简化了代码。
     KVO只能被KVC触发,包括使用setValue:forKey:方法和点语法。
   // 通过下方方法为属性添加KVO观察
   - (void)addObserver:(NSObject *)observer
                     forKeyPath:(NSString *)keyPath
                     options:(NSKeyValueObservingOptions)options
                     context:(nullable void *)context;
   // 当被观察的属性发送变化时,会自动触发下方方法                   
   - (void)observeValueForKeyPath:(NSString *)keyPath
                              ofObject:(id)object
                                  change:(NSDictionary *)change
                                 context:(void *)context{}
    
KVC 和 KVO 的 keyPath 可以是属性、实例变量、成员变量。

23、KVC的底层实现?
当一个对象调用setValue方法时,方法内部会做以下操作:
1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。
24、KVO基本原理
1.KVO是基于runtime机制实现的

2.当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制

3.如果原类为Person,那么生成的派生类名为NSKVONotifying_Person

4.每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法

5.键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
25、KVO深入原理
1.Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:?NSKVONotifying_A的新类,该类继承自对象A的本类,且KVO为NSKVONotifying_A重写观察属性的setter?方法,setter?方法会负责在调用原?setter?方法之前和之后,通知所有观察对象属性值的更改情况。

2.NSKVONotifying_A类剖析:在这个过程,被观察对象的 isa 指针从指向原来的A类,被KVO机制修改为指向系统新创建的子类 NSKVONotifying_A类,来实现当前类属性值改变的监听;

3.所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对KVO的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”的类(),就会发现系统运行到注册KVO的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为NSKVONotifying_A的中间类,并指向这个中间类了。

4.(isa 指针的作用:每个对象都有isa 指针,指向该对象的类,它告诉 Runtime 系统这个对象的类是什么。所以对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。)?因而在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值通知机制。

5.子类setter方法剖析:KVO的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的前后分别调用2个方法: 被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath?的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath?的属性值已经变更;之后,?observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的setter?方法这种继承方式的注入是在运行时而不是编译时实现的。
KVO原理图.png
26、ViewController生命周期
按照执行顺序排列:
1. initWithCoder:通过nib文件初始化时触发。
2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。      
3. loadView:开始加载视图控制器自带的view。
4. viewDidLoad:视图控制器的view被加载完成。  
5. viewWillAppear:视图控制器的view将要显示在window上。
6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9. viewDidAppear:视图控制器的view已经展示到window上。 
10. viewWillDisappear:视图控制器的view将要从window上消失。
11. viewDidDisappear:视图控制器的view已经从window上消失。

27、以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的。利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响ScrollView的滑动。

如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。

同时因为mode还是可定制的,所以:

Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决。代码如下:

//将timer添加到NSDefaultRunLoopMode中
[NSTimer scheduledTimerWithTimeInterval:1.0
     target:self
     selector:@selector(timerTick:)
     userInfo:nil
     repeats:YES];
//然后再添加到NSRunLoopCommonModes里
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
     target:self
     selector:@selector(timerTick:)
     userInfo:nil
     repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
28、runloop和线程有什么关系?
总的说来,Run loop,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分, Cocoa 和 CoreFundation 都提供了 run loop 对象方便配置和管理线程的 run loop (以下都以 Cocoa 为例)。每个线程,包括程序的主线程( main thread )都有与之相应的 run loop 对象。

runloop 和线程的关系:

主线程的run loop默认是启动的。
iOS的应用程序里面,程序启动后会有一个如下的main()函数

int main(int argc, char * argv[]) {
   @autoreleasepool {
       return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
   }
}
重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。

对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。

在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。

NSRunLoop *runloop = [NSRunLoop currentRunLoop];

29、runloop的mode作用是什么?
model 主要是用来指定事件在运行循环中的优先级的,分为:

NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
UITrackingRunLoopMode:ScrollView滑动时
UIInitializationRunLoopMode:启动时
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
苹果公开提供的 Mode 有两个:

NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
NSRunLoopCommonModes(kCFRunLoopCommonModes)
30、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
不能向编译后得到的类中增加实例变量;
能向运行时创建的类中添加实例变量;
解释下:

因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量;

运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。
31、什么是谓词?
谓词就是通过NSPredicate给定的逻辑条件作为约束条件,完成对数据的筛选。
//定义谓词对象,谓词对象中包含了过滤条件(过滤条件比较多)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30];
//使用谓词条件过滤数组中的元素,过滤之后返回查询的结果
NSArray *array = [persons filteredArrayUsingPredicate:predicate];

32、什么是懒加载?
懒加载就是只在用到的时候才去初始化。也可以理解成延时加载。
我觉得最好也最简单的一个例子就是tableView中图片的加载显示了, 一个延时加载, 避免内存过高,一个异步加载,避免线程堵塞提高用户体验。
33、如何对iOS设备进行性能测试?
 Profile-> Instruments ->Time Profiler
34、开发项目时你是怎么检查内存泄露?
1). 静态分析 analyze。
2). instruments工具里面有个leak可以动态分析。
35、@protocol 和 category 中如何使用 @property
在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性

category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:
objc_setAssociatedObject
objc_getAssociatedObject
36、用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
  1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
  2. 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.

copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

举例说明:

定义一个以 strong 修饰的 array:

@property (nonatomic ,readwrite, strong) NSArray *array;

然后进行下面的操作:

NSArray *array = @[ @1, @2, @3, @4 ];
   NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array];
   
   self.array = mutableArray;
   [mutableArray removeAllObjects];;
   NSLog(@"%@",self.array);
   
   [mutableArray addObjectsFromArray:array];
   self.array = [mutableArray copy];
   [mutableArray removeAllObjects];;
   NSLog(@"%@",self.array);

打印结果如下所示:

2017-09-27 19:10:32.523 CYLArrayCopyDmo[10681:713670] (
)
2017-09-27 19:10:32.524 CYLArrayCopyDmo[10681:713670] (
   1,
   2,
   3,
   4
)

为了理解这种做法,首先要知道,两种情况:

  1. 对非集合类对象的 copy 与 mutableCopy 操作;
  2. 对集合类对象的 copy 与 mutableCopy 操作。

1. 对非集合类对象的copy操作:

在非集合类对象中:对 immutable 对象进行 copy 操作,是指针复制,mutableCopy 操作时内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。用代码简单表示如下:

  • [immutableObject copy] // 浅复制
  • [immutableObject mutableCopy] //深复制
  • [mutableObject copy] //深复制
  • [mutableObject mutableCopy] //深复制
    比如以下代码:
NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];

查看内存,会发现 string、stringCopy 内存地址都不一样,说明此时都是做内容拷贝、深拷贝。即使你进行如下操作:

[string appendString:@"origion!"]

stringCopy 的值也不会因此改变,但是如果不使用 copy,stringCopy 的值就会被改变。 集合类对象以此类推。 所以,

用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,
是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,
他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,
应该在设置新属性值时拷贝一份。

2.集合类对象的copy与mutableCopy
集合类对象是指 NSArray、NSDictionary、NSSet ... 之类的对象。下面先看集合类immutable对象使用 copy 和 mutableCopy 的一个例子:

NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看内容,可以看到 copyArray 和 array 的地址是一样的,而 mCopyArray 和 array 的地址是不同的。说明 copy 操作进行了指针拷贝,mutableCopy 进行了内容拷贝。但需要强调的是:此处的内容拷贝,仅仅是拷贝 array 这个对象,array 集合内部的元素仍然是指针拷贝。这和上面的非集合 immutable 对象的拷贝还是挺相似的,那么mutable对象的拷贝会不会类似呢?我们继续往下,看 mutable 对象拷贝的例子:

NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看内存,如我们所料,copyArray、mCopyArray和 array 的内存地址都不一样,说明 copyArray、mCopyArray 都对 array 进行了内容拷贝。同样,我们可以得出结论:

在集合类对象中,对 immutable 对象进行 copy,是指针复制, mutableCopy 是内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:

[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //单层深复制
[mutableObject copy] //单层深复制
[mutableObject mutableCopy] //单层深复制
37、一个objc对象的isa的指针指向什么?有什么作用?
指向他的类对象,从而可以找到对象上的方法

isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调 用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

38、如何访问并修改一个类的私有属性?
1). 一种是通过KVC获取。
2). 通过runtime访问并修改私有属性。
39、下面的代码输出什么?
@implementation Son : Father
   - (id)init
   {
       self = [super init];
       if (self) {
           NSLog(@"%@", NSStringFromClass([self class]));
           NSLog(@"%@", NSStringFromClass([super class]));
       }
       return self;
   }
   @end
都输出 Son

NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son

这个题目主要是考察关于 Objective-C 中对 self 和 super 的理解。

我们都知道:self 是类的隐藏参数,指向当前调用方法的这个类的实例。那 super 呢?

很多人会想当然的认为“ super 和 self 类似,应该是指向父类的指针吧!”。这是很普遍的一个误区。其实 super 是一个 Magic Keyword, 它本质是一个编译器标示符,和 self 是指向的同一个消息接受者!他们两个的不同点在于:super 会告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。

上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *xxx 这个对象。

当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。

这也就是为什么说“不推荐在 init 方法中使用点语法”,如果想访问实例变量 iVar 应该使用下划线( _iVar ),而非点语法( self.iVar )。

点语法( self.iVar )的坏处就是子类有可能覆写 setter 。假设 Person 有一个子类叫 ChenPerson,这个子类专门表示那些姓“陈”的人。该子类可能会覆写 lastName 属性所对应的设置方法:

#import "ChenPerson.h"

@implementation ChenPerson

@synthesize lastName = _lastName;

- (instancetype)init
{
   self = [super init];
   if (self) {
       NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([self class]));
       NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([super class]));
   }
   return self;
}

- (void)setLastName:(NSString*)lastName
{
   //设置方法一:如果setter采用是这种方式,就可能引起崩溃
//    if (![lastName isEqualToString:@"陈"])
//    {
//        [NSException raise:NSInvalidArgumentException format:@"姓不是陈"];
//    }
//    _lastName = lastName;
   
   //设置方法二:如果setter采用是这种方式,就可能引起崩溃
   _lastName = @"陈";
   NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, @"会调用这个方法,想一下为什么?");

}

@end

在基类 Person 的默认初始化方法中,可能会将姓氏设为空字符串。此时若使用点语法( self.lastName )也即 setter 设置方法,那么调用将会是子类的设置方法,如果在刚刚的 setter 代码中采用设置方法一,那么就会抛出异常,

为了方便采用打印的方式展示,究竟发生了什么,我们使用设置方法二。

如果基类的代码是这样的:

#import "Person.h"

@implementation Person

- (instancetype)init
{
   self = [super init];
   if (self) {
       self.lastName = @"";
       //NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([self class]));
       //NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, self.lastName);
   }
   return self;
}

- (void)setLastName:(NSString*)lastName
{
   NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, @"根本不会调用这个方法");
   _lastName = @"炎黄";
}

@end

那么打印结果将会是这样的:

🔴类名与方法名:-[ChenPerson setLastName:](在第36行),描述:会调用这个方法,想一下为什么?
🔴类名与方法名:-[ChenPerson init](在第19行),描述:ChenPerson
🔴类名与方法名:-[ChenPerson init](在第20行),描述:ChenPerson

接下来让我们利用 runtime 的相关知识来验证一下 super 关键字的本质,使用clang重写命令:

 $ clang -rewrite-objc test.m

将这道题目中给出的代码被转化为:

NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));

   NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));

从上面的代码中,我们可以发现在调用 [self class] 时,会转化成 objc_msgSend函数。看下函数定义:

id objc_msgSend(id self, SEL op, ...)

我们把 self 做为第一个参数传递进去。

而在调用 [super class]时,会转化成 objc_msgSendSuper函数。看下函数定义:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

第一个参数是 objc_super 这样一个结构体,其定义如下:

struct objc_super {
      __unsafe_unretained id receiver;
      __unsafe_unretained Class super_class;
};

结构体有两个成员,第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self 。第二个成员是记录当前类的父类是什么。

所以,当调用 [self class] 时,实际先调用的是 objc_msgSend函数,第一个参数是 Son当前的这个实例,然后在 Son 这个类里面去找 - (Class)class这个方法,没有,去父类 Father里找,也没有,最后在 NSObject类中发现这个方法。而 - (Class)class的实现就是返回self的类别,故上述输出结果为 Son。

objc Runtime开源代码对- (Class)class方法的实现:

- (Class)class {
   return object_getClass(self);
}

而当调用 [super class]时,会转换成objc_msgSendSuper函数。第一步先构造 objc_super 结构体,结构体第一个成员就是 self 。 第二个成员是 (id)class_getSuperclass(objc_getClass(“Son”)) , 实际该函数输出结果为 Father。

第二步是去 Father这个类里去找 - (Class)class,没有,然后去NSObject类去找,找到了。最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用,

此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son。

40、isKindOfClass、isMemberOfClass、selector作用分别是什么?
isKindOfClass:作用是某个对象属于某个类型或者继承自某类型。
isMemberOfClass:某个对象确切属于某个类型。
selector:通过方法名,获取在内存中的函数的入口地址。
41、delegate 和 notification 的区别
1). 二者都用于传递消息,不同之处主要在于一个是一对一的,另一个是一对多的。
2). notification通过维护一个array,实现一对多消息的转发。
3). delegate需要两者之间必须建立联系,不然没法调用代理的方法;notification不需要两者之间有联系。
42、block的注意点
1). 在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针:
    __weak typeof(self) weakSelf = self; 
2). 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。
    __strong typeof(self) strongSelf = weakSelf;
3). 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量。

43、runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法

每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.

44、objc中的类方法和实例方法有什么本质区别和联系?
  • 类方法:
    类方法是属于类对象的
    类方法只能通过类对象调用
    类方法中的self是类对象
    类方法可以调用其他的类方法
    类方法中不能访问成员变量
    类方法中不能直接调用对象方法

  • 实例方法:
    实例方法是属于实例对象的
    实例方法只能通过实例对象调用
    实例方法中的self是实例对象
    实例方法中可以访问成员变量
    实例方法中直接调用实例方法
    实例方法中也可以调用类方法(通过类名)

45、iOS的沙盒目录结构是怎样的?
沙盒结构:
1). Application:存放程序源文件,上架前经过数字签名,上架后不可修改。
2). Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)
3). Library:
        Caches:存放体积大又不需要备份的数据。(常用的缓存路径)
        Preference:设置目录,iCloud会备份设置信息。
4). tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。
46、GCD 与 NSOperation 的区别:
GCD 和 NSOperation 都是用于实现多线程:
    GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。
    NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现
47、weak属性实现原理:

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。

1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

追问的问题一:

1.实现weak后,为什么对象释放后会自动为nil?

runtime?对注册的类, 会进行布局,对于?weak?对象会放入一个?hash?表中。 用?weak?指向的对象内存地址作为?key,当此对象的引用计数为?0?的时候会?dealloc,假如?weak?指向的对象内存地址是?a?,那么就会以?a?为键, 在这个?weak?表中搜索,找到所有以?a?为键的?weak?对象,从而设置为?nil?。

追问的问题二:

2.当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?

1、调用objc_release

2、因为对象的引用计数为0,所以执行dealloc

3、在dealloc中,调用了_objc_rootDealloc函数

4、在_objc_rootDealloc中,调用了object_dispose函数

5、调用objc_destructInstance

6、最后调用objc_clear_deallocating,详细过程如下:

a. 从weak表中获取废弃对象的地址为键值的记录

b. 将包含在记录中的所有附有 weak修饰符变量的地址,赋值为 nil

c. 将weak表中该记录删除

d. 从引用计数表中删除废弃对象的地址为键值的记录
48、堆和栈的区别?
  • 管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

  • 申请大小:

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

  • 碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出

  • 分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

  • 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的。

49、假如Controller太臃肿,如何优化?
  • 1.将网络请求抽象到单独的类中

方便在基类中处理公共逻辑;

方便在基类中处理缓存逻辑,以及其它一些公共逻辑;

方便做对象的持久化。

  • 2.将界面的封装抽象到专门的类中

构造专门的 UIView 的子类,来负责这些控件的拼装。这是最彻底和优雅的方式,不过稍微麻烦一些的是,你需要把这些控件的事件回调先接管,再都一一暴露回 Controller。

  • 3.构造 ViewModel

借鉴MVVM。具体做法就是将 ViewController 给 View 传递数据这个过程,抽象成构造 ViewModel 的过程。

  • 4.专门构造存储类

专门来处理本地数据的存取。

  • 5.整合常量
50、项目中网络层如何做安全处理?
  • 1、尽量使用https

https可以过滤掉大部分的安全问题。https在证书申请,服务器配置,性能优化,客户端配置上都需要投入精力,所以缺乏安全意识的开发人员容易跳过https,或者拖到以后遇到问题再优化。https除了性能优化麻烦一些以外其他都比想象中的简单,如果没精力优化性能,至少在注册登录模块需要启用https,这部分业务对性能要求比较低。

  • 2、不要传输明文密码

不知道现在还有多少app后台是明文存储密码的。无论客户端,server还是网络传输都要避免明文密码,要使用hash值。客户端不要做任何密码相关的存储,hash值也不行。存储token进行下一次的认证,而且token需要设置有效期,使用refresh

token去申请新的token。

  • 3、Post并不比Get安全

事实上,Post和Get一样不安全,都是明文。参数放在QueryString或者Body没任何安全上的差别。在Http的环境下,使用Post或者Get都需要做加密和签名处理。

  • 4、不要使用301跳转

301跳转很容易被Http劫持攻击。移动端http使用301比桌面端更危险,用户看不到浏览器地址,无法察觉到被重定向到了其他地址。如果一定要使用,确保跳转发生在https的环境下,而且https做了证书绑定校验。

  • 5、http请求都带上MAC

所有客户端发出的请求,无论是查询还是写操作,都带上MAC(Message Authentication

Code)。MAC不但能保证请求没有被篡改(Integrity),还能保证请求确实来自你的合法客户端(Signing)。当然前提是你客户端的key没有被泄漏,如何保证客户端key的安全是另一个话题。MAC值的计算可以简单的处理为hash(request

params+key)。带上MAC之后,服务器就可以过滤掉绝大部分的非法请求。MAC虽然带有签名的功能,和RSA证书的电子签名方式却不一样,原因是MAC签名和签名验证使用的是同一个key,而RSA是使用私钥签名,公钥验证,MAC的签名并不具备法律效应。

  • 6、http请求使用临时密钥

高延迟的网络环境下,不经优化https的体验确实会明显不如http。在不具备https条件或对网络性能要求较高且缺乏https优化经验的场景下,http的流量也应该使用AES进行加密。AES的密钥可以由客户端来临时生成,不过这个临时的AES

key需要使用服务器的公钥进行加密,确保只有自己的服务器才能解开这个请求的信息,当然服务器的response也需要使用同样的AES

key进行加密。由于http的应用场景都是由客户端发起,服务器响应,所以这种由客户端单方生成密钥的方式可以一定程度上便捷的保证通信安全。

  • 7、AES使用CBC模式

不要使用ECB模式,记得设置初始化向量,每个block加密之前要和上个block的秘文进行运算。

51、main()之前的过程有哪些?

1)dyld 开始将程序二进制文件初始化

2)交由ImageLoader 读取 image,其中包含了我们的类,方法等各种符号(Class、Protocol 、Selector、 IMP)

3)由于runtime 向dyld 绑定了回调,当image加载到内存后,dyld会通知runtime进行处理

4)runtime 接手后调用map_images做解析和处理

5)接下来load_images 中调用call_load_methods方法,遍历所有加载进来的Class,按继承层次依次调用Class的+load和其他Category的+load方法

6)至此 所有的信息都被加载到内存中

7)最后dyld调用真正的main函数

注意:dyld会缓存上一次把信息加载内存的缓存,所以第二次比第一次启动快一点

52、什么是 Runtime
Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。
53、Runtime实现的机制是什么,怎么用,一般用于干嘛?
1). 使用时需要导入的头文件 <objc/message.h> <objc/runtime.h>
2). Runtime 运行时机制,它是一套C语言库。
3). 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
    比如:
        类转成了 Runtime 库里面的结构体等数据类型,
        方法转成了 Runtime 库里面的C语言函数,
        平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)
    // OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
    // [stu show];  在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));    
4). 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。

有了Runtime库,能做什么事情呢?
Runtime库里面包含了跟类、成员变量、方法相关的API。
比如:
(1)获取类里面的所有成员变量。
(2)为类动态添加成员变量。
(3)动态改变类的方法实现。
(4)为类动态添加新的方法等。
因此,有了Runtime,想怎么改就怎么改。

54、tableView的重用机制?

查看UITableView头文件,会找到NSMutableArray* visiableCells,和NSMutableDictnery* reusableTableCells两个结构。visiableCells内保存当前显示的cells,reusableTableCells保存可重用的cells。

TableView显示之初,reusableTableCells为空,那么tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。开始的cell都是通过[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]来创建,而且cellForRowAtIndexPath只是调用最大显示cell数的次数。

比如:有100条数据,iPhone一屏最多显示10个cell。程序最开始显示TableView的情况是:

1.用[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]创建10次cell,并给cell指定同样的重用标识(当然,可以为不同显示类型的cell指定不同的标识)。并且10个cell全部都加入到visiableCells数组,reusableTableCells为空。

  1. 向下拖动tableView,当cell1完全移出屏幕,并且cell11(它也是alloc出来的,原因同上)完全显示出来的时候。cell11加入到visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。

  2. 接着向下拖动tableView,因为reusableTableCells中已经有值,所以,当需要显示新的cell,cellForRowAtIndexPath再次被调用的时候,tableView dequeueReusableCellWithIdentifier:CellIdentifier,返回cell1。cell1加入到visiableCells,cell1移出reusableTableCells;cell2移出visiableCells,cell2加入到reusableTableCells。之后再需要显示的Cell就可以正常重用了。

55、iOS程序运行流程
  1. 系统调用app的main函数

  2. main函数调用UIApplicationMain.

  3. UIApplicationMain创建shared application instance, UIApplication默认的instance.

  4. UIApplicationMain读取Info.plist找到主nib文件, 加载nib,把shared application instance 设为nib的owner.

  5. 通过nib文件,创建app的独立UIWindows object.

  6. 通过nib,实例化了程序的AppDelegate object.

  7. app内部启动结束,application:didFinishLaunchingWith- Options: 被设定成 wAppDelegate instance.

  8. AppDelegate向UIWindow instance发makeKeyAndVisible消息, app界面展示给用户. app准备好接收用户的操作指令.

56、什么是 Method Swizzle(黑魔法),什么情况下会使用?
1). 在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法 Method Swizzle。
2). Method Swizzle 指的是改变一个已存在的选择器对应的实现的过程。OC中方法的调用能够在运行时通过改变,通过改变类的调度表中选择器到最终函数间的映射关系。
3). 在OC中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用OC的动态特性,可以实现在运行时偷换selector对应的方法实现。
4). 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的方法实现。
5). 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP。
6). 我们可以利用 class_replaceMethod 来修改类。
7). 我们可以利用 method_setImplementation 来直接设置某个方法的IMP。
8). 归根结底,都是偷换了selector的IMP

57、_objc_msgForward 函数是做什么的,直接调用它将会发生什么?
_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
58、nil、Nil、NULL的区别

nil:指向oc中对象的空指针,针对对象。
Nil:指向oc中类的空指针,针对类。
NULL:指向其他类型的空指针,如一个c类型的内存指针,基本数据类型为空,基本类型。
NSNull:在集合对象中,表示空值的对象。

若obj为nil:

[obj message]将返回NO,而不是异常NSException

若obj为NSNull:

[obj message]将抛出异常NSException

nil和NULL从字面意思来理解比较简单,
nil是一个对象,而NULL是一个值,我的理解为nil是将对象设置为空,
而NULL是将基本类型设置为空的。而且我们对于nil调用方法,不会产生crash或者抛出异常。
看一下如下代码:

// nil指向对象
NSURL *url = nil;
// Nil指向类
Class class = Nil;
// NULL是基本数据类型设置为空
int *pointerInt = NULL;

nil是一个对象指针为空,Nil是一个类指针为空,NULL是基本数据类型为空。

59、什么是 TCP / UDP ?
TCP:传输控制协议。
UDP:用户数据协议。

TCP 是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议。
UDP 是面向无连接的,数据传输是不可靠的,它只管发,不管收不收得到。
简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般。
60、通信底层原理(OSI七层模型)
OSI采用了分层的结构化技术,共分七层:
    物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
61、用伪代码写一个线程安全的单例模式
static id _instance;
+ (id)allocWithZone:(struct _NSZone *)zone {
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       _instance = [super allocWithZone:zone];
   });
   return _instance;
}

+ (instancetype)sharedData {
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       _instance = [[self alloc] init];
   });
   return _instance;
}

- (id)copyWithZone:(NSZone *)zone {
   return _instance;
}

62、http协议
  • 1.HTTP协议,即超文本传输协议(Hypertext transfer protocol)。是一种详细规定了浏览器和万维网(WWW = World Wide Web)服务器之间互相通信的规则.

  • 2.HTTP协议作为TCP/IP模型中应用层的协议也不例外。HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。如下图:


    http协议.png
  • 3.HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。

  • 4.HTTP默认的端口号为80,HTTPS的端口号为443。

  • 5.浏览网页是HTTP的主要应用,但是这并不代表HTTP就只能应用于网页的浏览。HTTP是一种协议,只要通信的双方都遵守这个协议,HTTP就能有用武之地。比如咱们常用的QQ,迅雷这些软件,都会使用HTTP协议(还包括其他的协议)。

HTTP特点

1、简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

2、灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。

3、HTTP 0.9和1.0使用非持续连接:限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接。HTTP 1.1使用持续连接:不必为每个web对象创建一个新的连接,一个连接可以传送多个对象,采用这种方式可以节省传输时间。

4、无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

5、支持B/S及C/S模式。

TCP 被称为“面向连接”的传输层协议。传输层主要有两个协议,分别是 TCP 和 UDP。TCP 比 UDP 更可靠。你可以把 TCP 协议想象成某个水管,发送端这头进水,接收端那头就出水。并且 TCP 协议能够确保,先发送的数据先到达(与之相反,UDP 不保证这点)

HTTP请求方法:
根据HTTP标准,HTTP请求可以使用多种请求方法。
HTTP1.0定义了三种请求方法: GET,POSTHEAD方法。
HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACECONNECT 方法。

http请求.png

TCP简介 :
TCP(Transmission Control Protocol) 传输控制协议
TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:
位码即tcp标志位,有6种标示:SYN(建立联机) ACK(确认) PSH(传送) FIN(结束) RST(重置) URG(紧急)
Sequence number(顺序号码) Acknowledge number(确认号码)

tcp通信.jpg

TCP三次握手

  • 第一次握手:客户端发送了一个带有SYN(建立连接)的Tcp报文到服务器,这个三次握手中的开始。表示客户端想要和服务端建立连接。

  • 第二次握手:服务端接收到客户端的请求,返回客户端报文,这个报文带有SYN(建立连接)和ACK(确认)标志,询问客户端是否准备好。

  • 第三次握手:.客户端再次响应服务端一个ACK(确认),表示我已经准备好。

思考:为什么要三次握手呢,有人说两次握手就好了
举一个例子:已失效的连接请求报文段,
client发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server,本来这已经是一个失效的报文,但是server端接收到这个请求报文后,还是会想client发出确认的报文,表示同意连接。假如不采用三次握手,那么只要server发出确认,新的建立就连接了,但其实这个请求是失效的请求,client是不会理睬server的确认信息,也不会向服务端发送确认的请求,但是server认为新的连接已经建立起来了,并一直等待client发来数据,这样,server的很多资源就没白白浪费掉了,采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道client并没有建立连接。这就是三次握手的作用。

TCP的四次挥手

  • 第一次握手:TCP发送一个FIN(结束),用来关闭客户到服务端的连接。

  • 第二次握手:服务端收到这个FIN,他发回一个ACK(确认),确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号。

  • 第三次握手:服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。

  • 第四次握手:客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成。

思考:那么为什么是4次挥手呢?
可能有人会有疑问,tcp我握手的时候为何ACK(确认)和SYN(建立连接)是一起发送。挥手的时候为什么是分开的时候发送呢.
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

GET和POST的区别

 1、GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditPosts.aspx?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的Body中.

 2、GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.

 3、GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。

 4、GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码.
63、如何高性能的给 UIImageView 加个圆角?

不好的解决方案:使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现。

self.view.layer.cornerRadius = 5.0f;
self.view.layer.masksToBounds = YES;

正确的解决方案:使用绘图技术

- (UIImage *)circleImage {
    // NO代表透明
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
    // 获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 添加一个圆
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
    CGContextAddEllipseInRect(ctx, rect);
    // 裁剪
    CGContextClip(ctx);
    // 将图片画上去
    [self drawInRect:rect];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    // 关闭上下文
    UIGraphicsEndImageContext();
    return image;
}

还有一种方案:使用了贝塞尔曲线"切割"个这个图片, 给UIImageView 添加了的圆角,其实也是通过绘图技术来实现的。

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
                       cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
64、本地通知与远程推送

iOS中通知机制又叫消息机制,其包括两类:一类是本地通知;另一类是推送通知,也叫远程通知。两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同。

(1)本地通知是由本地应用触发的,它是基于时间行为的一种通知形式,例如闹钟定时、待办事项提醒,又或者一个应用在一段时候后不使用通常会提示用户使用此应用等都是本地通知。
创建一个本地通知通常分为以下几个步骤:
1)创建UILocalNotification
2)设置处理通知的时间fireDate
3)配置通知的内容:通知主体、通知声音、图标数字等
4)配置通知传递的自定义数据参数userInfo(这一步可选)
5)调用通知,可以使用scheduleLocalNotification:按计划调度一个通知,也可以使用presentLocalNotificationNow立即调用通知。

创建本地通知.png

在上面的试图控制器中写完通知方法,可以在AppDelegate.m中添加以下方法:
收到本地通知.png

(2)远程推送是由应用服务提供商发起的,通过苹果的APNs(Apple Push Notification Server)发送到应用客户端。

下面进行简单的推送知识介绍:


远程推送.png

推送的机制是:设备应用中首次打开向APNS(苹果推送服务器)注册推送,并获取设备令牌deviceToken->应用内将deviceToken发送到自己的推送服务器或第三方推送服务器进行保存->自己的服务器或第三方推送服务器将消息和deviceToken捆绑发送到APNS->APNS发送消息到设备,设备显示推送消息。

deviceToken类似UUID,在应用第一次请求deviceToken后,如果应用没有删除重新安装deviceToken是不会变的,应用删除后再重新安装,再注册远程推送后deviceToken会变化。所以每次应用打开都应该去注册推送并请求deviceToken,第一次会有系统提示是否推送,选择推送后,会收到回调函数中deviceToken,第一次会有些延迟,以后再开应用请求deviceToken就秒回了。

65、AFNetworking与ASIHTTPRequest的区别

底层实现
1、AFN的底层实现基于OC的NSURLConnection和NSURLSession
2、ASI的底层实现基于纯C语言的CFNetwork框架
3、因为NSURLConnection和NSURLSession是在CFNetwork之上的一层封装,因此ASI的运行性能高于AFN
对服务器返回的数据处理
1、ASI没有直接提供对服务器数据处理的方式,直接返回的是NSData/NSString
2、AFN提供了多种对服务器数据处理的方式
(1)JSON处理-直接返回NSDictionary或者NSArray
(2)XML处理-返回的是xml类型数据,需对其进行解析
(3)其他类型数据处理
监听请求过程
1、AFN提供了success和failure两个block来监听请求的过程(只能监听成功和失败)
success : 请求成功后调用
failure : 请求失败后调用
2、ASI提供了3套方案,每一套方案都能监听请求的完整过程
(监听请求开始、接收到响应头信息、接受到具体数据、接受完毕、请求失败)

  • 成为代理,遵守协议,实现协议中的代理方法
  • 成为代理,不遵守协议,自定义代理方法
  • 设置block
    在文件下载和文件上传的使用难易度
    1、AFN
  • 不容易实现监听下载进度和上传进度 】
  • 不容易实现断点续传 、
  • 一般只用来下载不大的文件
    2、ASI
  • 非常容易实现下载和上传
  • 非常容易监听下载进度和上传进度
  • 非常容易实现断点续传
  • 下载大文件或小文件均可
    3、实现下载上传推荐使用ASI

网络监控
1、AFN自己封装了网络监控类,易使用
2、ASI使用的是Reachability,因为使用CocoaPods下载ASI时,会同步下载Reachability,但Reachability作为网络监控使用较为复杂(相对于AFN的网络监控类来说)
3、推荐使用AFN做网络监控-AFNetworkReachabilityManager
ASI提供的其他实用功能
1、控制信号旁边的圈圈要不要在请求过程中转
2、可以轻松地设置请求之间的依赖:每一个请求都是一个NSOperation对象
3、可以统一管理所有请求(还专门提供了一个叫做ASINetworkQueue来管理所有的请求对象)

  • 暂停/恢复/取消所有的请求
  • 监听整个队列中所有请求的下载进度和上传进度
66、描述下SDWebImage里面给UIImageView加载图片的逻辑
SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。
    
加载图片的过程大致如下:
    1.首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
    2.如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
    3.如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
    4.下载后的图片会加入缓存中,并写入磁盘中
    5.整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
    
SDWebImage原理:
调用类别的方法:
    1. 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。
    2. 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。
    3. 从网络上获取,使用,缓存到内存,缓存到沙盒。

67、排序算法

选择排序、冒泡排序、插入排序三种排序算法可以总结为如下:

都将数组分为已排序部分和未排序部分。

1. 选择排序将已排序部分定义在左端,然后选择未排序部分的最小元素和未排序部分的第一个元素交换。
2. 冒泡排序将已排序部分定义在右端,在遍历未排序部分的过程执行交换,将最大元素交换到最右端。
3. 插入排序将已排序部分定义在左端,将未排序部分元的第一个元素插入到已排序部分合适的位置。

选择排序

/** 
 *  【选择排序】:最值出现在起始端
 *  
 *  第1趟:在n个数中找到最小(大)数与第一个数交换位置
 *  第2趟:在剩下n-1个数中找到最小(大)数与第二个数交换位置
 *  重复这样的操作...依次与第三个、第四个...数交换位置
 *  第n-1趟,最终可实现数据的升序(降序)排列。
 *
 */
void selectSort(int *arr, int length) {
    for (int i = 0; i < length - 1; i++) { //趟数
        for (int j = i + 1; j < length; j++) { //比较次数
            if (arr[i] > arr[j]) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

冒泡排序:

/** 
 *  【冒泡排序】:相邻元素两两比较,比较完一趟,最值出现在末尾
 *  第1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n个元素位置
 *  第2趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n-1个元素位置
 *   ……   ……
 *  第n-1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第2个元素位置 
 */
void bublleSort(int *arr, int length) {
    for(int i = 0; i < length - 1; i++) { //趟数
        for(int j = 0; j < length - i - 1; j++) { //比较次数
            if(arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        } 
    }
}

68、runtime 运行时机制
  • Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。
  • 这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。
    对于C语言,函数的调用在编译的时候会决定调用哪个函数。对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
  • 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
    在编译阶段,C语言调用未实现的函数就会报错。

运行时的作用

  • 能获得某个类的所有成员变量
  • 能获得某个类的所有属性
  • 能获得某个类的所有方法
  • 交换方法实现
  • 能动态添加一个成员变量
  • 能动态添加一个属性
  • 字典转模型
  • runtime归档/反归档

常见的函数,头文件

#import<objc/runtime.h> : //成员变量,类,方法
class_copyIvarList : 获得某个类内部的所有成员变量
class_copyMethodList : 获得某个类内部的所有方法
class_getInstanceMethod : 获得某个具体的实例方法 (对象方法,减号-开头)
class_getClassMethod : 获得某个具体的类方法 (加号+开头)
method_exchangeImplementations : 交换两个方法的实现
#import<objc/message.h> : //消息机制
objc_msgSend(...)

69、 HTTP与HTTPS的区别

超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。
  为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
HTTP和HTTPS的基本概念
HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。

HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
HTTP与HTTPS有什么区别?
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

HTTPS和HTTP的区别主要如下:

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
HTTPS的工作原理
我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取,所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。

https的工作原理.jpg

客户端在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示。

(1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。

(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。

(3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。

(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。

(5)Web服务器利用自己的私钥解密出会话密钥。

(6)Web服务器利用会话密钥加密与客户端之间的通信。

HTTPS的工作原理.gif

HTTPS的优点
尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个好处:

(1)使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;

(2)HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。

(3)HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。

(4)谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”。

** HTTPS的缺点**
虽然说HTTPS有很大的优势,但其相对来说,还是存在不足之处的:

(1)HTTPS协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电;

(2)HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;

(3)SSL证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。

(4)SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。

70、
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,340评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,093评论 1 32
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,134评论 30 470
  • 愿一个人 在虚空中 孤寂的 像扇贝里孑然的珍珠 似灯光下无形的虚影 或是黑土下沉默的胚芽 没有关爱 亦无须应承 于...
    寂梧阅读 426评论 0 5