主要内容:
面向对象设计原则 iOS应用导航模式有哪些 iOS持久化方式有哪些NSClassFromString加载静态库中的类什么状况是nilid和NSObject*的区别 简单描述一下Runtime Runtime给类添加属性、成员变量 KVO原理 Property修饰符 程序内存分区extern的做用 指针函数/函数指针/Block __weak、__strong、__block理解 事件传递链/事件响应链 简述RunLoopNSTimer原理 简述GCD 自动释放池 iOS中的定时器UIView/UILayer关系 简述你了解的锁 ISO七层、TCP/IP四层协议 什么是ARC iOS类和结构体有什么区别 iOS通知和协议的区别 iOS内存使用注意事项和优化 ViewController完整生命周期 frame和bounds区别@synthesize和@dynamic的做用 SDWebImage做用 XML解析方式 AFNetWorking做用 Http协议特色,GET/POST请求区别 Socket链接和Http链接区别 Tcp三次握手、四次挥手 performSelector传三个参数(未解答) main方法前过程 线程安全方法NSOperationQueue和GCD区别联系 iOS经常使用设计模式 简述Block 消息动态处理/转发流程weak变量怎么置为nil对nil发消息会发生什么 安全区域的理解UITableView优化方法 ssl/tls证书做用 MVC、MVP、MVVM
相关分析:
单一职责原则,开闭原则,依赖倒置原则(面向接口编程),迪米特原则,里氏替换原则,接口隔离原则。html
谈谈面向对象设计(OOD)原则java
这个问题更可能是设计人员考虑的,不过咱们也须要了解,否则咱们都不知道UITabBarController和UINavigationController等存在的意义是啥。
iOS应用属于客户端应用,问题实际上是问下面两个部分:ios
1:什么是导航模式?客户端导航模式有哪些常见的? 2:iOS中存在哪些导航模式?
导航模式:将信息以最优的方式组织起来展示给用户。
客户端常见模式:tab、抽屉、列表、平铺/轮播、宫格和悬浮icon等。
注:不要太在乎名称,你会在网上搜到一种模式有多种名称。git
移动端导航的七种设计模式
8种移动APP导航设计模式大对比程序员
这里并非问你哪些控件/控制器对应这些导航模式,因此iOS具备上面提到的全部导航模式。github
首先这里的持久化指的是数据持久化,目前客户端的持久化也只有这一个含义。
为什么要持久化:iOS开发能够没有持久化,持久化更多的是业务需求;好比记录用户是否登录,下次进应用不须要再登录。
由于iOS的沙盒机制,因此持久化分为两类:沙盒内和沙盒外。web
只要遵循了NSCoding协议并正确实现了initWithCoder和encodeWithCoder方法的类均可以经过NSKeyedArchiver来序列化。
归档使用archiveRootObject,解归档使用unarchiveObjectWithFile;须要指定文件路径。objective-c
[NSUserDefaults standardUserDefaults]获取NSUserDefaults对象,以key-value方式进行持久化操做。sql
写入使用writeToFile,读取使用xxxWithContentsOfFile;须要指定文件路径。shell
数据库无疑是大量数据最好的持久化方案,数据库目前有:sqlite、CoreData和Realm等。这里就不用回答FMDB它只是封装了sqlite而已。
这里要和plist区分一下,plist方式是字典/数组数据格式写入文件;而这里的文件方式不限数据格式。
沙盒内的方式在应用被删除后数据都会丢失,若是想要不丢失则须要使用KeyChain。
KeyChain本质是一个sqlite数据库,其保存的全部数据都是加密过的。
KeyChain分为私有和公有,公有则须要指定group,一个group中的应用能够共享此KeyChain。
使用KeyChain过程当中要理解下面几个问题:
1:本身使用的KeyChain和系统自带的KeyChain数据是隔离的,内部应该是不一样数据库文件; 2:KeyChain数据可备份到iCloud中; 3:不须要联网,也不用登录iCloud帐号;一个设备一个sqlite数据库,可是不一样应用组不共享数据; 4:要在另外一台设备上使用当前设备存储的KeyChain信息,须要当前设备进行数据备份, 再在另外一设备上复原数据;比较经常使用的是iCloud备份方式; 5:系统自带的KeyChain中帐号密码分类数据可在系统设置->帐号与密码里面看到, 你退出iCloud帐号仍是存在,只是iCloud会帮你备份若是你设置了的话;这个和照片是同样的道理。
持久化
iOS 数据持久化的几种方法
聊聊iOS KeyChain
NSClassFromString动态加载是OC中runtime的一个方法,用来从字符串获得一个class对象,当系统给应用分配的运行内存中没有这个类时会返回nil;静态库在连接阶段会被写入到执行文件,这里要注意了,若是工程中没有用到静态库中的某些类,那么这些类是不会写入到执行文件的,天然系统给应用分配的运行内存中没有这个类。因此NSClassFromString返回nil只在工程中没有使用到该类的状况下。
有人可能会问了,那我能够在运行的时候手动加载库到运行内存吗?动态库是能够的,这样就是插件化了;静态库由于最后打包的包里没有这个文件了,因此没办法获取到该静态库。而动态库在工程General的Embedded Binaries中加入该动态库,则打包后包内有一个framework文件夹专门放动态库,则能够实现手动加载。
又有人问了,General的Linked Frameworks and Libraries又是什么做用呢?好吧通常咱们都忽略这个了,由于拖入库到工程默认就会把该库加入到此处,若是不加且你工程直接使用了该类则build通不过;使用workspace设置工程依赖实现组件化等为了解决相应问题而使用此实现思路状况下,要在适当的project的此处手动添加被依赖库,若是不加且你工程直接使用了该类则build通不过。
这个问题没有固定的答案,只须要答到比较重要的点就能够了。
你能够从中看到以下的内容。
id的定义:
typedef struct objc_object { struct objc_class *isa; } *id;
NSObject的定义:
@interface NSObject { struct objc_class *isa; }
开始分析得先知道这样一个事实,iOS中不是全部的类都继承自NSObject:
@interface NSProxy { struct objc_class *isa; }
因此也就得出了答案:id能够指向oc中的任何对象,而NSObject*只能指向NSObject及子类对象。
Runtime是一个运行时系统,用来执行编译连接后的可执行文件;它将不少静态语言在编译和连接时期作的事放到了运行时来处理。这种咱们写代码更具灵活性,如咱们能够把消息转发给咱们想要的对象,或者随意交换一个方法的实现等。
objc_class的定义:
struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;};
从中能够看出,咱们能够修改一些值达到运行时改变原有行为的目的;好比给对象调用方法是从methodLists查找方法实现等。
延伸内容举个例子:Runtime是怎么对对象发送消息的呢?
先须要了解object_class中isa和super_class指的是什么。
其中图最左边列表示类的实例对象,中间列表示类,右边列表示元类。
最左边实例对象只存在一个objc_object结构体; 类则有objc_object和objc_class两个结构体,由于类是元类的实例对象; 元类则只有一个objc_class结构体; 对类、实例对象调用方法都是从objc_object中isa查找。
接下来咱们来分析MyClass *myClass = [[MyClass alloc] init];[myClass test]。
首先咱们要拆分红MyClass *myClass = [MyClass alloc];myClass = [myClass init];[myClass test]。
1:先会执行[MyClass alloc]语句,这是对MyClass类调用类方法,MyClass也是MyClass元类的实例对象, 一样也有objc_object结构体,objc_object结构体中isa指向MyClass元类;MyClass元类也是一个类, 在其objc_class中的methodLists中并无发现alloc方法; 2:则从MyClass元类的objc_class中super_class进行递归查找,最终在NSObject元类中找到alloc方法; 3:[MyClass alloc]返回MyClass的实例对象myClass,这样myClass就有了一个objc_object结构体, objc_object是经过alloc中class_createInstance建立的,isa指针指向MyClass类; 4:对myClass调用init,由于myClass的objc_object中isa指向MyClass类,因此会在MyClass类的 objc_class中methodLists进行查找,发现没有,则去MyClass类的objc_class中 super_class进行递归查找,最终在NSObject类中找到init方法,init只是作一个初始化的操做, 返回自身;这里要注意是从类中不是元类中去查找,由于是对实例对象调用方法; 5:这样实例对象分配空间和初始化就完成了,接下来是对myClass调用test方法, 由于test就是MyClass类中定义的,前面说了myClass的objc_object中isa指向MyClass类,因此直接 就在MyClass的objc_class中methodLists找到了test方法,直接执行test; 6:整个过程执行完毕。
那么我怎么获取objc_class中isa和super_class指向谁呢?
能够用objc_getClass获取isa,用class_getSuperclass获取super_class,class方法则只会返回类自己。
//ClassTwo : ClassOne,ClassOne : NSObject //获取ClassTwo对应的元类的super_class Class currentClass = objc_getMetaClass("ClassTwo"); //打印这些元类的super_class ClassTwo ClassOne NSObject NSObject nil //基元类的super_calss指向基类 基类的super_class为nil 造成闭环 for (int i = 0; i < 5; i++) { NSLog(@"Following the super_class pointer %d times gives %p", i, currentClass); currentClass = class_getSuperclass(currentClass); } //打印ClassTwo类的super_class指向 ClassTwo ClassOne NSObject nil currentClass = [ClassTwo class]; for (int i = 0; i < 4; i++) { NSLog(@"Following the super_class pointer %d times gives %p", i, currentClass); currentClass = class_getSuperclass(currentClass); } //打印ClassTwo的isa指向 ClassTwo nil nil nil 任何元类的isa指向基类的元类,就是nil currentClass = [ClassTwo class]; for (int i = 0; i < 4; i++) { NSLog(@"Following the isa pointer %d times gives %p", i, currentClass); currentClass = objc_getClass((__bridge void *)currentClass); }
Objective-C Runtime 运行时之一:类与对象
Objective-C对象模型及应用
iOS中isa的深层理解
Objective-C 中的元类(meta class)是什么?
iOS底层原理总结 - 探寻Class的本质
能够给任意类添加属性,用class_addProperty。
这里须要注意了,若是咱们在类定义中加的属性,那么编译器会默认生成一个成员变量和getter/setter方法,若是咱们动态加属性,则只是表示这是一个属性罢了,咱们须要再添加对应的成员变量和getter/setter方法才能正常使用。
只能给动态添加类添加成员变量,用class_addIvar;该类不能是元类。
能够给任意类添加关联对象达到添加"属性"做用,用objc_setAssociatedObject。
//设置objc_setAssociatedObject(self, (const void *)"key", @(1), OBJC_ASSOCIATION_ASSIGN);//获取id object =objc_getAssociatedObject(self, (const void *)"key");
对象关联的使用objc_setAssociatedObject
Ivar 详解
要了解KVO,咱们得知道KVC和KeyPath。
KeyPath:键路径;运行时系统根据键路径找到最后的属性/成员变量/关联对象进行相应的操做。
KVC:容许开发者经过Key名直接访问对象的属性/成员变量/关联对象;并有一组api供开发者使用,像操做字典同样操做对象属性/成员变量/关联对象。
... - (void)setValue:(nullable id)value forKey:(NSString *)key - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath ...
为何说是属性/成员变量/关联对象呢?
声明属性编译器默认会给咱们生成对应的私有成员变量,其实属性就是私有成员变量+getter+setter罢了; 这里咱们不考虑幺蛾子状况,好比声明了两个属性year和month,你又写了这样的代码@synthesize year = month;那么很差意思,这样的话编译器不会生成_month和_year成员变量了,也不会生成month的getter和setter方法,只会生成一个month成员变量和year的getter和setter方法,操做self.year 就至关于操做了month成员变量; 成员变量是本身写在类扩展、类定义或类实现后成员变量声明区中的变量;类定义中声明的默认是受保护 类型,类扩展和类实现声明的默认是私有类型;任何类型的变量均可以被子类继承; 也可用Runtime修改/获取值。 关联对象是若是在分类中声明了"属性"且本类中没声明,若是本类中声明了那么操做的将会是本类中的属性, 编译器不会生成对应的成员变量,只是生成了getter和setter罢了;KeyPath了分类中的"属性"实际上是调用了分类中对应"属性"重写的getter和setter罢了, 内部实现通常是设置/返回关联对象的值。
这里须要注意了。若是咱们声明一个属性为year,那么你下面两种方式均可以修改到year的值,理由上面已经说过了。
[xxx setValue:@(10) forKey:@"year"]; [xxx setValue:@(20) forKey:@"_year"];
若是你声明了属性year,你又添加了一个成员变量year,那么将会有year和_year两个成员变量,self.year操做的_year成员变量。你能够加@synthesize year = year告诉编译器year属性使用year成员变量而不用再生成_year;那么下面的这句话就会崩溃。
[xxx setValue:@(20) forKey:@"_year"];
KVO:键值对观察者,在监听属性值变化时发出一个通知给监听者。
... - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context ...
系统提供的接口,在新/旧值同样时也会发出通知。
实现KVO方式不少种,咱们看看系统KVO的内部大概实现思路。
假设有一个Person类,该类有一个name属性。执行下面语句的时候。 [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; 其实系统动态建立了一个子类NSKVONotifying_Person,并把person的isa指针指向了NSKVONotifying_Person;若是咱们打印出NSKVONotifying_Person因此方 法,能够获得这么几个:setName:、class、dealloc和_isKVOA。重点是setName:方法,咱们打印 setName:的内容为:(Foundation`_NSSetObjectValueAndNotify);这个是一个私有方法,不过咱们 不难猜到通知是从这个方法发出来的。那么当咱们改变person的name属性时,实际上是走了NSKVONotifying_Person的setName:方法,该方法先调用Person的setName:方法给name赋值后 发出一个通知给监听者。
我看了一些网上本身实现KVO的作法,网上的大部分实现只能给实例添加一个监听对象、不支持实例变量添加KVO、不支持keyPath;要本身仿写KVO实际上是很是困难。
iOS开发技巧系列---详解KVC(我告诉你KVC的一切)
iOS KVO的底层实现原理
KVO 的内部实现
这里咱们讲ARC环境下;修饰符主要分为下面的几类。
nonatomic:原子性访问,对属性赋值的时候不加锁,多线程并发访问会提升性能。
atomic:属性默认为atomic,提供多线程安全,在多线程环境下,原子操做是必要的,不然有可能引发错误的结果。
在iOS开发中,几乎全部属性都声明为 nonatomic。 atomic的做用只是给getter和setter加了个锁,atomic只能保证代码进入getter或者setter函数内部时是安全的,一旦出了getter和setter,多线程安全只能靠程序员本身保障了。
readwrite:同时产生setter/getter方法。
readonly:只产生简单的getter,没有setter。
有的朋友会问怎么没有writeonly?由于写权限包含了读权限。
copy:目标对象引用计数不变,拷贝一份引用计数为1的对象,该属性指向拷贝对象。
weak:目标对象引用计数不变,该属性指向目标对象地址,当目标对象销毁时,该属性置为nil。
strong:目标对象引用计数+1,该属性指向目标对象地址。
对于NSString对象须要单独考虑。
NSString *a = @"abc";//@"abc"被放到常量区,对a对象copy和strong引用计数不会变化,a是NSCFConstantString类型。NSString *a = [NSString stringWithFormat:@"abc"];//在堆上分配内存获得的a是NSCFString对象,对a对象copy和strong都只是引用计数+1。
对于可变对象赋值给copy属性时会变成不可变对象。
nonnull:对象不该该为nil;当赋值为nil时编译器会给出警告。
NS_ASSUME_NONNULL_BEGIN//之间的属性都被认为是nonnull的NS_ASSUME_NONNULL_END
nullable:对象能够为nil;属性默认是nullable的。
iOS中property的关键字(史上最详解)
iOS属性经常使用关键字解析
NSString特性分析学习
iOS多线程到底不安全在哪里?
如下是比较经常使用的五分区方式,固然也不排除网上有其余的分区方式。
栈的大小在编译时就已经肯定了,通常是2M;栈是一块从高到低地址的连续区域,存放临时变量和执行函数时的内存等。栈内存分配分为动态和静态,静态如自动变量(局部变量)等,动态如alloc等。
堆是从低到高地址的不连续区域,相似链表;用来存放malloc或new申请的内存。
存放静态/全局变量;全局区细分为未初始化/初始化区。
存放常量;程序中使用的常量会到常量区获取。
能够看看这个例子来理解一下。
...int a;//a在全局未初始化区int a = 10;//a在全局初始化区 10在常量区static int a = 10;//a在静态区 10在常量区//程序入口int main(...) { int a = 10;//a在栈区 10在常量区 static int a = 10;//a在静态区 10在常量区 char *a = (char *)malloc(10); //a在栈区 malloc后的内存在堆区 ... }
存放二进制代码,运行程序就是执行代码,代码要执行就要加载进内存(RAM运行内存)。
iOS程序中的内存分配分区
iOS基础全局变量·静态变量·局部变量·自动变量
告诉编译器,这个全局变量在本文件找不到就去其余文件去找。若有必要须要使用#import "x.h"这样编译器才知道到哪里去找。
//.hint age = 10;//error 不能.h此处声明全局非静态变量,.m中能够extern int age = 10;//error 和int age = 10;等价extern static int age = 10;//全局静态变量声明不和extern一块儿用@interface Class : NSObject...@end//.mextern static int age = 10;//全局静态变量声明不和extern一块儿用@implementation Class { int age;//成员变量不能用做extern;} - (void)test { extern int age = 10;//error 由于这并非全局变量 static int age = 10;//error 由于这并非全局变量 extern int age;//error 由于这并非全局变量} ...@end
使用extern前要保证对应变量被编译过
//.hextern int age;//error extern在声明前extern static int age;//error extern没有static@interface Class : NSObject...@end//.mstatic int age = 10;@implementation Class ...@end
//.hstatic int age = 10;extern int age;//正确 @interface Class : NSObject...@end//.m@implementation Class ... - (void)test { extern int age;//正确 声明和extern能够在不一样文件中}@end
全局非静态变量
//.hextern int age;//正确@interface Class : NSObject...@end//.mint age = 10;@implementation Class ...@end
C语言的概念;本质是函数,返回指针。
char *fun() { char *p = ""; return p; }
C语言的概念;本质是指针,指向函数。
int fun(int a,int b) { return a + b; }int (*func)(int,int); func = fun; func(1,2);//3
OC语言的概念;表示一个代码块,OC中视为对象;挺像C函数指针的。
//typedeftypedef int (^SumBlock)(int a,int b); SumBlock sumBlock = ^(int a,int b) { return a + b; }; sumBlock(1,2);//3//普通 int (^sumBlock)(int a,int b) = ^(int a,int b) { return a + b; }; sumBlock(1,2);//3
指针函数与函数指针(C语言)
iOS开发-由浅至深学习block
咱们基本都是ARC环境,因此回答以ARC角度。
讲这个以前,咱们须要搞清楚一个概念,这个block存在内存什么区域的?
若是这个block内部没有访问栈、堆的变量,那么这个block存在代码区;反之存在堆区。block内部修改栈区的变量,该变量须要加__block修饰,这样会将变量从栈上复制到堆上。栈上那个变量会指向复制到堆上的变量。block内部修改堆区的变量不用加__block。
由于堆区不断有变量建立和销毁,block做为属性时咱们须要加copy或者strong修饰。
__weak咱们就在block和声明属性中看到过。
如block是被self强引用的。
@property (nonatomic, copy) void (^Block)(void);
那么在Block内部使用self时,Block内部又会对self进行一次强引用;这就造成了循环引用,因此须要对self进行__weak。
__weak typeof(self) weakSelf = self;
弱引用不会影响对象的释放,当对象被释放时,全部指向它的弱引用都会自定被置为nil。
固然了,self没有强引用block时是不须要__weak的。
- (void)func() { void (^Block)(void) = ^(void) { [self test]; }; }
对self进行了__weak,那么在block执行时weakSelf随时可能被释放,因此内部须要对weakSelf进行__strong让self不被释放。
__strong typeof(self) strongSelf = weakSelf;
在block执行完成后,strongSelf会被释放,不会形成循环引用。
iOS中block块的存储位置&内存管理
__block & __weak & __strong
当点击一个按钮的时候,事件若是传递到按钮这个第一响应者上,这就是事件传递链要作的事情。系统根据下面两个方法来传递事件。
//该点是否在本视图点击范围内 point已经被转换了成本视图对应frame- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { //内部实现大概是这样 return CGRectContainsPoint(self.bounds, point); }//本视图/子视图是否可以传递本事件 point已经被转换了成本视图对应frame- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { //内部实现大概是这样 //用户交互为NO,不处理 if(self.userInteractionEnabled == NO) { return nil; } if([self pointInside:point withEvent:event]) { NSArray * superViews = self.subviews; //倒序从最上面的一个视图开始查找 for (NSUInteger i = superViews.count; i > 0; i--) { UIView * subview = superViews[i - 1]; //转换坐标系 使坐标基于子视图 CGPoint newPoint = [self convertPoint:point toView:subview]; //获得子视图 hitTest 方法返回的值 UIView * view = [subview hitTest:newPoint withEvent:event]; //若是子视图返回一个view 就直接返回 不在继续遍历 if (view) { return view; } } //全部子视图都没有返回 则返回自身 return self; } return nil; }
当点击按钮的时候,其实事件是这样传递的:AppDelegate->UIApplication->UIWindow->xxx->UIViewController->UIView->UIButton。
当找到事件第一响应者以后,该事件如何响应,就是事件响应链要作的事情。
接着上面的例子,UIButton就是系统找出来的第一响应者,那么会执行以下方法:
//触摸事件开始- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}//触摸事件移动- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}//触摸事件结束- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {}//触摸事件取消- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {}
若是你本身不处理,你能够self.nextResponder让下一个响应者处理。
//触摸事件开始- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //让下一个响应者处理 [self.nextResponder touchesBegan:touches withEvent:event]; } ...
响应者链也就是传递链的倒序。
这里须要注意的就是,若是给UIButton添加了target和UITapGestureRecognizer那么点击按钮只会执行UITapGestureRecognizer,也就是说若是手势和target同时知足条件则只会执行手势。target也是touchesxxx中断定的,你能够重写touchesxxx内部实现为空,你会发现并不影响手势但会影响target。
iOS事件拦截和事件转发
UIView之userInteractionEnabled属性介绍
iOS触摸事件那点儿事
RunLoop是iOS中的Event Loop实现,简单来讲是一个do while循环,须要GCD等协做执行;循环体内没事件须要处理就休眠,被mach_port唤醒以后处理相应事件后判断条件继续进入循环。一个线程只能有一个根RunLoop,RunLoop保存在TSD中;一次RunLoop执行只能指定一个RunLoopMode,mode有timer、source、common和observer等;几乎全部的操做都是经过Call out方法进行回调的,好比点击是经过source1到source0再到action回调;要切换mode必须退出当前RunLoop并指定新mode从新执行。
iOS刨根问底-深刻理解RunLoop
Run Loop 记录与源码注释
深刻理解RunLoop
RunLoop 源码阅读
有容错值用gcd timer实现,反之用mk_timer实现,mk_timer更准确;触发点并非一开始就计算出的,而是每次触发后动态计算;当RunLoop执行一个阻塞操做时,触发点可能延迟,可能会跳过中间的触发点。
iOS的一个多核调度器,用于优化应用程序以支持多核处理器;内部默认建立一个串行主队列和12种不一样优先级的并发队列,能够本身建立默认优先级为Default的串行/并发队列;获得队列后能够向队列同步/异步方式添加任务,异步GCD会按需建立线程;向主队列添加的任务将由主线程RunLoop处理;GCD还能够实现定时器、延迟、栅栏、信号量和组等。
iOS多线程:『GCD』详尽总结
iOS GCD之dispatch_semaphore(信号量 )
GCD源码分析
释放池是由n个page组成的双向链表,线程和释放池一一对应;释放池push时会放入哨兵对象,根据next指针放置添加进来的自动释放对象;释放池pop时会将hotpage中next指针依次向前移动,对所指对象调用release直到遇到结束标志,清理过程可跨越page;从main方法中知道iOS项目默认是包裹在大的释放池中;RunLoop开始循环、休眠和退出时会对释放池进行poolpush/poolpop操做。
理解 iOS 的内存管理
iOS 自动释放池原理探究
黑幕背后的Autorelease
iOS中autorelease的那些事儿
NSTimer、GCD定时器和CADisplayLink。
NSTimer根据容错值使用GCD定时器或mk_timer。
NSTimer和CADisplayLink依赖于RunLoop,GCD定时器不依赖。
iOS定时器 NSTimer、CADisplayLink、GCD
view是layer的代理对象;view负责管理layer,layer负责渲染;view初始化的时候默认会建立一个layer;设置view的frame和bounds等内部实际上是修改layer对应属性。
互斥锁:NSLock、pthread_mutex、@synchronized。
加锁后,其余加锁操做阻塞直到解锁。
递归锁:NSRecursiveLock。
一个线程能够屡次加锁,相应的要对应屡次解锁其余线程才能够加锁。
条件锁:NSCondition、NSConditionLock。
锁知足指定条件时才继续执行,不然阻塞。
信号量:dispatch_semaphore。
wait操做阻塞直到signal被调用。
读写锁:pthread_rwlock。
读模式占有锁时其余线程只能读;写模式占有锁时其余线程不能进行任何操做。
应用、表示、会话、传输、网络、数据链路、物理。
应用、传输、网络、数据链路。
传输层单位:段; 网络层单位:报; 链路层单位:帧。
ARC是引用计数,是一个简单而有效的管理对象生命周期的方式;编译器在代码合适的地方自动给咱们加了一些关键字,好比:retain、release和autorelease等;这样咱们就不用手动管理对象生命周期。
区别仍是有不少的,答到核心的就能够了。
1:类指针赋值时只是复制了地址,结构体是复制内容;
2:类不能有同名同参数个数的方法,结构体能够;
3:结构体方法实现编译时就肯定了,类方法实现可动态改变;
4:内存分配不同,结构体在栈,类在堆;
5:结构体能够多重继承,类只能单继承。
网上不少文章说结构体不能有方法,结构体不能继承; 请看清楚题,说的是iOS中的结构体,不是C中的结构体。
一个协议一时间只能有一个代理对象,而一个通知一时间能够有多个监听者。
通知的发送和监听依靠通知中心,协议则能够本身建立,经过setDelegate指定代理对象。
IOS中的协议Protocol与代理Delegate以及通知
访问野指针:数据越界、对象已经释放但对其发送消息等;
内存泄漏:循环引用、imageNamed读取图片等;
触碰内存峰值:for循环声明变量等;
申请了不使用的内存:声明变量但未使用、只在某个逻辑分支用到某些变量但一开始就初始化等。
访问野指针:访问前加判断;
这部分问题大可能是多线程形成的,好比两个线程同时执行一个方法,方法内部对数组有更新操做。
内存泄漏:Instrument Leaks/Allocations检测、使用imageWithContentsOfFile读取图片等;
imageNamed会缓存加载的图片;imageWithContentsOfFile只是简单的加载图片。
触碰内存峰值:手动添加释放池;
for循环内大量建立局部变量,这些局部变量会等到RunLoop的下一个循环才释放, 而手动加入释放池则会提早释放。
申请了不使用的内存:懒加载。
init loadView viewDidLoad viewWillAppear viewDidAppear viewWillDisappear viewDisDisappear viewWillUnload(Deprecated) viewDidUnload(Deprecated) delloc
这里要注意loadView方法,平时咱们都没有去管这个事情;
这个方法执行后self.view才有值,也就是说这个方法完成了view的加载,内部会根据控制器名字、是否本身实现、xib等条件来加载view。
若是本身实现loadView方法,而且方法体为空,则self.view为nil, 而且viewDidLoad方法会调用两次; 这说明viewDidLoad是被loadView调起的,在viewDidLoad中若是self.view为nil会再调用一次loadView。
延伸了解。
viewWillUnload和viewDidUnload被弃用了,内部是在清理self.view。 storyboard加载的是控制器+View,而xib加载的是View,也就是说storyboard加载时会调用 ViewController的awakeFromNib方法。 awakeFromNib:表示ViewController/View从xib加载。
iOS开发笔记(九):UIViewController的生命周期
frame表示在superview坐标系中的位置和大小,bounds表示自身坐标系,默认左上角为0,0;
bounds给subview参考,其结合自身frame肯定显示位置;
改变bounds不会改变本身在superview中的位置,但会改变subview的位置。
改变bounds时若是宽高和frame宽高不一致则会以center为中心缩放,此时将改变frame并从新显示。
若是属性没有自动生成getter/setter方法,则告诉编译器去生成。
告诉编译器不要生成此属性的getter/setter方法,开发者本身去实现。
iOS @property、@synthesize和@dynamic
先了解一下NSURLCache。
NSURLCache默认会对部分GET请求进行缓存; 请求一张图片,第一次请求成功后NSURLCache会缓存图片内容,第二次请求的时候直接从缓存中取就能够了, 并无真正发起请求;NSURLCache系统也是默认启动的。
看了上面的介绍你发现这不就是你最终要实现的功能吗?那SDWebImage到底作了什么呢?
1:正在下载该url的图片,直接返回;2:处理多线程问题,好比cell复用形成的显示错乱问题,也就是下载以前先取消;3:增长一层内存缓存,直接从url获得image,NSURLCache存的是data;4:相同url请求时只加了一个finish回调;5:请求作了排队处理,控制资源。
因此能够把SDWebImage当作是NSURLCache的封装,等同于NSOperationQueue和GCD的关系。
SAX:基于事件驱动,逐行解析,采用协议回调;文件比较大时建议用此方式。
DOM:文档对象模型,解析时将整个文档读入并结构化成树,经过树状结构读取相关数据;文件比较小时建议用此方式。
我看的是AFNetWorking 3.0版本,是基于NSURLSession封装的,NSURLSession使用起来已经足够方便了,因此AFNetWorking作的内容相对来讲少了不少。
1:设置请求/响应序列化对象,这样能够帮你检查请求/响应参数是否正确; 2:https验证功能,能够用于自签名证书等特殊状况,若是不启用则让系统帮你验证, 那么就只能是苹果官方承认的CA证书才能经过; 3:对于不一样的请求方式和参数,帮你设置请求,好比头部的Content-type。
快速:协议简单,通信速度快;
灵活:经过Content-Type能够传递任何类型的数据;
长链接:一次链接处理多个请求,并支持管线(同时发出多个请求,不用等到前面请求响应)、多路复用(一个请求屡次响应);
无状态:协议对于事务处理没有记忆能力,若是本次请求须要上次的数据则须要重传。
GET在URL后以?形式拼接参数,POST将参数放在body中;
GET也能够在body里面放数据,POST也能够在URL放数据,Http只是规定,你也能够不遵照啊。 至于服务器获不获取就看具体实现了。
POST比GET安全,由于GET参数在URL中,用户一眼就能看到;
POST固然也能够用工具看到参数。
语意理解,GET用来获取数据,POST用来上传数据;
客户端和服务器对URL数据通常有1kb的限制。
Http协议对URL并无限制。
iOS-网络编程(一)HTTP协议
http2.0的时代真的来了...
Http是基于Tcp的,而Socket是一套编程接口让咱们更方便的使用Tcp/Ip协议;Http是应用层协议,在Tcp/Udp上一层。
1:Http是基于"请求-响应"的,服务器不能主动向客户端推送数据,只能借助客户端请求到后向客户端推送数据,而Sokcet双方随时能够互发数据;
2:Http不是持久链接的,Socket用Tcp是持久链接;
3:Http基于Tcp,Socket能够基于Tcp/Udp;
4:Http链接是经过Socket实现的;
5:Http链接后发送的数据必须知足Http协议规定的格式:请求头、请求头和请求体,而Socket链接后发送的数据没有格式要求。
Tcp是传输层可靠传输协议,发送数据前要进行握手,数据发送完时能够挥手断开链接释放资源;挥手比握手多一次是由于断开须要双方单专断开,而握手被链接端是被动打开的。
首先,咱们必需要明白一些重要的Tcp头部标志。
序号:每个包都有一个序号,此序号mod2^32; 确认号:用来确认收到对方的包,此序号mod2^32; SYN:为1时表示但愿与对方创建链接; FIN:为1时表示我已经没有数据发送了,但愿断开链接; ACK:为1时确认号有效,Tcp规定除了第一次创建链接的包ACK都要置为1。
这是我抓的访问百度握手/挥手过程,Http协议也是基于Tcp的。
10.10.9.141:客户端ip;180.97.33.107:百度ip。 第一次握手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 64) 10.10.9.141.50806 > 180.97.33.107.http: Flags [S], cksum 0x95b1 (correct), seq 589117916, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 815096723 ecr 0,sackOK,eol], length 0 第一次握手 SYN=1 seq=589117916 第二次握手 IP (tos 0x0, ttl 53, id 0, offset 0, flags [DF], proto TCP (6), length 64) 180.97.33.107.http > 10.10.9.141.50806: Flags [S.], cksum 0x2cb0 (correct), seq 1775662715, ack 589117917, win 8192, options [mss 1408,nop,wscale 5,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,sackOK,eol], length 0 第二次握手 SYN=1 ACK=1 seq=1775662715 ack=589117917第三次握手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 10.10.9.141.50806 > 180.97.33.107.http: Flags [.], cksum 0xa25d (correct), seq 589117917, ack 1775662716, win 8192, length 0 第三次握手 ACK=1 seq=589117917 ack=1775662716数据传输... 数据传输... 第一次挥手 IP (tos 0x0, ttl 50, id 43779, offset 0, flags [DF], proto TCP (6), length 40) 180.97.33.107.http > 10.10.9.141.50806: Flags [F.], cksum 0xb3c8 (correct), seq 1775665497, ack 589118060, win 808, length 0 第一次挥手 FIN=1 ACK=1 seq=1775665497 ack=589118060第二次挥手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 10.10.9.141.50806 > 180.97.33.107.http: Flags [.], cksum 0x973b (correct), seq 589118060, ack 1775665498, win 8117, length 0 第二次挥手 ACK=1 seq=589118060 ack=1775665498第三次挥手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 10.10.9.141.50806 > 180.97.33.107.http: Flags [F.], cksum 0x96ef (correct), seq 589118060, ack 1775665498, win 8192, length 0 第三次挥手 ACK=1 FIN=1 seq=589118060 ack=1775665498第四次挥手 IP (tos 0x0, ttl 50, id 0, offset 0, flags [DF], proto TCP (6), length 40) 180.97.33.107.http > 10.10.9.141.50806: Flags [.], cksum 0xb3c7 (correct), seq 1775665498, ack 589118061, win 808, length 0 第四次挥手 ACK=1 seq=1775665498 ack=589118061
通俗大白话来理解TCP协议的三次握手和四次分手
tcpdump查看三次握手
1:系统内核作好准备工做,好比把应用数据从ROM移到RAM;
2:libdyld接管后续工做,加载动态库到内存等;
3:ImageLoader把动态库和可执行文件加载到内存;
4.1:Runtime解析文件符号表,注册类、调用load方法;
4.2:libdispath等其余库初始化;
5:libdyld调用main方法。
线程安全:多线程环境下保证数据的完整性。
把操做放入队列线性执行,可用GCD和NSOperationQueue。
用锁/信号量造成操做互斥。
让操做原子执行,系统提供了一些原子执行的方法。
NSOperationQueue没有串行/并发队列,但能够设置最大并发数;
NSOperationQueue支持方法和block,GCD只支持block;
NSOperationQueue能够暂停/取消操做;
NSOperationQueue支持更多的功能,好比KVO和自定义操做;
NSOperationQueue能够设置操做的优先级,GCD只能设置队列的优先级。
提供的功能是类似的;
NSOperationQueue是GCD的封装。
其实设计模式有不少,你只要答上6个就能够了。
装饰模式:分类; 代理模式:协议; 工厂模式:UIButton建立; 原型模式:[object copy]; 观察者模式:KVO; 迭代器模式:数组的遍历; 单例模式:Appdelegate; 命令模式:给对象发消息; 职责链模式:事件传递链; 中介者模式:模块解耦; 解释器模式:Siri语意识别;
Block本质就是函数,根据有无返回值/参数有4种Block;
在ARC下根据是否访问栈/堆变量可分为全局/堆Block;
Block内修改栈变量时须要__block修饰,__block的做用其实就是在堆上建立一个指向栈变量的指针达到修改栈变量值的目的。
当向对象发送了一个不存在的消息时,会先走resolvexxxMethod动态处理流程,你能够在这之中用Runtime动态添加该方法;你也能够不处理,则进入转发流程。
先走forwardingTargetForSelector,你能够在这里返回一个能够处理aSelector的对象,不然走forwardInvocation操做anInvocation;forwardInvocation须要实现methodSignatureForSelector获得一个方法签名。
系统维护了一个weak变量组成的hash表,key为weak指向的变量地址,value为weak变量的地址;当对象引用计数为0时,遍历hash表,设置对应的weak变量为nil。
咱们能够从Runtime的源码中看到,发消息最终会走objc_msgSend()并把nil最为第一个参数,其内部用汇编实现,里面会判断第一个参数是否为nil,若是为nil则返回0,因此iOS容许对nil发送消息;这个0针对不一样的selector返回值有不一样的表示,好比:0、nil和结构体等。
SafeArea是View的属性,是iOS11出来用来代替bottomLayoutGuide/topLayoutGuide的,bottomLayoutGuide/topLayoutGuide是ViewController的属性;从这里能够看出,SafeArea更灵活,能够对每个View进行配置;他们都是让控件不被父View遮挡系统自动计算的距离,你固然也能够关闭这个功能;iOS11前用automaticallyAdjustsScrollViewInsets,iOS11后用contentInsetAdjustmentBehavior。
cell重用,异步执行耗时操做这种就不用提了,这都是你们能想到的,咱们能够说一点其余的。
1:减小视图层次;
2:正确使用api,好比设置rowHeight而不去取dataSource;
3:减小离屏渲染;
圆角图片 阴影 ...
4:设置视图不透明减小渲染代价,若是有透明度则会根据多个视图才知道一个像素点显示什么颜色;
5:RunLoop空闲计算cell高度并缓存;
6:利用RunLoop的mode在滚动时不执行耗时操做。
若是没有ssl/tls证书,每一个不一样客户端访问服务器都须要生成一对密钥,这会形成服务器端存储的密钥太多等问题;让所用客户端使用统一的服务器ssl/tls证书则能够解决这些问题。
https=http+ssl/tls,拿咱们客户端请求api来讲,咱们适配https时要后台给咱们一个cer证书,这个证书里面包括了:颁发机构、有效期、RSA公钥和Hash指纹等信息;客户端会把这个证书交给请求库,请求库负责完成加/解密;为何须要从颁发机构申请证书呢?由于咱们的api和web可能在一个域名下,一个域名只能一个证书,也就是说若是只有api访问这个域名咱们彻底能够本身建立一个证书设置受信用;然而若是web也要访问该域名则浏览器不会认为这个证书受信用会拒绝访问。
HTTPS 原理详解
图解 HTTPS:Charles 捕获 HTTPS 的原理