计算机教程

当前位置:3522.com > 计算机教程 > 3522.comiOS面试的常见问题清单

3522.comiOS面试的常见问题清单

来源:http://www.4sports-uk.com 作者:3522.com 时间:2019-12-01 01:49

多媒体编程——ios视频图像绘制工具类。

IOS上视频级的图像绘制

ios上的图像绘制常规的是 UIView的drawRect函数,但是这个函数是异步触发,并且由主线程执行。虽然可以通过一定技巧达到主动绘制的效果:

1、传递图像给UIView缓存着。

2、然后调用UIView的setNeedDisplay 改写重绘标志。

(以上两步是讲图像丢给UIView,让它自己进行绘制,但是绘制的时机不可控,有时候我们需要它马上绘制,甚至有时候我们需要知道它什么时候绘制完成了,就需要下面两步)

3、在播放线程中调用UIView的 perfromOnMainThread 最后一个参数 waitUtilDone = true, 执行某个自定义函数比如叫 mydrawImage

4、mydrawImage中 调用 【NSRunloop mainLoop】run 。。。。 (执行一次消息泵抽送)

(这样调用perfromOnMainThread的地方就会阻塞,知道真正的绘制完成。)

但是这种方式的模拟同步方式绘制 知识等待主线程绘制完成,并且如果扩展到多帧缓存 就比较麻烦了,并且UIView必须自己继承然后重写。

下面附上代码 基于类似思想,但是是基于CALayer 完成的渲染工具类,同步和异步切换只需要改一下 waitUtilDone的参数即可。

实际测试 帧率可以达到25左右 (ipad mini1),如果机器好点 速度应该更快。

头文件

#import 
#import 
#import 

/*
  渲染视频,只支持RGB RGB RGB 32bit格式。
 */


@interface TKVideoPlayer : NSObject

- (bool) create:(UIView*)target width:(uint16_t)width height:(uint16_t)height frate:(float)frate ;
- (bool) destory ;
- (bool) update:(uint8_t*)buf len:(uint32_t) len ;
- (bool) start ;
- (bool) stop ;
@end

实现文件

//
//  TKVideoPlayer.m
//  FLVPlayer
//
//  Created by administrator on 14-7-11.
//  Copyright (c) 2014年 trustsky. All rights reserved.
//

#import "TKVideoPlayer.h"
#import "TKTimer.h"
#import "TKTimer2.h"
#import "TKLock.h"
#import "TKTicker.h"


#include 

#define TKVIDEO_FRAME_CACHE_COUNT 8

@interface TKVideoPlayer ()
{
    UIView*         _view    ;
    float           _frate   ;

    uint16_t        _width   ;
    uint16_t        _height  ;

    uint8_t*        _buffer  ;
    uint32_t        _length  ;

    TKTimer2*       _timer   ;

    bool            _state   ;

    TKLock*         _lockEmptyQueue ;
    TKLock*         _lockFilledQueue ;

    std::queue _fmEmptyQueue ;
    std::queue _fmFiledQueue ;

    uint8_t*             _fmbuffr[TKVIDEO_FRAME_CACHE_COUNT];

    dispatch_semaphore_t _sgEmpty ;
    dispatch_semaphore_t _sgfilled ;
}

@end


@implementation TKVideoPlayer


- (bool) create:(UIView*)target width:(uint16_t)width height:(uint16_t)height frate:(float)frate;
{
    self->_view    = target ;
    self->_width   = width  ;
    self->_height  = height ;
    self->_frate   = frate  ;
    self->_length  = width * height * 4 ;

    self->_view.layer.delegate = self ;

    self->_sgfilled = dispatch_semaphore_create(TKVIDEO_FRAME_CACHE_COUNT);
    self->_sgEmpty  = dispatch_semaphore_create(TKVIDEO_FRAME_CACHE_COUNT);

    for(int idx=0; idx_lockFilledQueue = [[TKLock alloc] init];
    [self->_lockFilledQueue open];

    self->_lockEmptyQueue = [[TKLock alloc] init];
    [self->_lockEmptyQueue open];

    return true ;
}




- (bool) destory
{
    self->_view.layer.delegate = nil ;
    self->_view = nil ;

    self->_buffer = NULL ;

    for(int idx=0; idx_lockFilledQueue close];
    [self->_lockFilledQueue release];
    self->_lockFilledQueue = nil ;

    [self->_lockEmptyQueue close];
    [self->_lockEmptyQueue release];
    self->_lockEmptyQueue = nil ;


    int lastCount = TKVIDEO_FRAME_CACHE_COUNT - _fmEmptyQueue.size() - _fmFiledQueue.size() ;
    for(int idx=0; idx<_fmemptyqueue.size dispatch_semaphore_signal="dispatch_semaphore_signal" idx="idx" lastcount="lastCount" self="self">_sgfilled);
    for(int idx=0; idx<_fmfiledqueue.size dispatch_semaphore_signal="dispatch_semaphore_signal" idx="idx" lastcount="lastCount" self="self">_sgEmpty);

    dispatch_release(self->_sgfilled);
    self->_sgfilled = nil ;

    dispatch_release(self->_sgEmpty);
    self->_sgEmpty = nil ;

    return true ;
}


- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
    //计算图像居中应该的尺寸
    CGRect frame = [layer bounds];

    float scalew = frame.size.width/_width ;
    float scaleh = frame.size.height/_height;

    float scale = scalew < scaleh ? scalew : scaleh ;

    float image_width  = _width * scale ;
    float image_height = _height * scale ;

    CGRect rect = CGRectMake((frame.size.width - image_width)/2, (frame.size.height - image_height)/2, image_width, image_height);

    if(_state && _buffer)
    {
        CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, _buffer, _length, NULL);
        CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB() ;
        CGImageRef imageRef = CGImageCreate(_width, _height, 8, 32, 4 * _width, colorSpaceRef, kCGBitmapByteOrder32Little|kCGImageAlphaFirst, provider, NULL, NO, kCGRenderingIntentDefault);

        CGContextTranslateCTM(context, 0.0, frame.size.height);
        CGContextScaleCTM(context, 1.0, -1.0);
        CGContextDrawImage(context, rect, imageRef);

        CGImageRelease(imageRef);
        CGColorSpaceRelease(colorSpaceRef);
        CGDataProviderRelease(provider);

        //NSLog(@"drawLayer Time Tick = %u.", get_tick32());
    }
    else
    {
        CGContextSetRGBFillColor(context, 0, 0, 0, 1);
        CGContextFillRect(context, frame);
    }
}

- (bool) update:(uint8_t*)buf len:(uint32_t) len
{
    if(_state)
    {
        dispatch_semaphore_wait(_sgEmpty, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC* 100));

        [_lockEmptyQueue lock];
        if(_fmEmptyQueue.size() == 0)
        {
            [_lockEmptyQueue unlock];
            return true;
        }
        uint8_t* cachebuf = _fmEmptyQueue.front();
        _fmEmptyQueue.pop();
        [_lockEmptyQueue unlock];

        memcpy(cachebuf, buf, len);

        [_lockFilledQueue lock];
        _fmFiledQueue.push(cachebuf);
        [_lockFilledQueue unlock];

        dispatch_semaphore_signal(self->_sgfilled);
    }
    return true ;
}

- (void) timer_call
{
    if(_state)
    {
        dispatch_semaphore_wait(self->_sgfilled, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC*100)); //等待100毫秒

        [_lockFilledQueue lock];
        if(_fmFiledQueue.size() == 0)
        {
            [_lockFilledQueue unlock];
            return ;
        }
        uint8_t* cachebuf = _fmFiledQueue.front();
        _fmFiledQueue.pop();
        [_lockFilledQueue unlock];

        [self performSelectorOnMainThread:@selector(timerDrawFrame:)
                               withObject:[NSNumber numberWithUnsignedLongLong:(uint64_t)cachebuf]
                            waitUntilDone:false];
    }
}

- (void) timerDrawFrame:(NSNumber*)bufNumber
{
    self->_buffer = (uint8_t*)bufNumber.unsignedLongLongValue ;

    if(_state && _buffer)
    {
        [self->_view.layer setNeedsDisplay];
        [self->_view.layer display];

        [_lockEmptyQueue lock];
        _fmEmptyQueue.push(self->_buffer);
        [_lockEmptyQueue unlock];

        dispatch_semaphore_signal(self->_sgEmpty);
    }
    else
    {
        [self->_view.layer setNeedsDisplay];
        [self->_view.layer display];
    }
}

- (bool) clear
{
    [self performSelectorOnMainThread:@selector(timerDrawFrame:)
                           withObject:[NSNumber numberWithUnsignedLongLong:NULL]
                        waitUntilDone:true];

    return true ;
}

- (bool) start
{
    if(_timer == nil)
    {
        _timer = [[TKTimer2 alloc] init];
        _timer.delay   = 1000/_frate ;
        _timer.objcall = self ;
        _timer.selcall = @selector(timer_call);
        [_timer start];
        _state = true ;
    }
    return true ;
}


- (bool) stop
{
    if(_timer)
    {
        _state = false ;
        [_timer stop];
        [self clear];
    }
    return true ;
}

@end

//里面用到了一个 TKTimer计时器

计时器的头文件是这样的

@interface TKTimer2 : NSObject

@property (assign, nonatomic) id        objcall ;
@property (assign, nonatomic) SEL       selcall ;
@property (assign, nonatomic) uint32_t  delay ;

- (void) start ;
- (void) stop ;

@end

设置回调的id SEL 然后设置延迟 毫秒单位,调用start之后该id sel会被重复执行。本人还在调研那种计时效果准确,所以就不发上来误导大家了,大家自己实现吧

还用到了一个TKLock

#import 

@interface TKLock : NSObject


- (void)open ;
- (void)close ;

- (void)lock ;
- (void)unlock ;

- (bool)trylock:(uint32_t)tick ;
@end

实现如下:

#import "TKLock.h"

@interface TKLock ()
{
    dispatch_semaphore_t    _sglock ; //是否缓存为空
}
@end

@implementation TKLock

- (void)open
{
    _sglock  = dispatch_semaphore_create(1);
}
- (void)close
{
    [self trylock:1];
    dispatch_semaphore_signal(_sglock);
    dispatch_release(_sglock);
    _sglock = nil ;
}

- (void)lock
{
    dispatch_semaphore_wait(_sglock, DISPATCH_TIME_FOREVER);
}

- (void)unlock
{
    dispatch_semaphore_signal(_sglock);
}

- (bool)trylock:(uint32_t)tick
{
    long retcode = dispatch_semaphore_wait(_sglock, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC*tick));
    return (retcode == 0) ;
}


@end

http://www.bkjia.com/IOSjc/864544.htmlwww.bkjia.comtruehttp://www.bkjia.com/IOSjc/864544.htmlTechArticle多媒体编程——ios视频图像绘制工具类。 IOS上视频级的图像绘制 ios上的图像绘制常规的是 UIView的drawRect函数,但是这个函数是异步触发,...

Q1: UIView常用的一些方法小记之setNeedsDisplay和setNeedsLayout


首先两个方法都是异步执行的。
setNeedsDisplay会自动调用drawRect方法,这样可以拿到 UIGraphicsGetCurrentContext,可以实现绘图;
setNeedsLayout会默认调用layoutSubViews,就可以处理子视图中的一些数据。

layoutSubviews在以下情况下会被调用:
1、init初始化不会触发layoutSubviews。
2、addSubview会触发layoutSubviews。
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
4、滚动一个UIScrollView会触发layoutSubviews。
5、旋转Screen会触发父UIView上的layoutSubviews事件。
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
7、直接调用setLayoutSubviews。

drawRect在以下情况下会被调用:
1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect调用是在Controller->loadView, Controller->viewDidLoad 两方法之后调用的,所以不用担心在控制器中,这些View的drawRect就开始画了,这样可以在控制器中设置一些值给View。
2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size,然后系统自动调用drawRect:方法。
3、通过设置contentMode属性值为UIViewContentModeRedraw,将在每次设置或更改frame的时候自动调用drawRect:。
4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。
以上1,2推荐;而3,4不提倡

drawRect方法使用注意点:
1、若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate的ref并且不能用于画图。
2、drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或者 setNeedsDisplayInRect,让系统自动调该方法。
3、若使用CALayer绘图,只能在drawInContext: 中(类似于drawRect)绘制,或者在delegate中的相应方法绘制,同样也是调用setNeedDisplay等间接调用以上方法。
4、若要实时画图,不能使用GestureRecognizer,只能使用touchbegan等方法来调用setNeedsDisplay实时刷新屏幕。

Q2:RunLoop在什么情况下会用到?


  • 定义一个NSTimer来隔一会调用某个方法 ,但这时你在拖动TextVIew不放手 ,主线程就被占用了,timer的监听方法就不调用 直到你松手,这时把NSTimer加到RunLoop里 ,就相当于告诉主循环腾出点时间来给timer ,再拖动TextView就不会因主线程被占用而不调用了。
  • 例如AFNetWorking和SDWebImage的源码中都是子类化NSOperation,在子线程中发起NSURLConnetion,connetion的代理就是这个operation,然后CFRunloopRun(),开启Runloop,Fail或者Finish的时候CFRunloopStop(CFRunloopGetCurrent())关闭Runloop。如果不写CFRunloopRun(),根本不会执行NSURLConnection的代理方法的,因为该线程没开启Runloop,马上就完了。RunLoop相当于子线程的循环,可以灵活控制子线程的生命周期。

Q3:Runtime在什么情况下会用到?


Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的。
我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething));

  • 获取列表
    我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)
unsigned int count;
    //获取属性列表
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (unsigned int i=0; i<count; i  ) {
        const char *propertyName = property_getName(propertyList[i]);
        NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
    }

    //获取方法列表
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i; i<count; i  ) {
        Method method = methodList[i];
        NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
    }

    //获取成员变量列表
    Ivar *ivarList = class_copyIvarList([self class], &count);
    for (unsigned int i; i<count; i  ) {
        Ivar myIvar = ivarList[i];
        const char *ivarName = ivar_getName(myIvar);
        NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
    }

    //获取协议列表
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i; i<count; i  ) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
    }
  • 方法调用

    • 重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了;
    • 如果想调用已经重写过的方法的父类的实现,只需使用super这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。
  • 动态添加方法

  • 关联对象
    现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。 这种情况的一般解决办法就是继承。 但是,只增加一个属性,就去继承一个类,总是觉得太麻烦。

//首先定义一个全局变量,用它的地址作为关联对象的key
static char associatedObjectKey;
//设置关联对象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
NSLog(@"AssociatedObject = %@", string);
  • objc_setAssociatedObject的四个参数:
    id object给谁设置关联对象。
    const void *key关联对象唯一的key,获取时会用到。
    id value关联对象。
    objc_AssociationPolicy关联策略。

  • objc_getAssociatedObject的两个参数。
    id object获取谁的关联对象。
    const void *key根据这个唯一的key获取关联对象。

  • 方法交换

#import "UIViewController swizzling.h"
#import <objc/runtime.h>

@implementation UIViewController (swizzling)
//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换
  (void)load{
    //方法交换应该被保证,在程序中只会执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        //获得viewController的生命周期方法的selector
        SEL systemSel = @selector(viewWillAppear:);
        //自己实现的将要被交换的方法的selector
        SEL swizzSel = @selector(swiz_viewWillAppear:);
        //两个方法的Method
        Method systemMethod = class_getInstanceMethod([self class], systemSel);
        Method swizzMethod = class_getInstanceMethod([self class], swizzSel);

        //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
        BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        if (isAdd) {
            //如果成功,说明类中不存在这个方法的实现
            //将被交换方法的实现替换到这个并不存在的实现
            class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
        }else{
            //否则,交换两个方法的实现
            method_exchangeImplementations(systemMethod, swizzMethod);
        }

    });
}

- (void)swiz_viewWillAppear:(BOOL)animated{
    //这时候调用自己,看起来像是死循环
    //但是其实自己的实现已经被替换了
    [self swiz_viewWillAppear:animated];
    NSLog(@"swizzle");
}

@end

在一个自己定义的viewController中重写viewWillAppear:

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear");
}

Q4: 线程创建的几种方式


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];

    //创建线程的第一种方式
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"universe"];
    [thread start];
    [thread release];


    //创建线程的第二种方式,NSThread类方法
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"yuzhou"];


    //创建线程的第三种方法  NSObject方法
    [self performSelectorInBackground:@selector(run:) withObject:@"nsobject thread"];

    //创建线程的第四种方式
    NSOperationQueue *oprationQueue = [[NSOperationQueue alloc] init];
    [oprationQueue addOperationWithBlock:^{
        //这个block语句块在子线程中执行
        NSLog(@"oprationQueue");
    }];
    [oprationQueue release];

    //第五种创建线程的方式
    NSOperationQueue *oprationQueue1 = [[NSOperationQueue alloc] init];
    oprationQueue1.maxConcurrentOperationCount = 1;//指定池子的并发数

    //NSOperation 相当于java中的runnable接口,继承自它的就相当一个任务
    NSInvocationOperation *invation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"invation"];
    [oprationQueue1 addOperation:invation];//将任务添加到池子里面,可以给池子添加多个任务,并且指定它的并发数
    [invation release];

    //第二个任务
    NSInvocationOperation *invation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2:) object:@"invocation2"];
    invation2.queuePriority = NSOperationQueuePriorityHigh;//设置线程优先级
    [oprationQueue1 addOperation:invation2];
    [invation2 release];

    [oprationQueue1 release];

    //调用主线程,用来子线程和主线程交互,最后面的那个boolean参数,如果为yes就是等这个方法执行完了在执行后面的代码;如果为no的话,就是不管这个方法执行完了没有,接着往下走
    [self performSelectorOnMainThread:@selector(onMain) withObject:self waitUntilDone:YES];

    //---------------------GCD----------------------支持多核,高效率的多线程技术
    //创建多线程第六种方式
    dispatch_queue_t queue = dispatch_queue_create("name", NULL);
    //创建一个子线程
    dispatch_async(queue, ^{
        // 子线程code... ..
        NSLog(@"GCD多线程");

        //回到主线程
        dispatch_sync(dispatch_get_main_queue(), ^{//其实这个也是在子线程中执行的,只是把它放到了主线程的队列中
            Boolean isMain = [NSThread isMainThread];
            if (isMain) {
                NSLog(@"GCD主线程");
            }
        });
    });


    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)onMain
{
    Boolean b = [NSThread isMainThread];
    if (b) {
        NSLog(@"onMain;;%d",b);
    }
}


- (void) run:(NSString*)str
{
    NSLog(@"多线程运行:::%@",str);
}

- (void) run2:(NSString*)str
{
    NSLog(@"多线程运行:::%@",str);
}

Q5: ____block 与 __ __weak的区别理解


  • __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
  • __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
  • ____block对象可以在block中被重新赋值,__ __weak不可以。

PS:____unsafe_unretained修饰符可以被视为iOS SDK 4.3以前版本的__weak的替代品,不过不会被自动置空为nil,所以尽可能不要使用这个修饰符。

Q6: KVO,NSNotification,delegate及block区别


解释:

  • KVO就是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以监测一个值的变化,比如View的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。

  • NSNotification是通知,也是一对多的使用场景。在某些情况下,KVO和NSNotification是一样的,都是状态变化之后告知对方。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。

  • delegate 是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate(代理人)就可以了,由其他类完成所需要的操作。所以delegate是一对一关系。

  • block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观。

本文由3522.com发布于计算机教程,转载请注明出处:3522.comiOS面试的常见问题清单

关键词: 3522.com