对移动客户端技术的思考

14年到16年 客户端相关技术快速发展,大家在这方面的研究和落地结果非常多,插件化,包管理,组件化,各种脚手架和研发体系,换肤,热更新,动态化,跨平台,等等。各种技术和项目不断出现,百花齐放,那是移动客户端快速发展的阶段。现在,客户端的技术早已趋于稳定,同时偏重于运营的移动互联网对动态化的需求更为强烈。在这方面,前端,大前端更有优势,也更容易落地,也更为标准化。早期在动态化上的探索,主要集中在H5和Hybrid H5,之后还出现各种native的json动态化方案,还有以各种特有DSL去描述UI的方案,16年Facebook发起的react native项目,彻底点燃了前端跨平台演进的激情,weex随后跟进,再到flutter的出现。同时,前端框架react,Vue,angular的不断成熟,在构建前端/移动应用上,变得更加简单。

总体上,移动客户端开发早已进入稳定期,技术框架开发模式成熟,各种配套设施齐全。前端这几年的发展日新月异,到目前为止,以Vue/React/Angular为代表的前端框架也趋于稳定,周边配套也较为齐全,开发模式也趋于稳定,都以组件化和标准化为指导原则。以RN/Weex/Flutter为基础的大前端技术也趋于稳定,开发模式稳定了,周边配套也完善了,后续更多地会在框架性能稳定性开发效率上做文章,应用层面不会大动干戈了,但小的变动仍会有不少。敬那些逝去的项目框架,期待这些新框架仍能不断带来惊喜。

在对这些框架的使用和反哺上,大公司和小公司也必然是迥异的策略,小公司拿来即用,再配合一些胶水层代码,甚至这些代码也取之三方。大部分框架在追求通用性的同时,必然会在易用性上打折,大公司有能力也有人力对其深度定制,满足自身需求,提高使用效率,也能反馈给社区,甚至新的轮子就出来了。

2019/3/20 posted in  移动开发

技术知识导图

硬件

  • CPU、内存、总线、I/O

操作系统

  • 对硬件资源封装,对外提供基础服务。
  • 内核、网络、进程/线程、文件、显示、其他各种硬件驱动。
  • 提供系统库,内核态/用户态。
  • 跨平台接口 POSIX

驱动开发/嵌入式开发

编程语言

  • C/C++、OC、Java、JS、Swift、Kotlin、Python、PHP、Dart。
  • 类型、集合、字符串、控制流、运算、函数/闭包/Lambada、结构体/类/枚举、方法/属性、接口/协议、继承、扩展、泛型、协程。
  • 编程范式:面向过程、面向对象、面向切片、函数反应式。
  • AOT/JIT、解释执行、编译执行。
  • 由系统直接运行还是在虚拟机上运行。
  • 代码编译、代码转换。

数据结构和算法

网络

数据库

设计模式和架构

分布式系统

虚拟机/容器技术

大数据

云计算

人工智能

服务端开发

PC客户端&Web

移动客户端&Web&小程序

基础能力建设

  • 通用能力,框架设计、网络、存储、图片、音视频、日志、配置、路由、数据管理/消息总线、H5容器、硬件能力、数据绑定。
  • UI能力,UI组件、布局、渲染、动画、主题。
  • 动态化能力,JSON组件、Weex/RN、Hybrid H5、Flutter。

上层业务解藕

  • 登录、分享、支付、广告、导购、下单、订单、售后、用户。

研发体系

  • webpack,脚手架
  • CI、包管理、ESLint/OSLint
  • 缺陷管理
  • 功能测试、性能测试、集成测试

运营体系

  • 大盘数据、ABTest、营销系统

运维体系

  • 渠道发布、数据监控

视频/游戏

AR/VR

2019/3/13 posted in  临时

React Native

  • https://juejin.im/post/5a6460f8f265da3e4f0a446d React Native for Android 原理分析与实践:实现原理

  • https://www.cnblogs.com/android-blogs/p/5623481.html 其实没那么复杂!探究react-native通信机制

  • Native与JS之间调用方式,Bridge。

  • 在jsi.h中Runtime抽象类,申明了JS执行已经与Native互调的接口。在iOS和Android中分别实现。iOS的JSCRuntime,直接调用iOS系统提供JavaScriptCore框架。Android则额外引入jsc.so包提供,JavaScriptCore的功能。

  • Native和JS两端都保存模块、方法数据的注册表。

  • 通道优化,JS调用Naitve,先push到MessageQueue,等Native有调用时,顺带批量返回(如果5毫秒内,没有Native调用事件,则主动flush queue)。

  • JS代码执行,Native与JS消息通信等都是通过该Bridge进行。

2019/3/12 posted in  临时

Swift

  • basic types
  • operators
  • strings & collections
  • flow control
  • method & closure
  • structure & class
  • enumeration
  • extension & protocol
  • optional chaining
  • generics
  • memory
  • access safety
  • error handle
2019/3/4 posted in  临时

技术

  • 技术、框架、功能都是为了解决某个问题、帮助某个业务而出现,在实现的选择上,有可能会受到当时场景、条件等各种因素影响,在当时可能是最优的选择,但也可能存在些副作用。
  • 更看重长期的收益,在短期上,可能需要额外付出,需要投入人力、物力。
  • 技术、框架、方案会过时,也许有些技术能存在很长时间,适应很多变化,但在新需求、新环境面前,总会有些心有余力不足。这时要么还在原来基础上修修补补,要么革就出新,刨除历史包袱,作出新的变革。
2019/3/1 posted in  临时

跨端方案

UI系统

  • UI系统包含UI元素的布局、渲染和UI事件的捕获、传递、处理等。
  • iOS、Android、Web、微信小程序、其他小程序都有各自的原生UI系统。

系统服务

  • 原生系统的各种能力,来自硬件的、软件的。

开发语言不同、运行环境不同

开发模式不同

跨端方案

  • 不同原生系统上的UI系统和系统服务是有差别和相似的,跨端方案是尽量屏蔽这些差异,对应用开发者来说,开发体验是一致的的、透明的,就像jQuery屏蔽不同Web浏览器间的差异性。
  • 需要做到这点,跨端方案需要提供包括DSL、Runtime、组件库、工具链等,一整套屏蔽原生系统开发和运行的差异。

Runtime

  • Runtime需要支撑DSL在原生系统的运行,需要桥接到到原生系统服务、原生UI系统,需要提供自有或轻或重的UI渲染系统,需要提供对自有组件库或三方组件库的访问等。
  • UI引擎,轻的方案是直接桥接到原生UI系统,引擎只做UI数据的处理、转换、传递,重的方案是基于跨平台的渲染方案如OpenGL做一套自己的UI系统。

组件库

  • 自有、三方、原生系统的组件库等等。

工具链

  • 开发阶段,需要提供转换器或编译器,对DSL的支持,IDE,等等。
  • 调试阶段,调试工具、方式。
  • 测试阶段,测试工具。
  • 部署阶段,本地部署、远程部署。
2019/2/27 posted in  移动开发

Swift

Pods

2019/2/12 posted in  临时

苹果开发文档

2019/2/11 posted in  临时

iOS Mach-O

/**
 * Created by BeeHive.
 * Copyright (c) 2016, Alibaba, Inc. All rights reserved.
 *
 * This source code is licensed under the GNU GENERAL PUBLIC LICENSE.
 * For the full copyright and license information,please view the LICENSE file in the root directory of this source tree.
 */


#import "BHAnnotation.h"
#import "BHCommon.h"
#include <mach-o/getsect.h>
#include <mach-o/loader.h>
#include <mach-o/dyld.h>
#include <dlfcn.h>
#import <objc/runtime.h>
#import <objc/message.h>
#include <mach-o/ldsyms.h>

NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp);
static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide)
{
    NSArray *mods = BHReadConfiguration(BeehiveModSectName, mhp);
    for (NSString *modName in mods) {
        Class cls;
        if (modName) {
            cls = NSClassFromString(modName);
            
            if (cls) {
                [[BHModuleManager sharedManager] registerDynamicModule:cls];
            }
        }
    }
    
    //register services
    NSArray<NSString *> *services = BHReadConfiguration(BeehiveServiceSectName,mhp);
    for (NSString *map in services) {
        NSData *jsonData =  [map dataUsingEncoding:NSUTF8StringEncoding];
        NSError *error = nil;
        id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
        if (!error) {
            if ([json isKindOfClass:[NSDictionary class]] && [json allKeys].count) {
                
                NSString *protocol = [json allKeys][0];
                NSString *clsName  = [json allValues][0];
                
                if (protocol && clsName) {
                    [[BHServiceManager sharedManager] registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
                }
                
            }
        }
    }
    
}
__attribute__((constructor))
void initProphet() {
    _dyld_register_func_for_add_image(dyld_callback);
}

NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp)
{
    NSMutableArray *configs = [NSMutableArray array];
    unsigned long size = 0;
#ifndef __LP64__
    uintptr_t *memory = (uintptr_t*)getsectiondata(mhp, SEG_DATA, sectionName, &size);
#else
    const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp;
    uintptr_t *memory = (uintptr_t*)getsectiondata(mhp64, SEG_DATA, sectionName, &size);
#endif
    
    unsigned long counter = size/sizeof(void*);
    for(int idx = 0; idx < counter; ++idx){
        char *string = (char*)memory[idx];
        NSString *str = [NSString stringWithUTF8String:string];
        if(!str)continue;
        
        BHLog(@"config = %@", str);
        if(str) [configs addObject:str];
    }
    
    return configs;

    
}

@implementation BHAnnotation

@end



__attribute__((constructor)) void before_main() {
    printf("--- %s\n", __func__);
}

__attribute__((destructor)) void after_main() {
    printf("--- %s\n", __func__);
}


2019/2/11 posted in  临时

HTTP2

HTTP,HTTP2.0,SPDY,HTTPS你应该知道的一些事 http://www.alloyteam.com/2016/07/httphttp2-0spdyhttps-reading-this-is-enough/

HTTP/2 新特性总结 https://www.jianshu.com/p/67c541a421f9

  • http2能带来如下这些收益:
    1、多路复用、多个HTTP可以在同个TCP连接上同时发送。
    2、优先级
    3、header压缩
    4、Server Push
2019/2/1 posted in  临时

对开发的思考

开发环节

  • 开发、集成、测试、部署、运维

如何协同,解藕、组件化、平台化

  • 研发平台
  • 运维平台
  • 营销平台

衡量

  • 效率、质量 -> 效能、价值

网络

  • 移动网关
  • HTTP DNS
  • 直连、加速
  • HTTP/2
  • 加密、压缩
  • 业务接口协议
  • 代理、Mock、Debug、测试、诊断
  • 长连接
  • 推送
  • 服务端、客户端二方包

存储

  • 关系数据存储
  • 非关系数据存储
  • 缓存
  • 读写分离
  • mmap

图片

  • 图片格式
  • 域名收敛
  • CDN
  • 图片质量、大小
  • 渐进式
  • 失败率

视频

  • 格式
  • 云存储、本地存储
  • 视频播放
  • 视频流
  • 弹幕

日志

  • 本地日志
  • 远程日志
  • 聚合

统计

调试工具

Web容器

设计模式&架构

动态化方案

UI框架

  • 组件库
  • 页面模式

三方业务

  • 支付
  • 分享
  • OAuth登录
  • 推送
  • 地图
  • 直播

自有业务

  • 登录
  • 电商
  • 营销
2019/2/1 posted in  临时

Fishhook

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //这里必须要先加载一次NSLog,如果不写NSLog,符号表里面根本就不会出现NSLog的地址
    NSLog(@"123"); 

    //定义rebinding结构体
    struct rebinding nslogBind;
    //函数的名称
    nslogBind.name = "NSLog";
    //新的函数地址
    nslogBind.replacement = myMethod;
    //保存原始函数地址变量的指针
    nslogBind.replaced = (void *)&old_nslog;
    
    //定义数组
    struct rebinding rebs[] = {nslogBind};
    
    /**
     arg1: 存放rebinding结构体的数组
     arg2: 数组的长度
     */
    rebind_symbols(rebs, 1);
}

//函数指针,用来保存原始的函数地址
static void (*old_nslog)(NSString *format, ...);

//新的NSLog
void myMethod(NSString *format, ...) {
    //再调用原来的
    old_nslog(@"勾上了!");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"点击屏幕");
}

lldb

2019/2/1 posted in  iOS

UI框架、编程范式

移动客户端、H5、小程序

  • iOS native编码、Android native编码、Weex方案、React Native方案、小程序的mpvue方案、小程序的wepy方案

iOS UI编码方式

  • 使用Objective-C/Swift构建基于ViewController的原生页面。

Android UI编码方式

  • Java、AndroidSDK、kotlin
  • Activity/Flagment + XML构建原生页面。

Weex/RN

  • DSL转换打包成Bundle,部署下发的目标环境上,在Weex引擎或RN引擎上运行,桥接到原生的UI框架。

DSL -- transform --> bundle -- distribution --> run in engine --> adaptor layer (bridge with native / render in native)

2019/1/30 posted in  临时

美团2018技术合集 阅读笔记

mpvue

https://tech.meituan.com/2018/03/11/mt-mpvue-development-framework.html

  • 使用vue.js开发小程序
  • 原理:增加一个中间层,将小程序的生命周期、事件、数据同步等桥接到vue对象。

Flutter

https://tech.meituan.com/2018/08/09/waimai-flutter-practice.html

  • 全新思路的跨平台的移动客户端UI方案。
  • 不同于Weex或ReactNative将JS数据代理到Native UI库做渲染,Flutter直接使用更加底层的图形渲染接口,从更底层去接管消息事件,在此基础上,封装跨平台的UI库。
  • 对于UI层来说,这样的跨平台更彻底,一致性更强。
  • 核心引擎+Widget库

Picasso 大众点评自研的Native动态化方案

https://tech.meituan.com/2018/06/21/picasso-the-future.html

  • 对比

Hybrid WebView
Weex、RN、Flutter

  • 基本原理
    DSL(TypeScript)-》 PModel -〉 iOS、Android、H5、小程序渲染引擎 -》 UI

  • DSL

  • 核心SDK + 适配层 + 脚手架

  • 开发流程

VSCode + 插件

  • 发布流程

Picasso文件 -〉JS Bundle -》 部署服务器 -〉Picasso执行环境(iOS、Android、H5、小程序)-》渲染引擎

美团客户端响应式框架Easy React

https://tech.meituan.com/2018/07/19/react-programming-framework-easyreact-opensource.html

  • 对比ReactiveCocoa、ReactiveX
  • 基于Objective-C
  • EasyMVVM

Logan:美团点评的开源移动端基础日志库 - 美团技术团队

https://tech.meituan.com/2018/10/11/logan-open-source.html

MCI:移动持续集成在大众点评的实践

https://tech.meituan.com/2018/07/12/mci.html

美团外卖Android Crash治理之路

https://tech.meituan.com/2018/06/14/waimai-android-crash.html

运营平台

  • 运营、平台管理、前端开发
  • 同一个运营位,多维度内容的展示。
  • 运营效果

监控系统

HTTP服务治理框架

2019/1/29 posted in  临时

读书目录

技术类

基础

  • 《代码整洁指导Clean Code》
  • 《图解HTTP》
  • 《深入浅出HTTPS》
  • 《Wireshark网络分析的艺术》
  • 《Wireshark网络分析就这么简单》
  • 《算法竞赛入门经典》
  • 《程序员代码面试指南》
  • 《架构即未来》

前端

  • 《高性能网站建设进阶指南》
  • 《网站性能监测与优化》
  • 《精通CSS:高级Web标准解决方案》
  • 《单页Web应用:JavaScript从前端到后端》
  • 《HTML5移动Web开发指南》
  • 《前端架构设计》
  • 《现代前端技术解析》
  • 《React全栈》
  • 《指尖上行 移动前端开发进阶之路》
  • 《现代前端技术解析》
  • 《前端工程化:体系设计和实践》
  • 《React Native移动开发实战》
  • 《JavaScript之美》
  • 《Vue.js权威指南》
  • 《移动Web手册》
  • 《响应式Web设计全流程解析》
  • 《网页制作与网站建设实战大全》

客户端

  • 《iOS面试之道》
  • 《iOS应用逆向工程》
  • 《iOS应用安全权威指南》
  • 《iOS应用安全攻防》
  • 《iOS和macOS性能优化》
  • 《iOS应用安全攻防实战》
  • 《疯狂iOS讲义》
  • 《Android高级进阶》
  • 《Android软件安全与逆向分析》
  • 《疯狂Android讲义》
  • 《Android进阶之光》
  • 《App架构师实践指南》
  • 《App研发录》
  • 《音视频开发进阶指南》
  • 《Building iPhone Apps with HTML CSS JavaScript》

后端

  • 《大型网站技术架构 核心原理与案例分析》
  • 《深入分析Java Web技术内幕》
  • 《Java程序员面试笔试宝典》
  • 《大型网站系统与Java中间件实践》
  • 《Java程序性能优化》
  • 《深入浅出MyBatis技术原理与实战》
  • 《JavaEE开发的颠覆者:Spring Boot实战》
  • 《云计算架构技术与实践》
  • 《Spring Cloud与Docker微服务架构实战》
  • 《这就是搜索引擎》
  • 《大数据之路》
  • 《阿里巴巴Java开发手册》
  • 《码出高效:Java开发手册》

架构

其他

  • 《写给大家看的设计书》
  • 《卓有成效的管理者》
  • 《麦肯锡教我的写作武器》
  • 《金字塔原理》
  • 《安静:内向性格的竞争力》
  • 《支付战争:互联网金融创世纪》★★★★☆
    PayPal诞生及成长史,面对众多竞争对手的生死突围,以及围绕PayPal的人的故事。
  • 《Web全栈工程师的自我修养》,余果,★★★☆☆
  • 《黑客与画家》
  • 《专业主义》
  • 《打造Facebook》
  • 《Getting Real》
  • 《从0到1:开启商业与未来的秘密》
  • 《代码大全》
  • 《代码未来》
  • 《禅与摩托维修艺术》
  • 《鸟哥的Linux私房菜》
  • 《只是为了好玩:Linux之父林纳斯自传》
  • 《软件随想录》
  • 《卓有成效的程序员》
  • 《联盟:互联网时代的人才变革》
  • 《创业维艰:如何完成比难更难的事》
  • 《数据化管理:洞悉零售及电子商务运营》
  • 《场景革命:重构人与商业的连接》
  • 《独角兽之路》
  • 《品牌课》
  • 《团队建设与管理》
  • 《互联网思维:云计算、物联网、大数据》
  • 《程序员修炼之道:从小工到专家》
  • 《区块链技术指南》
  • 《互联网时代盈利模式》
  • 《人工智能》
  • 《码农翻身》
  • 《未来地图:技术、商业和我们的选择》

鸡汤

  • 《掘金:互联网+时代创业黄金指南》
  • 《合伙人时代》
  • 《联盟》
  • 《创业维艰》
  • 《我看电商3:零售的变革》《我看电商》《我看电商2》《走出电商困局》
  • 《硅谷之谜》
  • 《浪潮之巅》
  • 《一个电商运营总监的自白》
  • 《软技能 代码之外的生存指南》
  • 《黑科技》
  • 《数学之美》
  • 《文明之光》
  • 《你就是极客:软件开发人员生存指南》
  • 《重来》
  • 《精益创业》

管理类

文学类

  • 《孤独深处》
  • 《去远方》
  • 《必然》
  • 反乌托邦小说三部曲:《1984》《美丽新世界》《我们》
  • 《火星崛起》三部
  • 《巨人的陨落》
  • 《流浪苍穹》
  • 《解忧杂货铺》
  • 《1984动物农场》
  • 丹·布朗系列

其他

2019/1/26 posted in  杂想

iOS审核

上传审核附件解决办法(注意事项)

1.上传的文件必须是png格式(直接修改后缀名不可以),并且文件大小限制在5M内。
2.上传文件的命名不要有汉字,要用字母。

2019/1/23 posted in  iOS

开发笔记

微服务

单体到微服务是一个演化过程,别在一开始就过度设计

“ The Art of Scalability ”一书描述了一种 三维可伸缩性模型 (AKF Scale Cube),其中三个轴分别代表了不同类型的缩放:

X:水平复制和克隆;-> (Docker,容器)
Y:功能分解和分割(微服务);
Z:水平数据分区(分片)。

微服务架构设计

微服务架构设计

vue

https://juejin.im/post/5c488a3cf265da615705cc2a?utm_source=tuicool&utm_medium=referral

社交

Tumblr、Pinterest……一个个想创造新模式的应用都失败了,它们改变了什么又为何没落? | 好奇心商业史

https://www.qdaily.com/articles/60688.html?utm_source=tuicool&utm_medium=referral

三个软件可能要被整合到一起:WhatsApp、Instagram和Facebook Messenger

https://www.qdaily.com/articles/60669.html

数据垄断和信息孤岛,是如何驯化我们的?
https://www.huxiu.com/article/282751.html?utm_source=tuicool&utm_medium=referral

2019/1/22 posted in  临时

2018-3-15

客户端上的一个页面搭建采用哪种架构,往往取决与页面类型。

展示型页面

数据从数据层到展示层单向流动

表单型页面

数据在数据层和展示层双向流动

交互型页面

展示层不仅展示数据,还有大量动画,操作行为等

组件

系统底层框架
网络
存储
缓存
序列化
架构
组件化
工程化
数据监控
日志
性能
Crash
安全
图片
视频、音频
AR、VR、ML
UI组件
屏幕分辨率
布局、样式
渲染
研发效率
编程语言、OC/Swift、OOP、AOP、FP
Hybrid
Native动态化
热补丁
小程序
PWA
编译
部署

2019/1/21 posted in  移动开发

技术图谱

2019/1/19 posted in  移动开发

关于软件架构的思考

2019/1/19 posted in  架构

h5 hybrid方案

三方库选择

方案比较通用,对UIWebView也支持。

需要改用DWKWebView,对原有代码略有侵入。

目前方案

  • 直接使用WKWebView原生接口

  • JS调用Native

[config.userContentController addScriptMessageHandler:[WKScriptMessageHandlerWrapper wrapper:self] name:JS_HANDLER_UrlRouter];

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSLog(@"Recv Js call : name =  %@", message.name);
    if ([message.name isEqualToString:JS_HANDLER_UrlRouter]) {
        [self handleUrlRouterMessage:message.body];
    } else if ([message.name isEqualToString:JS_HANDLER_ShareService]) {
        [self handleShareServiceMessage:message.body];
    }
}
  • Native调用JS
[self.webView evaluateJavaScript:@"window.JSHandler()" completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
    if (obj) {
        NSLog(@"receive js response %@", obj);
    }
}];

  • JS调用Native不支持返回值。

  • 接口调用不支持回调:JS和Native侧都增加个Callback队列,根据CallbackId执行对应的回调函数。

2019/1/19 posted in  移动开发

我想说的是,在接下来的几年里,我们已经发展起来的微型库架
构将逐渐被那些单体框架所取代。它们将采用一种约定配置
(convention-over-configuration)的方式,用透明度
换取生产力的提升。

RN

  • 优势:跨平台、动态性、性能媲美原生、Web开发人力
  • 劣势:兼容性问题(也许花在处理多端不一致、兼容性等问题上的时间比使用原生方案实现业务所花的时间更多)、包大小、与原生数据的异步交互等等
2019/1/19 posted in  移动开发

weex代码走读

  • WXSDKInstance -> WXBridgeManager->WXBridgeContext->WXJSCoreBridge->JSContext

  • WXSDKInstance -> WXComponentManager

  • WXBridgeManager : 对WXBridgeContext进一步封装,提供JS API调用,JS 事件发送,注册Native Call Handler,注册Native模块Handler、注册Native组件Handler、注册Native服务Handler等接口。提供Bridge独立线程,上述接口基本上都在该线程中执行。

  • WXComponentManager :管理着组件的布局,样式等

// WXComponentManager.mm

- (void)_layoutAndSyncUI
{
    [self _layout];
    if(_uiTaskQueue.count > 0){
        [self _syncUITasks];
        _noTaskTickCount = 0;
    } else {
        // suspend display link when there's no task for 1 second, in order to save CPU time.
        _noTaskTickCount ++;
        if (_noTaskTickCount > 60) {
            [self _suspendDisplayLink];
        }
    }
}

- (void)_layout
{
    BOOL needsLayout = NO;

    needsLayout = [_rootComponent needsLayout];

    if (!needsLayout) {
        return;
    }
#ifdef DEBUG
    WXLogDebug(@"flexLayout -> action__ calculateLayout root");
#endif

        std::pair<float, float> renderPageSize;
        renderPageSize.first = self.weexInstance.frame.size.width;
        renderPageSize.second = self.weexInstance.frame.size.height;
        _rootFlexCSSNode->calculateLayout(renderPageSize);
    NSMutableSet<WXComponent *> *dirtyComponents = [NSMutableSet set];
    [_rootComponent _calculateFrameWithSuperAbsolutePosition:CGPointZero gatherDirtyComponents:dirtyComponents];
    [self _calculateRootFrame];

    for (WXComponent *dirtyComponent in dirtyComponents) {
        [self _addUITask:^{
            [dirtyComponent _layoutDidFinish];
        }];
    }
}
  • WXSDKInstance : 加载bundle文件,管理、渲染根视图。

  • WXSDKManager :管理WXSDKInstance多实例

//WXSDKInstance.m

- (void)_renderWithMainBundleString:(NSString *)mainBundleString
{
    if (!self.instanceId) {
        WXLogError(@"Fail to find instance!");
        return;
    }
    self.performance.renderTimeOrigin = CACurrentMediaTime()*1000;

    if (![WXUtility isBlankString:self.pageName]) {
        WXLog(@"Start rendering page:%@", self.pageName);
    } else {
        WXLogWarning(@"WXSDKInstance's pageName should be specified.");
        id<WXJSExceptionProtocol> jsExceptionHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXJSExceptionProtocol)];
        if ([jsExceptionHandler respondsToSelector:@selector(onRuntimeCheckException:)]) {
            WXRuntimeCheckException * runtimeCheckException = [WXRuntimeCheckException new];
            runtimeCheckException.exception = @"We highly recommend you to set pageName.\n Using WXSDKInstance * instance = [WXSDKInstance new]; instance.pageName = @\"your page name\" to fix it";
            [jsExceptionHandler onRuntimeCheckException:runtimeCheckException];
        }
    }
    if (!self.userInfo) {
        self.userInfo = [NSMutableDictionary new];
    }
    if (!self.userInfo[@"jsMainBundleStringContentLength"]) {
        self.userInfo[@"jsMainBundleStringContentLength"] = @([mainBundleString length]);
    }
    if (!self.userInfo[@"jsMainBundleStringContentLength"]) {
        self.userInfo[@"jsMainBundleStringContentMd5"] = [WXUtility md5:mainBundleString];
    }

    id<WXPageEventNotifyEventProtocol> pageEvent = [WXSDKEngine handlerForProtocol:@protocol(WXPageEventNotifyEventProtocol)];
    if ([pageEvent respondsToSelector:@selector(pageStart:)]) {
        [pageEvent pageStart:self.instanceId];
    }

    WX_MONITOR_INSTANCE_PERF_START(WXPTFirstScreenRender, self);
    WX_MONITOR_INSTANCE_PERF_START(WXPTAllRender, self);

    NSMutableDictionary *dictionary = [_options mutableCopy];
    if ([WXLog logLevel] >= WXLogLevelLog) {
        dictionary[@"debug"] = @(YES);
    }

    if ([WXDebugTool getReplacedBundleJS]) {
        mainBundleString = [WXDebugTool getReplacedBundleJS];
    }

    //TODO WXRootView
    WXPerformBlockOnMainThread(^{
        _rootView = [[WXRootView alloc] initWithFrame:self.frame];
        _rootView.instance = self;
        if(self.onCreate) {
            self.onCreate(_rootView);
        }
    });
    // ensure default modules/components/handlers are ready before create instance
    [WXSDKEngine registerDefaults];
     [[NSNotificationCenter defaultCenter] postNotificationName:WX_SDKINSTANCE_WILL_RENDER object:self];

    _needDestroy = YES;
    _mainBundleString = mainBundleString;
    if ([self _handleConfigCenter]) {
        NSError * error = [NSError errorWithDomain:WX_ERROR_DOMAIN code:9999 userInfo:nil];
        if (self.onFailed) {
            self.onFailed(error);
        }
        return;
    }

    _needDestroy = YES;
    [WXTracingManager startTracingWithInstanceId:self.instanceId ref:nil className:nil name:WXTExecJS phase:WXTracingBegin functionName:@"renderWithMainBundleString" options:@{@"threadName":WXTMainThread}];
    [[WXSDKManager bridgeMgr] createInstance:self.instanceId template:mainBundleString options:dictionary data:_jsData];
    [WXTracingManager startTracingWithInstanceId:self.instanceId ref:nil className:nil name:WXTExecJS phase:WXTracingEnd functionName:@"renderWithMainBundleString" options:@{@"threadName":WXTMainThread}];

    WX_MONITOR_PERF_SET(WXPTBundleSize, [mainBundleString lengthOfBytesUsingEncoding:NSUTF8StringEncoding], self);
}
//WXBridgeProtocol.h

@protocol WXBridgeProtocol <NSObject>

@property (nonatomic, readonly) JSValue* exception;

/**
 * Executes the js framework code in javascript engine
 * You can do some setup in this method
 */
- (void)executeJSFramework:(NSString *)frameworkScript;

/**
 * Executes the js code in javascript engine
 * You can do some setup in this method
 */
- (void)executeJavascript:(NSString *)script;

/**
 * Executes global js method with specific arguments
 */
- (JSValue *)callJSMethod:(NSString *)method args:(NSArray*)args;

/**
 * Register callback when call native tasks occur
 */
- (void)registerCallNative:(WXJSCallNative)callNative;

/**
 * Reset js engine environment, called when any environment variable is changed.
 */
- (void)resetEnvironment;
// WXJSCoreBridge.m

- (void)executeJSFramework:(NSString *)frameworkScript
{
    WXAssertParam(frameworkScript);
    if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
        NSString * fileName = @"native-bundle-main.js";
        if ([WXSDKManager sharedInstance].multiContext) {
            fileName = @"weex-main-jsfm.js";
        }
        [_jsContext evaluateScript:frameworkScript withSourceURL:[NSURL URLWithString:fileName]];
    }else{
        [_jsContext evaluateScript:frameworkScript];
    }
}

- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
{
    WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
    return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}

- (void)registerCallNative:(WXJSCallNative)callNative
{
    JSValue* (^callNativeBlock)(JSValue *, JSValue *, JSValue *) = ^JSValue*(JSValue *instance, JSValue *tasks, JSValue *callback){
        NSString *instanceId = [instance toString];
        NSArray *tasksArray = [tasks toArray];
        NSString *callbackId = [callback toString];
        WXLogDebug(@"Calling native... instance:%@, tasks:%@, callback:%@", instanceId, tasksArray, callbackId);
        return [JSValue valueWithInt32:(int32_t)callNative(instanceId, tasksArray, callbackId) inContext:[JSContext currentContext]];
    };

    _jsContext[@"callNative"] = callNativeBlock;
}
2019/1/19 posted in  移动开发

研发体系-》开发、测试
监控体系-〉多维度监控
系统架构

2019/1/17 posted in  临时

JavaScriptCore

  • OC->JS
//创建虚拟机
JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];

//创建上下文
JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];

//执行JavaScript代码并获取返回值
JSValue *value = [context evaluateScript:@"1+2*3"];

//转换成OC数据并打印
NSLog(@"value = %d", [value toInt32]);

Output

value = 7
  • JS->OC
2019/1/15 posted in  iOS

直接呼起微信内的h5页面

2019/1/15 posted in  前端

java的gc检查内存回收时,使用有向图机制,及检查一个或一组对象的可达性。如果是不可达状态,则将该对象从内存中回收,所谓不可达状态,也就是一个对象不被任何引用所指向或者叫持有。
另外,java的循环引用不同于oc或者c++,当一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

2019/1/15 posted in  临时

问题:UICollectionView Cell之间存在缝隙

原因

  • UICollectionViewCell的size不为整数时,UICollectionViewFlowLayout在布局计算时,可能会调整Cell的frame.origin,使Cell按照最小物理像素(渲染像素)对齐,导致出现缝隙。

  • 假设当前Cell的frame.origin.y=100.8(逻辑像素),转化成渲染像素(参考备注)是201.6(iPhone 8)或302.4(iPhone 8 Plus)。为了按渲染像素对齐,UICollectionViewFlowLayout应该会四舍五入取整,取整后为202(iPhone 8)或302(iPhone 8 Plus),转成逻辑像素101(iPhone 8)或100.667(iPhone 8 Plus),导致在iphone8上就会出现0.2像素的缝隙。

  • 分辨率相关的,可以百度下。

简单解决办法:

  • 主动把Cell的size取整,不丢给UICollectionViewFlowLayout处理。 - (CGSize)fixedCollectionCellSize:(CGSize)size {
    CGFloat scale = [UIScreen mainScreen].scale;
    return CGSizeMake(round(scale * size.width) / scale, round(scale * size.height) / scale);
    }

Demo实验

  • UITableView,Cell高度设置成100.12,没有强制被按渲染像素对齐,如 99.62 1802.16 1902.28
(lldb) po [0x7fb85b83b800 recursiveDescription]
<UITableView: 0x7fb85b83b800; frame = (0 0; 375 667); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x6000004451f0>; layer = <CALayer: 0x60000022dc40>; contentOffset: {0, 1272.5}; contentSize: {375, 2002.4000549316406}; adjustedContentInset: {20, 0, 0, 0}>
   | <UITableViewCell: 0x7fb85c862a00; frame = (0 1902.28; 375 100.12); autoresize = W; layer = <CALayer: 0x6040004265a0>>
   |    | <UITableViewCellContentView: 0x7fb85b70fea0; frame = (0 0; 375 99.62); gestureRecognizers = <NSArray: 0x6040002426a0>; layer = <CALayer: 0x604000425e80>>
   |    |    | <UIImageView: 0x7fb85b41a330; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x6000008215c0>>
   |    | <_UITableViewCellSeparatorView: 0x7fb85b710820; frame = (15 99.62; 360 0.5); layer = <CALayer: 0x604000427640>>
   | <UITableViewCell: 0x7fb85c862400; frame = (0 1802.16; 375 100.12); autoresize = W; layer = <CALayer: 0x6040002212e0>>
  • iPhone Plus 8,UICollectionView,Cell高度设置成100.12,强制被按渲染像素对齐了,frame.origin.y被调整后的值,如200.333 500.667 600.667
   | <UICollectionViewCell: 0x7fdd9f71f010; frame = (0 200.333; 414 100.12); hidden = YES; layer = <CALayer: 0x60000023d800>>
   | <UICollectionViewCell: 0x7fdd9f71f470; frame = (0 300.333; 414 100.12); layer = <CALayer: 0x60000023e380>>
   | <UICollectionViewCell: 0x7fdd9f72aa30; frame = (0 400.333; 414 100.12); layer = <CALayer: 0x60000023f4e0>>
   | <UICollectionViewCell: 0x7fdd9f72ae90; frame = (0 500.667; 414 100.12); layer = <CALayer: 0x60000023f9a0>>
   | <UICollectionViewCell: 0x7fdd9f72b510; frame = (0 600.667; 414 100.12); layer = <CALayer: 0x60000023fbc0>>
   | <UICollectionViewCell: 0x7fdd9f72d1a0; frame = (0 701; 414 100.12); layer = <CALayer: 0x60000023fdc0>>
  • iPhone Plus,UICollectionView,Cell高度设置成100.12,强制被按渲染像素对齐了,frame.origin.y被调整后的值,如300.5 400.5 500.5
   | <UICollectionViewCell: 0x7f85b441b6c0; frame = (0 0; 375 100.12); hidden = YES; layer = <CALayer: 0x600000035ec0>>
   | <UICollectionViewCell: 0x7f85b4502e10; frame = (0 100; 375 100.12); hidden = YES; layer = <CALayer: 0x600000035c20>>
   | <UICollectionViewCell: 0x7f85b4505c10; frame = (0 200; 375 100.12); layer = <CALayer: 0x600000036300>>
   | <UICollectionViewCell: 0x7f85b4506400; frame = (0 300.5; 375 100.12); layer = <CALayer: 0x600000036be0>>
   | <UICollectionViewCell: 0x7f85b4506d70; frame = (0 400.5; 375 100.12); layer = <CALayer: 0x600000227ac0>>
   | <UICollectionViewCell: 0x7f85b4507560; frame = (0 500.5; 375 100.12); layer = <CALayer: 0x6000002277c0>>

备注

Points(逻辑像素)<--->Rendered Pixels(渲染像素)<--->Physical Pixels(物理像素)

iOS不同机型尺寸.png

2018/6/1 posted in  iOS

Auto Layout

1、添加约束条件时,必须存在父视图或共同的父视图
2、auto layout engine计算时,必须知道位置和大小的约束
3、但某子视图启动auto layout约束后,父视图也随之启动
4、frame自动转换成auto layout约束
5、UILabel、UIImageVIew、UIButton、UITextField等,如果没有设置大小约束,则由intrinsicContentSize隐式提供,当内容改变后,intrinsicContentSize随之改变,并驱动auto layout engine重新计算
6、content hugging priority(内容收缩优先级)和content compression resistance priority (内容放大优先级)和intrinsicContentSize 一起决定大小的计算

2018/3/7 posted in  iOS

AppLink

屏幕快照 2017-09-13 下午2.09.46.png

2017/9/13 posted in  移动开发

WebView In Mobile App

2017/7/25 posted in  前端

关于电商的脑爆、YY

屏幕快照 2017-07-22 下午2.20.43.png

屏幕快照 2017-07-22 下午2.21.00.png

1、用户系统:会员信息、地址管理等

2、商品系统:商品库、SKU、类目、库存、购物车

3、营销系统:运营位、优惠券、活动

4、交易系统:下单、支付、订单、逆向

5、报表 对账 结算

6、内容系统:文章、专辑、视频

7、搜索、推荐

7、其他:物流、推送

2017/7/22 posted in  杂想

* [什么是软件架构](https://baike.baidu.com/item/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84/7485920?fr=aladdin)

2017/7/19 posted in  架构

网络相关

2017/6/23 posted in  移动开发

Android存储

2017/6/23 posted in  Android

iOS存储

2017/6/22 posted in  iOS

开发笔记4

2017/6/17 posted in  移动开发

开发笔记2

数据

看了下自己app的友盟统计,还有6%的iOS 8.x设备。等iOS 11出来后,再考虑放弃iOS 8.x用户吧。

OC稳住呀~

文章推荐(转)

最近看到Lottie项目,一个iOS,Android和React Native库,可以实时渲染After Effects动画,并且允许本地app像静态资源那样轻松地使用动画。回过头又把这篇图片格式调研的文章看了一遍,哈。这篇文章把移动开发常用的几种静态、动态图片做了详细的比较。

WKWebView不支持NSURLProtocol,导致基于UIWebView+NSURLProtocol并做了一些深度优化的,很难一下子就迁移到WKWebViewWKWebView内部使用了NSURLProtocol,但没有对外开放,不知道出于什么考虑。这篇文章以比较hack的方式让WKWebView支持NSURLProtocol

另一种Native动态化的方案,在OC编译器上动手脚,将OC代码自动编译成可动态下发的JS代码。

有些特性需要配合ios 10设备使用,比如内存循环引用、DYLD_PRINT_STATISTICS(打印应用预加载时间)等。

在Scheme的Environment Variables添加DYLD_PRINT_STATISTICS=YES,可以打印应用预加载时间。从打印出来的日志看,dylib loading time占用了不少时间,应该是跟加载的系统库和三方库有关。
The loading of Apple system frameworks is highly optimized but loading your embedded frameworks can be expensive. 当三方库不采用Framework形式打包,加载时间是少了不少。

// 三方库以Frameworks形式打包
Total pre-main time: 392.25 milliseconds (100.0%)
         dylib loading time: 336.73 milliseconds (85.8%)
        rebase/binding time:  10.88 milliseconds (2.7%)
            ObjC setup time:  15.91 milliseconds (4.0%)
           initializer time:  28.65 milliseconds (7.3%)
           slowest intializers :
             libSystem.B.dylib :   4.63 milliseconds (1.1%)
                  AFNetworking :  12.03 milliseconds (3.0%)
// 三方库以.a静态链接库形式链接
Total pre-main time:  93.71 milliseconds (100.0%)
         dylib loading time:  11.46 milliseconds (12.2%)
        rebase/binding time:  27.05 milliseconds (28.8%)
            ObjC setup time:  13.16 milliseconds (14.0%)
           initializer time:  41.94 milliseconds (44.7%)
           slowest intializers :
             libSystem.B.dylib :   5.47 milliseconds (5.8%)
   libBacktraceRecording.dylib :   4.48 milliseconds (4.7%)
                        Fanmei :  54.14 milliseconds (57.7%) 

项目

After Effects动画的渲染引擎。

A library for converting Adobe AE shape based animations to a data format and playing it back on Android and iOS devices.

Lottie类似,也是After Effects动画的渲染引擎。

A data-driven UICollectionView framework for building fast and flexible lists.

An in-app debugging and exploration tool for iOS

iOS设备调试工具,可以查看沙盒,抓包,查看视图信息等。

仿android的meterial design风格的控件

2017/6/16 posted in  移动开发

开发笔记3

数据

主要是针对Crash和ANR的统计。

文章推荐

监控先于优化。没有数据支撑的优化,往往没有说服力,也很难衡量优化的效果如何。

大部分App或多或少都会涉及到存储(持久化或缓存)。ibireme/YYCache,支持在硬盘持久化和在内存中缓存,从功能、性能和接口易用性上都不错,目前项目中也在用,推荐。

老文章了,视图布局机制在iOS、Android、Html CSS是相通的,可以互相借鉴。youngsoft/MyLinearLayout实现很多布局机制。

MyLinearLayout is equivalent to: UIStackView of iOS and LinearLayout of Android.

MyRelativeLayout is equivalent to: AutoLayout of iOS and RelativeLayout of Android.

MyFrameLayout is equivalent to: FrameLayout of Android.

MyTableLayout is equivalent to: TableLayout of Android and table of HTML.

MyFlowLayout is equivalent to: flexbox of CSS3.

MyFloatLayout is equivalent to: float of CSS.

借鉴Android的LinearLayout以及Html CSS的FlexBox,自己写了FMLayouts,对iOS的布局机制做补充。

Android

项目

SnapKit/Masonry的Swift版本

2017/6/16 posted in  移动开发

iOS测试框架XCTest

2017/6/3 posted in  iOS

开发笔记1

文章推荐

徐川大神对2016年移动技术发展的概述,以及对2017年的展望。总结了各种技术,热补丁、组件化、动态化、跨平台、直播、AR/VR等等。自己在项目实践中,也尝试了热补丁、动态化、组件化等技术,比较有体会。推荐他的博客移动开发前线,还是相当不错的。

对于往往存在大量异构元素的app首页或集合页,该方案的思路还是可以借鉴下的。

为满足大部分动态化需求,在App中内嵌一个WebView支持H5页,这是比较常见的做法。iOS上就UIWebView和WKWebView,WKWebView相比较UIWebView,性能更优,但也多了不少限制。目前自己项目中也是直接使用WKWebView,好在H5和Native的交互比较少,对WKWebView也没有做多少优化。

异步编程,特别是多层嵌套的情况,即使用了GCD&Block回调方式去写,代码仍会过于凌乱,可读性也大打折扣。我会通过以下两种方法去处理,将每个异步回调的处理逻辑封装在一个函数内,或将每个异步过程封装起来(即Future/Promise的做法)。无论哪种,也都会额外增加些成本,结合自己项目内的代码风格和使用成本再选择一个合适的。

知识点比较基础,罗列得比较多,可以结合项目多看看。

代码推荐

收集了大量的iOS开源项目、网页等等

2017/1/24 posted in  移动开发

Alamofire源码分析

概述

  • AlamofireAFNetworking的功能差不多,都是对URLSession的封装,对上层提供易用的网络请求接口。

  • AlamofireAFNetworking分别是Swift和OC的实现版本。

  • 目前,这两个网络封装库的关注度和使用率非常高,代码质量也相当不错。本文想通过对Alamofire源码的简单分析,了解其基本框架和设计思路。

  • 源码链接:Alamofire

一个GET请求的源码分析

  • 从最简单的Get请求入手,分析Alamofire的代码。一个请求流程,可以分为请求发送流程和请求响应流程,下文将从这两个流程展开分析。
// Get请求的调用方式
Alamofire.request("https://httpbin.org/get").responseJSON { response in
    print(response.request)  // original URL request
    print(response.response) // HTTP URL response
    print(response.data)     // server data
    print(response.result)   // result of response serialization

    if let JSON = response.result.value {
        print("JSON: \(JSON)")
    }
}

请求发送流程

  • Alamofire.Swift可以认为Alamofire一些对外接口的包装(Facade API)。 Alamofire.request实际上是调用了SessionManager.request
// 调用request方法
/// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of a URL based on the specified `urlRequest`.
@discardableResult
public func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
    return SessionManager.default.request(urlRequest)
}
  • SessionManager.requestRequest被组装创建,并加到发送队列中,然后等待一系列的响应事件。而SessionManager主要职责是管理发送队列,组装请求消息,设置Session相关的配置,设置工作线程等。
// 创建request对象,并开始发送
/// Creates a `DataRequest` to retrieve the contents of a URL based on the specified `urlRequest`.
    open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
        var originalRequest: URLRequest?

        do {
            originalRequest = try urlRequest.asURLRequest()
            let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)

            let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
            let request = DataRequest(session: session, requestTask: .data(originalTask, task))

            delegate[task] = request

            if startRequestsImmediately { request.resume() }

            return request
        } catch {
            return request(originalRequest, failedWith: error)
        }
    }
  • 接着,通过Request.responseJSON设置JSON响应回调的处理方法。
// 设置回调
/// Adds a handler to be called once the request has finished.
    @discardableResult
    public func responseJSON(
        queue: DispatchQueue? = nil,
        options: JSONSerialization.ReadingOptions = .allowFragments,
        completionHandler: @escaping (DataResponse<Any>) -> Void)
        -> Self
    {
        return response(
            queue: queue,
            responseSerializer: DataRequest.jsonResponseSerializer(options: options),
            completionHandler: completionHandler
        )
    }
  • Request.responseJSON实际上是调用Request.response,将回调添加到Request.delegate.queue,然后等待响应事件。
    /// Adds a handler to be called once the request has finished.
    @discardableResult
    public func response<T: DataResponseSerializerProtocol>(
        queue: DispatchQueue? = nil,
        responseSerializer: T,
        completionHandler: @escaping (DataResponse<T.SerializedObject>) -> Void)
        -> Self
    {
        delegate.queue.addOperation {
            let result = responseSerializer.serializeResponse(
                self.request,
                self.response,
                self.delegate.data,
                self.delegate.error
            )

            var dataResponse = DataResponse<T.SerializedObject>(
                request: self.request,
                response: self.response,
                data: self.delegate.data,
                result: result,
                timeline: self.timeline
            )

            dataResponse.add(self.delegate.metrics)

            (queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
        }

        return self
    }

  • 至此,发送流程完成,接着就等待响应事件。

请求响应流程

  • 一个请求的响应事件会有多个,并按循序上报,例如以下几个主要事件,

  • HTTPS鉴权事件 func urlSession(_ session: URLSession,task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

  • 收到Response响应头事件 func urlSession(_ session: URLSession,dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)

  • 收到Response Body数据事件 func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)

  • 响应流程完成事件 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)

  • 本文以最后一个响应流程完成事件为例,梳理下整个响应流程。

  • 首先,SessionDelegate会收到由URLSession.delegate上报的urlSession:task:didCompleteWithError,根据task找到URLSessionTask并通过其delegate上报事件给TaskDelegate

    /// Tells the delegate that the task finished transferring data.
    open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        /// Executed after it is determined that the request is not going to be retried
        let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
            guard let strongSelf = self else { return }

            if let taskDidComplete = strongSelf.taskDidComplete {
                taskDidComplete(session, task, error)
            } else if let delegate = strongSelf[task]?.delegate {
                delegate.urlSession(session, task: task, didCompleteWithError: error)
            }

            NotificationCenter.default.post(
                name: Notification.Name.Task.DidComplete,
                object: strongSelf,
                userInfo: [Notification.Key.Task: task]
            )

            strongSelf[task] = nil
        }

        guard let request = self[task], let sessionManager = sessionManager else {
            completeTask(session, task, error)
            return
        }

        // Run all validations on the request before checking if an error occurred
        request.validations.forEach { $0() }

        // Determine whether an error has occurred
        var error: Error? = error

        if let taskDelegate = self[task]?.delegate, taskDelegate.error != nil {
            error = taskDelegate.error
        }

        /// If an error occurred and the retrier is set, asynchronously ask the retrier if the request
        /// should be retried. Otherwise, complete the task by notifying the task delegate.
        if let retrier = retrier, let error = error {
            retrier.should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, timeDelay in
                guard shouldRetry else { completeTask(session, task, error) ; return }

                DispatchQueue.utility.after(timeDelay) { [weak self] in
                    guard let strongSelf = self else { return }

                    let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false

                    if retrySucceeded, let task = request.task {
                        strongSelf[task] = request
                        return
                    } else {
                        completeTask(session, task, error)
                    }
                }
            }
        } else {
            completeTask(session, task, error)
        }
    }
  • 接着,TaskDelegate收到该事件后,恢复queue队列,按循序执行其中的回调,如ResponseJSON
    @objc(URLSession:task:didCompleteWithError:)
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let taskDidCompleteWithError = taskDidCompleteWithError {
            taskDidCompleteWithError(session, task, error)
        } else {
            if let error = error {
                if self.error == nil { self.error = error }

                if
                    let downloadDelegate = self as? DownloadTaskDelegate,
                    let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
                {
                    downloadDelegate.resumeData = resumeData
                }
            }

            // queue队列中的operaion开始按循序执行,回调到上层。
            queue.isSuspended = false
        }
    }

其他模块

  • 除了发送,响应相关的代码,Alamofire还有许多其他模块。例如,

  • NetworkReachabilityManager管理网络状态。

  • ParameterEncoding 入参编解码方式。

  • ResponseSerialization 响应的反序列化方式。

  • ServerTrustPolicy HTTPS的鉴权

  • 等等。

总结

  • 分析得比较简单,抱砖引玉。
2017/1/24 posted in  iOS

* 经过一个月多的努力,app终于成型了,欣喜,期待上线后效果,在这里稍作技术上的总结和展望。

  • 虽然是首版,功能不多,但麻雀虽小五脏俱全,详情、评论、登录、分享、支付、推送、地图、数据统计、错误统计、Hotfix等基础功能也都要有。

  • 分层,从上到下,将整体框架划分为业务层,基础服务层,容器运行层,和三方库层。

  • 组件划分,业务组件化、服务组件化、基础功能组件化

  • 考虑前面几个版本的快速开发迭代,只做了逻辑上的分层和组件划分,代码仍是在同一个工程内,没有做Bundle化。

  • 应用整体框架如下,具体细节暂不展开了,后续有时间再细化总结

应用整体框架
APP闪屏页

2016/6/25 posted in  杂想

iOS学习导图

2016/6/19 posted in  iOS

iOS Push相关的资料

  • 苹果官方的APNS,支持App不同状态下(前台、后台、未启动等)的消息推送。

  • 另外个方案是,App和应用服务间维护一个长链接,应用服务通过心跳感知App状态。当App为在线状态时,应用服务可以通过该长链接推送消息。一般跟APNS配合使用。

  • 自己的项目中使用的是友盟推送。

  • 在Push消息中设置自定义参数,App在- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo收到消息后解析参数并做相应逻辑(如跳转到相应页面等)

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSLog(@"didRegisterForRemoteNotificationsWithDeviceToken");
    
    // 注册用户登录登出监听
    [self handleRemoteNotificationDidRegistered];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@"didFailToRegisterForRemoteNotificationsWithError");
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    NSLog(@"didReceiveRemoteNotification");
    
    [UMessage didReceiveRemoteNotification:userInfo];
    
    NSString *jsonString = [userInfo objectForKey:@"custom"];
    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
                                                        options:NSJSONReadingMutableContainers
                                                          error:nil];
    if (dic==nil || [dic isEqual:[NSNull null]]) {
        return;
    }

    [self handleRemoteNotification:dic];
}

其他一些资料

2016/6/18 posted in  iOS

移动安全

安全检查项

  • 移动App上关键数据(密码、个人信息等)在存储、传输等各个阶段需要做一定的安全保护。下图罗列了常见的检查项。 app_security_checks

传输安全

  • 保密性,消息是加密的,内容没有泄露,可以用对称加密算法进行加解密,如AES256。

  • 完整性,消息是完整的,内容没有被篡改,对消息做哈希得到摘要,作为消息的签名。哈希算法有SHA256等,MD5、SHA1已经不太安全,尽量用SHA256等相对安全的算法。

  • 真实性,消息是可靠的,内容来自受信的来源,验证来源的数字证书是否可靠。

常见工具

  • class-dump-z
    一个跨平台的 Objective-C 接口提取器,用于分析 iPhoneOS 可执行文件中私有的 API。需 通过 Cydia 安装。

  • darwin cc tools (otools) 一个开源的苹果程序编译和连接器。需通过 Cydia 安装。

  • HTTP代理抓包工具(Fiddler)

  • Plist文件查看器(plistEditor)

  • iOS内存修改工具(iGameGuard)

  • 内购破解工具(LocalIAPStore、iAPFree)

  • keychain-dumper
    该工具可以读取已越狱 IOS 设备中的 keychain。需通过 Cydia 安装。

  • Link Identity Editor (ldid)
    该工具可以协助测试人员修改 Mach-O 二进制文件的签名信息。需通过 Cydia 安装。

  • OpenSSH
    OpenSSH 是 Linux 下常用的服务,装上后设备可充当 SSH 服务端。需通过 Cydia 安装。

  • Snoop-it
    IOS APP 安全评估工具。可对 APP 进行静态、动态分析。需通过 Cydia 安装。

相关博文

2016/5/4 posted in  移动开发

移动三方应用分享

iOS原生分享控件

    NSString *textToShare = @"要分享的文本内容";
    UIImage *imageToShare = [UIImage imageNamed:@"shop"];
    NSURL *urlToShare = [NSURL URLWithString:@"http://blog.csdn.net/hitwhylz"];
    NSArray *activityItems = @[textToShare, imageToShare, urlToShare];
    
    UIActivityViewController *vc = [[UIActivityViewController alloc]initWithActivityItems:activityItems applicationActivities:nil];
    [self presentViewController:vc animated:YES completion:nil];

三方开放平台

URL Schemes

  • iOS应用间可以通过URL Schemes解决相互通信的问题。
  • DemoA通过openURL唤起DemoB,并带上参数。
DemoA

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"demob://page?sourcescheme=demoa"]];
  • DemoB收到并解析参数并作逻辑处理,然后同样通过openURL返回到DemoA,并带上参数。
DemoB

- (BOOL)handleUrl:(NSURL *)url {
    NSArray *querys = [url.query componentsSeparatedByString:@"&"];
    
    NSMutableDictionary *queryDict = [[NSMutableDictionary alloc] init];
    for (NSString *q in querys) {
        NSArray *kv = [q componentsSeparatedByString:@"="];
        if (kv.count == 2) {
            queryDict[kv[0]] = kv[1];
        }
    }
    
    NSString *returnScheme = queryDict[@"sourcescheme"];
    
    UIAlertController *vc = [UIAlertController alertControllerWithTitle:@"url"
                                                                message:url.absoluteString
                                                         preferredStyle:UIAlertControllerStyleAlert];
    [vc addAction:[UIAlertAction actionWithTitle:@"返回原应用"
                                           style:UIAlertActionStyleDefault
                                         handler:^(UIAlertAction * _Nonnull action) {
                                             [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://", returnScheme]]];
                                         }]];
    
    [vc addAction:[UIAlertAction actionWithTitle:@"留在当前应用"
                                           style:UIAlertActionStyleCancel
                                         handler:^(UIAlertAction * _Nonnull action) {
                                         }]];
    
    [self.window.rootViewController presentViewController:vc animated:YES completion:nil];
    
    return YES;
}

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url NS_DEPRECATED_IOS(2_0, 9_0, "Please use application:openURL:options:") __TVOS_PROHIBITED {
    return [self handleUrl:url];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation NS_DEPRECATED_IOS(4_2, 9_0, "Please use application:openURL:options:") __TVOS_PROHIBITED {
    return [self handleUrl:url];
}

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options NS_AVAILABLE_IOS(9_0) {
    return [self handleUrl:url];

}

urlschemedemo

iosappsinteraction

三方分享或认证

  • 三方应用分享或认证都会用到URL Schemes流程,实际过程和参数会更复杂点,还需要考虑安全性。
  • 如微信三方登录认证,除了三方应用通过URL Scheme跳转微信拿到code,再通过https请求微信后台拿到access_token,后续用https带上access_token可以请求各种资源。
    weixinlogin

  • 三方分享则是通过URL Schemes带上不同格式的数据(文本、图片、链接等)。

2016/5/3 posted in  移动开发

模式和架构

编程模式

  • 函数式编程 vs 面向对象编程
  • 响应式编程 vs 命令式编程
  • promise

架构、框架、设计模式:

  • 1、架构属于顶层的设计,划分软件或解决方案的整体结构,倾向于抽象,常见的有分层架构、事件驱动架构、微内核架构、微服务架构等。
  • 2、框架类似于提供工具库或类库,倾向于实现。
  • 3、设计模式应该是更细力度的代码层级的复用、可维护性、可扩展性的设计。

设计模式:

  • 1、基于某种具体场景,代码组织结构或交互行为的设计方法,使得代码更易于复用、维护、扩展。
  • 2、基本的6个原则:开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、单一职责原则、最少知识原则。
  • 3、创建型模式:工厂方法、抽象工厂、原型、单例、builder
  • 4、结构型:适配器、组合、桥接、decorator、facade、享元、proxy
  • 5、行为型:解释器、模版方法、责任链、命令、迭代器、中介者、memento、观察者、状态、策略、访问者等。

相关文章

23种设计模式

系统架构师

前篇

  1、系统架构师-基础到企业应用架构系列之--开卷有益
  2、系统架构师-基础到企业应用架构-系统建模[上篇]
  3、系统架构师-基础到企业应用架构-系统建模[中篇](上)
  4、系统架构师-基础到企业应用架构-系统建模[中篇](下)
  5、系统架构师-基础到企业应用架构-系统建模[下篇]
  6、系统架构师-基础到企业应用架构-系统设计规范与原则[上篇]
  7、系统架构师-基础到企业应用架构-系统设计规范与原则[下篇]
  8、系统架构师-基础到企业应用架构-设计模式[上篇]
  9、系统架构师-基础到企业应用架构-设计模式[中篇]
  10、系统架构师-基础到企业应用架构-设计模式[下篇]

中篇

  11、系统架构师-基础到企业应用架构-企业应用架构
  12、系统架构师-基础到企业应用架构-分层[上篇]
  13、系统架构师-基础到企业应用架构-分层[中篇]
  14、系统架构师-基础到企业应用架构-分层[下篇]
  15、系统架构师-基础到企业应用架构-表现层
  16、系统架构师-基础到企业应用架构-服务层
  17、系统架构师-基础到企业应用架构-业务逻辑层
  18、系统架构师-基础到企业应用架构-数据访问层
  19、系统架构师-基础到企业应用架构-组件服务
  20、系统架构师-基础到企业应用架构-安全机制

后篇

  21、单机应用、客户端/服务器、多服务、企业数据总线全解析
  22、系统架构师-基础到企业应用架构-单机应用(实例及demo)
  23、系统架构师-基础到企业应用架构-客户端/服务器(实例及demo)
  24、系统架构师-基础到企业应用架构-多服务(实例及demo)
  25、系统架构师-基础到企业应用架构-企业数据总线(实例及demo)
  26、系统架构师-基础到企业应用架构-性能优化(架构瓶颈)
  27、系统架构师-基础到企业应用架构-完整的架构方案实例[上篇]
  28、系统架构师-基础到企业应用架构-完整的架构方案实例[中篇]
  29、系统架构师-基础到企业应用架构-完整的架构方案实例[下篇]
  30、系统架构师-基础到企业应用架构-总结及后续
2016/5/3 posted in  架构

iOS App安装包过大

2016/4/29 posted in  iOS

iOS设备ID

NSString *identifierForVendor = [[UIDevice currentDevice].identifierForVendor UUIDString];
NSString *identifierForAdvertising = [[ASIdentifierManager sharedManager].advertisingIdentifier UUIDString];

设备标识
identifierForVendor,
app在一个device上的标识,从ios6开始支持。一个设备上相同vendor的app获取到相同值,不同vendor则不同值。
app如果从App Store直接下载,该值由App Store分配,如果app是企业版、Ad Hoc、或开发版,则该值由bundle identifier计算。
设备上删除所有该Vendor的apps后,下次再安装app,值会发生变化。
If the value is nil, wait and get the value again later. This happens, for example, after the device has been restarted but before the user has unlocked the device.

advertisingIdentifier
一个设备上所有app获取到都是相同的值。
If the value is nil, wait and get the value again later. This happens, for example, after the device has been restarted but before the user has unlocked the device.

iOS获取设备唯一标识的各种方法?IDFA、IDFV、UDID分别是什么含义?

[摘要:1、UDID (Unique Device Identifier) UDID的齐称是Unique Device Identifier,望文生义,它便是苹果IOS装备的独一辨认码,它由40个字符的字母战数字构成。正在良多须要限定]

一、UDID (Unique Device Identifier)

UDID的全称是Unique Device Identifier,顾名思义,它就是苹果IOS设备的唯一识别码,它由40个字符的字母和数字组成。在很多需要限制一台设备一个账号的应用中经常会用到。在iOS5中可以获取到设备的UDID,后来被苹果禁止了。

二、UUID(Universally Unique Identifier)

UUID是Universally Unique Identifier的缩写,中文意思是通用唯一识别码。它是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。这样,每个人都可以建立不与其它人冲突的 UUID。在此情况下,就不需考虑数据库建立时的名称重复问题。苹果公司建议使用UUID为应用生成唯一标识字符串。
开发者可以在应用第一次启动时调用一 次,然后将该串存储起来,替代UDID来使用。但是,如果用户删除该应用再次安装时,又会生成新的字符串,所以不能保证唯一识别该设备。使用UUID,就要考虑应用被删除后再重新安装时的处理。一个解决的办法是:UUID一般只生成一次,保存在iOS系统里面,如果应用删除了,重装应用之后它的UUID还是一样的,除非系统重置 。但是不能保证在以后的系统升级后还能用(如果系统保存了该信息就能用)。

三、MAC Address

用来表示互联网上每一个站点的标识符,采用十六进制数表示,共六个字节(48位)。其中,前三个字节是由IEEE的注册管理机构 RA负责给不同厂家分配的代码(高位24位),也称为“编制上唯一的标识符” (Organizationally Unique Identifier),后三个字节(低位24位)由各厂家自行指派给生产的适配器接口,称为扩展标识符(唯一性)。

MAC地址在网络上用来区分设备的唯一性,接入网络的设备都有一个MAC地址,他们肯定都是不同的,是唯一的。一部iPhone上可能有多个MAC地址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一个WIFI的,因此只需获取WIFI的MAC地址就好了,也就是en0的地址。
MAC地址就如同我们身份证上的身份证号码,具有全球唯一性。这样就可以非常好的标识设备唯一性,类似与苹果设备的UDID号,通常的用途有:1)用于一些统计与分析目的,利用用户的操作习惯和数据更好的规划产品;2)作为用户ID来唯一识别用户,可以用游客身份使用app又能在服务器端保存相应的信息,省去用户名、密码等注册过程。
使用Mac地址生成设备的唯一标识主要分三种:
1、直接使用“MAC Address”
2、使用“MD5(MAC Address)”
3、使用“MD5(Mac Address+bundle_id)”获得“机器+应用”的唯一标识(bundle_id 是应用的唯一标识)
在iOS7之后,如果请求Mac地址都会返回一个固定值。
四、IDFA(identifierForIdentifier)

广告标示符,适用于对外:例如广告推广,换量等跨应用的用户追踪等。

是iOS 6中另外一个新的方法,提供了一个方法advertisingIdentifier,通过调用该方法会返回一个NSUUID实例,最后可以获得一个UUID,由系统存储着的。不过即使这是由系统存储的,但是有几种情况下,会重新生成广告标示符。如果用户完全重置系统((设置程序 -> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。另外如果用户明确的还原广告(设置程序-> 通用 -> 关于本机 -> 广告 -> 还原广告标示符) ,那么广告标示符也会重新生成。关于广告标示符的还原,有一点需要注意:如果程序在后台运行,此时用户“还原广告标示符”,然后再回到程序中,此时获取广 告标示符并不会立即获得还原后的标示符。必须要终止程序,然后再重新启动程序,才能获得还原后的广告标示符。

在同一个设备上的所有App都会取到相同的值,是苹果专门给各广告提供商用来追踪用户而设的,用户可以在 设置|隐私|广告追踪 里重置此id的值,或限制此id的使用,故此id有可能会取不到值,但好在Apple默认是允许追踪的,而且一般用户都不知道有这么个设置,所以基本上用来监测推广效果,是戳戳有余了。

注意:由于idfa会出现取不到的情况,故绝不可以作为业务分析的主id,来识别用户。

代码:

import

NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
五、IDFV(identifierForVendor)

Vindor标示符,适用于对内:例如分析用户在应用内的行为等。

是给Vendor标识用户用的,每个设备在所属同一个Vender的应用里,都有相同的值。其中的Vender是指应用提供商,但准确点说,是通过BundleID的DNS反转的前两部分进行匹配,如果相同就是同一个Vender,例如对于com.somecompany.appone,com.somecompany.apptwo 这两个BundleID来说,就属于同一个Vender,共享同一个idfv的值。和idfa不同的是,idfv的值是一定能取到的,所以非常适合于作为内部用户行为分析的主id,来标识用户,替代OpenUDID。

注意:如果用户将属于此Vender的所有App卸载,则idfv的值会被重置,即再重装此Vender的App,idfv的值和之前不同。

代码:

NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
六、OPEN UDID

每台iOS设备的OpenUDID是通过第一个带有OpenUDID SDK包的App生成,如果你完全删除全部带有OpenUDID SDK包的App(比如恢复系统等),那么OpenUDID会重新生成,而且和之前的值会不同,相当于新设备;
优点是没有用到MAC地址;不同设备能够获取各自唯一的识别码,保证了唯一性,可以用于以往UDID的相关用途;从代码分析OpenUDID的获取,识别码获取方便并且保存谨慎。缺点是当将设备上所有使用了OpenUDID方案的应用程序删除,且设备关机重启,xcode彻底清除并重启,重装应用程序去获取OpenUDID,此时OpenUDID变化,与之前不一样了,所有OpenUDID应用卸载后,由UIPasteboard保存的数据即被清除,重装故会重新获取新的OpenUDID。

那么当因为用户干预或者恶意程序,致使UIPasteboard数据清除,从而导致OpenUDID被删除,重装也会获取新的OpenUDID。

OpenUDID生成唯一识别码的代码: 

unsigned char result[16];

const charchar *cStr = [[[NSProcessInfo processInfo] globallyUniqueString] UTF8String];

CC_MD5( cStr, strlen(cStr), result );

_openUDID = [NSString stringWithFormat:

@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%08x",

result[0], result[1], result[2], result[3],

result[4], result[5], result[6], result[7],

result[8], result[9], result[10], result[11],

result[12], result[13], result[14], result[15],

arc4random() % 4294967295];

2016/4/27 posted in  iOS

iOS程序生命周期

程序的生命周期

  • 程序的生命周期,包含非运行、非激活、激活、后台、挂起五个状态

  • 状态间切换都会有响应的回调和通知

  • 程序挂起状态将接收不到Terminate通知,因此进入后台,能够处理程序随时被结束。 applicationWillTerminate:—Lets you know that your app is being terminated. This method is not called if your app is suspended.

程序如何在后台运行

  • 1、短时间运行,进入后台后,可以调用beginBackgroundTaskWithName方法启动有限时间的任务。
  • 2、NSURLSession支持后台下载。
  • 3、长时间运行,支持以下几种模式,音频及视频(airplay)播放、更新定位、VOIP、下载Newsstand、外部附件、蓝牙、push,在info.plist添加相应配置,代码中引入相应的framework并调用相应接口。
  • 调用application:performFetchWithCompletionHandler: 在后台获取少量数据。
2016/4/27 posted in  iOS

Deep Linking

2016/4/12 posted in  移动开发

iOS Touch事件处理流程

概述

Hit-Test找第一响应视图

  1. 用户在屏幕上的点击、滑动等都会转换成Touch事件,并加到待处理队列。
  2. 接着,RunLoop设置的回调__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__被触发,然后由_UIApplicationHandleEventQueue从待处理队列取出事件并分发给UIApplication
  3. -[UIApplication sendEvent:]分发事件到UIWindow
  4. UIWindow先通过hit-test递归找出第一响应的视图,然后再从第一响应视图开始处理该事件。这里可以重载这两个函数改变第一响应视图的查找,- [pointInside:withEvent:]-[hitTest:withEvent:]

基于响应链的事件传递

  1. 第一响应视图通过重载以下几个方法-[touchesBegan:withEvent:]-[touchesMoved:withEvent:]-[touchesEnded:withEvent:]开始处理touch事件。
  2. 如果第一响应视图上存在Gesture,则Gesture也会收到touch事件并将自己置为UIGestureRecognizerStatePossible状态。
  3. Gesture识别到对应手势后,则切换到UIGestureRecognizerStateBegan状态,并cancel掉第一响应视图的事件处理-[touchesCancelled:withEvent:]
  4. 如果存在多个Gesture都在识别手势,则可以通过Gesture的属性和委托处理多个Gesture的协同问题。
  5. 如果第一响应视图没有处理,则事件沿着响应链继续传递。(默认的响应链是当前视图->父视图->当前视图控制器->UIWindow->UIApplication
2015/9/16 posted in  iOS

OC Runtime(6)之Load&Initialize

+load

  • +load方法只会被执行一次,且是在程序加载阶段,这时运行环境并不完备。
  • 不同类或类别的+load方法会按一定顺序被执行。
1、依赖库的`+load`。
2、当前库`+load`,`class`的`+load`优先于`category`,父`class`优先于子`class`。
3、当前库的`All C++ static initializers and C/C++ __attribute__(constructor) functions`。
4、被依赖库的`+load`。

  • +load方法中可以安全地发送消息给当前库内其他类的消息,虽然这些类的+load可能还没有执行过。

  • 从源码中,也可以看出class+load优先于category,父class优先于子class

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();

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

    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &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);
    }
}
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); 
}

initialize

  • 向类或对象发送消息时,会调用lookUpImpOrForward查找,并会检查是否初始化过,如果没有,则会调用_class_initialize做初始化。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    // ...

    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    // ...

    return imp;
}

参考

2014/4/9 posted in  iOS

OC Runtime(5)之Method Swizzling

Method的几个概念

  • Method包含方法名Selector、函数地址IMP、参数类型TypeEncoding
NSLog(@"    method name = %s", sel_getName(method_getName(method)));
NSLog(@"    method type encode = %s", method_getTypeEncoding(method));
NSLog(@"    NumberOfArguments = %d", method_getNumberOfArguments(method));
NSLog(@"    ReturnType = %s", method_copyReturnType(method));
2016-04-08 20:54:59.351 OCRuntime[54195:953880]     method name = setNumberProperty:
2016-04-08 20:54:59.352 OCRuntime[54195:953880]     method type encode = v24@0:8@16
2016-04-08 20:54:59.352 OCRuntime[54195:953880]     NumberOfArguments = 3
2016-04-08 20:54:59.352 OCRuntime[54195:953880]     ReturnType = v

动态修改Method的几个接口

// 设置或交换IMP
IMP method_setImplementation(Method m, IMP imp);
void method_exchangeImplementations(Method m1, Method m2);
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
                                 
// 如果原来没有这个方法,则直接添加Method,效果同class_addMethod
// 如果原来有,则替换掉原来的IMP,忽略参数类型
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);
  • 例子:
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        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);
        }
    });
}

参考

2014/4/2 posted in  iOS

OC Runtime(4)之Message Forwarding

消息发送

  • 对象方法的调用就是向该对象发送消息。
    OCHelper *object = [[OCHelper alloc] init];
    
    // 以下两句代码效果是相同的。
    [object onHandler];
    objc_msgSend(object, @selector(onHandler));
  • 编译阶段,会自动将OC方法调用转化成内联的objc_msgSend方法调用。

消息转发

  • 有了OC的消息机制,就可以在运行时修改消息执行路径。
  • objc_msgSend会调用lookUpImpOrForward从类(或父类)的methodsCachemethodList寻找方法。

  • lookUpImpOrForward的源码如下:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP methodPC = nil;
    Method meth;
    bool triedResolver = NO;

    methodListLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        methodPC = _cache_getImp(cls, sel);
        if (methodPC) return methodPC;    
    }

    // Check for freed class
    if (cls == _class_getFreedObjectClass())
        return (IMP) _freedHandler;

    // Check for +initialize
    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    // The lock is held to make method-lookup + cache-fill atomic 
    // with respect to method addition. Otherwise, a category could 
    // be added but ignored indefinitely because the cache was re-filled 
    // with the old value after the cache flush on behalf of the category.
 retry:
    methodListLock.lock();

    // Ignore GC selectors
    if (ignoreSelector(sel)) {
        methodPC = _cache_addIgnoredEntry(cls, sel);
        goto done;
    }

    // Try this class's cache.

    methodPC = _cache_getImp(cls, sel);
    if (methodPC) goto done;

    // Try this class's method lists.

    meth = _class_getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        log_and_fill_cache(cls, cls, meth, sel);
        methodPC = method_getImplementation(meth);
        goto done;
    }

    // Try superclass caches and method lists.

    curClass = cls;
    while ((curClass = curClass->superclass)) {
        // Superclass cache.
        meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
        if (meth) {
            if (meth != (Method)1) {
                // Found the method in a superclass. Cache it in this class.
                log_and_fill_cache(cls, curClass, meth, sel);
                methodPC = method_getImplementation(meth);
                goto done;
            }
            else {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method 
                // resolver for this class first.
                break;
            }
        }

        // Superclass method list.
        meth = _class_getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            log_and_fill_cache(cls, curClass, meth, sel);
            methodPC = method_getImplementation(meth);
            goto done;
        }
    }

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        methodListLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    _cache_addForwardEntry(cls, sel);
    methodPC = _objc_msgForward_impcache;

 done:
    methodListLock.unlock();

    // paranoia: look for ignored selectors with non-ignored implementations
    assert(!(ignoreSelector(sel)  &&  methodPC != (IMP)&_objc_ignored_method));

    return methodPC;
}
  • 如果找到方法,则直接执行。
  • 如果没有找到方法,则提供一套Hock机制,允许运行时解决。
  • NSObject提供了些可重载的方法,如下:
@interface NSObject
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
@end
  • 如果没有找到方法,会先调用resolveInstanceMethod,在该方法中可动态添加方法到类。

messaging

  • 如果不行,接下来会调用forwardingTargetForSelector转发,可动态替换掉响应对象。
  • 如果还不行,则调用methodSignatureForSelectorforwardInvocation转发Invocation
  • 如果还不行,则会抛异常doesNotRecognizeSelector

参考

2014/3/21 posted in  iOS

OC Runtime(3)之Associated Objects

关联对象

  • 提供了三个方法,分别是get、set、remove
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);

源码

  • 获取
id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
    return value;
}
  • 设置
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}
  • 释放
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);

    free(obj);

    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = !UseGC && obj->hasAssociatedObjects();
        bool dealloc = !UseGC;

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        if (dealloc) obj->clearDeallocating();
    }

    return obj;
}

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

参考

2014/3/18 posted in  iOS

OC Runtime(2)之Ivar&Property&Method&Protocol

Ivar

struct old_ivar {
    char *ivar_name;
    char *ivar_type;
    int ivar_offset;
#ifdef __LP64__
    int space;
#endif
};

Property

struct old_property {
    const char *name;
    const char *attributes;
};

Method

struct old_method {
    SEL method_name;
    char *method_types;
    IMP method_imp;
};

protocol

struct old_protocol {
    Class isa;
    const char *protocol_name;
    struct old_protocol_list *protocol_list;
    struct objc_method_description_list *instance_methods;
    struct objc_method_description_list *class_methods;
};
struct objc_method_description *
lookup_protocol_method(old_protocol *proto, SEL aSel, 
                       bool isRequiredMethod, bool isInstanceMethod, 
                       bool recursive)
{
    struct objc_method_description *m = nil;
    old_protocol_ext *ext;

    if (isRequiredMethod) {
        if (isInstanceMethod) {
            m = lookup_method(proto->instance_methods, aSel);
        } else {
            m = lookup_method(proto->class_methods, aSel);
        }
    } else if ((ext = ext_for_protocol(proto))) {
        if (isInstanceMethod) {
            m = lookup_method(ext->optional_instance_methods, aSel);
        } else {
            m = lookup_method(ext->optional_class_methods, aSel);
        }
    }

    if (!m  &&  recursive  &&  proto->protocol_list) {
        int i;
        for (i = 0; !m  &&  i < proto->protocol_list->count; i++) {
            m = lookup_protocol_method(proto->protocol_list->list[i], aSel, 
                                       isRequiredMethod,isInstanceMethod,true);
        }
    }

    return m;
}

category

struct old_category {
    char *category_name;
    char *class_name;
    struct old_method_list *instance_methods;
    struct old_method_list *class_methods;
    struct old_protocol_list *protocols;
    uint32_t size;
    struct old_property_list *instance_properties;
};

参考

2014/3/17 posted in  iOS

OC Runtime(1)之Object&Class

Objective-C Runtime

OC-Runtime

  • Objective-C是面向运行时的编程语言,这就意味着运行阶段才知道如何执行,而不是编译链接阶段就确定好。

  • What is the Objective-C Runtime?

The Objective-C Runtime is a Runtime Library, it's a library written 
mainly in C & Assembler that adds the Object Oriented capabilities 
to C to create Objective-C. This means it loads in Class information, 
does all method dispatching, method forwarding, etc. The Objective-C 
runtime essentially creates all the support structures that make 
Object Oriented Programming with Objective-C Possible.
  • 有了Objective-C Runtime,就有了各种在运行时修改代码的hack手段。

  • 先看下的结构体定义,除了存放类的基本信息,还存放对象的变量、方法、协议的元信息。程序加载时,结构体会被实例化,并放到全局列表中g_classList,结构体的isasuper_classmethodLists等属性也一起被初始化。
// 伪代码,对源码稍做修改。

static Class *g_classList;

struct objc_class {
    Class isa;          // 指向元类
    Class super_class;  // 指向父类
    
    // 类基本信息
    const char *name;
    long version;
    long info;
    long instance_size;
    
    // 类的变量、方法、协议的元信息
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_protocol_list *protocols;
    
    // 方法缓存
    struct objc_cache *cache;
};
typedef struct objc_class *Class;

bool isMetaClass() {
        return info & CLS_META;
    }

元类

  • 元类使用相同的结构体,只是通过isMetaClass方法做区分。
  • 元类结构体中,存放类的变量、方法、协议的元信息。

  • 对象、类、元类的关系如下:

OC-Runtime-类图

对象

  • 对象的结构体如下,存放对象的变量数据,其他的都是先通过isa找到类,再从类中找出变量、方法等的元信息。
// 伪代码,对源码稍做修改。

struct objc_object {
    Class isa;
    
    // 变量的数据
    void *varsLayout;
}

typedef struct objc_object *id
  • 创建对象,NSObject *obj = [[NSObject alloc] init];可能对应以下一些动作。
// 伪代码

{
    // 找到类
    Class cls = findClass("NSObject");
    
    // 找到alloc方法,执行生成对象
    IMP imp = findImp(cls, "alloc");
    id obj = imp(cls);
    
    // 找到init方法,执行初始化对象
    IMP imp = findImp(cls, "init");
    imp(obj);
    
    // 返回对象
    return obj;
}   
    
id alloc(Class cls) {
    id obj = malloc(sizeof(struct objc_object));
    obj->isa = cls;
    return obj;
}

id init(id obj) {
    obj->varsLayout = xxx;
}   

总结

2014/3/12 posted in  iOS