ios

作者: yoolooo | 来源:发表于2020-08-24 18:03 被阅读0次

    多线程

    线程是什么?进程是什么?二者有什么区别和联系? (UI 第二 十二讲 多线程编程)

    线程是CPU独立运行和独立调度的基本单位(可以理解为一个进程中执行的代码片段),进程是资源分配的基本单(进程是一块包含了某些资源的内存区域)。进程是线程的容器,真正完成代码执行的是线程,而进程则作为线程的执行境。一个程序至少包含一个进程,一个进程至少包含一个线程,一个进程中的多个线程共享当前进程所 拥有的资源.

    你用过 NSOperationQueue 么?如果用过或者了解的话,你为什 么要使用NSOperationQueue,实现了什么?请描述它和 GCD 的区别和类似的地方(提示:可以从两者的实现机制和适用范围来描述)。

    1、 GCD 是纯 C 语言的 API,NSOperationQueue 是基于 GCD 的 OC 版 本封装
    2、GCD 只支持 FIFO 的队列,NSOperationQueue 可以很方便地调整执 行顺序、设置最大并发数量
    3、NSOperationQueue 可以在轻松在 Operation 间设置依赖关系,而 GCD 需要写很多的代码才能实现
    4、NSOperationQueue 支持 KVO,可以监测 operation 是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
    5、GCD 的执行速度比 NSOperationQueue 快
    任务之间不太互相依赖:GCD
    任务之间有依赖\或者要监听任务的执行情况:NSOperationQueue

    GCD 的队列(dispatch_queue_t)分哪两种类型?

    串行队列 Serial Dispatch Queue
    并行队列 Concurrent Dispatch Queue

    线程安全

    @synchronized(self){
    // 需要锁定的代码
    }
    

    block

    block 在 ARC 中和 MRC 中的用法有什么区别,需要注意什么

    1.对于没有引用外部变量的 Block,无论在 ARC 还是非 ARC 下,类 型都是NSGlobalBlock,这种类型的 block可以理解成一种全局的 block,不需要考虑作用域问题。同时,对他进行 Copy 或者 Retain 操 作也是无效的
    2.应注意避免循环引用

    在 block 内如何修改 block 外部变量?

    默认情况下,在 block 中访问的外部变量是复制过去的,即:写操作 不对原变量生效。但是你可以加上__block来让其写操作生效。

    使用block时什么情况会发生引用循环,如何解决?

    一个对象中强引用了 block,在 block 中又使用了该对象,就会发生循环引用。 解决方法是将该对象使用__weak或者__block 修饰符修饰之后再在 block 中使用。

    RunLoop

    RunLoop 是什么?

    RunLoop 就是一个事件处理的循环,用来不停的调度工作以及处理输 入事件。使用 RunLoop的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。runloop的设计是为了减少cpu无谓的空转。
    使用场景:1、需要使用 Port 或者自定义 Input Source 与其他线程进 行通讯;2、子线程中使用了定时器;3、Cocoa中使用任何 performSelector到了线程中运行方法;4、线程执行周期性任务。仅当在为你的程序创建辅助线程的时候,你才需要显式运行一个 RunLoop。

    runloop 和线程有什么关系?

    总的说来,Run loop,正如其名,loop 表示某种循环,和 run 放在一 起就表示一直在运行着的循环。实际上,run loop和线程是紧密相连 的,可以这样说 run loop 是为了线程而生,没有线程,它就没有存在 的必要。Run loops是线程的基础架构部分,Cocoa 和 CoreFundation 都提供了 run loop 对象方便配置和管理线程的 runloop(以下都以Cocoa 为例)。每个线程,包括程序的主线程( main thread )都有 与之相应的 run loop 对象。
    runloop 和线程的关系:主线程的 run loop 默认是启动的。iOS 的应 用程序里面,程序启动后会有一个如下的 main()函数

    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    重点是 UIApplicationMain()函数,这个方法会为 main thread 设置一 个 NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操 作的时候休息,需要让它干活的时候又能立马响应。对其它线程来说,run loop 默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当 前线程的 run loop 。
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];

    runloop 和线程一一对应,主线程的runloop已经创建,子线程的必须手动创建
    runloop在第一次获取时创建,在线程结束时销毁。
    在runloop中有多个运行模式,但是只能选择一种模式运行,mode中至少要有一个timer 或者是 source
    Mode:
    系统默认注册5个Mode:
    DefaultMode: app默认mode,通常主线程在这个mode下运行
    TrackingMode:界面跟踪mode,用于scrollView追踪触摸滑动,保证滑动时不受其他mode影响。
    kCFRunLoopCommonModes:展位用的mode,不是一个真正的mode,相当于DefaultRunLoopMode + TrackingRunLoopMode。
    UIInitialzationRunLoopMode:刚启动app时进入的第一个mode,启动完成之后不再使用
    CGSEventReceiveRunLoopMode:接受系统事件的内部mode,通常用不到

    runtime

    iOS 的动态性

    iOS 的动态性来自三个方面:动态类型、动态绑定、动态载入、SEL 类型
    1、动态类型<弱类型>(id):在代码的运行阶段判断代码的类型, 使用 id类型可以让应用在“运行时”使用任何类型来替换。动态类型让程序更加灵活,但是会使数据的统一性降低和代码的可性。我 们常用静态类型<强类型>(如 NSString),使用静态类型编译器可以完全分析你的代码,这让代码的性能和可预知性更高。 2、动态绑定:让代码在运行时判断需要调用什么方法,而不是在编译时。动态类型和动态绑定使得选择哪个接收者已经调用什么方法 都放到运行时去完成。
    3、动态载入:应用程序可以根据需要加载可执行代码以及资源,而 不是在启动时就加载所有资源。
    4、SEL 类型 iOS 在编译的时候会根据方法的名字(包括参数序列), 生成一个用来区分这个方法的唯一的 ID,这个 ID 是SEL 类型的,SEL 的本质就是类方法的编号[函数地址]。(类似 C 语言里面的函数指针, 但是 OC的类不能直接使用函数指针,这样只能做一个@selector 语 法来取。注意:@selector 是查找当前类(含子类)的方法。)

    我们说的 OC 是动态运行时语言是什么意思?

    多态。 主要是将数据类型的确定由编译时,推迟到了运行时。 这个问题其实浅涉及到两个概念,运行时和多态。 简单来说,运行时机制使我们直到运行时才去决定一个对象的类别, 以及调用该类别对象指定方法。 多态:不同对象以自己的方式响应相同的消息的能力叫做多态。意思 就是假设生物类(life)都用有一个相同的方法-eat; 那人类属于生物,猪也属于生物,都继承了 life 后,实现各自的 eat, 但是调用是我们只需调用各自的 eat 方法。 也就是不同的对象以自己的方式响应了相同的消息(响应了 eat 这个 选择器)。
    因此也可以说,运行时机制是多态的基础?

    讲述一下 runtime 的概念,message send 如果寻找不到相应的对 象,会如何进行后续处理 ?

    简单来说,Objective-C runtime 是一个实现 Objective-C 语言的 C 库。 对象可以用C语言中的结构体表示,而方法(methods)可以用 C 函数实现。事实上,他们差不多也是这么干了,另外再加上了一些额外的特性。这些结构体和函数被runtime 函数封装后,Objective-C 程序员可以在程序运行时创建,检查,修改类,对象和它们的方法。

    runtime 不需要知道 Foundation。runtime 会让程序定义转发函数 (forwarding function),当 objc_msgSend()无法找到该 selector 的实现时,那个转发函数就会被调用。程序一启动,CoreFoundation 就将 -forwardInvocation:定义成转发函数。

    什么时候会报unrecognized selector 的异常?

    当调用该对象上的某个方法,而该对象上没有实现这个方法的时候,可以通过“消息转发”解决。
    objc在向一个对象发送消息是,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类的方法列表以及父类的方法列表中寻找方法,如果,在最顶层的父类中依然没有找到相应的方法实现,程序在运行时会挂掉并抛出异常unrecognized selector send to xxx。但是在这之前,objc的运行时会给出3次拯救程序崩溃的机会。

    OC中的objc_msgSend执行流程

    OC中的方法调用,其实都是转换为objc_msgSend函数的调用
    objc_msgSend的执行流程可以分为3大阶段

    1. 消息发送
    receiverisnull=>condition: receiver是否为空
    exit=>end: 退出
    findForCache=>condition: 是否从revicerClass的cache中查找到方法
    cacheResultFind=>end: 调用方法,结束查找
    rwtFind=>condition: 是否从revicerClass的class_rw_t中查找到方法
    rwtResultFind=>end: 调用并将方法缓存到revicerClass的cache
    cacheSuperClass=>condition: 是否从superCalss的cache中查找到方法
    rwtSuperClassFind=>condition: 是否从superClass的class_rw_t中查找到方法
    hasSuperCalss=>condition: 是否还有superClass
    resolveInstance=>operation: 动态方法解析
    receiverisnull(yes)->exit(bottom)
    receiverisnull(no)->findForCache
    findForCache(yes)->cacheResultFind(right)
    findForCache(no)->rwtFind(bottom)
    
    rwtFind(yes)->rwtResultFind
    rwtFind(no)->cacheSuperClass
    cacheSuperClass(yes)->rwtResultFind
    cacheSuperClass(no)->rwtSuperClassFind
    rwtSuperClassFind(yes)->rwtResultFind
    rwtSuperClassFind(no)->hasSuperCalss
    hasSuperCalss(yes)->cacheSuperClass
    hasSuperCalss(no)->resolveInstance
    

    如果是从class_rw_t中查找方法

    • 已经排序的,二分查找
    • 没有排序的,遍历查找

    receiver通过isa找到reveiverClass
    receiverClass通过superClass指针找到superClass

    1. 动态方法解析
    ifResolve=>condition: 是否曾经有动态解析
    hasResolve=>end: 消息转发
    resolve=>operation: 调用动态解析方法
    setResolve=>operation: 标记为已经动态解析
    sendMessage=>end: 消息发送
    
    ifResolve(yes)->hasResolve
    ifResolve(no)->resolve->setResolve->sendMessage
    

    开发者可以实现以下方法,来动态添加方法实现

    + (BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(test)) {
            SEL newSel;
            Method  method = class_getInstanceMethod(self, newSel);
            class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    + (BOOL)resolveClassMethod:(SEL)sel{}
    

    动态解析过后,会重走“消息发送”的流程

    1. 消息转发
    
    forwardingTarget=>condition: forwardingTargetForSelector:返回值是否为空
    msdSend=>operation: objc_msgSend(返回值,SEL)
    methodSignatture=>condition: methodSignattureForSelector:返回值是否为空
    forwardInvocation=>operation: 调用forwardInvocation方法
    doesNotRecognizeSelector=>end: 调用doesNotRecognizeSelector方法
    
    forwardingTarget(no)->msdSend
    forwardingTarget(yes)->methodSignatture
    methodSignatture(no)->forwardInvocation
    methodSignatture(yes)->doesNotRecognizeSelector
    
    
    
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        return [[XXX alloc] init];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"i@:i"];
    //    NSMethodSignature *signature = [[[NSObject alloc] init] methodSignatureForSelector:@selector(test:)];
        return signature;
    }
    
    

    super的本质

    super的调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数

    其他

    isKindOfClass、isMemberOfClass 作用分别是什么?

    -(BOOL) isKindOfClass: classObj 判断是否是这个类或者是这个类子类 的实例
    -(BOOL) isMemberOfClass: classObj 判断是否是这个类的实例

    NSAutoreleasePool 是怎么工作的?

    自动释放池以栈的形式实现:当你创建一个新的自动释放池时,它将被添加到栈顶。当一个对象收到发送 autorelease消息时,它被添加到 当前线程的处于栈顶的自动释放池中,当自动释放池被回收时,它们从 栈中被删除,并且会给池子里面所有的对象都会做一次 release 操作.

    代理和协议什么区别

    代理是一种概念,协议是一种技术,代理是用协议来实现的,代理 是 2 个对象之间通讯的一种方式。代理主要做反向传值的。实现系统的 一些回调方法,比如 scrollview 滑动事件,选择照片,asi网络下载完成 等.iOS 开发和 Objective-c 区别

    简述 NotificationCenter、KVC、KVO、Delegate?并说明它们之间 的区别?

    Notification:观察者模式,controller向 defaultNotificationCenter添加自己的notification,其他类注册这个 notification就可以收到通知,这些类可以在收到通知时做自己的操作(多观察者默认随机顺序发通 知给观察者们,而且每个观察者都要等当前的某个观察者的操作做完才能轮到他来操作,可以用 NotificationQueue的方式安排观察者 的反应顺序,也可以在添加观察者中设定反映时间,取消观察需要 在viewDidUnload 跟 dealloc 中都要注销)。
    KVC 键值编码,可以直接通过字符串的名字(key)来间接访问属性 的机制,而不是通过调用 getter 和 setter 方法访问。 KVO:观测指定对象的属性,当指定对象的属性更改之后会通知相应的观察者。
    delegate:一对一,delegate 遵循某个协议并实现协议声明的方法。

    计时器 NSTimer

    一方面,NSTimer 经常会被作为某个类的成员变量,而 NSTimer 初始 化时要指定 self 为target,容易造成循环引用。 另一方面,若 timer 一直处于 validate 的状态,则其引用计数将始终大于 0.

    响应者链以及它的工作流程

    OS 系统检测到手指触摸(Touch)操作时会将其打包成一个 UIEvent 对象,并放入当前活动 Application的事件队列,单例的 UIApplication 会从事件队列中取出触摸事件并传递给单例的 UIWindow来处理, UIWindow 对象首先会使用 hitTest:withEvent:方法寻找此次 Touch 操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图, 这个过程称之为 hit-test view。
    响应者对象(Responder Object): 指的是有响应和处理事件能力的对象。 响应者链就是由一系列的响应者对象构成的一个层次结构。
    UIAppliction --> UIWiondow -->递归找到最适合处理事件的控件-->控件调用 touches 方法-->判断是否实现 touches 方法-->没有实现默认会将事件传递给上一个响应者-->找到上一个响应者
    UIResponder是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的UIApplication、UIViewController、UIWindow和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应 者链的响应者对象

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

    @property有两个对应的词,@synthesize 和 @dynamic,如果@synthesize 和 @dynamic都没写,那么默认的就是@synthesize var = _var;
    @synthesize 的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上者两个方法。
    @dynamic告诉编译器:属性的setter和getter方法由用户自己实现,不自动生成。

    在有了自动合成属性实例变量之后,@sysnthesize还有哪些使用场景?

    1. 同时重写了setter和getter时,系统不回省城ivar,使用@sysnthesize foo = _foo;关联@property于ivar
    2. 重写了只读属性的getter时
    3. 使用@dynamic时
    4. 在@protocol中定义的所有属性
    5. 在category 中定义的所有属性
    6. 重载的属性,当在子类中重载了父类中的属性,必须使用@sysnthesize来手动合成ivar

    在ARC下,不显示指定任何属性关键字,默认的关键字有哪些?

    1. 基本数据类型 : atomic , readwrite, assign
    2. 普通OC对象: atomic,readwrite, strong

    堆栈

    内存中的栈和堆的区别是什么?

    管理方式:
    对于栈来讲,是由编译器自动管理的,无需我们手动控制,对于堆来讲,释放工作有程序猿控制,这样就容易产生memory Leak
    申请大小: 栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶上的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是2M(也有的说1M,总之是编译器确定的一个常数),如果申请的空间超过了栈的剩余空间时候,就overflow。因此,能获得栈的空间较小。
    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
    碎片问题:
    对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中弹出。
    分配方式:
    堆都是动态分配的,没有静态分配的堆。栈有两种分配方式:静态分配和动态分配。静态分配是编译器完的,比如局部变量的分配。动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同的,他的动态分配由编译器进行释放,无需我们手工实现。
    分配效率:
    栈是机器系统提供的数据结构,计算机会在底层堆栈提供支持,分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是 C/C++函数库提供的,他的机制是很复杂的。

    哪些数据在栈上,哪些在堆上?

    栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。

    性能优化

    UITableView 如何优化

    提前计算并缓存好高度(布局),因为 heightForRowAtIndexPath:是调用最频繁的方法;
    异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
    滑动时按需加载,这个在大量图片展示,网络加载的时候很管用! (SDWebImage已经实现异步加载,配合这条性能杠杠的)。
    正确使用 reuseIdentifier 来重用 Cells
    尽量使所有的 view opaque,包括 Cell 自身
    尽量少用或不用透明图层
    如果 Cell 内现实的内容来自 web,使用异步加载,缓存请求结果
    减少 subviews 的数量
    在 heightForRowAtIndexPath:中尽量不使用 cellForRowAtIndexPath:, 如果你需要用到它,只用一次然后缓存结果
    尽量少用 addView 给 Cell 动态添加 View,可以初始化时就添加,然 后通过 hide 来控制是否显示

    不同版本的 APP,数据库结构变化了,如何处理?(数据库迁移)

    数据库迁移问题,一般项目中使用数据框架有两种 Sqlite 和 CoreData。
    在 Sqlite 中有 Alter 命令修改数据库的结构和库名。
    对于老用户,数据库已经存在,需要修改
    1.查询旧表 NSString *searchSql = [NSString stringWithFormat:@"select sql from sqlite_master where tbl_name='表名' and type='table'"];
    2.若找到旧表,判断是否存储记录,添加新字段或改名
    NSString *sql_add = "ALTER TABLE 表名 ADD 字段名 字段类型”;
    或者修改表名 ALTER TABLE 原表名 RENAME TO 新表名;

    1. 执 行 改 sqlite3_exec( 数 据 库 , sql_add, NULL, NULL, NULL)!=SQLITE_OK。
      4.释放伴随指针。

    OC 语法

    一个NSObject对象占用多少内存

    系统分配了16个字节给NSObject对象(通过malloc_size函数获得)但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

    对象的isa指针指向哪里?

    instance对象的isa指向class对象
    class对象的isa指向meta-class对象
    meta-class对象的isa指向基类的meta-class对象

    OC的类信息存放在哪里

    对象方法,属性,成员变量,协议信息,存放在class对象中。
    类方法 存放在meta-class对象中
    成员变量的具体值存放在instance对象中

    iOS用什么方式对一个对象KVO?(KVO的本质是什么)

    利用runtime API动态生成一个子类,并且让instance对象的isa指向这个全新的子类。当修改instance对象的属性时,会调用Foundation的_NSSetxxxValueAndNotify函数
    willChangeValueForKey:
    父类原来的setter
    didChangeValueForKey:
    内部会触发监听器的监听方法(observeValueForKeyPath:ofObject:change:context:)

    手动触发KVO:手动调用willChangeValueForKey: 和 didChangeValueForKey:
    KVC修改属性会触发KVC,直接修改不会

    Category

    Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法,类方法,属性,协议信息
    在程序编译的时候,runtime会将category的数据合并到类信息中(类对象,元类对象)

    category和Class Extension的区别是什么?

    Class Extension在编译的时候,它的数据就已经包含在类信息中
    Category是在运行时才会把数据合并到类信息中

    Category中有load方法吗?load是在什么时候掉用的?load方法能继承吗?

    有load方法
    load方法在runtime加载类,分类的时候调用
    load方法可以继承,但是一般情况下不会主动去调用load方法,都是系统主动调用
    每个类。分类的+load,在程序运行过程中只调用一次
    调用顺序:

    1. 先调用类的+load
    • 按照编译先后顺序调用(先编译先调用)
    • 调用子类的+load之前会先调用父类的+load
    1. 再调用分类的+load
    • 按照编译先后顺序调用(先编译先调用)

    +initialize和+load的很大区别是:+initialize是通过objc_msgSend进行调用的,所以会有以下特点:

    • 如果子类没有实现+initialize,会调用父类的+initialize,所以父类的+initialize会调用多次
    • 如果分类实现了+initialize,就覆盖类本身的+initialize调用

    + initialize方法

    • initialize方法会在类第一次接收到消息时调用
    • 调用顺序:
      • 先调用父类,再调用子类
      • 先初始化父类,再初始化子类,每个类只会出实话1次

    Category不能直接添加成员变量,但是可以间接实现Category有成员变量的效果。(关联对象)

    OC对象的分类

    OC对象主要可以分为3种:
    instance对象(实例对象)
    class对象(类对象)
    meta-class对象(元类对象)

    每个类在内存中有且只有一个class对象

    class对象在内存中存储的信息主要包括:

    1. isa指针
    2. superclass指针
    3. 类的属性信息(@property),类的对象方法信息(instance method)
    4. 类的协议信息(protocol),类的成员变量信息(ivar)
    5. ...

    每个类在内存中有且只有一个meta-class对象
    meta-class对象和class对象的内存结构是一样的,但用途不一样,在内存中存储的信息主要包括:

    1. isa指针
    2. superclass指针
    3. 类的类方法信息(class method)

    查看class是否是meta-class:class_isMetaClass([NSObject class]);

    isa,superclass总结

    • instance的isa指向class
    • class的isa指向meta-class
    • meta-class的isa指向基类的meta-class
    • class的superclass指向父类的class(如果没有父类,superclass指针为nil)
    • meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class
    • instance调用对象方法的轨迹:isa找到class,方法不存在,就通过superclass找到父类
    • class调用类方法的轨迹:isa找到meta-class。方法不存在,就通过superclass找到父类

    RTMP

    RTMP(Real Time Messaging Protocol)实时消息传送协议。
    它有多种变种:

    1. RTMP工作在TCP之上,默认使用端口1935;
    2. RTMPE在RTMP的基础上增加了加密功能;
    3. RTMPT封装在HTTP请求之上,可穿透防火墙;
    4. RTMPS类似RTMPT,增加了TLS/SSL的安全功能;

    Publish

    participant Client
    participant Server
    
    
    note left of Client: HandShark
    Client->Server: C0:0x03
    Client->Server: C1:1536 random bytes
    Server-->Client: S0:0x03
    Server-->Client: S1:1536 random bytes
    Client->Server: C2:复制S1
    Server-->Client: S2:复制C1
    
    note left of Client: Connect
    Client->Server: Command Message(connect)
    Server-->Client: Window Acknowledgement Size
    Server-->Client: Set peer Bandwidth
    Client->Server: Window Acknowledgement Size
    Server-->Client: User Control Message(StreamBegin)
    Server-->Client: Command Message(_result-connect response)
    
    note left of Client: Create Stream
    Client->Server: Command Message(createStream)
    Server-->Client: Command Message(_result- createStream response)
    
    note left of Client: publish
    Client->Server: Command Message(publish)
    Server-->Client: User Control Message(StreamBegin)
    Client->Server: Data Message(Metadata)
    Client->Server: Audio Message
    Client->Server: SetChunkSize
    Server->Client: Command Message(_result- publish result)
    Client->Server: Video Message
    
    

    Play

    participant Client
    participant Server
    
    note left of Client: HandShark
    Client->Server: 
    Server-->Client:
    
    note left of Client: Create Stream
    Client->Server: Command Message(createStream)
    Server-->Client: Command Message(_result- createStream response)
    
    note left of Client: play
    Client->Server: Command Message(play)
    Server-->Client: SetChunkSize
    Server-->Client: User Control Message(StreamIsRecorded)
    Server-->Client: User Control Message(StreamBegin)
    Server-->Client: Command Message(onStatus-play reset)
    Server-->Client: Command Message(onStatus-play start)
    Server-->Client: Audio Message
    Server-->Client: Video Message
    

    librtmp

    ![avatar][librtmp-image-base64]

    音视频采集

    • 视频采集:AVFoundation AVCaptureSession
    • 音频采集:AVFoundation AVAudioSession

    视频处理 GPUImage

    视频帧渲染:顶点->光栅化->着色。光栅化系统自动处理,ios能编写顶点函数和着色器函数,之前用OpenGL,在ios之后的版本中废弃,现在采用metal,GPUImage3版本使用swift和metal编写

    • 美颜
    • 滤镜
    • 贴图
    • 特效

    人脸识别

    音视频编码

    • 视频编码:VideoToolBox,编码格式: h.264,h.265
    • 音频编码:AudioToolbox

    视频编码

    • 提取参数集:sps(序列参数集),pps(图像参数集)
    • 视频帧转为NALU (大端转小端:NALUnitLength = CFSwapInt32BigToHost(NALUnitLength))

    RTMP发送

    封包

    发送缓存区

    • 丢弃过期帧:如果缓存区满了,丢掉第一个P到第一个I之间的P帧
    • 监控发送网络动态

    发送

    • 发送元数据(AMF)
    • 先发送Head

    视频head: 0x17
    视频data: I帧 0x17 p帧:0x27
    音频head: AF 00 + AAC RAW data
    音频data: AF 01 + AAC RAW data

    发送视频头

    unsigned char *body = NULL;
    NSInteger iIndex = 0;
    NSInteger rtmpLength = 1024;
    const char *sps = videoFrame.sps.bytes;
    const char *pps = videoFrame.pps.bytes;
    NSInteger sps_len = videoFrame.sps.length;
    NSInteger pps_len = videoFrame.pps.length;
    
    body = (unsigned char *)malloc(rtmpLength);
    memset(body, 0, rtmpLength);
    
    body[iIndex++] = 0x17;
    body[iIndex++] = 0x00;
    
    body[iIndex++] = 0x00;
    body[iIndex++] = 0x00;
    body[iIndex++] = 0x00;
    
    body[iIndex++] = 0x01;
    body[iIndex++] = sps[1];
    body[iIndex++] = sps[2];
    body[iIndex++] = sps[3];
    body[iIndex++] = 0xff;
    
    /*sps*/
    body[iIndex++] = 0xe1;
    body[iIndex++] = (sps_len >> 8) & 0xff;
    body[iIndex++] = sps_len & 0xff;
    memcpy(&body[iIndex], sps, sps_len);
    iIndex += sps_len;
    
    /*pps*/
    body[iIndex++] = 0x01;
    body[iIndex++] = (pps_len >> 8) & 0xff;
    body[iIndex++] = (pps_len) & 0xff;
    memcpy(&body[iIndex], pps, pps_len);
    iIndex += pps_len;
    
    [self sendPacket:RTMP_PACKET_TYPE_VIDEO data:body size:iIndex nTimestamp:0];
    free(body);
    

    发送视频

    NSInteger i = 0;
    NSInteger rtmpLength = frame.data.length + 9;
    unsigned char *body = (unsigned char *)malloc(rtmpLength);
    memset(body, 0, rtmpLength);
    
    if (frame.isKeyFrame) {
        body[i++] = 0x17;        // 1:Iframe  7:AVC  0x17  0x1c
    } else {
        body[i++] = 0x27;        // 2:Pframe  7:AVC  0x27  0x2c
    }
    body[i++] = 0x01;    // AVC NALU
    body[i++] = 0x00;
    body[i++] = 0x00;
    body[i++] = 0x00;
    body[i++] = (frame.data.length >> 24) & 0xff;
    body[i++] = (frame.data.length >> 16) & 0xff;
    body[i++] = (frame.data.length >>  8) & 0xff;
    body[i++] = (frame.data.length) & 0xff;
    memcpy(&body[i], frame.data.bytes, frame.data.length);
    
    [self sendPacket:RTMP_PACKET_TYPE_VIDEO data:body size:(rtmpLength) nTimestamp:frame.timestamp];
    free(body);
    
    

    相关文章

      网友评论

          本文标题:ios

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