小毛的博客

追求卓越,精益求精


  • 首页

  • 分类

  • 归档

  • 标签

面试总结(一)

发表于 2018-06-04 | 分类于 Interview

前言

很早就想把自己遇到的面试的相关技术问题进行记录和总结一下,但一直因为这一年的时间上班都比较忙,就没有整理。最近为了招人,公司出了套面试题,然后分部也让我出一套,我最后就去网上找了一些,最后结合总部的,弄了一套。感觉也没什么特别的难度,现在正好偶尔有些时间就把它整理一下。

题

1. 请说明并比较以下关键词:strong, weak, assign, copy?

(ARC跟MRC,strong代替retain. weak代替assign)
strong : 强引用, 引用计数会加1,只要某一对象被一个strong指向,该对象就不会被销毁。
weak: 弱引用,当指身的对象被释放后自动置为nil
assign的作用:简单赋值,不改变引用计数,对基础数据类型 (例如NSInteger,CGFloat)和C数据类型(int, float, double, char, 等) 适用简单数据类型。若指身对象,则对象被释放后不能自动为空
copy的作用:建立一个索引计数为1 的新对象,然后释放旧对象。

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

  1. readwrite 是可读可写特性;需要生成getter方法和setter方法时;
  2. readonly 是只读特性 只会生成getter方法 不会生成setter方法 ;不希望属性在类外改变;
  3. assign 是赋值特性,setter方法将传入参数赋值给实例变量;仅设置变量时;
  4. retain 表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1;
  5. copy 表示赋值特性,setter方法将传入对象复制一份;需要完全一份新的变量时;
  6. nonatomic 非原子操作,决定编译器生成的setter getter是否是原子操作,atomic表示多线程安全,一般使用nonatomic;atomic:设置成员变量的@property属性时,默认为atomic,提供多线程安全。在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成下面这样:

    if (property != newValue) {

    [property release]; 
    property = [newValue retain]; 
    }

    

    nonatomic:禁止多线程,变量保护,提高性能。

3. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?与Extension(延展)的主要区别?

Object-c的类不可以多重继承;可以实现多个接口,通过实现多个接口可以完成C++的多重继承;Category是类别(分类, 类目),一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。通常情况下Category只可以添加方法, 而Extension可以添加方法和属性(都是私有的);但是Category可以通过runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。通过这种方法来模拟生成属性。(需要重写属性的set和get方法)

4. 面的代码有什么问题?如果有问题怎样修改?

1
2
3
4
5
6
7
8
9
10
@property (nonatomic, strong) NSString *target;

dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 10000000000; i++) {

    dispatch_async(queue, ^{
        self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
       });
 }

直接运行会崩溃 EXC_BAD_ACCESS(坏内存地址,对象被释放), 属性target的setter方法有关:

1
2
3
4
5

-(void)setTarget:(NSString *)target {  
[target retain];//先保留新值
[_target release];//再释放旧值
_target = target;//再进行赋值
}

因为self.target 重新赋值是个多线程任务(并行队列+异步),所以存在着当其中一个线释放了同一个对象时,另一个线程就去给訪对象赋值。也就是多个线程操作同一对象造成的内存释放空的崩溃。这属于线程安全问题,可以用原子性atomic属性关键字进行加锁。如果只是解决崩溃问题的话,用atomic和strong加锁,或者用weak和 nonatomic都可以解决这个问题。但是另外还存在3个问题:

  1. 你创建这么大的循环数值干嘛,专门消耗运行,阻塞运算卡线程吗
  2. 用weak和atomic你重新赋值代码运算,有大部分运算等同于没有执行,
  3. 这个结果也不可控,结果得到一个随机值

5. 中有什么bug?如果有请指出怎样修改?

1
2
3
4
5
6
7
8
9
- (void)viewDidLoad {
  UILabel *alertLabel=[[UILabel alloc] initWithFrame:CGRectMake(100,100,100,100)];
  alertLabel.text = @"Wait 3 seconds...";
  [self.view addSubview:alertLabel];
  NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
  [backgroundQueue addOperationWithBlock:^{
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:4];
    alertLabel.text = @"begin!";
  }];

页面上的label的修改,需要在主线程中实现,现在在子线程中,运行会崩,将二次赋值的代码修改到主线程即可

6. Objective-C如何进行内存管理的,说说你的看法和解决方法?OC有垃圾回收机智吗?iOS有垃圾回收机智吗?

Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。

  1. (Garbage Collection)(垃圾回收)自动内存计数:这种方式和java类似,在你的程序的执行过程中。始终有一个高人在背后准确地帮你收拾垃圾,你不用考虑它什么时候开始工作,怎样工作。你只需要明白,我申请了一段内存空间,当我不再使用从而这段内存成为垃圾的时候,我就彻底的把它忘记掉,反正那个高人会帮我收拾垃圾。遗憾的是,那个高人需要消耗一定的资源,在携带设备里面,资源是紧俏商品所以iPhone不支持这个功能。

    解决: 通过alloc – initial方式创建的, 创建后引用计数+1, 此后每retain一次引用计数+1, 那么在程序中做相应次数的release就好了.

  2. (Reference Counted)手动内存计数:就是说,从一段内存被申请之后,就存在一个变量用于保存这段内存被使用的次数,我们暂时把它称为计数器,当计数器变为0的时候,那么就是释放这段内存的时候。比如说,当在程序A里面一段内存被成功申请完成之后,那么这个计数器就从0变成1(我们把这个过程叫做alloc),然后程序B也需要使用这个内存,那么计数器就从1变成了2(我们把这个过程叫做retain)。紧接着程序A不再需要这段内存了,那么程序A就把这个计数器减1(我们把这个过程叫做release);程序B也不再需要这段内存的时候,那么也把计数器减1(这个过程还是release)。当系统(也就是Foundation)发现这个计数器变成了0,那么就会调用内存回收程序把这段内存回收(我们把这个过程叫做dealloc)。顺便提一句,如果没有Foundation,那么维护计数器,释放内存等等工作需要你手工来完成。*

    解决:一般是由类的静态方法创建的, 函数名中不会出现alloc或init字样, 如[NSString string]和[NSArray arrayWithObject:], 创建后引用计数+0, 在函数出栈后释放, 即相当于一个栈上的局部变量. 当然也可以通过retain延长对象的生存期.

  3. (AutoRealeasePool)内存池:可以通过创建和释放内存池控制内存申请和回收的时机.

    解决:是由autorelease加入系统内存池, 内存池是可以嵌套的, 每个内存池都需要有一个创建释放对, 就像main函数中写的一样. 使用也很简单, 比如[[[NSString alloc]initialWithFormat:@”Hey you!”] autorelease], 即将一个NSString对象加入到最内层的系统内存池, 当我们释放这个内存池时, 其中的对象都会被释放.

7. 我们说的oc是动态运行时runtime语言是什么意思?

主要是将数据类型的确定由编译时,推迟到了运行时。

这个问题其实浅涉及到两个概念,运行时和多态。

简单来说,运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。多态:不同对象以自己的方式响应相同的消息的能力叫做多态。意思就是假设生物类(life)都拥有一个相同的方法-eat;那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。也就是不同的对象以自己的方式响应了相同的消息(响应了eat这个选择器).因此也可以说,运行时机制是多态的基础.

8. 简述一下block和使用delegate 各有什么区别?
代理(delegate)需要写协议,代理方法和设置代理对象 block需要定义block, 实现block,调用block

delegate:

  1. “一对一”,对同一个协议,一个对象只能设置一个代理delegate,所以单例对象就不能用代理;
  2. 代理更注重过程信息的传输:比如发起一个网络请求,可能想要知道此时请求是否已经开始、是否收到了数据、数据是否已经接受完成、数据接收失败

block:

  1. 写法更简练,不需要写protocol、函数等等
  2. block注重结果的传输:比如对于一个事件,只想知道成功或者失败,并不需要知道进行了多少或者额外的一些信息
  3. block需要注意防止循环引用:(ARC中,Block中如果引用了__strong修饰符的自动变量,则相当于Block对该变量的引用计数+1, 在ARC下,由于__block抓取的变量一样会被Block retain,所以必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak,之前则只能使用__unsafeunretained了,\_unsafe_unretained缺点是指针释放后自己不会置空)*
1
2
3
4
5
6
7
8
9
10
// ARC:
__weak typeof(self) weakSelf = self;
 
  [yourBlock:^(NSArray *repeatedArray, NSArray *incompleteArray) {
  [weakSelf doSomething];
 }];
 
// 非ARC:

__block typeof(self) weakSelf = self;
[yourBlock:^(NSArray *repeatedArray, NSArray *incompleteArray) {
  [weakSelf doSomething];
 }];


9. 简述一下你对进程和线程的认识和了解?

关系:

  1. 数量角度:一个线程只能属于一个进程,而一个进程可以包含多个线程,但至少包含一个线程。
  2. 资源角度:资源分配给进程,同一个进程中的线程共享该进程的所有资源。
  3. 线程是进程内的一个可执行单元,也是进程内的可调度实体。
  4. 真正运行在处理机上的是线程.
  5. 线程在执行过程中需要协作同步。不同进行的线程间要利用消息通信的办法实现同步。

区别:

  1. 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于所在进程的资源。
  2. 调度:线程作为调度和分配的基本单元,进程作为拥有资源的基本单位(系统根据进程分配资源)。
  3. 并发性:不仅进程间可以并发执行,同一个进程中的多个线程之间也可以并发执行。
  4. 系统开销:创建或者销毁进程的开销比较大,因为进程在分配和撤销时需要创建和回收资源。
  5. 健壮性:进程有自己独立的系统空间,线程没有,进程的撤销对其他进程没有影响,而线程的撤销将可能使所在进程死掉。所以,多进程的程序比多线程的程序健壮。但在进程切换时耗费的资源比较大,效率差。

10. ios 平台怎么做数据的持久化?coredata 和sqlite有无必然联系?coredata是一个关系型数据库吗?

iOS 中可以有四种持久化数据的方式:属性列表(plist)、对象归档、 SQLite3 和 Core Data; core data 可以使你以图形界面的方式快速的定义 app 的数据模型,同时在你的代码中容易获取到它。 coredata 提供了基础结构去处理常用的功能,例如保存,恢复,撤销和重做,允许你在 app 中继续创建新的任务。在使用 core data 的时候,你不用安装额外的数据库系统,因为 core data 使用内置的 sqlite 数据库。 core data 将你 app 的模型层放入到一组定义在内存中的数据对象。 coredata 会追踪这些对象的改变,同时可以根据需要做相反的改变,例如用户执行撤销命令。当 core data 在对你 app 数据的改变进行保存的时候, core data 会把这些数据归档,并永久性保存。 mac os x 中sqlite 库,它是一个轻量级功能强大的关系数据引擎,也很容易嵌入到应用程序。可以在多个平台使用, sqlite 是一个轻量级的嵌入式 sql 数据库编程。与 core data 框架不同的是, sqlite 是使用程序式的, sql 的主要的 API 来直接操作数据表。 Core Data 不是一个关系型数据库,也不是关系型数据库管理系统 (RDBMS) 。虽然 Core Dta 支持SQLite 作为一种存储类型,但它不能使用任意的 SQLite 数据库。 Core Data 在使用的过程种自己创建这个数据库。 Core Data 支持对一、对多的关系。

11. Swift中,类(class)和结构体(struct)有什么区别?

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

class有这几个功能struct没有的:

  1. class可以继承,这样子类可以使用父类的特性和方法
  2. 类型转换可以在runtime的时候检查和解释一个实例的类型
  3. 可以用deinit来释放资源
  4. 一个类可以被多次引用

struct也有这样几个优势:

  1. 结构较小,适用于复制操作,相比于一个class的实例被多次引用更加安全
  2. 无须担心内存memory leak或者多线程冲突问题

12. 比较Swift和Objective-C中的初始化方法(init)有什么异同?

总的来说,Swift中的初始化方法更加严格和准确

Objective-C中,初始化方法无须保证所有成员变量都完成初始化;编译器对属性设置并无警告,但是实际操作中会出现初始化不完全的问题;初始化方法与普通方法并无实际差别,可以多次调用。

swift中,初始化方法必须保证所有optional的成员变量都完成初始化。同时新增convenience和required两个修饰初始化方法的关键词。convenience只是提供一种方便的初始化方法,必须通过调用同一个类中designated初始化方法来完成。required是强制子类重写父类中所修饰的初始化方法。

13. 请说明并比较以下关键词:Open, Public, Internal, File-private, Private?

Swift 有五个级别的访问控制权限,从高到底依次为比如 Open, Public, Internal, File-private, Private

他们遵循的基本原则是:高级别的变量不允许被定义为低级别变量的成员变量。比如一个 private 的 class 中不能含有 public 的 String。反之,低级别的变量却可以定义在高级别的变量中。比如 public 的 class 中可以含有 private 的 Int。

  1. Open 具备最高的访问权限。其修饰的类和方法可以在任意 Module 中被访问和重写;它是 Swift 3 中新添加的访问权限。
  2. Public 的权限仅次于 Open。与 Open 唯一的区别在于它修饰的对象可以在任意 Module 中被访问,但不能重写。
  3. Internal 是默认的权限。它表示只能在当前定义的 Module 中访问和重写,它可以被一个 Module 中的多个文件访问,但不可以被其他的 Module 中被访问。
  4. File-private 也是 Swift 3 新添加的权限。其被修饰的对象只能在当前文件中被使用。例如它可以被一个文件中的 class,extension,struct 共同使用。
  5. Private 是最低的访问权限。它的对象只能在定义的作用域内使用。离开了这个作用域,即使是同一个文件中的其他作用域,也无法访问。

14. Swift 到底是面向对象还是函数式的编程语言?

Swift 既是面向对象的,又是函数式的编程语言。

说 Swift 是 Object-oriented,是因为 Swift 支持类的封装、继承、和多态,从这点上来看与 Java 这类纯面向对象的语言几乎毫无差别。

说 Swift 是函数式编程语言,是因为 Swift 支持 map, reduce, filter, flatmap 这类去除中间状态、数学函数式的方法,更加强调运算结果而不是中间过程。

15. 泛型是什么?用来解决什么问题?

泛型可以使某一个类型的算法能够更安全的工作。在Swift中泛型可以用在函数和数据类型上,如类,结构体和枚举类型。

泛型还能解决代码重复的问题。普遍现象是当你已经有一个带参数的方法,但你又不得不再重新写一遍有着类似类型的方法。

16. 在Swift和Objective-C的混编项目中,如何在Swift文件中调用Objective-C文件中已经定义的方法?如何在Objective-C文件中调用Swift文件中定义的方法?反之又如何?

Swift调用OC:

创建桥接头文件 xxxx Bridging-Header.h,在头文件中导入OC类的头文件.h.在swift中就可以使用了

OC调用Swift:

  1. 配置命名空间 在TARGETS -> Build Setting中:
    Asset->Enable Asset Packs In Product Bundle中设置为NO

    Packaging -> Defines module 设置为YES

    Packaging -> Product Module Name 设置swift的命名空间名字(namespace)

    1. 在需要使用swift的OC类中导入头文件使用#import “ namespace -Swift.h”,就可以使用swift的类了

Swift-可选项

发表于 2018-05-24 | 分类于 Swift

Swift中新增加了可选项,用问号’?’来表示,那我们如何运用和理解了?

定义

可选项的主要目的就是为了解决‘有’和‘无’的问题。表示变量可以为nil,或者一个值。而且它只明定义,没有分配空间和初始化。定义属性只需要在后面加上?如:var label : UILabel?

解包

Swift是特别在意安全性问题的,对可选项可以进行强行解包和可选解包。

强行解包

用问号’!’来表示,强行解包后的值主要是为了参与计算,但是强行解包存在着风险,当为nil时,程序运行会崩溃,所以需要判断好是否为nil。

可以用 guard let / if let 来处理解包问题,简化代码。

可选解包

用问号’?’来表示,不能参与计算。但可以用来调用方法,它的意思是如果对象存在不为空时就调用对象的方法,如果对象为nil,就什么也不用做,程序也不会崩溃。

类型转化

as?

  • 用as?进行转化的话,得到的结果是可选的
  • 用guard let / if let时 一定要用 as? 来修饰

as!

前面的结果一定要有正确值,不然会崩溃,在使用时就需要程序员考虑值的安全性。

as

OC和Swift的类型转化,底层做了结构体和OC的对象桥接

NSSString -> String
NSArray -> []
NSDictionary -> [string : Any]

将父类转化成子类,或者将子类转化成父类时候需要类型转化,因为要调用子类或者父类的方法。

Swift调用OC的类或者方法时。

两位数的乘法快捷算法

发表于 2018-05-22 | 分类于 Math

前言

昨天看到抖音上有人关于头相尾的快捷计算方法,觉得确实有趣。不过,我从高中时也是无意间从一本书上看到的简便计算方法,从那时开始我就一直在用,到现我都不知道这种计算有没有名字。确实要比按照常规计算要容易一些,而且会快一些。

但总的来说,万变不离其宗,不管是怎么样的计算方法,它都是遵从计算的基本原理,然后从中找出的规律来总结出来的。


通用的两位数计算法

如图如示

1

计算过程 (数a x 数b)

1. 个位相乘: a个位数 x b个位数 = 结果1,结果1以个位为基数
2. 十位相乘: a十位数 x b十位数 = 结果2,结果2以百位为基数
3. 交叉相乘: a十位数 x b个位数 + b十位数 X a个位数 = 结果3,结果3以十位为基数
4. 总和结果: 结果1 + 结果2 + 结果3 = 总结果

综述

* 其实只要细细一想,它跟基本的计算方法是一脉相承的
* 可以减少进位数在常规计算中造成的错误
* 降低了一些常规计算中的思维处理细节,仅仅只有第3步会在数大的时候
* 通用于2位数的相乘计算

总之,它确实从一定的方面简化了计算难度,减少了常规计算中容易造成的错误。

头同尾补

头同就是十位数都一样的,尾补就是个位两数之和刚好是10.所以了,这种计算就是要先判断条件是否满足。
2

  • (相同的十位数 + 1)x 相同的十位数 = 结果1,结果1以百位为基数
  • 两尾数相乘 = 结果2,结果2以个位为基数
  • 总和结果 = 结果1 + 结果2

a乘数互补,b乘数数字相同

即一个乘数的十位数和个位数互补,另一个乘数的十位数跟个位数相同。
3

  • (a的十位数 + 1)x b的十位数 = 结果1,结果1以百位为基数
  • a的个位数 x b的个位数 = 结果2,结果2以个位为基数
  • 总和结果 = 结果1 + 结果2

其他几种

十几乘十几

4

  • 两数十位数相乘 = 结果1,结果1以百位为基数
  • 两数个位数相加 = 结果2,结果2以十位为基数
  • 两数个位数相乘 = 结果3,结果3以个位为基数
  • 总和结果 = 结果1 + 结果2 + 结果3

几十一乘几十一

5

  • 两数十位数相乘 = 结果1,结果1以百位为基数
  • 两数十位数相加 = 结果2,结果2以十位为基数
  • 两数个位数相乘 = 结果3,结果3以个位为基数
  • 总和结果 = 结果1 + 结果2 + 结果3

任意数 x 11

6
任意数逻位相加

可扩展到 乘数为11 ,111,1111……,其中只是逻的数的个数不同。

总结

总之,除此之外还有一些方法,但是相乘的实质都是一样的,这些方法只是在特殊情况下可以运用这些简便地运算而已。一般情况下能运用前两种方法就已经够用了。

Swift中的高阶函数

发表于 2017-07-04 | 分类于 Swift

前言

Swift的标准数组支持三个高阶函数:map,filter和reduce,以及map的扩展flatMap。Objective-C的NSArray没有实现这些方法,但是开源社区弥补了这个不足。

极大地体现了Swift的高大功能和优点。


了解闭包

  • 阐述

    Swift一大特性便是使用简洁的头等函数(闭包)语法代替了复杂的blocks语法。头等函数-即可将函数当作参数传递给其他的函数,或从其他的函数里返回出值,并且可以将他们设定为变量,或者将他们存储在数据结构中。

    闭包的形式: ((参数) -> 返回值)

  • 我们先写一个求立方的函数:

    1
    2
    3
    4
     //MARK: -- 立方
    func cube(_ a: Float) -> Float{
    return a*a*a
    }
  • 求两数进行f函数运算后的平均值的函数

    1
    2
    3
    func averageOfFunction(_ a: Float ,_ b: Float ,_ f: ((Float) -> Float)) -> Float{   
    return (f(a) + f(b))/2
    }
  • 进行调用,简化闭包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //直接传参数和函数名
    let sum1 = averageOfFunction(2, 3, cube)

    //直接把闭包做为它的函数方法参数
    let sum2 = averageOfFunction(2, 3, {(x : Float) -> Float in x*x*x })

    //隐式推断输入值为一个Float
    let sum3 = averageOfFunction(2, 3, {x -> Float in x*x*x })

    //把retuen一起忽略
    let sum4 = averageOfFunction(2, 3, {x in x*x*x })

    //使用默认参数名,最简化地写法
    let sum5 = averageOfFunction(2, 3, { $0*$0*$0 })
  • 默认参数名补充

    我们还可以忽略指定参数名,使用默认参数名$0(如果函数接收多个参数,使用$K作为第K-1个参数,如$0,$1,$2……)

    在高阶函数中也会根据闭包的的简化来进行简化代码。


map函数

  • map需求

    map用于将每个数组元素通过某个方法进行转换。
    我们用一个Int类型数组,想把每个数后面添加一个字符“元”,把数组转成字符串数组

    1
    2
    let moneyArray = [12,34,8,17,91,2,82]
    var stringArray : [String] = []//结果数组
  • 一般的思维

    1
    2
    3
    4
    for money in moneyArray {
    stringArray.append("\(money)元")
    }
    print(stringArray)
  • map的函数介绍

    public func map(_ transform: (Element) throws -> T) rethrows -> [T]

    以一个命名函数transform作为参数,transform负责把元素Element转成类型T并返回一个类型T的数组。

    在我们的事例中,T为Int,T为String,作为转换函数传给map的是一个把Int转成String

  • map的方法

    1
    2
    3
    stringArray = moneyArray.map({ (a: Int) -> String in
    return "\(a)元"
    })
1
2
3
4
5
6
7
//MARK: -- 可以直接把代码换成一个简化的简约闭包
stringArray = moneyArray.map({money in "\(money)元"})
print(stringArray)

//MARK: -- 最简约的写法,采用默认参数
stringArray = moneyArray.map({"\($0)元"})
print(stringArray)

filter

  • filter需求

    filter用于选择数组元素中满足某种条件的元素。使用上面的例子,筛选出大于20的元素,结果应该是[34,91,82]

    1
    var  filterArray : [Int] =[]//结果数组
  • 一般的思维

    1
    2
    3
    4
    5
    6
    7
     for money in moneyArray {
    if money > 20{
    filterArray.append(money)
    }
    }

    print(filterArray)
  • filter的函数介绍

    public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]

    以一个命名函数isIncluded作为参数,isIncluded返回true或者false,对原数组元素Element调用isIncluded时,只有返回true的元素会通过筛选,从而放入返回数组 [Element]中

  • filter的方法

    1
    2
    3
    4
     filterArray = moneyArray.filter({ (a: Int) -> Bool in
    return a > 20
    })
    print(filterArray)
1
2
3
4
5
6
7
 //MARK: — 一个简化的简约闭包
filterArray = moneyArray.filter({money in money > 30})
print(filterArray)

//MARK: -- 最简约的写法,采用默认参数
filterArray = moneyArray.filter({$0 > 30})
print(filterArray)

reduce

  • reduce需求

    reduce方法把数组元素组合计算为一个值。我们使用上面例子计算数组元素的和,结果应该为246(12+34+8+17+91+2+82) 以及: 828102912(12 X 34 X 8 X 17 X 91 X 2 X 82)

    1
    var  filterArray : [Int] =[]//结果数组
  • 一般的思维

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //MARK: — 求和
    var sum = 0
    for money in moneyArray {
    sum += money
    }
    print(sum)

    //MARK: — 求乘积
    var product = 1
    for money in moneyArray {
    product = product * money
    }
    print(product)
  • reduce的函数介绍

    Reduce便可用于快速完成这类操作,通过指定一个初始值和一个组合元素的方法.

    public func reduce( initialResult: Result, nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

    接收两个参数,一个为initialResult的命名参数,类型跟Result一样的的初始值参数,另一个是命名函数nextPartialResult,把Result类型和数组元素Element进行运算,得到一个Result,逐个运行算成后得到最终的Result,将其返回。

  • reduce的方法

    1
    2
    3
    4
    5
    6
    7
    8
    //MARK: --一个简化的简约闭包 
    let sum = moneyArray.reduce(0) { (a : Int, b : Int) -> Int in
    return a + b
    }
    let product = moneyArray.reduce(1) { (a : Int, b: Int) -> Int in
    return a * b
    }
    print("数组总和==\(result),数组乘积==\(product)”)
1
2
3
4
//MARK: -- 最简约的写法,采用默认参数
let sum = moneyArray.reduce(0) { $0 + $1}
let product = moneyArray.reduce(1) { $0 * $1}
print("数组总和==\(result),数组乘积==\(product)”)
  • 特殊情况下可再简化

    上面的都不是最简化的,其实最简化地写法是这样的:

    1
    2
    3
    4
    //MARK:— Swift中操作符可用着函数,可简化成:
    let sum = moneyArray.reduce(0, +)
    let product = moneyArray.reduce(1, *)
    print("数组总和==\(sum),数组乘积==\(product)")

reduce可能是三个高阶函数中最难理解的一个。 需要注意的是nextPartialResult函数的两参数类型不同,Result为计算结果类型,Element为数组元素类型。


flatMap

  • flatMap介绍

    比map更深一样,在自定义函数时,可能把元素生成更多的元素,然后会把这些元素都加到新的元素数组中去。

  • 代码示例

    1
    2
    3
    4
    5
    6
    7
      let oldArray = [20,10,30]

    let newArray = oldArray.flatMap { (a : Int) -> [String] in
    return ["\(a)元","\(a)分"]
    }
    print(newArray)
    // 结果: ["20元", "20分", "10元ƒ", "10分", "30元", "30分”]
    1
    2
    3
    4
     //MARK: -- 简约写法
    let newArray = oldArray.flatMap({["\($0)元","\($0)分"]})
    print(newArray)
    // 结果: ["20元", "20分", "10元", "10分", "30元", "30分”]
  • 对比map

    1
    2
    3
       let newArr = oldArray.map({["\($0)元","\($0)分"]})     
    print (newArr)
    // 结果: [["20元", "20分"], ["10元", "10分"], ["30元", "30分"]]

    对比一下结果就看出运算的不同之处了。map是由一个元素生成object,返回后替代原来的元素,数组的这个元素就成了是object,数组的这个元素就是object。而flatMap可以由一个元素生成一个数组newArr,结果数组在这个元素的位置插入newArr.count个数的newArr,结果数组的个数就增加了newArr.count元素了。

    所以flatmap是map的扩展功能,因为它生成的不是单个元素,而可以是数组。


总结

  1. 数据比较大的时候,高阶函数会比传统实现更快,因为它可以并行执行(如运行在多核上),除非真的需要更高定制版本的map,reduce和filter,否则可以一直使用它们以获得更快的执行速度。

  2. 当使用map,filter,reduce的代码质量会更好。

    小毛的博客

3D Touch

发表于 2017-06-28 | 分类于 iOS

前言

3D Touch是一种立体触控技术,被苹果称为新一代多点触控技术,是在Apple Watch上采用的Force Touch,屏幕可感应不同的感压力度触控。3D Touch,苹果iPhone 6s的新功能,系统版本需要iOS 9.0+ ,有Peek Pop 两种新手种势。

2015年9月10日,苹果在新品发布会上宣布了3D-Touch功能。 2016年6月13日,苹果开发者大会WWDC在旧金山召开,会议宣布可以在待机画面用3D Touch操作通知。

现在,许多安卓手机也用上了3D Touch。

– 摘自百度百科


3D Touch四大功能模块

  • Home Screen Quick Actions (主屏幕快捷入口)
  • peek And pop (预览和弹出 展示框)
  • Force Properties (按压力度属性)
  • Web View peek and pop API (HTML链接预览功能)

设备支持判断

  • 所有遵守UITraitEnvironment(特征环境)协议的类,都会有UITraitCollection(特征集合类)的属性,都有forceTouchCapability(力的触觉能力)属性,
    而UIResponder就遵守了UITraitEnvironment协议,所以所有继承于UIResponder的类都会使用3DTouch.包括UIView也可以使用

  • forceTouchCapability是枚举型

  • 代码示例

    示例代码* OC

    1
    2
    3
    4
    5
    6
    // UIForceTouchCapability:  
    typedef NS_ENUM(NSInteger, UIForceTouchCapability) {
    UIForceTouchCapabilityUnknown =0, //3D Touch检测失败
    UIForceTouchCapabilityUnavailable =1, //3D Touch不可用
    UIForceTouchCapabilityAvailable =2 //3D Touch可用
    };
1
2
3
4
5
 // 判断是否支持:
if (self.traitCollection.forceTouchCapability !=UIForceTouchCapabilityAvailable) {
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"提示" message:@"设备不支持3DTouch" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alertView show];
}
  • swift
1
2
3
4
5
6
 // UIForceTouchCapability:
public enum UIForceTouchCapability : Int{
case unknown //3D Touch检测失败
case unavailable //3D Touch不可用
case available //3D Touch可用
}
1
2
3
4
5
6
7
8
9
// 判断是否支持:

if traitCollection.forceTouchCapability != .available{

let alert = UIAlertView(title: "提示", message: "设备不支持3DTouch", delegate: nil, cancelButtonTitle: "确定")
alert.show()
return

}

Home Screen Quick Actions开发

简介

通过主屏幕的应用Icon,我们可以用3D Touch呼出一个菜单,进行快速定位应用功能模块相关功能的开发,。如微信、京东的App,本文也将重点介绍这项开发技术。

效果如下:

3D Touch快捷入口标签有两种添加方式:

  1. 一种是静态添加,在工程中的info.plist文件中添加相关项目。通过这种方式添加的标签,在app第一次运行前,就可以唤出这些标签。

  2. 另一种是动态添加,通过代码给App添加快速入口。这种方式添加的标签,第一次运行App前是看不到这些标签的,必须先运行一次App,以后就可以唤出这些标签了。

静态添加快捷入口标签

  1. 静态添加入口标签不需要写代码,只需要在info.plist文件中添加相关功能设置即可。不过因为没有语句提示,所以我们只能一个字母一个字母的去添加,这一点不是很友好。

  2. 示例

  3. 说明

    • 首先是UIApplicationShortcutItems,他是一个数组类型,数组中的每一个元素表示一个入口标签。
    • 每个Item是UIApplicationShortcutItems数组中的一个元素,字典类型。在这个字典中配置各个标签的相关属性。
    • 如图如示

    • 效果如图

    • 注:UIApplicationShortcutItemIconFile:当填写上图片图标的我名字就可以显示出图标。

动态添加快捷入口标签

需要用到的类
  • 如图
代码示例
  • OC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    // UIApplicationShortcutIconType是系统的图片类型,29种类型图片

    UIApplicationShortcutIcon *shareShortcutIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeShare];

    UIApplicationShortcutItem *shareShortcutItem = [[UIApplicationShortcutItem alloc] initWithType:@"" localizedTitle:@"分享" localizedSubtitle:@"来点我啊" icon:shareShortcutIcon userInfo:nil];

    //可以用iconWithTemplateImageName:来自定义图片 shortcut_QR:图片名
    UIApplicationShortcutIcon *payShortcutIcon = [UIApplicationShortcutIcon iconWithTemplateImageName:@"shortcut_imageName"];

    UIApplicationShortcutItem *payShortcutItem = [[UIApplicationShortcutItem alloc] initWithType:@"" localizedTitle:@"消息" localizedSubtitle:@"不怕不怕" icon:payShortcutIcon userInfo:nil];

    //将实例化的shortcutItem将入到UIApplication的shortcutItems中就完成了。
    [UIApplication sharedApplication].shortcutItems = @[shareShortcutItem, payShortcutItem];
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //分享
    let icon1 = UIApplicationShortcutIcon(type: .share)
    let shareItem = UIApplicationShortcutItem(type: "", localizedTitle: "分享", localizedSubtitle: "来点我", icon: icon1, userInfo: nil)

    //支付 shortcut_imageName:图片名称
    let icon2 = UIApplicationShortcutIcon(templateImageName: "shortcut_imageName")
    let playItem = UIApplicationShortcutItem(type: "", localizedTitle: "支付", localizedSubtitle: "不怕不怕", icon: icon2, userInfo: nil)

    UIApplication.shared.shortcutItems = [shareItem,playItem]
  • UIApplicationShortcutIconType补充说明

    系统一共提供了29种类型样式:

    typedef NS_ENUM(NSInteger, UIApplicationShortcutIconType) {
    UIApplicationShortcutIconTypeCompose,
    UIApplicationShortcutIconTypePlay,
    UIApplicationShortcutIconTypePause,
    UIApplicationShortcutIconTypeAdd,
    UIApplicationShortcutIconTypeLocation,
    UIApplicationShortcutIconTypeSearch,
    UIApplicationShortcutIconTypeShare,

    UIApplicationShortcutIconTypeProhibit, //从这开始,iOS 9.1+才支持
    UIApplicationShortcutIconTypeContact,
    UIApplicationShortcutIconTypeHome,
    UIApplicationShortcutIconTypeMarkLocation,
    UIApplicationShortcutIconTypeFavorite,
    UIApplicationShortcutIconTypeLove,
    UIApplicationShortcutIconTypeCloud,
    UIApplicationShortcutIconTypeInvitation,
    UIApplicationShortcutIconTypeConfirmation,
    UIApplicationShortcutIconTypeMail,
    UIApplicationShortcutIconTypeMessage,
    UIApplicationShortcutIconTypeDate,
    UIApplicationShortcutIconTypeTime,
    UIApplicationShortcutIconTypeCapturePhoto,
    UIApplicationShortcutIconTypeCaptureVideo,
    UIApplicationShortcutIconTypeTask,
    UIApplicationShortcutIconTypeTaskCompleted,
    UIApplicationShortcutIconTypeAlarm,
    UIApplicationShortcutIconTypeBookmark,
    UIApplicationShortcutIconTypeShuffle,
    UIApplicationShortcutIconTypeAudio,
    UIApplicationShortcutIconTypeUpdate
    }

快捷启动后的处理(检测App启动方式)

代理接受

通过快捷入口进入或者启动app,会实现appDelegate的代理方法:

1
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandle

我们可以判断shortcutItem.localizedTitle属性的值来判断是从哪一个标签进入的App。

示例代码
  • OC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
       
    - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {

    //如果是从"分享"这个标签进来的,我们把页面背景颜色修改为黄色,如果是从@"消息"这个标签进来的,我们讲页面背景颜色修改为红色。都不是的话,就直接是白色。
    if ([shortcutItem.localizedTitle isEqualToString:@"分享"]) {
    self.window.rootViewController.view.backgroundColor = [UIColor yellowColor];
    } else if ([shortcutItem.localizedTitle isEqualToString:@"消息"]) {
    self.window.rootViewController.view.backgroundColor = [UIColor redColor];
    }
    }
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {

    if shortcutItem.localizedTitle == "来啊"{
    window?.rootViewController?.view.backgroundColor= #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)
    }else if shortcutItem.localizedTitle == "后门" {
    window?.rootViewController?.view.backgroundColor= #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1)
    }

    }

补充说明

  • UIApplicationLaunchOptionsShortcutItemKey

    在- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(nullable NSDictionary )launchOptions的方法中 。launchOptions 中有个值为UIApplicationLaunchOptionsShortcutItemKey,这个也是获取选择的shortcutItem的方法。

  • 注意

    1. 快捷入口 无论是启动app,还是将后台运行的app转为前台都会执行代理方法。
    2. 反之,如果快捷入口 是将后台运行的app转为前台都会执行代理方法。是不会走didFinishLaunchingWithOptions:的方法。

    3. 总结:

      我们处理Home Screen Quick Actions功能时最好统一在代理回调的方法里处理。如果在遇到特殊功能,需要判明该功能是启动了整个app时,我们就可以在didFinishLaunchingWithOptions:的时候来判断加以处理!


peek And pop

简介

  • 效果演示:

    1. 进 “邮件” 收件箱列表:

    2. 按住环信发过来的邮件,力度慢慢增加,会pop出详情效果框:

    3. 再使劲达到它的力度,就会等同于你选择了该项,进入详情页面了。

功能的基本思路:

  1. 控制器注册预览协议(UIViewControllerPreviewingDelegate)
  2. 遵守协议并实现其必须实现的方法

示例代码

  • OC

    1. 注册

      1
      2
       // self:为视图控制器, soureView: 为实现3DTouch的视图,如果是单独的一个view的话,则效果只对该view有作用, 那到时的gesture和location都是相对于这个soureView的。
      [self registerForPreviewingWithDelegate:self sourceView:self.view];
2. 遵守代理

1
@interface ViewController ()<UIViewControllerPreviewingDelegate>
  1. 实现方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
       //MARK: — UIViewControllerPreviewingDelegate
    //MARK: -- 返回要显示的控制器
    - (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {

    //初始化要显示的控制器
    UIViewController *childVc = [[UIViewController alloc]init];

    //1.获取手势,我在view上加了一个tableView,这里我是想要获取我们重按的点是点在哪个cell上。
    UIGestureRecognizer *gesture = previewingContext.previewingGestureRecognizerForFailureRelationship;
    //2.获取手势所在的tableView中的点位
    CGPoint point = [gesture locationInView:self.tableView];
    //3. 根据这个点获取是第几行, ROWHight是单个cell的高度
    NSInteger index = floor(point.y/ROWHight);
    //4.得到第行,而且得到cell
    self.indexPath = [NSIndexPath indexPathForRow:index inSection:0];
    self.cell =[self.tableView cellForRowAtIndexPath:self.indexPath];

    // 加个白色背景,这个背影是高清显示的。
    UIView *bgView =[[UIView alloc] initWithFrame:CGRectMake(20, 10, kScreenWidth - 40,kScreenHeight - 20 - 64 * 2)];
    bgView.backgroundColor = [UIColor whiteColor];
    bgView.layer.cornerRadius = 10;
    bgView.clipsToBounds = YES;
    [childVc.view addSubview:bgView];

    //展示的标题
    UILabel *lable1 = [[UILabel alloc] initWithFrame:CGRectMake(0,bgView.bounds.size.height/2-40 , bgView.bounds.size.width, 40)];
    lable1.textAlignment = NSTextAlignmentCenter;
    lable1.textColor=[UIColor redColor];
    lable1.text = self.cell.textLabel.text;
    [bgView addSubview:lable1];

    // 展示的详情
    UILabel *lable2 = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(lable1.frame), bgView.bounds.size.width, 40)];
    lable2.textAlignment = NSTextAlignmentCenter;
    lable2.textColor = [UIColor lightGrayColor];
    NSString *text = self.cell.detailTextLabel.text;
    lable2.text= text;
    [bgView addSubview:lable2];

    //提示lable
    UILabel *detail = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(lable2.frame), bgView.bounds.size.width, 40)];
    detail.textAlignment = NSTextAlignmentCenter;
    detail.textColor=[UIColor yellowColor];
    detail.text = @"再按重一点就选择该项了";
    [bgView addSubview: detail];

    //最后返回的展示的控制器
    return childVc;
    }



    //MARK: -- 再次重按的时候表示进行选择了,然后消失后会下走的方法
    - (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController*)viewControllerToCommit{

    //让它走选择了某项的代理方法。
    [self tableView:self.tableView didSelectRowAtIndexPath:self.indexPath];
    }

补充说明

  • location是相对于注册source来获取的。
  • UIViewControllerPreviewing 主要可用的属性,是相对于注册source的属性:

    1
    2
    3
    @property (nonatomic, readonly) UIGestureRecognizer *previewingGestureRecognizerForFailureRelationship //手势
    @property (nonatomic, readonly) UIView *sourceView //source当初注册传入的视图
    @property (nonatomic) CGRect sourceRect //source的frame
  • 注册的时候直接用SourceView为Cell。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
            //MARK:-- 返回cell
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellID forIndexPath:indexPath];
    if(cell) {
    cell.textLabel.text = self.dataSouce[indexPath.row][@"title"];
    cell.detailTextLabel.text = self.dataSouce[indexPath.row][@"detail"];
    [self registerForPreviewingWithDelegate:self sourceView:cell];
    }
    return cell;
    }

    // 2.在代理中获取点击cell就简单多了
    - (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {

    UIViewController *childVc = [[UIViewController alloc]init];

    // 方法一:
    self.cell = (UITableViewCell *) previewingContext.sourceView;//这个可以获取注册的sourceView
    CGRect frame = previewingContext.sourceRect;//这个可以获取注册的sourceView.frame及大小

    // 方法二:
    // UIGestureRecognizer *gesture = previewingContext.previewingGestureRecognizerForFailureRelationship;// //2.获取手势
    // UIGestureRecognizer *gesture = previewingContext.previewingGestureRecognizerForFailureRelationship;// //2.获取手势所在的tableView中的点位
    // self.cell = (UITableViewCell *)gesture.view;//这个可以获取点击所得到的cell;

    return childVc;
    }
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
      class NexViewController: UIViewController{
    let CellId = "CellId"
    var currentCell : UITableViewCell?

    @IBOutlet weak var tableView: UITableView!

    var dataSource : [[String : Any]] = [];

    override func viewDidLoad() {
    super.viewDidLoad()
    getData()
    }
    }
    //遵守协议
    extension NexViewController : UITableViewDataSource,UITableViewDelegate,UIViewControllerPreviewingDelegate
    {
    //TODO: -- 加载数据
    func getData() {
    for i in 0..<20{
    let dic = ["title" : "测试标题 + \(i+1)", "detail" : "详情资料是这样的 + \(i+1)"];
    dataSource.append(dic)
    }
    tableView.reloadData()
    }

    //MARK: -- UITableViewDataSource
    //TODO:-- 每个section有多少条
    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
    return dataSource.count
    }

    //TODO:-- 返回Cell
    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
    let cell = tableView.dequeueReusableCell(withIdentifier: CellId, for: indexPath)
    cell.textLabel?.text = (dataSource[indexPath.row] as! Dictionary)["title"]
    cell.detailTextLabel?.text = (dataSource[indexPath.row] as! Dictionary)["detail”]

    // 注册3DTouch
    registerForPreviewing(with: self, sourceView: cell)
    return cell
    }


    //TODO: -- UITableViewDelegate public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
    tableView.deselectRow(at: indexPath, animated: true)
    performSegue(withIdentifier: "detail", sender: dataSource[indexPath.row])
    }

    //重写segue跳转方法
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == “detail"{
    if let viewcontroller = segue.destination as? DetailViewController{
    viewcontroller.info = sender as! [String : Any]?
    }
    }
    }

    //MARK: -- UIViewControllerPreviewingDelegate
    public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController?{

    let childVC = UIViewController()
    currentCell = previewingContext.sourceView as? UITableViewCell

    //TODO: -- 加白色背景
    let bgView = UIView(frame: .init(x: 20, y: 10, width: (view.window?.bounds.size.width)!-40 , height: (view.window?.bounds.size.height)! - 20-64*2))
    bgView.backgroundColor= #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
    bgView.layer.cornerRadius = 10.0
    bgView.clipsToBounds = true
    childVC.view.addSubview(bgView)

    // 标题
    let lable1 = UILabel(frame: .init(x: 0, y: bgView.bounds.size.height/2 - 40, width: bgView.bounds.size.width, height: 40))
    lable1.textAlignment = .center
    lable1.textColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
    lable1.text = currentCell?.textLabel?.text
    bgView.addSubview(lable1)

    //详情
    let lable2 = UILabel(frame: .init(x: 0, y: lable1.frame.maxY, width: bgView.bounds.size.width, height: 40))
    lable2.textAlignment = .center
    lable2.textColor = #colorLiteral(red: 0.501960814, green: 0.501960814, blue: 0.501960814, alpha: 1)
    lable2.text = currentCell?.detailTextLabel?.text
    bgView.addSubview(lable2)

    //提示
    let detail = UILabel(frame: .init(x: 0, y: lable2.frame.maxY, width: bgView.bounds.size.width, height: 40))
    detail.textAlignment = .center
    detail.textColor= #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1)
    detail.text = "再按重一点就选择该项了"
    bgView.addSubview(detail)

    return childVC
    }

    //再按重些会选择该项,消失展示框后需要走的代码
    public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController){

    let indexPath = tableView.indexPath(for: currentCell!)
    if indexPath != nil{
    tableView(tableView, didSelectRowAt: indexPath!)
    }
    }

    }

Force Properties 力量属性

简介

iOS9为我们提供了一个新的交互参数:力度。我们可以检测某一交互的力度值,来做相应的交互处理。例如,我们可以通过力度来控制快进的快慢,音量增加的快慢等。

UITouch新增了两个属性:

  • open var force: CGFloat{ get } //force : 手指按下的力度
  • open var maximumPossibleForce: CGFloat { get } //maximumPossibleForce : 最大可能的力度

    在touch中就可以获取这两个值。所以我们就可以触摸按压的时候实现用按压力度来实现酷炫的效果

    示例代码

  • OC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 根据按压的力度来改变当前视图的颜色
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event{

    if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
    UITouch *touch = [[touches allObjects] firstObject];
    NSLog(@"\n force:%f,maximumPossibleForce:%ff",touch.force,touch.maximumPossibleForce);
    CGFloat dito = touch.force/touch.maximumPossibleForce;
    self.view.backgroundColor = [UIColor colorWithRed:dito green:dito blue:1/dito alpha:1];

    }
    }
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     //TODO:--重写touchesMoved的方法,根据按压的力度来改变当前视图的颜色
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

    if traitCollection.forceTouchCapability == .available{
    let touch = touches.first
    print("\n force: \(touch!.force),maximumPossibleForce: \(touch?.maximumPossibleForce)")
    let dito = (touch?.force)! / (touch?.maximumPossibleForce)!
    view.backgroundColor = UIColor(red: 0.3+5*dito, green: 0.3+5*dito, blue: dito, alpha:1)
    }
    }

Web View peek and pop API HTML链接预览功能

简介

原生的safari浏览器中的超链接可以支持3D touch,出现超链接的预览,使用方法和前文中提到的方法类似。ios 9 中增加了一个新的浏览器控制器SFSafariViewController。它可以在你的程序中直接嵌入 Safari浏览器,而且支持3DTouch效果。

示例代码

  • OC

    1
    2
    3
    4
    5
    6
    7
    8
    // 1.首先导入SafariServices.h
    #import <SafariServices/SafariServices.h>

    // 2.设置连接:
    SFSafariViewController *safariView = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:@"https://www.baidu.com"] entersReaderIfAvailable:YES];

    // 3.推出控制器:
    [self.navigationController pushViewController:safariView animated:YES];
  • Swift

    1
    2
    3
    4
    5
    6
    7
     // 1. 导入框架
    import SafariServices

    // 2. 使用
    let url = URL(string: "https://www.baidu.com")
    let safariView = SFSafariViewController(url: url! , entersReaderIfAvailable: true)
    navigationController?.pushViewController(safariView, animated: true)

结尾

3D Touch确实挺炫酷的,特别是Home Screen Quick Actions(主屏幕快捷入口)和
peek And pop(预览和弹出功能),增加了用户体验。

这里我只是简单地介绍了它的使用,如有疑问请Q我: 342112780

iOS手电筒功能开发

发表于 2017-06-28 | 分类于 iOS

前言

AVCaptureDevice(捕捉设备类)可以完成手机手电筒功能的开发,它属于AVFoundation框架。开发也比较简单,我这里简单地介绍一下。


OC中的代码示例

导入框架

#import <AVFoundation/AVFoundation.h>

定义属性

@property(nonatomic,strong)AVCaptureDevice *device;
@property(nonatomic,assign)BOOL available;

初始化捕捉设备对象

//初始化捕捉设备对象
self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

手电筒功能是否可用

 //获取是否可用手电筒功能
 self.available = self.device.hasTorch;
//提示
if (!self.available) {
    UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"提示" message:@"设备不可用" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
    [alertView show];
}

打开手电筒

 if (self.available) {
    [self.device lockForConfiguration:nil];
    // AVCaptureTorchModeOn:开 AVCaptureTorchModeOff:关 AVCaptureTorchModeAuto:自动
    self.device.torchMode = AVCaptureTorchModeOn;
    [self.device unlockForConfiguration];
}

关闭手电筒

 if (self.available) {
    [self.device lockForConfiguration:nil];
    // AVCaptureTorchModeOn:开 AVCaptureTorchModeOff:关 AVCaptureTorchModeAuto:自动
    self.device.torchMode = AVCaptureTorchModeOff;
    [self.device unlockForConfiguration];
}

Switf中的代码示例(跟OC同理)

导入框架

import AVFoundation

定议成属性并懒加载捕捉设备对象

var isflashlight = false
lazy var device:AVCaptureDevice? = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)

手电筒功能是否可用

 //MARK: -- 是否支持手电筒功能
func supportFlashlight(){
    isflashlight = device?.hasTorch ?? false
    if !isflashlight {
    let alert =  UIAlertView(title: "提示", message: "设备不支持", delegate: nil, cancelButtonTitle: "确定")
    alert.show()
    }  
}

打开或者关闭手电筒

//MARK: -- true:为打开,false:为关闭

func flashlight(_ sure : Bool ) {
    if sure {
        //打开
        do {
            try device!.lockForConfiguration()
            device!.torchMode = AVCaptureTorchMode.on
            device!.unlockForConfiguration()
         } catch  {

          }

    }else{
        //关闭
         do {
            try device!.lockForConfiguration()
            device!.torchMode = AVCaptureTorchMode.off
            device!.unlockForConfiguration()
         } catch  {

          }

     }
}

总结

是不是特别简单啊?如果有什么疑问可以Q我:342112780

遇到的问题集 1

发表于 2017-06-20 | 分类于 Debug

制作证书时报错

如图

这个原因是在使用钥匙串时, 证书助理 -> 颁发机构申请 -> 证书填写信息的时候少写了”常用名“导致的,把常用名称加上就可以了。


真机运行时报错

Xcode will continue when “iPhone名称” is finished.
解决方法:重新拔一下真机,再重启一下Xcode,读取完就好了。


使用相机时报错

contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.

在info.plist文件中给 NSCameraUsageDescription(Privacy - Camera Usage Description) 添加key,即提示用户应用将使用您的相机。如图:


UIScrollView上加子视图的坑

注意: 在控制器里要设置self.automaticallyAdjustsScrollViewInsets=NO;
不然添加的子视图位置会变。


我们在开发中经常还是要好好看看打印台信息

在网络请求无反应,没有注意打印台的同志,可能还到处打断点调试。

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

不必详说,这个是从Xcode 7开始开发对网络请求的限制。需要配置一下:


控制台打印报错

程序崩溃并在打印台显示信息:[aqme] 255: AQDefaultDevice (173): skipping input stream 0 0 0x0

解决方法:

  1. 选择 Product –>Scheme–>Edit Scheme
  2. 选择 Arguments
  3. 在Environment Variables添加一个环境变量
    OS_ACTIVITY_MODE 设置值为”disable”

设置值为”disable”


linker command failed with exit code 1 (use -v to see invocation)报错

  1. 第一是三方库文件不支持,

    但是在信息中,你会发现提示为三方名称,看报错详情可以查出来。这时三方库,可以是缺少依赖的库,也可能是三言库不支持当前的框架,模拟器的i386(32位),X86_64(64位),真机:armv7,armv7s (32位) ,arm64(64位)。

  2. 工程文件中,有名字为相同的。

    这个情况一定要注意。因为报错都不知道报的是什么。
    例:


Xcode 错误- Could not launch app - No such file or directory Error.

蛋疼的一个XCode bug,基本上应该不是工程本身问题。

解决方法:

  1. 拔掉设备,删除之前Build的内容
  2. 退出Xcode,不是关闭窗口
  3. 删除那个/Users/XXX/Library/Developer/Xcode/DerivedData/XXX-grgrmtzqajhyqgghabyjttajwbsm文件夹

  4. 启动XCode连接设备,现在应该OK了


Cannot assign to ‘self’ outside of a method in the init family

当使用xib的时候,想自定义一个方来初始化对象View.初始化的名字的方法:以init +大写字母开头+其他。不然就会把这个错。


系统自带的按钮风格

发表于 2017-06-20 | 分类于 iOS

UIButton的风格

UIButton的风格不多,只有几种样式

  1. UIButtonTypeCustom = 0, //自定义风格
  2. UIButtonTypeRoundedRect, //圆角矩形
  3. UIButtonTypeDetailDisclosure, //蓝色小箭头按钮,主要做详细说明用
  4. UIButtonTypeInfoLight, //亮色感叹号
  5. UIButtonTypeInfoDark, //暗色感叹号
  6. UIButtonTypeContactAdd, //十字加号按钮

UIBarButtonItem风格就比较多

UIBarButtonSystemItem:

  • UIBarButtonSystemItemDone

  • UIBarButtonSystemItemCancel

  • UIBarButtonSystemItemEdit

  • UIBarButtonSystemItemSave

  • UIBarButtonSystemItemAdd

  • UIBarButtonSystemItemFlexibleSpace

    无样式

  • UIBarButtonSystemItemFixedSpace

    无样式

  • UIBarButtonSystemItemCompose
  • UIBarButtonSystemItemReply
  • UIBarButtonSystemItemAction
  • UIBarButtonSystemItemOrganize
  • UIBarButtonSystemItemBookmarks
  • UIBarButtonSystemItemSearch
  • UIBarButtonSystemItemRefresh
  • UIBarButtonSystemItemStop
  • UIBarButtonSystemItemCamera
  • UIBarButtonSystemItemTrash
  • UIBarButtonSystemItemPlay
  • UIBarButtonSystemItemPause
  • UIBarButtonSystemItemRewind
  • UIBarButtonSystemItemFastForward
  • UIBarButtonSystemItemUndo
  • UIBarButtonSystemItemRedo
  • UIBarButtonSystemItemPageCurl

大括号{}和小括号()的作用

发表于 2017-06-20 | 分类于 others
  • 代码
1
2
3
4
5
6
7
8
9
10
11
UILabel *l = ({

UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 300, 25)];

label.text = @"Hello world";

[self.view addSubview: label];

label;

});

执行代码都放在大括号和小括号的中间的。而且运行我们会发现最后l也是被赋值成功的。

  • 总结
    这样可以深思一下{}和()各有什么作用

    1. {}包装代码

      大家在写程序的时候不难发现,在OC函数方法都用它来包括在里面,包括block也是被大括号包在里面的。所以,它的主要作用就是用来包装代码块。

    2. ()执行代码

      在执行代码后,你会发现大括号中的代码都执行了,这就是因为小括号的原因。小括号的作用跟js,swift,以及java中其实感觉都是一样的,代码执行某个函数,只不过这里传入的不像参数,其他也可以把代码块当作参数。

  • 示例
1
2
3
4
5
6
7
UITableView *tableView = ({

UITableView *tabV = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, 300, 200)];

//最末尾的tabV会给tableView 赋值
tabV;
});

查看代码行数

发表于 2017-06-20 | 分类于 others

查看代码的行数

  • 终端进入项目

    打开终端,在终端

    $ cd + 项目文件

  • 输入命令

$ find . -name “*.swift” | xargs wc -l

其中 “*.swift” 检查以.swift的文件,这样会显示出swift的文件的代码行数

同理在OC中可以 “*.h”或者”.m”文件

  • OC中可以一次性检查 多种文件如下抒写

    $ find . “(“ -name “.m” -or -name “.mm” -or -name “.cpp” -or -name “.h” -or -name “*.rss” “)” -print | xargs wc -l

  • 代码行数就会显示在终端



12
小毛纯一郎

小毛纯一郎

致力于苹果移动应用的开发

20 日志
6 分类
17 标签
GitHub
友情链接
  • 毛建祥
  • CepheusSun
© 2018 小毛纯一郎
由 Hexo 强力驱动
主题 - NexT.Mist
本站访客数人次 本站总访问量次