Fishhook
Mach-O文件结构 https://www.jianshu.com/p/1f22d1e667e3
MachOView
- (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
- iOS之LLDB常用命令 https://www.jianshu.com/p/7fb43e0b956a
iOS存储
存储方式
- NSUserDefault,可以存放Key-Value对形式的轻量数据。
- NSKeyedArchiver,存储对象到二进制文件。
- writeToFile,将NSString、NSArray、NSDictionary、NSData对象存储到文件。
- CoreData.Framework,对象管理并支持对象持久。
- Sqlite,轻量数据库
- Keychain,钥匙串存储
- NSUbiquitousKeyValueStore,存储数据到iCloud
关于安全性
- 敏感数据不要明文存储
- 存放在Keychain,不越狱还是安全的
- Safety is relative, choose a safer way.
三方库
- AwesomeCache
- YYCache 基于Disk、Memory的缓存
- YTKKeyValueStore 基于Sqlite的健值对数据的存储
- SwiftyUserDefaults
- FMDB
- ObjectiveRecord CoreData的封装
- realm Realm is a mobile database: a replacement for Core Data & SQLite.
- Valet 基于Keychain的数据存储,接口简洁易用,支持同一开发者下应用间数据共享,支持iCloud数据同步,通过TouchID或设备密码保护数据。
- UICKeyChainStore
- KeychainAccess
- Keychain-Dumper Keychain is not safe.
参考
iOS测试框架XCTest
- XCTest已基本满足单元测试和UI测试的需求,支持对同步流程、异步流程、性能的测试,UI测试代码的自动生成,代码测试覆盖率的统计等。
- UI测试也还有不足的地方,例如录制生成的代码仍不够完善,无法直接访问目标APP的接口,无法修改边界条件,在单设备或单模拟器运行效率过低,每次重启目标App后只能运行一个Case。
- 一些三方库
- Bluepill is a tool to run iOS tests in parallel using multiple simulators.
- XCTest官方手册
- iOS单元测试和UI测试
- 单元测试之旅--预见优秀
- iOS UnitTest单元测试
- Android单元测试的整理
Alamofire源码分析
概述
Alamofire
跟AFNetworking的功能差不多,都是对URLSession的封装,对上层提供易用的网络请求接口。Alamofire
和AFNetworking分别是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.request
,Request
被组装创建,并加到发送队列中,然后等待一系列的响应事件。而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的鉴权等等。
总结
- 分析得比较简单,抱砖引玉。
iOS学习导图
本文简单罗列了下iOS相关的点。
基础
- Runloop
- 多线程&同步
- Core Animation
Core Animation编程指南
iOS-Core-Animation-Advanced-Techniques
- Block
- GCD
- Core Foundation
应用优化
OC Runtime(1)之Object&Class
Objective-C 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
,结构体的isa
、super_class
、methodLists
等属性也一起被初始化。
// 伪代码,对源码稍做修改。
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
方法做区分。元类
结构体中,存放类的变量、方法、协议的元信息。对象、类、元类的关系如下:
对象
- 对象的结构体如下,存放对象的变量数据,其他的都是先通过
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;
}
总结
- 总的来说,全局维护一张类表,存放类方法、实例方法、实例变量的元信息等。程序加载阶段会初始化这张表。运行阶段,也可以通过
addMethod
、class_replaceMethod
、class_addIvar
、class_addProtocol
、class_addProperty
等接口动态修改这张表。
### 参考 - ObjCRuntimeGuide
- runtime源码
- Objective-C 的动态提示和技巧
- NSObject的load和initialize方法
- associated-objects
Copyright © 2015 Powered by MWeb, Theme used GitHub CSS.