多任务页面底部 banner 出现的种类

多任务页面底部banner出现的种类 1、handoff(接力) 2、xxx,早上好 3、基于您的当前位置 4、“耳机"已连接 5、(未知。。。) 实现 handoff 设置 -> 通用 -> 接力 -> 打开接力 通过“接力”功能,您在某设备开始操作后,可立即使用iCloud账号从另一设备继续相同操作。您需要的应用会显示在应用切换器和Mac的程序坞上。 这种一般应用在需要跨设备共享编辑的应用上,例如“印象笔记”,在手机上编辑了一段文字后,可以切换到Mac上继续编辑。 官方简介:https://support.apple.com/zh-cn/HT204681 这是开发者唯一能控制的底部banner种类 Siri应用建议 xxx,早上好 基于您的当前位置 “耳机”已连接 关于前两种情况: 系统会记录用户长期的使用应用的习惯,自主地向用户推荐相关的应用。例如,早上起床后我会习惯打开微博,系统早上就会给我推荐微博;来到公司我会习惯打开微信,系统也会根据地理位置给我推荐微信。系统会通过记录用户的使用频率、时间、地理位置等维度,向用户推荐应用。 关于“耳机”已连接 手机连接蓝牙耳机或者有线耳机后,系统会弹出 “耳机”已连接 的banner,如果手机装有多个音乐应用,目前观察到的情况是会弹出最常用的那个应用。但是有时候又不会弹,而且弹出维持的时间也没有规律。 国外用户如是说: This is a feature suggesting which app you normally use in a certain situation. Like location, time of day, headphones or Bluetooth connected. Phone tries to learn if you always do something. Like I always open maps when I connect to my car Bluetooth. Or always open music when by headphones are plugged in. Or always open weather at 7am. ...

2018年11月12日 · Darren Ou

iOS 学习笔记

一、内存分配 结构体分配内存,内存对齐(8的倍数) 对象分配内存,内存对齐(16的倍数) 一个NSObject对象占用多少内存?(一个指针变量所占用的大小(64bit,8个字节,32bit,4个字节)) class_getInstanceSize() 至少需要多少内存 malloc_size() 实际分配了多少内存 二、对象 对象类型(实例对象、类对象、元类对象) 对象的isa指针指向哪里?(instance对象的isa指向class对象;class对象的isa指向meta-class对象;meta-class对象的isa指向基类的meta-class对象) OC的类信息存放在哪里?(对象方法、属性、成员变量、协议信息,存放在class对象中;类方法存放在meta-class对象中;成员变量的具体值存放在instance对象中) isa & ISA_MASK 才是真正的类对象或元类对象地址(arm64下是0x0000000ffffffff8ULL) 三、KVO、KVC KVO的本质(利用runtime动态生成一个子类,重写set方法、class、dealloc、isKVOA方法,并且让instance对象的isa指向这个全新的子类;当修改instance对象的属性时,会调用_NSSetXXXValueAndNotify函数;willChangeValueForKey/setter/didChangeValueForKey) 如何手动触发KVO(手动调用willChangeValueForKey/didChangeValueForKey,必须两个方法都调用) 直接修改成员变量会触发KVO吗(不会) KVC set原理(查找set方法:setKey -> _setKey;查看accessInstanceVariablesDirectly;查找成员变量:_key -> _isKey -> key -> isKey) KVC get原理(查找get方法:getKey -> key -> isKey -> _key;查看accessInstanceVariablesDirectly;查找成员变量:_key -> _isKey -> key -> isKey) 四、Category Category 原理(编译成struct category_t,存储这对象方法、类方法、属性、协议信息,程序运行时会将Category的数据合并到类信息中) Category和Class Extension区别(Class Extension在编译的时候数据就包含在类信息中;Category在运行时才会将数据合并到类信息中) +load方法的调用顺序(类:先编译的先调用;先调用父类的 分类:按照编译顺序调用) +initialize方法在类第一次接收到消息时调用,先调用父类的+initialize,再调用子类的+initialize +load和+initialize最大的区别是,+load通过函数地址直接调用,+initialize通过objc_msgSend进行调用,遵循消息发送的机制(如果分类实现了+initialize,则调用分类的;如果子类没有实现,则调用父类的+initialize;父类的可能被调用多次) 五、关联对象 key的设置(@selector(key) / static const char key; &key; / static const void *key = &key / @“key") 关联对象不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationManager中 AssciationsManager -> AssociationHashMap -> object: ObjectAssociationMap -> key: ObjectAssociation -> policy/value 对象销毁时会把对象对应的ObjectAssociationMap销毁 六、Block 本质是结构体对象(__main_block_impl_0) block类型(NSGlobalBlock:没有访问auto变量;NSStackBlock:访问了auto变量;NSMallocBlock:NSStackBlock copy) 捕获auto变量(NSStackBlock:不会强引用auto变量,栈block随时释放,没必要强引用) NSMallocBlock:当block拷贝到堆上时,会调用desc里面的copy函数,把auto变量拷贝到堆上;当从堆上移除时,会调用desc里面的dispose函数,release auto变量 __block 修饰符,会将其修饰的变量包装成一个对象;不能修饰全局变量、静态变量 MRC下,block不会对__block修饰的对象进行强引用(MRC下,可用__block解决循环引用问题) __weak:不会产生强引用,指向的对象销毁时会自动置nil; __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变 ...

2018年3月8日 · Darren Ou

iOS 项目集成 Reveal

Reveal是一款UI调试工具,在iOS开发过程中可查看UI的层级关系并可动态修改界面,可以有效提高开发效率。这篇文章主要介绍如何把Reveal集成到实际的项目中。 一、Reveal主要有两种集成方式: 1、framework集成(可忽略,直接看第二点) (1)打开Reveal,选择 Reveal –> Help –> Show Reveal Library in Finder –> iOS Library (2)把Reveal.framework导入到项目中 (3)配置Target,Target –> Build Setting –> Other Linker Flags 添加以下4项: (4)运行项目 –> 打开Reveal –> 选择连接设备 项目的UI图层就可以出来了。 这种方法有几个弊端:1、需要配置项目的Build Setting,修改Other Linker Flags,多人协作开发的话如果有人没有配置就会编译出错;2、如果不留意容易打包的时候把Reveal的framework也打包进去,增大包的体积。 以下推荐另一种集成方法。 2、LLDB集成(推荐!!) (1)运行项目 –> 点击Pause program execution。 (2)在LLDB依次输入以下两条命令 # expr (Class)NSClassFromString(@“IBARevealLoader”) == nil ? (void*)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib", 0x2) : ((void*)0) # expr (void)[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] postNotificationName:@“IBARevealRequestStart” object:nil]; 第一条命令用于加载Reveal的动态链接库; 第二条命令用于启动Reveal调试服务。 ...

2017年12月21日 · Darren Ou

iOS 利用 Speech Kit 实现语音识别

一、前言 前一段时间彩云小译上了App Store的推荐,我下载试玩了一下,效果还是非常不错的。它可以实现实时翻译的功能,我自己粗浅地分析了一下彩云小译的实现原理,其中最重要的一步就是声音转文字。 目前市面上也有很多服务商提供声音转文字的服务,有收费的有免费的,但是毕竟是第三方的服务商,接口的性能和稳定性都不一定能保证。 2016年Apple在发布重磅产品iOS10的同时也发布了Speech Kit语音识别框架,大名鼎鼎的Siri的语音识别就是基于Speech Kit实现的。有了Speech Kit,我们就可以非常简单地实现声音转文字的功能。下面我就简单介绍一下Speech Kit的用法。 二、实现 1、页面布局 因为只是实现一个Demo,页面不需要多复杂,只需要在Storyboard上拖入两个控件:一个UITextView用于展示声音转文字的结果,一个UIButton用于触发语音识别,最好布置好约束即可。具体效果如下图: 2、申请用户权限 首先需要引入Speech Kit框架 #import <Speech/Speech.h> 申请权限非常简单,在识别前(viewDidLoad:)加入以下代码即可申请语音识别的权限: - (void)viewDidLoad { [super viewDidLoad]; // 请求权限 [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) { NSLog(@"status %@", status == SFSpeechRecognizerAuthorizationStatusAuthorized ? @"授权成功" : @"授权失败"); }]; } 这时候运行起来会崩溃,原因是在iOS10后需要在info.plist文件中添加麦克分和语音识别权限申请信息: <key>NSSpeechRecognitionUsageDescription</key> <string>请允许语音识别</string> <key>NSMicrophoneUsageDescription</key> <string>请打开麦克风</string> 运行项目,会提示打开语音识别和打开麦克风权限,至此我们已经完成了权限的申请。 3、初始化语音识别引擎 添加以下代码: - (void)initEngine { if (!self.speechRecognizer) { // 设置语言 NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"zh-CN"]; self.speechRecognizer = [[SFSpeechRecognizer alloc] initWithLocale:locale]; } if (!self.audioEngine) { self.audioEngine = [[AVAudioEngine alloc] init]; } AVAudioSession *audioSession = [AVAudioSession sharedInstance]; [audioSession setCategory:AVAudioSessionCategoryRecord mode:AVAudioSessionModeMeasurement options:AVAudioSessionCategoryOptionDuckOthers error:nil]; [audioSession setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; if (self.recognitionRequest) { [self.recognitionRequest endAudio]; self.recognitionRequest = nil; } self.recognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init]; self.recognitionRequest.shouldReportPartialResults = YES; } 初始化SFSpeechRecognizer时需要传入一个NSLocle对象,用于标识用户输入的语种,如"zh-CN"代表普通话,“en_US"代表英文。 AVAudioEngine是音频引擎,用于音频输入。 利用AVAudioSession对象进行音频录制的配置。 在语音识别产生最终结果之前可能产生多种结果,设置SFSpeechAudioBufferRecognitionRequest对象的shouldReportPartialResult属性为YES意味着每产生一种结果就马上返回。 4、启动语音识别引擎 添加以下代码: ...

2017年12月10日 · Darren Ou

对 iOS 数据安全的一次小探索

一、主流保证数据安全的方式 1、网络传输安全 1.采用HTTPS通信协议 可防止抓包窃取、篡改传输数据,大大增加了中间人攻击的成本。 2.对敏感数据进行签名校正 采用非对称加密的方式,对敏感数据使用密钥加密,到了客户端用公钥解密,验证数据一致性,防止通信过程中被篡改。 3.采用密文传输 别人即使截取了传输信息,也无法看懂其中的意思。 2、本地存储数据安全 1.数据库加密 通过越狱设备可以很容易地把整个应用包(包括里面的数据)copy出来,这样就可以获取里面的数据库,对于没有加密的数据库就可以非常轻易地读取里面的信息,造成信息的泄漏。 数据库加密可分两个维度:1.整个数据库加密;2.对部分字段先加密再存数据库。 对部分字段加密并不适合多字段的加密存储,容易导致加密数据太过分散,影响性能。所以推荐对整个数据库加密。 2.KeyChain存储敏感数据 KeyChain是iOS系统级的存储方式,安全性无需质疑,且删除应用或者升级系统依然可以保留里面的信息。 3、源码安全 使用越狱设备可以很轻易地把应用砸壳,从而把源码dump下来,即使是没有太多经验的开发者也可以得到应用的类信息,包括函数名等。使用IDA等反编译工具可以看到应用的一些类名和方法名,进而可以分析功能实现的逻辑。 1.字符串混淆 对应用程序中使用到的字符串进行加密,保证源码被逆向后也能保护明文字符串。 2.类名、方法名混淆 市面上很多iOS应用都没有混淆类名方法名,以致于很容易使用class-dump下来,从而进行hook操作,一步一步实现iOS微信自动抢红包(非越狱)是很有趣的一个应用。这个库可以混淆OC的类名、协议、属性还有方法名。 3.程序结构混淆加密 对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低。可参考这个库 4.反调试、反注入等一些主动保护策略 加入第三方安全性SDK 二、对数据库加密的研究 1、主流数据库加密方式 The SQLite Encryption Extension(收费) SQLiteEncrypt(收费) SQLiteCrypt(收费) SQLCipher(开源免费) 只有SQLCipher是免费的,所以本文主要对SQLCipher进行研究。 2、引入SQLCipher第三方库 1.手动引入 请参考官方教程。 2.使用CocoaPods pod 'FMDB/SQLCipher', '2.5' 3、SQLCipher的可行性研究 1.新建加密数据库 若没有旧数据的情况下使用很简单,只需要在FMDatabase里面的-open和-openWithFlags:方法里面添加[self setKey:kDatabaseEncryptKey];即可。 如下图所示 但是在团队协作中,如果直接修改pod仓库里面的文件,可能不好同步,下面有一个技巧,就是继承FMDatabase和FMDatabaseQueue并重载其中的方法。 继承FMDatabase的子类需要重载以下方法 - (BOOL)open { if (_db) { return YES; } int err = sqlite3_open([self sqlitePath], &_db ); if(err != SQLITE_OK) { NSLog(@"error opening!: %d", err); return NO; } else { // 设置密钥 [self setKey:kDatabaseEncryptKey]; } if (_maxBusyRetryTimeInterval > 0.0) { // set the handler [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval]; } return YES; } - (BOOL)openWithFlags:(int)flags { if (_db) { return YES; } int err = sqlite3_open_v2([self sqlitePath], &_db, flags, NULL /* Name of VFS module to use */); if(err != SQLITE_OK) { NSLog(@"error opening!: %d", err); return NO; } else { // 设置密钥 [self setKey:kDatabaseEncryptKey]; } if (_maxBusyRetryTimeInterval > 0.0) { // set the handler [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval]; } return YES; } - (const char*)sqlitePath { if (!_databasePath) { return ":memory:"; } if ([_databasePath length] == 0) { return ""; // this creates a temporary database (it's an sqlite thing). } return [_databasePath fileSystemRepresentation]; } 继承FMDatabaseQueue的子类只需要重载一个方法 ...

2017年3月30日 · Darren Ou

iOS 性能优化汇总

参考:https://i.cmgine.net/archives/14896.html 入门: 1、用ARC管理内存 2、在正确的地方使用reuseIdentifier 3、尽量吧views设置为完全不透明 4、避免过于庞大的xib 5、不要阻塞主线程 6、不要在ImageView中调整图片大小,尽量使用UIImageView大小相同的图片 7、选择正确的Collection Arrays: 有序的一组值。使用index来lookup很快,使用value lookup很慢, 插入/删除很慢。 Dictionaries: 存储键值对。 用键来查找比较快。 Sets: 无序的一组值。用值来查找很快,插入/删除很快。 8、打开gzip压缩 中级: 1、重用和懒加载views 2、Cache,缓存不大可能改变但是需要经常读取的东西(NSCache) 3、权衡渲染方法(View?CALayer?CoreGraphics?OpenGL?) 4、处理内存警告 在app delegate中使用applicationDidReceiveMemoryWarning:的方法 在你的自定义UIViewController的子类(subclass)中覆盖didReceiveMemoryWarning 注册并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知 5、重用大开销对象(如NSDateFormatter,NSCalendar) 6、使用Sprite Sheets 7、避免反复处理数据 解析JSON会比XML更快一些,JSON也通常更小更便于传输。从iOS5起有了官方内建的JSON deserialization 就更加方便使用了。 但是XML也有XML的好处,比如使用SAX 来解析XML就像解析本地文件一样,你不需像解析json一样等到整个文档下载完成才开始解析。当你处理很大的数据的时候就会极大地减低内存消耗和增加性能。 8、正确设定背景图片 使用UIColor的 colorWithPatternImage来设置背景色; 在view中添加一个UIImageView作为一个子View。(不使用“imageName:”) 9、减少使用Web特性 10、设定Shadow Path view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath]; 11、优化UITableView 正确使用reuseIdentifier来重用cells 尽量使所有的view opaque,包括cell自身 避免渐变,图片缩放,后台选人 缓存行高 如果cell内现实的内容来自web,使用异步加载,缓存请求结果 使用shadowPath来画阴影 减少subviews的数量 尽量不适用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果 使用正确的数据结构来存储数据 尽量使用rowHeight,sectionFooterHeight和sectionHeaderHeight来设定固定的高,不要请求delegate 12、选择正确的数据存储选项 使用NSUerDefaults 使用XML, JSON, 或者 plist 使用NSCoding存档 ...

2017年2月11日 · Darren Ou

事件传递:响应者链条(译)

当你设计你的应用程序时,你可能需要动态的响应事件。例如,一个触摸事件可以由屏幕上的很多对象产生,你必须决定由哪个对象响应对应的事件和明白该对象如何接收到事件的。 当用户产生了一个事件,UIKit创建了一个包含处理事件所需信息的事件对象。然后把这个事件对象放置在全局事件队列。对于触摸事件,会被包装成一个UIEvent对象。对于动作事件,会根据你所使用的框架和你感兴趣的动作事件而有所不同。 一个事件沿着一个具体的路径向前传递,直到有一个对象可以对其进行处理。首先, UIApplication单例对象会从事件队列里获取一个事件并把它分发,一贯地将事件发送到应用的key window对象,key window对象会把事件传递到原始对象进行处理,原始对象会根据事件的类型而有所不同。 · 触摸事件。对于触摸事件,window对象首先会尝试传递事件到产生触摸事件的视图。这个视图被称为hit-test视图,寻找hit-test视图的过程被称为hit-testing。 · 动作和远程控制事件。window对象会把摇一摇动作或者远程控制事件发送到第一响应者进行处理。 这些事件路径的最终目标是寻找一个对象可以处理和相应一个事件。因此,UIKit首先会事件发送到最适合处理该事件的对象。最适合处理触摸事件的对象就是hit-test视图,对于其他事件,对象就是第一响应者。接下来的部分会详细展开hit-test视图和第一响应者对象是如何被确认的。 Hit-Testing返回触摸事件发生的视图 iOS利用hit-tesing寻找被触摸的视图。hit-tesing需要检查一个触摸事件是否在相关的视图对象的范围内,如果是,它会递归地检查这个视图对象的所有子视图。处于视图层级中包含触摸点的最底层视图就会成为hit-test视图。当iOS确定hit-test视图后,它会把触摸事件抛给这个视图去处理。 例如,假如用户触摸视图E(图2-1)。iOS通过以下顺序从子视图中找出hit-test视图 1.触摸点在视图A里面,所以会检查子视图B和C。 2.触摸点不在视图B的范围内,但是在视图C的范围内,所以它会检查子视图D和E。 3.触摸点不在视图D,但在视图E的范围内。 视图E是在视图层级中包含触摸点的最底层视图,所以它就成为了hit-test视图 (图2-1) (hitTest:withEvent:)方法返回一个给定CGPoint和UIEvent的hit-test视图。(hitTest:withEvent:)方法通过(pointInside:withEvent:)方法调用。如果一个点通过(hitTest:withEvent:)方法判断是在视图的范围内,(pointInside:withEvent:)方法就会返回YES。然后这个方法就会在返回YES的子视图上面递归地调用(hitTest:withEvent:)方法。 如果这个点通过(hitTest:withEvent:)方法判断不在视图的范围内,最先调用的(pointInside:withEvent:)方法就会返回NO。这个点被忽略了,(hitTest:withEvent:)方法就会返回nil。如果一个子视图返回NO,那么在视图层级中整个分支都会被忽略,因为如果这个触摸点不在这个视图中产生,那么也不会再这个视图的子视图中产生。这就意味着一些在父视图外的子视图的点不能接收到触摸事件,因为触摸点必须在父视图和子视图的范围内。子视图的范围超出父视图的情况会发生在子视图的clipsToBounds属性被设为NO。 注意:一个触摸对象的生命周期会跟它的hit-test密切关联,即使后来触摸移到view的外面。 hit-test视图有第一次机会去处理一个触摸事件。如果hit-test视图不能处理一个事件,这个事件就会在视图的响应者链条上传递,直到系统找到一个可以找到该事件的对象。 响应者链条由响应者对象组成 很多类型的事件依赖一个响应者链条进行传递。响应者链条是一系列的响应者对象。由第一个响应者开始,并由application对象结束。如果第一响应者不能处理一个事件,事件会向前传递给响应者链条的下一个响应者。 响应者对象是一个可以处理事件的对象。UIResponder类是所有响应者对象的基类,它定义的程序接口不只是为了事件的处理,而且为了普通的响应者行为。UIApplication,UIViewController和UIView的实例都可以成为响应者,意味着所有的视图和很多的关键控制器对象都可以是响应者。注意:核心动画图层都不可以成为响应者。 首先由第一响应者接收事件。一般的,第一项响应者是一个视图对象。一个对象通过以下两种方式成为第一响应者: 1、复写(canBecomeFirstResponder)方法并返回YES。 2、接收一个(becomFirstResponder)信息。如果有必要,一个对象可以发送这条信息给它自己。 注意:一个对象成为第一响应者前确保应用已经把这个对象的图层渲染出来。例如,我们一般在(viewDidAppear:)方法里面调用(becomeFirstResponder)方法,如果尝试在(viewWillAppear:)方法里面分配第一响应者,对象的图层还没有渲染出来,所以(becomeFirstResponder)方法就会返回NO。 事件不是唯一的依赖响应者链条的对象。响应者链条被用于以下的情况: · 触摸事件。如果hit-test视图不能处理一个触摸事件,那么这个事件就会从hit-test视图开始沿着响应者链条传递。 · 动作事件。通过UIKit来处理摇一摇动作事件,第一响应者必须实现UIResponder类的(motionBegan:withEvent:)方法或者(motionEnded:withEvent:)方法之一。 · 远程控制事件。第一响应者处理远程控制事件必须实现UIResponder类的(remoteControlReceivedWithEvent:)方法。 · 动作信息。

2017年1月13日 · Darren Ou