UIDynamic和iOS7的物理效果

接着上一篇文章,这里整理一下UIMotionEffect的姊妹篇,说说iOS7中的UIDynamics和物理效果。做过游戏开发、用过游戏开发引擎的朋友们可能对Box2D、chipmunk并不陌生,他们可以使你开发出来的游戏具有各种物理效果。在iOS7中,也有类似的工具,不过它不是物理引擎,他是UIKit提供的非常方便的API,UIDynamic。

0. UIDynamic概述

在iOS7的锁屏页,在右下角可以看到拍照的按钮,拖动这个按钮垂直向上,则照相的应用会直接打开。假如我们向上推到一半,松开,页面会沿着屏幕垂直向下做“自由落体”运动。或者,更进一步,我们用力向下回滑已经抬起的锁屏页面,页面会加速滑下并和页面底部边缘撞击反弹。如下图所示:

锁屏页的重力和碰撞效果

滑动锁屏页时的Dynamic效果

在这个过程中,实际上至少涉及了两类Dynamic效果,“重力”和“碰撞”。

我们来看一下UIDynamic的几个主要概念:

  • Behavior 。直接翻译过来就是“行为”,实际上也就是UIDynamic的物理效果。这个对象定义了一类效果,包括效果的具体属性。
  • Dynamic Item 。动态效果项目,也就是实际中应用了效果的对象,通常是UIView对象,比如上图例子中的锁屏页View。
  • Animator 。“动画绘制者”,实际上就是动画效果发挥作用的上下文,类似于物理引擎中物理效果的作用空间。这个通常也和一个View对应。

下面单独介绍下UIDynamicBehavior和UIDynamicAnimator,也看看这几个主要对象是怎么结合起来的。

1. UIDynamicBehavior和UIDynamicAnimator

按照上面的介绍,UIDynamicBehavior和UIDynamicAnimator就是UIDynamic中最重要的两个角色,一个代表着具体的物理效果,另一个则是这个效果展示的上下文。是的,没错,UIDynamicAnimator中也刚好有一个我们期待的,最重要的方法,那就是:

- (void)addBehavior:(UIDynamicBehavior *)behavior;

这样,效果和展示的上下文就关联了起来。就是这么简单!

那么问题来了,这两个对象和传统的UIKit中的其它类的对象,比如UIView是什么关系呢?我们回头再来看UIDynamicBehavior和UIDynamicAnimator这两个类的文档或头文件,发现其声明非常简单,都是直接继承自NSObject,方法也不多。

在UIDynamicAnimator的头文件中,给了这样一个初始化方法:

- (instancetype)initWithReferenceView:(UIView*)view;

这个方法的参数是一个UIView,我们叫做ReferenceView。无论是那种物理效果,其运动都是相对于一个上下文的,这个上下文就是我们这里的“参照视图”,ReferenceView。比如锁屏效果的例子,我们可以认为这个ReferenceView就是占据整个屏幕的rootView。

除了这个ReferenceView以外,就是具体发生物理效果的对象,在UIDynamic中我们叫做Item。这个Item的定义有一个protocol,即UIDynamicItem,不过通常情况下它就是一个具体的View,所以我们看到UIView在新版本的API中也有了<UIDynamicItem>这样一个声明。

虽然在UIDynamicBehavior中并没有直接提供类似ReferenceView参数的初始化方法,不过UIDynamicBehavior的各个子类几乎都提供了类似的方法:

- (instancetype)initWithItems:(NSArray *)items;

这样UIKit中传统的View对象就和UIDynamicAnimator、UIDynamicBehavior关联了起来,一起实现了UIDynamic物理效果。

2. Gravity和Collision效果

UIDynamicBehavior有很多子类,或者说iOS7中提供了很多具体的Dynamic效果。我们这里就简单以Gravity和Collision效果为例,介绍一下。

Gravity不管翻译为“重力”还是“万有引力”,效果其实都可以认为是一致的,结果是类似落体的加速运动,而最重要的参数是受力的方向和大小。

animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[itemView]];
CGVector gravityDirection = {0.0, -1.0};
[gravity setGravityDirection:gravityDirection];
[animator addBehavior:gravity];

像这样,牛顿的苹果就向上飞而不是向下落了。

类似的,碰撞反弹的效果也比较简单:

UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[ itemView ]];
[collision setTranslatesReferenceBoundsIntoBoundary:YES];
[animator addBehavior:collision]

这是最简单的碰撞效果。如果想做得更漂亮一些,可以考虑用UIDynamicItemBehavior来设置一些通用的物理属性,比如弹性等。

另外,如果在碰撞过程中的各个环节想做定制处理,可以考虑使用UICollisionBehavior的delegate。

3. 其它

除了上面提到的Gravity和Collision外,还有Attach、Push等UIDynamic物理效果。

由于UIDynamic是iOS7的新特性,有很多兼容性考虑比较多的App并没有大范围使用。关于UIDynamic的文档苹果也提供的不是很多。在WWDC 2013的Session中有两篇做了介绍。

在我整理本文时,看到github上这里有一个完整的例子不错,大家感兴趣的还可以去看一下。

发表在 iOS, iOS开发基础, 开发, 计算机技术 | 标签为 , , , , | 9 条评论

UIMotionEffect和Home页背景视差效果

当你打开装有iOS7以上的iPhone主屏,默认的背景是一幅蓝色的星空图片。当上下左右翻转iPhone时,有趣的效果将会出现,星空背景也会沿着各个方向发生位移,这与主屏上的各个App Icon形成了一种独特的视差效果。

这不是iOS系统本身独有的。在iOS7之前,这种效果需要陀螺仪相关API的支持,实现起来也比较复杂。而在iOS7以后,系统提供了这样的API,大大简化了实现方案。

0. UIMotionEffect

在iOS7以上的的API中,有一个UIMotionEffect的类。通过它,我们也可以在自己开发的App当中实现本文开头提到的效果。

我们来看一下UIMotionEffect:

NS_CLASS_AVAILABLE_IOS(7_0) @interface UIMotionEffect : NSObject <NSCopying, NSCoding>
- (NSDictionary *)keyPathsAndRelativeValuesForViewerOffset:(UIOffset)viewerOffset;
@end

从头文件的声明来看,UIMotionEffect非常简单,直接继承自NSObject,支持了NSCopying和NSCoding protocol。同时,就像刚刚提到的,他是iOS7之后才有的类。

除此之外,只提供了一个可供override的方法。这个方法的输入参数只有一个,观察者角度的偏移。而这个方法的输出则是一个灵活的NSDictionary对象,里面包含了最终影响视差效果的各项属性和对应的值。

在同一个头文件中,还有一个UIInterpolatingMotionEffect类的声明。

1. UIInterpolatingMotionEffect

UIInterpolatingMotionEffect是UIMotionEffect的子类,虽然扩展也不复杂,提供的方法也很简单,但在很多场景下可以比较直接和方便的满足我们的需求。

它有4个property:

  • keyPath,左右翻转屏幕将要影响到的属性,比如center.x。
  • type(UIInterpolatingMotionEffectType类型),观察者视角,也就是屏幕倾斜的方式,目前区分水平和垂直两种方式。
  • minimumRelativeValue和maximumRelativeValue,keyPath对应的值的变化范围,注意这个是id类型。min对应最小的offset,max对应最大的offset。

以本文开头的效果为例,我们可以在某个controller对特定的view实现类似效果:

    UIInterpolatingMotionEffect * xEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
    xEffect.minimumRelativeValue =  [NSNumber numberWithFloat:-40.0];
    xEffect.maximumRelativeValue = [NSNumber numberWithFloat:40.0];
    [targetView addMotionEffect:xEffect];

当然,这只是x轴的,垂直方向同理。
怎么样?是不是很简单?

2. 自定义MotionEffect

由于目前iOS对MotionEffect的支持还比较简单,通过UIInterpolatingMotionEffect我们基本上可以实现大部分效果。
当然,如果我们比较任性,也可以通过继承UIMotionEffect,自定义自己的效果。
比如说,当水平方向发生偏移的时候,我们让targetView上下移动:

- (NSDictionary *)keyPathsAndRelativeValuesForViewerOffset:(UIOffset)viewerOffset
{
    CGFloat originalOffset = viewerOffset.horizontal ;
    CGFloat targetOffset = 512 * originalOffset;
    return @{@"center.y": @(targetOffset)};
}

如上,我们在继承UIMotionEffect的子类中实现这样一个方法就完成了。够任性!

当然,UIMotionEffect不仅仅能够支持本文提到的各种位移,这里只是简要的一个介绍。只要是targetView支持的动画属性,都可以做出效果来。

UIMotionEffect是iOS7才有的东西,虽然目前很多App还要考虑支持iOS的6.0、5.0、甚至4.3,但随着目前iOS7和iOS8的用户比例逐步扩大,甚至达到90%,iOS7的效果一定在不久的将来就可以被广泛使用。

发表在 iOS, iOS开发基础, 开发, 计算机技术 | 标签为 , , | 7 条评论

了解iOS上的可执行文件和Mach-O格式

很多朋友都知道,在Windows上exe是可直接执行的文件扩展名,而在Linux(以及很多版本的Unix)系统上ELF是可直接执行的文件格式,那么在苹果的操作系统上又是怎样的呢?在iOS(和Mac OS X)上,主要的可执行文件格式是Mach-O格式。本文就关于iOS上的可执行文件和Mach-O格式做一个简要整理。

Mach-O格式是iOS系统上应用程序运行的基础,了解Mach-O的格式,对于调试、自动化测试、安全都有意义。在了解二进制文件的数据结构以后,一切就都显得没有秘密。

0. Mach与Mach-O

这里先提醒大家一下,Mach不是Mac,Mac是苹果电脑Macintosh的简称,而Mach则是一种操作系统内核。Mach内核被NeXT公司的NeXTSTEP操作系统使用。在Mach上,一种可执行的文件格是就是Mach-O(Mach Object file format)。1996年,乔布斯将NeXTSTEP带回苹果,成为了OS X的内核基础。所以虽然Mac OS X是Unix的“后代”,但所主要支持的可执行文件格式是Mach-O。

iOS是从OS X演变而来,所以同样是支持Mach-O格式的可执行文件。

1. iOS可执行文件初探

作为iOS客户端开发者,我们比较熟悉的一种文件是ipa包(iPhone Application)。但实际上这只是一个变相的zip压缩包,我们可以把一个ipa文件直接通过unzip命令解压。

解压之后,会有一个Payload目录,而Payload里则是一个.app文件,而这个实际上又是一个目录,或者说是一个完整的App Bundle。

在这个目录中,里面体积最大的文件通常就是和ipa包同名的一个二进制文件。找到它,我们用file命令来看一下这个文件的类型:

XXX: Mach-O universal binary with 2 architectures
XXX (for architecture armv7): Mach-O executable arm
XXX (for architecture armv7s): Mach-O executable arm

由此看来,这是一个支持armv7和armv7s两种处理器架构的通用程序包,里面包含的两部分都是Mach-O格式。

对于一个二进制文件来讲,每个类型都可以在文件最初几个字节来标识出来,即“魔数”。比如PNG图片的最初几个字节是\211 P N G \r \n \032 \n (89 50 4E 47 0D 0A 1A 0A)。我们再来看下这个Mach-O universal binary的:

0000000 ca fe ba be 00 00 00 02 00 00 00 0c 00 00 00 09

没错,开始的4个字节是cafe babe,即“Cafe baby”。了解Java或者说class文件格式的同学可能会很熟悉,这也是.class文件开头的“魔数”,但貌似是Mach-O在更早的时候就是用了它。在OS X上,可执行文件的标识有这样几个魔数(也就是文件格式):

  • cafebabe
  • feedface
  • feadfacf
  • 还有一个格式,就是以#!开头的脚本

cafebabe就是跨处理器架构的通用格式,feedface和feedfacf则分别是某一处理器架构下的Mach-O格式,脚本的就很常见了,比如#!/bin/bash开头的shell脚本。

这里注意一点是,feedface和cafebabe的字节顺序不同,我们可以用lipo把上面cafebabe的文件拆出armv7架构的,看一下开头的几个字节:

0000000 ce fa ed fe 0c 00 00 00 09 00 00 00 02 00 00 00

2. Mach-O格式

接下来我们再来看看这个Mach-O格式到底是什么样的格式。我们可以通过二进制查看工具查看这个文件的数据,结果发现,不是所有数据都是相连的,而是被分成了几个段落。

在一位叫做JOE SAVAGE的老兄发布的图片上来看,Mach-O的文件数据显现出来是这个样子的:

图形化的Mach-O文件数据

大家可以对数据的分布感受下。

虽然被五颜六色的标记出来,可能这还不是特别直接。再来引用苹果官方文档的示意图:

Mach-O文件格式基本结构

Mach-O文件格式基本结构

从这张图上来看,Mach-O文件的数据主体可分为三大部分,分别是头部(Header)、加载命令(Load commands)、和最终的数据(Data)。

回过头来,我们再看上面那张图,也许就都明白了。黄色部分是头部、红色是加载命令、而其它部分则是被分割成Segments的数据。

3. Mach-O头部

这里,我们用otool来看下Mach-O的头部信息,得到:

      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedface      12          9  0x00          2    45       4788 0x00218085

更详细的,我们可以通过otool的V参数得到翻译版:

Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
   MH_MAGIC     ARM         V7  0x00     EXECUTE    45       4788   NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE

前面几个字段的意义,上下对比就能看懂,我这里主要说下这样几个字段:

  • filetype,这个可以有很多类型,静态库(.a)、单个目标文件(.o)都可以通过这个类型标识来区分。
  • ncmds和sizeofcmds,这个cmd就是加载命令,ncmds就是加载命令的个数,而sizeofcmds就是所占的大小。
  • flags里包含的标记很多,比如TWOLEVEL是指符号都是两级格式的,符号自身+加上自己所在的单元,PIE标识是位置无关的。

4. 加载命令

上面头部中的数据已经说明了整个Mach-O文件的基本信息,但整个Mach-O中最重要的还要数加载命令。它说明了操作系统应当如何加载文件中的数据,对系统内核加载器和动态链接器起指导作用。一来它描述了文件中数据的具体组织结构,二来它也说明了进程启动后,对应的内存空间结构是如何组织的。

我们可以用otool -l xxx来看一个Mach-O文件的加载命令:

Load command 0
      cmd LC_SEGMENT
  cmdsize 56
  segname __PAGEZERO
   vmaddr 0x00000000
   vmsize 0x00004000
  fileoff 0
 filesize 0
  maxprot ---
 initprot ---
   nsects 0
    flags (none)
Load command 1
      cmd LC_SEGMENT
  cmdsize 736
  segname __TEXT
   vmaddr 0x00004000
   vmsize 0x00390000
  fileoff 0
 filesize 3735552
  maxprot r-x
 initprot r-x
   nsects 10
    flags (none)
Section
  sectname __text
   segname __TEXT
      addr 0x0000b0d0
      size 0x0030e7f4

上面这段是执行结果的一部分,是加载PAGE_ZERO和TEXT两个segment的load command。PAGE_ZERO是一段“空白”数据区,这段数据没有任何读写运行权限,方便捕捉总线错误(SIGBUS)。TEXT则是主体代码段,我们注意到其中的r-x,不包含w写权限,这是为了避免代码逻辑被肆意篡改。

我再提一个加载命令,LC_MAIN。这个加载指令,会声明整个程序的入口地址,保证进程启动后能够正常的开始整个应用程序的运行。

除此之外,Mach-O里还有LC_SYMTAB、LC_LOAD_DYLIB、LC_CODE_SIGNATURE等加载命令,大家可以去官方文档查找其含义。

至于Data部分,在了解了头部和加载命令后,就没什么特别可说的了。Data是最原始的编译数据,里面包含了Objective-C的类信息、常量等。

本文是对Mach-O文件格式的一个理解小结,希望能够抛砖引玉,帮助各位朋友把握可执行文件的主题脉络,进而解决各类问题。

参考:

发表在 iOS, iOS开发基础, 开发, 计算机技术 | 标签为 , , , , | 11 条评论

NSObject的load和initialize方法

在Objective-C中,NSObject是根类,而NSObject.h的头文件中前两个方法就是load和initialize两个类方法,本篇文章就对这两个方法做下说明和整理。

0. 概述

Objective-C作为一门面向对象语言,有类和对象的概念。编译后,类相关的数据结构会保留在目标文件中,在运行时得到解析和使用。在应用程序运行起来的时候,类的信息会有加载和初始化过程。

其实在Java语言中也有类似的过程,JVM的ClassLoader也对类进行了加载、连接、初始化。

就像Application有生命周期回调方法一样,在Objective-C的类被加载和初始化的时候,也可以收到方法回调,可以在适当的情况下做一些定制处理。而这正是load和initialize方法可以帮我们做到的。

+ (void)load;
+ (void)initialize;

可以看到这两个方法都是以“+”开头的类方法,返回为空。通常情况下,我们在开发过程中可能不必关注这两个方法。如果有需要定制,我们可以在自定义的NSObject子类中给出这两个方法的实现,这样在类的加载和初始化过程中,自定义的方法可以得到调用。

从如上声明上来看,也许这两个方法和其它的类方法相比没什么特别。但是,这两个方法具有一定的“特殊性”,这也是这两个方法经常会被放在一起特殊提到的原因。详细请看如下几小节的整理。

1. load和initialize的共同特点

load和initialize有很多共同特点,下面简单列一下:

  • 在不考虑开发者主动使用的情况下,系统最多会调用一次
  • 如果父类和子类都被调用,父类的调用一定在子类之前
  • 都是为了应用运行提前创建合适的运行环境
  • 在使用时都不要过重地依赖于这两个方法,除非真正必要

2. load方法相关要点

废话不多说,直接上要点列表:

  • 调用时机比较早,运行环境有不确定因素。具体说来,在iOS上通常就是App启动时进行加载,但当load调用的时候,并不能保证所有类都加载完成且可用,必要时还要自己负责做auto release处理。
  • 补充上面一点,对于有依赖关系的两个库中,被依赖的类的load会优先调用。但在一个库之内,调用顺序是不确定的。
  • 对于一个类而言,没有load方法实现就不会调用,不会考虑对NSObject的继承。
  • 一个类的load方法不用写明[super load],父类就会收到调用,并且在子类之前。
  • Category的load也会收到调用,但顺序上在主类的load调用之后。
  • 不会直接触发initialize的调用。

3. initialize方法相关要点

同样,直接整理要点:

  • initialize的自然调用是在第一次主动使用当前类的时候(lazy,这一点和Java类的“clinit”的很像)。
  • 在initialize方法收到调用时,运行环境基本健全。
  • initialize的运行过程中是能保证线程安全的。
  • 和load不同,即使子类不实现initialize方法,会把父类的实现继承过来调用一遍。注意的是在此之前,父类的方法已经被执行过一次了,同样不需要super调用。

由于initialize的这些特点,使得其应用比load要略微广泛一些。可用来做一些初始化工作,或者单例模式的一种实现方案。

4. 原理

“源码面前没有秘密”。最后,我们来看看苹果开放出来的部分源码。从中我们也许能明白为什么load和initialize及调用会有如上的一些特点。

其中load是在objc库中一个load_images函数中调用的,先把二进制映像文件中的头信息取出,再解析和读出各个模块中的类定义信息,把实现了load方法的类和Category记录下来,最后统一执行调用。

其中的prepare_load_methods函数实现如下:

void prepare_load_methods(header_info *hi)
{
    size_t count, i;

    rwlock_assert_writing(&runtimeLock);

    classref_t *classlist = 
        _getObjc2NonlazyClassList(hi, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

这大概就是主类中的load方法先于category的原因。再看下面这段:

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

这正是父类load方法优先于子类调用的原因。

再来看下initialize调用相关的源码。objc的库里有一个_class_initialize方法实现,如下:

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    BOOL reallyInitialize = NO;

    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }

    monitor_enter(&classInitLock);
    if (!cls->isInitialized() && !cls->isInitializing()) {
        cls->setInitializing();
        reallyInitialize = YES;
    }
    monitor_exit(&classInitLock);

    if (reallyInitialize) {
        _setThisThreadIsInitializingClass(cls);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: calling +[%s initialize]",
                         cls->nameForLogging());
        }

        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
                         cls->nameForLogging());
        }

        monitor_enter(&classInitLock);
        if (!supercls  ||  supercls->isInitialized()) {
            _finishInitializing(cls, supercls);
        } else {
            _finishInitializingAfter(cls, supercls);
        }
        monitor_exit(&classInitLock);
        return;
    }

    else if (cls->isInitializing()) {
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else {
            monitor_enter(&classInitLock);
            while (!cls->isInitialized()) {
                monitor_wait(&classInitLock);
            }
            monitor_exit(&classInitLock);
            return;
        }
    }

    else if (cls->isInitialized()) {
        return;
    }

    else {
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

在这段代码里,我们能看到initialize的调用顺序和线程安全性。

最近工作上的事情比较多,本该在2014年整理出来的文章还是推到了2015年,在这里祝给位朋友新年快乐!

发表在 iOS, Objective-C, 开发, 计算机技术 | 标签为 , , , , | 24 条评论

iOS开发使用半透明模糊效果

本篇文章主要是对在iOS上做半透明模糊效果的开发实现做整理。

虽然iOS很早就支持使用模糊效果对图片等进行处理,但尤其在iOS7以后,半透明模糊效果得到大范围广泛使用。包括今年最新发布的iOS8也沿袭了这一设计,甚至在OS X 10.10版Yosemite中也开始大量使用半透明模糊。

在iOS开发当中,我们有很多选择可以做半透明模糊效果,下面就是一些常见的方式或者说工具。

0. Core Image

作为设计和体验方面的领导者,苹果自己对图片效果和图片处理的支持一定是非常好的,在iOS平台上,5.0之后就出现了Core Image的API。Core Image的API被放在CoreImage.framework库中。

在iOS和OS X平台上,Core Image都提供了大量的滤镜(Filter),这也是Core Image库中比较核心的东西之一。按照官方文档记载,在OS X上有120多种Filter,而在iOS上也有90多。

下面是一段Core Image做模糊的示例代码:

CIContext *context = [CIContext contextWithOptions:nil];
CIImage *image = [CIImage imageWithContentsOfURL:imageURL];
CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
[filter setValue:image forKey:kCIInputImageKey];
[filter setValue:@2.0f forKey: @"inputRadius"];
CIImage *result = [filter valueForKey:kCIOutputImageKey];
CGImageRef outImage = [context createCGImage: result fromRect:[result extent]];
UIImage * blurImage = [UIImage imageWithCGImage:outImage];

这里可以看到,Core Image为了做得比较灵活,Filter都是按字符串的名字去创建的,比如高斯模糊滤镜就是“CIGaussianBlur”,这里有一个列表可以参看:

https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIGaussianBlur

除了这里提到的多种Filter之外,Core Image还提供了CIDetector等类,可以支持人脸识别等,在OS X上Core Image也做了更多支持。

1. GPUImage

除了苹果官方提供的之外,第三方也有这方面图片处理的工具。一个叫Brad Larson的老兄就搞了一套叫做GPUImage的开源库。同样的,里面提供了很多Filter。

https://github.com/BradLarson/GPUImage

同样是做高斯模糊,用GPUImage可以这样:

GPUImageGaussianBlurFilter * blurFilter = [[GPUImageGaussianBlurFilter alloc] init];
blurFilter.blurRadiusInPixels = 2.0;
UIImage * image = [UIImage imageNamed:@"xxx"];
UIImage *blurredImage = [blurFilter imageByFilteringImage:image];

至少看起来,代码上比使用Core Image的情况简单得多。

2. vImage

其实,说完上面的Core Image和GPUImage,很多情况下就已经足够用了。下面我们再来看一个,那就是vImage。vImage也是苹果推出的库,在Accelerate.framework中。

Accelerate这个framework主要是用来做数字信号处理、图像处理相关的向量、矩阵运算的库。我们可以认为我们的图像都是由向量或者矩阵数据构成的,Accelerate里既然提供了高效的数学运算API,自然就能方便我们对图像做各种各样的处理。

基于vImage我们可以根据图像的处理原理直接做模糊效果,或者使用现有的工具。UIImage+ImageEffects是个很好的图像处理库,看名字也知道是对UIImage做的分类扩展。这个工具被广泛地使用着。

3. 性能与选择

既然已经知道了3个方法做到半透明模糊效果,那么我们要用的时候应该选择哪个呢?这是个问题。

  • 从系统版本的支持上来看,这几个都差不多,都是iOS4、iOS5就支持了的,对于身在iOS8时代的开发者,这点兼容已经够了。
  • Core Image是苹果自己的图像处理库,本来就不错,如果苹果自身在某个版本做了优化处理,自然更好。主要是用起来比较麻烦,还要知道Filter的名字。
  • GPUImage来自第三方,但实现开放,用起来也比较简单,在很多场景下是由于Core Image的选择。
  • 图像模糊处理是很复杂的计算,最终往往要看性能。这点上看,我更倾向选择vImage。

在本人开发的iOS应用中,选择了vImage,出发点是性能,这并不是说有非常精确的benchmark。但在几个调试时的主流机型上测,包括5c、5s等,在模糊半径(blur radius)达到10左右的时候,配合动画,vImage的处理时间会明显比较短,不会“卡顿”。

以上是本人对iOS上实现半透明模糊效果实现的整理。

参考:

发表在 iOS, iOS开发基础, 开发, 计算机技术 | 标签为 , , , , , , | 9 条评论
第 2 页,共 23 页12345...91215...最旧 »