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

iOS审核

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

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

2019/1/23 posted in  iOS

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

问题: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

iOS存储

2017/6/22 posted in  iOS

iOS测试框架XCTest

2017/6/3 posted in  iOS

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

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

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

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