Love at First Launch

  • 直接显示内容而不是弹出需要注册登录的页面
  • 通过交互引导用户,而不是一些文字引导
  • 延迟请求授权,而不是一下登录之后就开始要权限(定位,相机,图片)。

针对延迟请求,想到一个好主意,就是新建一个类,可以是单例,负责管理这些请求。

App Startup Time: Past, Present, and Future

了解在苹果平台上使用的dyld动态链接器,它是如何在这些年来发生变化的,以及它下一步的发展方向。了解改进的工具如何使优化应用程序的启动时间变得更容易,并了解dyld中的新更改如何进一步改善启动时间。

  • startup time 启动时间

    main函数执行之前所用的时间

  • launch closure启动收尾

    启动你的程序所需要的全部信息,比如使用什么dylib,他们的哪些偏移位置用于不同的符号,代码签名是什么,

Improving App Startup Time

Do less!

  • Ember fewer dylibs
  • Declear fewer classes/methods
  • Use fewer initializers

减少代码,代码越少,启动速度越快。使用更少的dylib,减少嵌入的dylib,使用系统库效果会更好。应该声明较少的库和方法,减少初始化函数( 初始化函数是在main函数执行之前执行 )。

Use more Swift

  • No initializers
  • Swift size improvements

因为Swift从设计上避免了许多的陷阱,在c、c+++、oc可能遇到这些陷阱。
Swift没有初始化器,不允许特定类型的未对齐的数据结构。所以转向Swift可以让你更容易获得快速的程序启动(Apple says)。

Static initilizer tracing

静态初始化追踪器,instrument提供每个静态初始化器的准确时间。方便知道初始化的过程花费了多久的时间。

屏幕快照 2018-12-11 下午3.33.52

dyld3

为什么推出dylb3

提升速度(启动应用的时候尽量多的减少工程量)
增强安全(更积极的安全检查)
可测试性和可靠性

怎么做到上述目标

  • 速度

    • 把复杂操作dylb移出进程

      • 现在大多数dylb只是普通的后台程序
    • 允许部分dylb驻留在进程之中

      • 减少受攻击面积(驻留部分要尽可能少)
      • 提高启动速度
        • 最快的代码就是你不写代码
        • 关注那些你几乎不执行的代码
  • 安全

    • 确定安全敏感性组件身份

      • 边界检查
      • @rpath 攻击
    • 标识可占用缓存的组件

      • 依赖关系不会改变
      • 符号在库中的偏移位置不会发生改变

屏幕快照 2018-12-11 下午5.36.31

dylb3有三个部分

  • 一个进程外的macho 解析编译器
  • 一个进程内的处理闭包(launch closures)的引擎
  • 一个缓存服务的启动闭包(launch closures)

大多数启动的时候使用缓存不会触发进程外的mach-o parser/compiler
launch closures比mach-o更简单
launch closures为速度而构建

使用dylb3要注意什么

完全兼容dylb2.x

  • 一些apis关闭了dylb3的优化导致程序变慢或者会在dylb3中使用回退模式
  • 一些为dylb2.x的优化不再有任何影响

严格的链接语义
在加入新动态连接器后,很多的语义可能现在还无法使用,甚至是错误的。

  • 放入一个支持旧二进制数据的工作区
  • 新的二进制数据可能导致链接错误
2019/9/11 posted in  iOS

Advanced Debugging with Xcode and LLDB

  1. expression语句

    不用重新运行程序直接实现代码效果

    1. 描述

      可以动态的改变条件,语法是expression xxx = "xxx" 后面是语句代码,类似程序中的代码并且支持加{}的判断等等语句。

    2. 使用方法:

      • 调试台去写这个语句
      • 加断点去添加action
  2. Symbolic Breakpoint

    1. 描述

      符号断点,可以针对某一个方法(函数)设置断点并暂停执行;有时候,我们并不清楚会在什么情况下调用某一个函数,那我们可以通过符号断点来跟踪获取调用该函数的程序堆栈。

      屏幕快照 2018-12-10 下午5.04.43

    2. 使用方法

      • 方法一

      添加符号断点,比如如下所述-[UILabel setText:]

      屏幕快照 2018-12-10 下午5.05.33

      还可以添加condition语句 类似如上所述

      • 方法二

      直接在指定的语句添加断点加入action语句,如图所示

      breakpoint set --one-shot true --name "-[UILabel setText:]"

      one-shot 是一个临时断点,一旦触发后就自动删除

  3. 跳过断点所指向的代码

    1. 方式一

      屏幕快照 2018-12-10 下午5.40.21

      移动手柄实现 接下来就可以在控制台中写expression语句
      expression jumpAstronaut(animated:false)

    2. 方式二

      直接编辑断点添加action语句
      屏幕快照 2018-12-10 下午5.47.11

      thread jump --by 1 是跳过一句代码的意思。

  4. watchPoint

    观察指针,值发生改变的时候自动暂停。

    右键某个属性,添加watchPoint,然后左侧会出现观察指针断点监测_tableView的变化,如下所示

    屏幕快照 2018-12-11 上午11.46.38

  5. 创建别名

    command alias poc expression - l objc -O --
    poc 就可以直接代替后面的语句,可以简化常用的命令行语句。

  6. 可以通过内存地址去获取对象

    1. Objective-C

      po 指针 可以直接打印出对象
      屏幕快照 2018-12-11 上午11.12.11

    2. Swift

      不能把数字当成指针去像OC一样打印出对象,要使用expression - l objc -O -- 0x7fa967d781191548b60转换为OC语言。 如果觉得这个语句太臃肿,可以为语句创建别名比如command alias poc expression - l objc -O --之后就可以直接poc 指针就可以达到oc当中同样的效果。

  7. 获取图层树

    1. Objective-C

      调用po [self.view recursiveDescription]打印出类似于这样的图层树。

    2. Swift

      不能这么直接打印出图层树(swift不允许去调用未定义的函数),如果要让swift去像OC一样打印出类似于这样的图层树。
      调用语句

      ```一定要加入反括号(反括号就像预处理器一样,它表示先评估其在当前帧中的内容,并插入结果,然后我们可以评估其余部分) 才能编译通过。
      
  8. unsafeBitCast查询对象的调试描述

    当只有指针,查看对象描述的时候在swift中可以使用 unsafeBitCast函数,给他一个内存地址,他不安全是因为它依赖你来提供正确的类型。

    po unsafeBitCast(指针,to:ScoreBoardView.self)
    还可以可以直接查看view的frame
    po unsafeBitCast(指针,to:ScoreBoardView.self).frame

    同理OC中可以更方便的实现根据指针对对象进行相应的调试,比如更改 label 的内容

    (lldb) expression [(UILabel*)0x7fb2c5f0fd70 setText:@"hello"];
    但是内存地址前面要跟相应的类型要不然会执行失败报错
    error: warning: receiver type 'long' is not 'id' or interface pointer, consider casting it to 'id'
  9. 刷新屏幕的帧缓存区
    如果写了expression语句之后界面没有及时刷新,但是又不想去重新运行代码,那么可以使用表达式
    Swiftexpression CATransaction.flush()
    OCexpression [CATransaction flush]刷新屏幕帧缓存区。

参考

[1]https://developer.apple.com/videos/play/wwdc2018/412/

2019/9/9 posted in  iOS

检测离屏渲染

instrumets 已经取消了debug options

可以在xcode的debug - view debugging 找到

“核心动画工具和模板已在工具中弃用。之前在”调试选项“下的功能已移至Xcode,位于”Debug> View Debugging“下。

BB55F33F-97CC-4C28-B3F1-22456A2A7BD8.png

开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。

正常:是这样的
正常渲染.png

有问题的图层:

调试.png
可以看见我设置了圆角的imageView有问题.

### 项目开发中怎么去处理?

抛出一个问题: 需求就是有很多圆角那我们项目中应该怎么去处理圆角呢?

  1. 使用YYWebImage去处理
  2. iOS中圆角图片的处理

相信看完两篇文章,多少都会能收获一点!

有些人说:

iOS 9.0 之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的(这句话不知道谁说的.自己有没有去尝试呢???)

结论: 经过测试

70915C7C-7523-4008-9A88-B5682407926D.png

大家可以看到,
UIButton 的 masksToBounds = YES下发生离屏渲染与 背景图存不存在有关系, 如果没有给按钮设置 btn.image = [UIImage imageName:@"xxxxx"]; 是不会产生离屏渲染的 .

关于 UIImageView,现在测试发现(现版本: iOS10),在性能的范围之内,给UIImageView设置圆角是不会触发离屏渲染的,但是同时给UIImageView设置背景色则肯定会触发.触发离屏渲染跟 png.jpg格式并无关联(可能采取的压缩格式不同,这里不做探讨,这里我给出结果是没有关系)

2019/8/15 posted in  iOS

_cmd 与 runtime 的结合

只有运行时给分类添加属性,大部分都是这样的

- (NSString *)customName {
     return objc_getAssociatedObject(self, &kExtendVarKey);
   }
- (void)setCustomName:(NSString *)customName {
    objc_setAssociatedObject(self, &kExtendVarKey,, customDelegate, OBJC_ASSOCIATION_RETAIN_COPY);
 }

_cmd 是隐藏的参数,表示当前方法的selector,另外隐藏参数self表示当前方法调用的对象实例。
这个参数是唯一的参数,在一个文件中方法名不会重复。
所以可以让它代替运行时当中的属性的键名。

- (NSString *)customName {
     return objc_getAssociatedObject(self, _cmd);
   }
- (void)setCustomName:(NSString *)customName {
    objc_setAssociatedObject(self, @selector(customName), customName, OBJC_ASSOCIATION_RETAIN_COPY);
  }
2019/7/15 posted in  iOS

高效的画线

先创建一个UIBeizerPath 的对象path

UIBezierPath *path = [[UIBezierPath alloc] init];
  [path moveToPoint:CGPointMake(175, 100)];
  
  [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];
  [path moveToPoint:CGPointMake(150, 125)];
  [path addLineToPoint:CGPointMake(150, 175)];
  [path addLineToPoint:CGPointMake(125, 225)];
  [path moveToPoint:CGPointMake(150, 175)];
  [path addLineToPoint:CGPointMake(175, 225)];
  [path moveToPoint:CGPointMake(100, 150)];
  [path addLineToPoint:CGPointMake(200, 150)];

然后创建CAShapeLayer添加到视图的layer上

CAShapeLayer *shapeLayer = [CAShapeLayer layer];
  shapeLayer.strokeColor = [UIColor redColor].CGColor;
  shapeLayer.fillColor = [UIColor clearColor].CGColor;
  shapeLayer.lineWidth = 5;
  shapeLayer.lineJoin = kCALineJoinRound;
  shapeLayer.lineCap = kCALineCapRound;
  shapeLayer.path = path.CGPath;
  //add it to our view
  [self.containerView.layer addSublayer:shapeLayer];
2019/5/11 posted in  iOS

检查无用代码

简便的方法是使用appcode进行分析

在appcode中打开代码,并选择code->inspect code就可以进行静态分析。

分析完之后,我们可以在unused code里看到所有的无用代码。如下图所示。

接下来,我和你说一下这些无用代码的主要类型。

  • 无用类:Unused class 是无用类,Unused import statement 是无用类引入声明,Unused property 是无用的属性;
  • 无用方法:Unused method 是无用的方法,Unused parameter 是无用参数,Unused instance variable 是无用的实例变量,Unused local variable 是无用的局部变量,Unused value 是无用的值;
  • 无用宏:Unused macro 是无用的宏。
  • 无用全局:Unused global declaration 是无用全局声明。

看似 AppCode 已经把所有工作都完成了,其实不然。下面,我再和你列举下 AppCode 静态检查的问题:

  • JSONModel 里定义了未使用的协议会被判定为无用协议;
  • 如果子类使用了父类的方法,父类的这个方法不会被认为使用了;
  • 通过点的方式使用属性,该属性会被认为没有使用;
  • 使用 performSelector 方式调用的方法也检查不出来,比如 self performSelector:@selector(arrivalRefreshTime);
  • 运行时声明类的情况检查不出来。比如通过 NSClassFromString 方式调用的类会被查出为没有使用的类,比如 layerClass = NSClassFromString(@“SMFloatLayer”)。还有以 [[self class] accessToken] 这样不指定类名的方式使用的类,会被认为该类没有被使用。像 UITableView 的自定义的 Cell 使用 registerClass,这样的情况也会认为这个 Cell 没有被使用。

define RW_INITIALIZED (1<<29)

bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}

2019/3/20 posted in  iOS

Debug 技巧(汇编层面)

在Xcode中调试代码 一般都使用po 和 p命令,但是还是一种方法就是进入到汇编语言 debug。这时候就会用到了 lldb 的一个指令叫寄存器读。

register read // 寄存器读

1.怎么开启汇编语言 debug

首先Xcode 菜单栏 debug 如下图所示选择 debug workflow

屏幕快照 2019-03-27 上午10.43.57

然后选择然后选择第一项总是显示汇编窗口

屏幕快照 2019-03-27 上午10.47.06

2.那么进入到指定的汇编断点之后应该如何打印信息呢

屏幕快照 2019-03-27 上午10.49.12

使用register read 就会把具体的信息给打印出来
如下所示,还可以具体使用 register read rsp 等打印出来逐条的信息,不过意义不是特别大,因为register read已经可以把所有信息打印出来

(lldb) register read
General Purpose Registers:
       rax = 0x0fc228d768f90060
       rbx = 0x00000001088ebc05  "count"
       rcx = 0x0000000000000111
       rdx = 0x00000001167855c0  dyld`_main_thread
       rdi = 0x00007fcf0bc0b300
       rsi = 0x00000001074a8971  "sayHello"
       rbp = 0x00007ffee87549f0
       rsp = 0x00007ffee8754970
        r8 = 0x000000010a5203a0  libsystem_pthread.dylib`_pthread_keys
        r9 = 0x0000000000000040
       r10 = 0x0000000000000000
       r11 = 0x0000000000000202
       r12 = 0x0000000000000018
       r13 = 0x00007fcf0bc0b300
       r14 = 0x000000010c37b9e4  UIKitCore`_UIApplicationLinkedOnVersion
       r15 = 0x0000000107ddbd80  libobjc.A.dylib`objc_msgSend
       rip = 0x00000001074a8341  ExchangeMethod`-[ViewController mwviewDidLoad] + 465 at ViewController.m:87
    rflags = 0x0000000000000206
        cs = 0x000000000000002b
        fs = 0x0000000000000000
        gs = 0x0000000000000000

利用image list命令列出所有image,第一个image就是我们APP的偏移值,也就是内存地址。

2019/1/20 posted in  iOS

通讯录排序

苹果提供的排序方法

NSArray *stringArr = @[@"我们",@"我的", @"重点", @"重庆",  @"三"]; 
NSArray *result = [stringArr sortedArrayUsingSelector:@selector(localizedCompare:)];

字典当中的keyvalue 可以一个键对应一个数组。

对数据进行排列按首字母进行分组

这是苹果的方法

    
    for ( modelAgreementCitizenItem *model in modelArr) {
        //获取汉子的首字母
        //把中文转拼音
        
        NSMutableString *ms = [[NSMutableString alloc] initWithString:[model patientName]];
        if (CFStringTransform((__bridge CFMutableStringRef)ms, 0, kCFStringTransformMandarinLatin, NO)) {
            NSLog(@"Pingying: %@", ms);
        }
        if (CFStringTransform((__bridge CFMutableStringRef)ms, 0, kCFStringTransformStripDiacritics, NO)) {
            NSLog(@"Pingying: %@", ms);
        }
        NSString *firstString = [[ms substringToIndex:1] uppercaseString];
        
        
        NSMutableArray * arr = [self.nameDic objectForKey:firstString];
        if (arr) {
            [arr addObject:model];
        }else{//如果没有 则需要创建一个添加到里面
            [self.nameDic setObject:[@[model] mutableCopy] forKey:firstString];
        }
        
    }
    
}

这个方法使用起来比较慢 耗时较长

    
    NSMutableArray *array = [[NSMutableArray alloc] init];
    if(self.indexArr.count){
        [self.indexArr removeAllObjects];
    }
    
    for(int i='A';i<='Z';i++)
    {
        NSMutableArray *rulesArray = [[NSMutableArray alloc] init];
        
        NSString *str1=[NSString stringWithFormat:@"%c",i];
        for(int j=0;j<modelArr.count;j++)
        {
            modelAgreementCitizenItem *model = [modelArr objectAtIndex:j];  //这个model 是我自己创建的 里面包含用户的姓名 手机号 和 转化成功后的首字母
            
            if([[NSString getFirstLetter:model.patientName] isEqualToString:str1])
            {
                [rulesArray addObject:model];    //把首字母相同的人物model 放到同一个数组里面
                [modelArr removeObject:model];   //model 放到 rulesArray 里面说明这个model 已经拍好序了 所以从总的modelArr里面删除
                j--;
                
            }else{
                
            }
        }
        if (rulesArray.count !=0) {
            [array addObject:rulesArray];
            [self.indexArr addObject:[NSString stringWithFormat:@"%c",i]]; //把大写字母也放到一个数组里面
        }
    }
    
    if (modelArr.count !=0) {
        [array addObject:modelArr];
        [self.indexArr addObject:@"#"];  //把首字母不是A~Z里的字符全部放到 array里面 然后返回
    }
    
    return array;
    
}



2019/1/10 posted in  iOS