IOS

WebViewJavascriptBridge 源码剖析

WebViewJavascriptBridge 是一个可以让 OC 与 JS 进行交互通信的第三方开源库。相比其他热门的第三方库,WebViewJavascriptBridge 代码量比较少,并且设计优雅巧妙,可以说是 “小而美”。 WebViewJavascriptBridge 库在 OC 端和 JS 端都有对等的逻辑实现,事先注册 handler,内部维护一个消息队列。透明的 iframe HTML 元素和 webview 的 stringByEvaluatingJavaScriptFromString 是通信的关键。OC 端发消息给 JS 端比较直观,调起 stringByEvaluatingJavaScriptFromString 执行脚本传入消息即可。JS 端发消息给 OC 端,需要事先把消息存到队列中,然后借助 iframe 发起一个伪请求,伪请求会被 webview 的代理方法拦截下来,OC 端因此得知 JS 端消息队列中有消息,最后调起 stringByEvaluatingJavaScriptFromString 方法解析 JS 方法拿到队列中的消息并处理。交互流程见下图: 整个库只有以下几个文件: WebViewJavascriptBridge.h WebViewJavascriptBridge.m WKWebViewJavascriptBridge.h WKWebViewJavascriptBridge.m WebViewJavascriptBridgeBase.h WebViewJavascriptBridgeBase.m WebViewJavascriptBridge_JS.h WebViewJavascriptBridge_JS.m 一般使用只需要关注 WebViewJavascriptBridge 类提供的接口,这个类的主要职责是用来做 Mac 和 iOS webview 的适配(包括 WKWebView,但是这部分代理出去给 WKWebViewJavaScriptBridge 类)并为客户端提供便利的使用接口。WebViewJavascriptBridgeBase 类负责有关数据加工、消息队列管理、消息派发及回调的处理工作。WebViewJavascriptBridge_JS 类包含 JS 端的实现代码,通过宏处理返回 JS 端实现代码的一个 OC 字符串,便于在适当时机将其注入到文档模型中完成 bridge 的初始化。

iOS 远程打包脚本制作

在 iOS 开发中,一般打发布包都是在本地打包,也就是工程师在自己开发电脑上使用 Xcode 编译并导出安装包来进行发布,为了提高效率可能会制作一些自动化打包脚本。本文聊的是远程打包的内容,通过资源拷贝及参数替换然后编译完成打包。 由于 HTML5 跨平台的特点,很多技术团队考虑到代码复用,在部分模块中会采用 h5 来描述界面。甚至有些不需要太复杂交互的 app,全部界面采用 h5 来编写,也就是一个 web 工程。对于大部分现有的 web 工程,能打包成 app 就已经满足了业务诉求。DCloud 团队开发的 HBuilder(IDE)工具中提供了云打包的功能,用起来很方便,简单的说,就是把 web 工程上传到云打包服务器,最后打包生成 app,点击下载即可安装使用。 虽然云打包服务很方便,但上传源码总感觉不太妥当,总有些秘密不想让别人看见,并且其他同事也有打包的需求,但不一定会使用 HBuilder。因此,搭建一个自己的打包服务很有必要。 按照 HBuilder 提供的云打包功能,先定一个初步的需求: 支持修改应用 id、版本号 、icon、启动图 支持导入签名文件 开工!!! 准备工作 首先,需要一台安装了 MacOS 的电脑(当做服务器使用)。 笔者手头上刚好有台闲置的电脑就拿来当服务器使用了,装了 WMWare,然后装了 MacOS 虚拟机(问题较多,不建议使用虚拟机)。 物理机 windows7,内存 4G;虚拟机 MacOS,内存 3G。 其次,在服务器上部署一个 web 服务,提供打包交互界面方便客户端上传资源文件及下载安装包。我们的界面只提供了一个 www zip 包的上传入口,所有应用资源及打包相关的配置文件都在里面。www 目录结构如下: appConfig.json 文件内容 { "id":"com.domain.pack", "appName":"我的应用", "debug":true, "launchPath": "index.html", "version": { "name": "1.0.0", "code": "100" }, ... } launchPath 对应 web 应用入口文件,iOS 工程使用这个文件路径作为 webview 的加载入口。

Local and Remote Notification Programming Guide 译文

原文地址:本地和远程通知编程指南 应用中的通知 本地和远程通知概览 重要 这篇文档包含开发中有关 API 或技术的初步信息,这些信息可能会改变,并且根据这篇文档来实现的软件应当在最终的操作系统软件中进行测试。 本地通知和远程通知是在应用有新数据可用时通知用户的两种方式,即使此时应用不在前台运行。例如,短信应用可能会让用户知道有新的短信来了,日历应用可能会通知用户即将到来的约会。本地通知和远程通知的区别很简单: 对于本地通知,应用在本地配置通知的细节并把这些细节传给系统,然后由系统来处理通知的传递(当应用不在前台时)。iOS、tvOS、watchOS 都支持本地通知。 对于远程通知,使用公司服务器中的一个通过苹果推送通知服务把数据推送到用户的设备。iOS、tvOS、watchOS、macOS 都支持远程通知。 本地通知和远程通知都需要添加代码来支持应用中的通知的调度和处理。对于远程通知,必须提供一个服务器环境,该环境能够接收来自用户设备的数据和发送通知相关的数据到 苹果推送消息服务 (简称 APNs,由苹果提供的用来处理远程通知传递的服务)。 User Notifications 和 User Notifications UI 框架 从 iOS 10、watchOS 3、tvOS 10 开始,User Notifications 框架提供一致的方式来和处理本地通知。除了管理本地通知,该框架也支持远程通知的处理,然而远程通知的配置仍然需要一些平台特有的 API。因为这是一个独立的框架,所以可以在应用中或者扩展中使用,比如 WatchKit 扩展。 注意 macOS 上远程通知的配置和处理需要使用平台特有的方法(在 AppKit 框架中找) User Notifications 框架也支持创建 通知服务应用扩展 (notification service app extension),它可以让你在远程通知传递之前修改通知的内容。如果在应用中包含通知服务应用扩展,系统会把收到的通知在传递给用户之前先传递给扩展。可以使用这类扩展来给应用的通知实现端到端的加密、在通知传递前修改其内容,又或者下载与通知相关的额外的图片或媒体文件。 User Notifications UI 框架是 User Notifications 的配套,它可以让你自定义系统的通知界面的外观。使用User Notifications UI 框架来定义 通知内容应用扩展(notification content app extension),它的任务就是提供一个包含自定义内容的视图控制器来显示在通知界面中。系统会显示自定义视图控制器而不是默认的系统界面。可以使用这种扩展在通知界面中加入多媒体或动态内容。 更多有关 User Notifications 框架的类的信息,请看 User Notifications Framework Reference。关于创建通知内容应用扩展的类的信息,请看 User Notifications UI Framework Reference。

OC 与 JS 通信的几种方式

在代理方法中拦截协议 使用 JavaScriptCore WKWebView 的 WKScriptMessagehandler 使用 NSURLProtocol 拦截请求 使用第三方库 WebViewJavascriptBridge 使用 WebSocket 这里只介绍第 6 种,其它的相关资料网上有很多。 使用 WebSocket 的方式需要在应用内起一个 websocket server 服务(有很多第三方的 websocket server 库),html 页面通过 Websocket 连接到服务,接着就是发送消息了,剩下的就跟代理方法拦截协议类似。 // OC code, 以 PocketSocket 这个库为例 _socketServer = [PSWebSocketServer serverWithHost:nil port:9001]; _socketServer.delegate = self; _socketServer.delegateQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); [_socketServer start]; #pragma mark - PSWebSocketServerDelegate - (void)serverDidStart:(PSWebSocketServer *)server { NSLog(@"Server did start…"); } - (void)serverDidStop:(PSWebSocketServer *)server { NSLog(@"Server did stop…"); } - (BOOL)server:(PSWebSocketServer *)server acceptWebSocketWithRequest:(NSURLRequest *)request { NSLog(@"Server should accept request: %@", request); return YES; } - (void)server:(PSWebSocketServer *)server webSocket:(PSWebSocket *)webSocket didReceiveMessage:(id)message { // 在这里拦截 NSLog(@"Server websocket did receive message: %@", message); NSString *text = message; NSURL *url = [NSURL URLWithString:text]; if ([url.

iOS Hook WebView 的代理方法

国内 DCloud 团队推出的 HTML5+ 技术框架可以用来开发 Hybrid 应用。经过调研,我们决定试一试 。框架的核心原理是使用 iOS 系统原生 UIWebView 和 WKWebView 来加载资源并渲染界面,Native 的能力(如拍照、蓝牙)通过自定义插件来提供。 我们的应用有个需求,就是在 webview 加载完页面或者加载页面之前加入一些东西。比如:加载完页面后,根据 HTML 的 title 标签来设置导航栏标题。 原生想要插手页面加载周期,只能靠代理方法。但是因为没法修改源码,所以只能找其它办法。主要思路是:使用 Method Swizzle 找出代理对象然后再换掉代理方法实现。 以 UIWebView 为例,具体操作如下: 第一步,通过交换 setDelegate 的实现,找到目标代理对象所属的类; UIWebView+Intercepter.m - (void)p_setDelegate:(id<UIWebViewDelegate>)delegate { [self p_setDelegate:delegate]; Class delegateClass = [self.delegate class]; // 进一步交换 delegateClass 的代理方法 [UIWebViewDelegateHook exchangeUIWebViewDelegateMethod:delegateClass]; } #pragma mark - Method Swizzling + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [super class]; // When swizzling a class method, use the following: // Class class = object_getClass((id)self); SEL originalSelector = @selector(setDelegate:); SEL swizzledSelector = @selector(p_setDelegate:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } 第二步,把目标代理对象所属类的代理方法实现换成我们自己写的方法实现。

iOS 10 需要在 info.plist 中添加权限设置

iOS 10 开始对隐私权限更加严格, 如需使用隐私权限需要在工程的 info.plist 文件中声明,如果不声明程序在调用隐私权限(如相机)时应用程序会崩溃。 key 可以从下拉列表选择,value 为弹框提示文字(类型 String) 权限名称 Key 值 通讯录 NSContactsUsageDescription 麦克风 NSMicrophoneUsageDescription 相册 NSPhotoLibraryUsageDescription 相机 NSCameraUsageDescription 持续获取地理位置 NSLocationAlwaysUsageDescription 使用时获取地理位置 NSLocationWhenInUseUsageDescription 蓝牙 NSBluetoothPeripheralUsageDescription 语音转文字 NSSpeechRecognitionUsageDescription 日历 NSCalendarsUsageDescription

Grand Central Dispatch

GCD(Grand Central Dispatch)是异步执行任务的技术之一。 一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的 Dispatch Queue 中,GCD 就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。 dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ /** * 长时间处理 * 例如:AR用图像识别、数据库访问 */ /** * 长时间处理结束,主线程使用该处理结果 */ dispatch_async(dispatch_get_main_queue(), ^{ /** * 只在主线程可以执行的处理 * 例如用户界面刷新 */ }); }); 在导入 GCD 之前,Cocoa 框架提供了 NSObject 类的performSelectorInBackground:withObject实例方法和performSelectorOnMainThread实例方法等简单的多线程编程技术。 线程 线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派 CPU 的基本单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。 “一个 CPU 执行的 CPU 命令列为一条无分叉路径”即为“线程”。 现在一个物理的 CPU 芯片实际上有64个(64核)CPU,尽管如此,“一个 CPU 执行的 CPU 命令列为一条无分叉路径”仍然不变。 OS X 和 iOS 的核心 XNU 内核在发生操作系统事件时(如每隔一定时间,唤起系统调用等情况)会切换执行路径。执行中路径的状态,例如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原 CPU 寄存器等信息,继续执行切换路径的 CPU 命令列。这称为“上下文切换”。 由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去好像1个 CPU 核能够并列地执行多个线程一样。而且在具有多个 CPU 核的情况下,就不是“看上去像”了,而是真的提供了多个CPU核并行执行多个线程的技术。 使用多线程容易引发的常见问题

Auto Layout 知识点梳理

视图需要有确定的位置与大小才能正确显示在屏幕上。Auto Layout 使用对齐矩阵来确定视图的位置与大小,也就是所谓的约束。我们创建的每一条规则都规定了界面的一部分与另一部分的关系,某一部分可以由另一部分计算得出结果。 y = ax + b; 是一种线性关系。 创建约束的常见的方式: Xib NSLayoutConstraint VFS 第一种,可以在(Interface Builder)IB中布局约束,并且根据需求自定义它们。 第二种,可以使用代码创建单个约束。NSLayoutConstraint 类提供constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:contant:方法,可以让你每次创建一个约束,它将某项的属性关联到另一项。 第三种,使用可视化格式语言来表示各项是如何沿着垂直和水平坐标轴布局的。 所有约束都是 NSLayoutConstraint 类的成员,无论你是以何种方式创建它们的。每个约束都在一个 Objective—C 对象中存储y = ax + b规则,并且通过 Auto Layout 引擎来表达该规则。可视化约束 是另一种实现相同效果的工具。

ARC

什么是自动引用计数 自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术。要使用 ARC,需要满足以下条件: 使用 Xcode4.2 或以上版本 使用 LLVM 编译器 3.0 或以上版本 编译器选项中设置 ARC 有效 生活例子:办公室开关灯 最早进入办公室的人开灯。 count = 1 之后进入办公室的人,需要照明。 count = 2 下班离开办公室的人,不需要照明。 count = 1 最后离开办公室的人关灯。(此时已无人需要照明) count = 0 内存管理 思考方式: 自己生成的对象,自己持有 非自己生成的对象,自己也能持有 不再需要自己持有的对象时释放 非自己持有的对象无法释放 表 1- 2 对象操作与 Objective-C 方法的对应 对象操作 Objective-C方法 生成并持有对象 alloc/new/copy/mutableCopy方法 持有对象 retain方法 释放对象 release方法 废弃对象 dealloc方法 区域(zone) NSDefaultMallocZone、NSZoneMalloc 等名称中包含的NSZone是什么呢?它是为防止内存碎片化而引入的结构。对内存分配的区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高了内存管理的效率。但是,如同苹果官方文档 Programming With ARC Release Notes 中所说,现在的运行时系统只是简单地忽略了区域的概念。运行时系统中的内存管理本身已极具效率,使用区域来管理内存反而会引起内存使用效率低下以及源代码复杂化问题。 修饰符 __strong 修饰符 __weak 修饰符 __unsafe_unretained 修饰符 __autoreleasing 修饰符 __unsafe_unretained 修饰符正如其名 unsafe 所示,是不安全的所有权修饰符。尽管 ARC 式的内存管理是编译器的工作,但附有 __unsafe_unretained 修饰符的变量不属于编译器的内存管理对象。同附有 __weak 修饰符的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。

iOS Block

blocks 是 C 语言的扩充功能。blocks 是带有自动变量(局部变量)的匿名函数。 截获自动变量 int main() { int dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (^blk)(void) = ^{ printf(fmt,val); }; val = 2; fmt = "These value were changed. val = %d\n"; blk(); return 0; } 结果:val = 10 分析:block 语法的表达式使用的是它之前声明的自动变量 fmt 和 val。block 表达式截获所使用的自动变量的值为瞬间值。因为 block 表达式保存了自动变量的值(截获),所以在执行 block 语法后,即使改写了 block 中使用的自动变量的值也不会影响 block 执行的结果。 需要在 block 中修改一个变量的值,需要使用 __block 说明符。 block 的实质 block 实际上是作为极普通的 C 语言源代码来处理的。通过支持 block 的编译器,含有 block 语法的源代码转换为一般 C 语言编译器能够处理的源代码,并作为极为普通的 C 语言代码被编译。