【iOS】总结

iOS中持久化方式有哪些?

NSUserDefaults 的存储,实际是本地生成一个 plist 文件,将所需属性存储在 plist 文件中
对象归档 -- 本地创建文件并写入数据,文件类型不限
SQLite 数据库 -- 本地创建数据库文件,进行数据处理
CoreData -- 同数据库处理思想相同,但实现方式不同

Runtime

Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。
Objective-C 语言是一门动态语言,编译器不需要关心接受消息的对象是何种类型,接收消息的对象问题也要在运行时处理。

常用用法:
关联对象 Associated Objects
消息发送 Messaging
消息转发 Message Forwarding
方法调配 Method Swizzling
KVC、KVO About Key-Value Coding

动态获取 class 和 slector
NSClassFromString(@"MyClass");
NSSelectorFromString(@"showShareActionSheet");

Runtime实现的机制是什么,怎么用,一般用于干嘛?

1). 使用时需要导入的头文件
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,想怎么改就怎么改。

什么是 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。

_objc_msgForward 函数是做什么的,直接调用它将会发生什么?

答:_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

比较关键词:strong, weak, assign, copy

strong表示指向并拥有该对象。其修饰的对象引用计数会增加1。该对象只要引用计数不为0则不会被销毁。当然强行将其设为nil可以销毁它。

weak表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。

assign主要用于修饰基本数据类型,如NSInteger和CGFloat,这些数值主要存在于栈上。

weak 一般用来修饰对象,assign一般用来修饰基本数据类型。原因是assign修饰的对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。

copy与strong类似。不同之处是strong的复制是多个指针指向同一个地址,而copy的复制每次会在内存中拷贝一份对象,指针指向不同地址。copy一般用在修饰有可变对应类型的不可变对象上,如NSString, NSArray, NSDictionary。

Objective-C 中,基本数据类型的默认关键字是atomic, readwrite, assign;普通属性的默认关键字是atomic, readwrite, strong。

属性readwrite,readonly,assign,retain,copy,nonatomic 各自什么作用,他们在那种情况下用?

readwrite:默认的属性,可读可写,生成setter和getter方法。
readonly:只读,只生成getter方法,也就是说不能修改变量。
assign:用于声明基本数据类型(int、float)仅设置变量,是赋值属性。
retain:持有属性,setter方法将传入的参数先保留,再赋值,传入的参数引用计数retaincount会加1

tip

在堆上开辟一块空间,用指针a指向,然后将指针a赋值(assign)给指针b,等于是a和b同时指向这块堆空间,当a不使用这块堆空间的时候,是否要释放这块堆空间?答案是肯定要的,但是这件堆空间被释放后,b就成了野指针。

如何避免这样的问题? 这就引出了引用计数器,当a指针这块堆空间的时候,引用计数器+1,当b也指向的时候,引用计数器变成了2,当a不再指向这块堆空间时,release-1,引用计数器为1,当b也不指向这块堆空间时,release-1,引用计数器为0,调用dealloc函数,空间被释放

总结:当数据类型为int,float原生类型时,可以使用assign。如果是上面那种情况(对象)就是用retain。

copy:是赋值特性,setter方法将传入对象赋值一份;需要完全一份新的变量时,直接从堆区拿。

当属性是 NSString、NSArray、NSDictionary时,既可以用strong 修饰,也可以用copy修饰。当用strong修饰的NSString 指向一个NSMutableString时,如果在不知情的情况下这个NSMutableString的别的引用修改了值,就会出现:一个不可变的字符串却被改变了的情况, 使用copy就不会出现这种情况。

nonatomic:非原子性,可以多线程访问,效率高。

atomic:原子性,属性安全级别的表示,同一时刻只有一个线程访问,具有资源的独占性,但是效率很低。

strong:强引用,引用计数+ 1,ARC下,一个对象如果没有强引用,系统就会释放这个对象。

weak:弱引用,不会使引用计数+1.当一个指向对象的强引用都被释放时,这块空间依旧会被释放掉。

使用场景:在ARC下,如果使用XIB 或者SB 来创建控件,就使用 weak。纯代码创建控件时,用strong修饰,如果想用weak 修饰,就需要先创建控件,然后赋值给用weak修饰的对象。

使用weak的时候需要特别注意的是:先将控件添加到superview上之后再赋值给self,避免控件被过早释放。

设计模式是什么? 你知道哪些设计模式,并简要叙述?

设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情。

1). MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。
2). MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。
3). 单例模式:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。
4). 观察者模式:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。
5). 委托模式:代理+协议的组合。实现1对1的反向传值操作。
6). 工厂模式:通过一个类方法,批量的根据已有模板生产对象。

#import跟 #include 有什么区别,@class呢

#import<> 跟 #import“”有什么区别?

1). #import是Objective-C导入头文件的关键字,#include是C/C++导入头文件的关键字,使用#import头文件会自动只导入一次,不会重复导入。
2). @class告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含。
3). #import<>用来包含系统的头文件,#import””用来包含用户头文件。

Objective-C的类可以多重继承么?可以实现多个接口么?

Objective-C的类不可以多重继承;可以实现多个接口(协议);

@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的?

@property 的本质是什么?

@property = ivar + getter + setter;

“属性” (property)有两大概念:ivar(实例变量)、getter+setter(存取方法)
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。

@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?

属性可以拥有的特质分为四类:

1.原子性--- nonatomic 特质
2.读/写权限---readwrite(读写)、readonly (只读)
3.内存管理语义---assign、strong、 weak、unsafe_unretained、copy
4.方法名---getter= 、setter=
5.不常用的:nonnull,null_resettable,nullable

属性关键字 readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用?

1). readwrite 是可读可写特性。需要生成getter方法和setter方法。
2). readonly 是只读特性。只会生成getter方法,不会生成setter方法,不希望属性在类外改变。
3). assign 是赋值特性。setter方法将传入参数赋值给实例变量;仅设置变量时,assign用于基本数据类型。
4). retain(MRC)/strong(ARC) 表示持有特性。setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1。
5). copy 表示拷贝特性。setter方法将传入对象复制一份,需要完全一份新的变量时。
6). nonatomic 非原子操作。决定编译器生成的setter和getter方法是否是原子操作,atomic表示多线程安全,一般使用nonatomic,效率高。

什么情况使用 weak 关键字,相比 assign 有什么不同?

1.在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。
2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。

IBOutlet连出来的视图属性为什么可以被设置成weak?
因为父控件的subViews数组已经对它有一个强引用。

不同点:
assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)。

怎么用 copy 关键字?

  1. NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
  2. block 也经常使用 copy 关键字。

block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。

用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?

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

  1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。
  2. 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。

浅拷贝和深拷贝的区别?

浅拷贝:只复制指向对象的指针,而不复制引用对象本身。
深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A时,A_copy不变。

系统对象的 copy 与 mutableCopy 方法?

不管是集合类对象(NSArray、NSDictionary、NSSet ... 之类的对象),还是非集合类对象(NSString, NSNumber ... 之类的对象),接收到copy和mutableCopy消息时,都遵循以下准则:

  1. copy 返回的是不可变对象(immutableObject);如果用copy返回值调用mutable对象的方法就会crash。
  2. mutableCopy 返回的是可变对象(mutableObject)。

非集合类对象的copy与mutableCopy

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

NSString *str = @"hello word!";

NSString *strCopy = [str copy] // 指针复制,strCopy与str的地址一样

NSMutableString *strMCopy = [str mutableCopy] // 内容复制,strMCopy与str的地址不一样

NSMutableString *mutableStr = [NSMutableString stringWithString: @"hello word!"];

NSString *strCopy = [mutableStr copy] // 内容复制

NSMutableString *strMCopy = [mutableStr mutableCopy] // 内容复制

集合类对象的copy与mutableCopy (同上)
在集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;
对可变对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对集合内的对象元素仍然是指针复制。(即单层内容复制)

NSArray *arr = @[@[@"a", @"b"], @[@"c", @"d"];
NSArray *copyArr = [arr copy]; // 指针复制
NSMutableArray *mCopyArr = [arr mutableCopy]; //单层内容复制
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArr = [mutableArr copy]; // 单层内容复制
NSMutableArray *mCopyArr = [mutableArr mutableCopy]; // 单层内容复制

【总结一句话】:
只有对不可变对象进行copy操作是指针复制(浅复制),其它情况都是内容复制(深复制)!

这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr;

问题:添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。
//如:-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
// copy后返回的是不可变对象(即 arr 是 NSArray 类型,NSArray 类型对象不能调用 NSMutableArray 类型对象的方法)
原因:是因为 copy 就是复制一个不可变 NSArray 的对象,不能对 NSArray 对象进行添加/修改。

@synthesize 和 @dynamic 分别有什么作用?

@property有两个对应的词,一个是@synthesize(合成实例变量),一个是@dynamic。

如果@synthesize和@dynamic都没有写,那么默认的就是 @synthesize var = _var;

// 在类的实现代码里通过 @synthesize 语法可以来指定实例变量的名字。(@synthesize var = _newVar;)

  1. @synthesize 的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

  2. @dynamic 告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成(如,@dynamic var)。

常见的 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。

id 声明的对象有什么特性?
答:id 声明的对象具有运行时的特性,即可以指向任意类型的Objcetive-C的对象。

Objective-C 如何对内存管理的,说说你的看法和解决方法?
Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。
1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。
2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。
3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。

Objective-C 中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?
线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:

Category(类别)、 Extension(扩展)和继承的区别

  1. 分类有名字,类扩展没有分类名字,是一种特殊的分类。
  2. 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法。
  3. 继承可以增加,修改或者删除方法,并且可以增加属性。

我们说的OC是动态运行时语言是什么意思?
主要是将数据类型的确定由编译时,推迟到了运行时。简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。

什么是 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 可以是属性、实例变量、成员变量。

KVC的底层实现?

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

KVO的底层实现?
KVO基于runtime机制实现。

你是否接触过OC中的反射机制?简单聊一下概念和使用
1). class反射
通过类名的字符串形式实例化对象。
Class class = NSClassFromString(@"student");
Student *stu = [[class alloc] init];
将类名变为字符串。
Class class =[Student class];
NSString *className = NSStringFromClass(class);

2). SEL的反射
通过方法的字符串形式实例化方法。
SEL selector = NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@"Mike"];
将方法变成字符串。
NSStringFromSelector(@selector*(setName:));

类变量的 @public,@protected,@private,@package 声明各有什么含义?
@public 任何地方都能访问;
@protected 该类和子类中访问,是默认的;
@private 只能在本类中访问;
@package 本包内使用,跨包不可以。

如何访问并修改一个类的私有属性?

1). 一种是通过KVC获取。
2). 通过runtime访问并修改私有属性。

下面的代码输出什么?
@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 这个对象。

isKindOfClass、isMemberOfClass、selector作用分别是什么

isKindOfClass:作用是某个对象属于某个类型或者继承自某类型。
isMemberOfClass:某个对象确切属于某个类型。
selector:通过方法名,获取在内存中的函数的入口地址。

BAD_ACCESS在什么情况下出现?

答:这种问题在开发时经常遇到。原因是访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等。

lldb(gdb)常用的控制台调试命令?

1). p 输出基本类型。是打印命令,需要指定类型。是print的简写

p (int)[[[self view] subviews] count]

2). po 打印对象,会调用对象description方法。是print-object的简写

po [self view]

3). expr 可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。
4). bt:打印调用堆栈,是thread backtrace的简写,加all可打印所有thread的堆栈
5). br l:是breakpoint list的简写

你一般是怎么用Instruments的?

Instruments里面工具很多,常用:
1). Time Profiler: 性能分析
2). Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。
3). Allocations:用来检查内存,写算法的那批人也用这个来检查。
4). Leaks:检查内存,看是否有内存泄露。

iOS的沙盒目录结构是怎样的?

1). Application:存放程序源文件,上架前经过数字签名,上架后不可修改。
2). Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)
3). Library:

    Caches:存放体积大又不需要备份的数据。(常用的缓存路径)

    Preference:设置目录,iCloud会备份设置信息。

4). tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。

GCD 与 NSOperation 的区别:

GCD 和 NSOperation 都是用于实现多线程:
GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。
NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现。

如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

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

// 创建队列组
dispatch_group_t group = dispatch_group_create();

// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

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(), ^{
        // 合并图片
});
dispatch_barrier_async(栅栏函数)的作用是什么?

函数定义:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
作用:

1.在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。

2.避免数据竞争
// 1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);

// 2.向队列中添加任务
dispatch_async(queue, ^{  // 1.2是并行的
    NSLog(@"任务1, %@",[NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"任务2, %@",[NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
    NSLog(@"任务 barrier, %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{   // 这两个是同时执行的
    NSLog(@"任务3, %@",[NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"任务4, %@",[NSThread currentThread]);
});

// 输出结果: 任务1 任务2 ——》 任务 barrier ——》任务3 任务4
// 其中的任务1与任务2,任务3与任务4 由于是并行处理先后顺序不定。

OC中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?
// 创建线程的方法
- [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]

- [self performSelectorInBackground:nil withObject:nil];

- [[NSThread alloc] initWithTarget:nil selector:nil object:nil];

- dispatch_async(dispatch_get_global_queue(0, 0), ^{});

- [[NSOperationQueue new] addOperation:nil];

// 主线程中执行代码的方法
- [self performSelectorOnMainThread:nil withObject:nil waitUntilDone:YES];

- dispatch_async(dispatch_get_main_queue(), ^{});

- [[NSOperationQueue mainQueue] addOperation:nil];
什么是 TCP / UDP ?

TCP:传输控制协议。
UDP:用户数据协议。
TCP 是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议。
UDP 是面向无连接的,数据传输是不可靠的,它只管发,不管收不收得到。
简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般。

通信底层原理(OSI七层模型)

OSI采用了分层的结构化技术,共分七层:
物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

介绍一下XMPP?

XMPP是一种以XML为基础的开放式实时通信协议。
简单的说,XMPP就是一种协议,一种规定。就是说,在网络上传东西,XMM就是规定你上传大小的格式。

什么是野指针、空指针?

野指针:不知道指向了哪里的指针叫野指针。即指针指向不确定,指针存的地址是一个垃圾值,未初始化。
空指针:不指向任何位置的指针叫空指针。即指针没有指向,指针存的地址是一个空地址,NULL。

对于Objective-C,你认为它最大的优点和最大的不足是什么?

最大的优点是它的运行时特性,不足是没有命名空间,对于命名冲突,可以使用长命名法或特殊前缀解决,如果是引入的第三方库之间的命名冲突,可以使用link命令及flag解决冲突。

AFNetworking 底层原理分析

AFNetworking主要是对NSURLSession的封装,其中主要有以下类:
AFHTTPSessionManager:内部封装是 NSURLSession, 负责发送网络请求,使用最多的一个类。
AFNetworkReachabilityManager:实时监测网络状态的工具类。当前的网络环境发生改变之后,这个工具类就可以检测到。
AFSecurityPolicy:网络安全的工具类, 主要是针对 HTTPS 服务。
AFURLRequestSerialization:序列化工具类,基类。上传的数据转换成JSON格式(AFJSONRequestSerializer).使用不多。

AFURLResponseSerialization:反序列化工具类;基类.使用比较多
AFJSONResponseSerializer; JSON解析器,默认的解析器.
AFHTTPResponseSerializer; 万能解析器; JSON和XML之外的数据类型,直接返回二进
制数据.对服务器返回的数据不做任何处理.
AFXMLParserResponseSerializer; XML解析器;

描述下SDWebImage里面给UIImageView加载图片的逻辑

SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。

加载图片的过程大致如下:
1.首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
2.如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
3.如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
4.下载后的图片会加入缓存中,并写入磁盘中
5.整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来

SDWebImage原理:
调用类别的方法:

  1. 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。
  2. 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。
  3. 从网络上获取,使用,缓存到内存,缓存到沙盒。
模拟栈操作
  • 栈是一种数据结构,特点:先进后出
  • 练习:使用全局变量模拟栈的操作
###### #include 
###### #include 
###### #include 

//保护全局变量:在全局变量前加static后,这个全局变量就只能在本文件中使用

static int data[1024];//栈最多能保存1024个数据

static int count = 0;//目前已经放了多少个数(相当于栈顶位置)

//数据入栈 push
void push(int x){
    assert(!full());//防止数组越界
    data[count++] = x;
}

//数据出栈 pop
int pop(){
    assert(!empty());
    return data[--count];
}

//查看栈顶元素 top
int top(){
    assert(!empty());
    return data[count-1];
}

//查询栈满 full
bool full() {
    if(count >= 1024) {
        return 1;
    }
     return 0; 
}

//查询栈空 empty
bool empty() {
    if(count <= 0) {
        return 1;
    }
    return 0;
}

int main(){
    //入栈
    for (int i = 1; i <= 10; i++) {
        push(i);
    }

    //出栈
    while(!empty()){
        printf("%d ", top()); //栈顶元素
        pop(); //出栈
    }
    printf("\n");
    return 0;
}

排序算法

选择排序、冒泡排序、插入排序三种排序算法可以总结为如下:
都将数组分为已排序部分和未排序部分。

  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;
            }
        } 
    }
}
折半查找(二分查找)
  • 折半查找:优化查找时间(不用遍历全部数据)
  • 折半查找的原理:
  • 1> 数组必须是有序的
  • 2> 必须已知min和max(知道范围)
  • 3> 动态计算mid的值,取出mid对应的值进行比较
  • 4> 如果mid对应的值大于要查找的值,那么max要变小为mid-1
  • 5> 如果mid对应的值小于要查找的值,那么min要变大为mid+1

// 已知一个有序数组, 和一个key, 要求从数组中找到key对应的索引位置

int findKey(int *arr, int length, int key) {

    int min = 0, max = length - 1, mid;

    while (min <= max) {
        mid = (min + max) / 2; //计算中间值

        if (key > arr[mid]) {
            min = mid + 1;
        } else if (key < arr[mid]) {
            max = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
}

struct和class的区别

swift中,class是引用类型,struct是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。

class有这几个功能struct没有的:
class可以继承,这样子类可以使用父类的特性和方法
类型转换可以在runtime的时候检查和解释一个实例的类型
可以用deinit来释放资源
一个类可以被多次引用

struct也有这样几个优势:
结构较小,适用于复制操作,相比于一个class的实例被多次引用更加安全。
无须担心内存memory leak或者多线程冲突问题

顺便提一下,array在swift中是用struct实现的。Apple重写过一次array,然后复制就是深度拷贝了。猜测复制是类似参照那样,通过栈上指向堆上位置的指针来实现的。而对于它的复制操作,也是在相对空间较为宽裕的堆上来完成的,所以性能上还是不错的。

var arr = [0,0,0]
var newArr = arr
arr[0] = 1
//Check arr and newArr
arr //[1, 0, 0]
newArr // before beta3:[1, 0, 0], after beta3:[0, 0, 0]

所以可以猜测其实在背后 Array和 Dictionary的行为并不是像其他 struct 那样简单的在栈上分配,而是类似参照那样,通过栈上指向堆上位置的指针来实现的。而对于它的复制操作,也是在相对空间较为宽裕的堆上来完成的。当然,现在还无法(或者说很难)拿到最后的汇编码,所以这只是一个猜测而已。

补充:
C语言中,struct与的class的区别:
struct只是作为一种复杂数据类型定义,不能用于面向对象编程。

C++中,struct和class的区别:
对于成员访问权限以及继承方式,class中默认的是private的,而struct中则是public的。class还可以用于表示模板类型,struct则不行。

在一个HTTPS连接的网站里,输入账号密码点击登录后,到服务器返回这个请求前,中间经历了什么

屏幕快照 2018-05-24 上午8.09.29.png

1.客户端打包请求。包括url,端口啊,你的账号密码等等。账号密码登陆应该用的是Post方式,所以相关的用户信息会被加载到body里面。这个请求应该包含三个方面:网络地址,协议,资源路径。注意,这里是HTTPS,就是HTTP + SSL / TLS,在HTTP上又加了一层处理加密信息的模块(相当于是个锁)。这个过程相当于是客户端请求钥匙。

2.服务器接受请求。一般客户端的请求会先发送到DNS服务器。 DNS服务器负责将你的网络地址解析成IP地址,这个IP地址对应网上一台机器。这其中可能发生Hosts Hijack和ISP failure的问题。过了DNS这一关,信息就到了服务器端,此时客户端会和服务器的端口之间建立一个socket连接,socket一般都是以file descriptor的方式解析请求。这个过程相当于是服务器端分析是否要向客户端发送钥匙模板。

3.服务器端返回数字证书。服务器端会有一套数字证书(相当于是个钥匙模板),这个证书会先发送给客户端。这个过程相当于是服务器端向客户端发送钥匙模板。

4.客户端生成加密信息。根据收到的数字证书(钥匙模板),客户端会生成钥匙,并把内容锁上,此时信息已经加密。这个过程相当于客户端生成钥匙并锁上请求。

5.客户端发送加密信息。服务器端会收到由自己发送出去的数字证书加锁的信息。 这个时候生成的钥匙也一并被发送到服务器端。这个过程是相当于客户端发送请求。

6.服务器端解锁加密信息。服务器端收到加密信息后,会根据得到的钥匙进行解密,并把要返回的数据进行对称加密。这个过程相当于服务器端解锁请求、生成、加锁回应信息。

7.服务器端向客户端返回信息。客户端会收到相应的加密信息。这个过程相当于服务器端向客户端发送回应。

8.客户端解锁返回信息。客户端会用刚刚生成的钥匙进行解密,将内容显示在浏览器上。

HTTPS加密过程详解请去https原理:证书传递、验证和数据加密、解密过程解析

在一个app中间有一个button,在你手触摸屏幕点击后,到这个button收到点击事件,中间发生了什么

响应链大概有以下几个步骤

设备将touch到的UITouch和UIEvent对象打包, 放到当前活动的Application的事件队列中
单例的UIApplication会从事件队列中取出触摸事件并传递给单例UIWindow
UIWindow使用hitTest:withEvent:方法查找touch操作的所在的视图view

RunLoop这边我大概讲一下
主线程的RunLoop被唤醒
通知Observer,处理Timer和Source 0
Springboard接受touch event之后转给App进程中
RunLoop处理Source 1,Source1 就会触发回调,并调用_UIApplicationHandleEventQueue() 进行应用内部的分发。
RunLoop处理完毕进入睡眠,此前会释放旧的autorelease pool并新建一个autorelease pool

UIResponder是UIView的父类,UIView是UIControl的父类。

你可能感兴趣的:(【iOS】总结)