美文网首页中高级iOS面试题精华集锦
[iOS] 跟着大神们学习代码(8)

[iOS] 跟着大神们学习代码(8)

作者: 木小易Ying | 来源:发表于2020-02-13 09:44 被阅读0次

    目录:

    1. 内存泄漏 Memory Graph
    2. 手动下载远程库来pod install / pod update
    3. podspec文件如果引入了其他模块引入的库也要声明
    4. app在后台时keyboard监听也会被触发
    5. if define防止头文件重复引入
    6. 打包release debug证书区别
    7. 淘口令
    8. 内存泄漏的一些情况
    9. presentVC iOS13适配

    1. 内存泄漏 Memory Graph

    内存泄漏的检查工具有好多啦,例如MLeaksFinder之类的,但是除了形成了cycle互相持有的那种,其他如果单纯知道了某个对象leak了,如何知道是什么持有了它呢?

    Xcode有个很好用的工具,就是Memory Graph,可以看到现在的各种对象的持有关系:


    memory graph

    通过这个东东就能从左边找到你想知道有木有泄漏的对象(filter),然后右边就会出现它的持有关系啦~ 是不是超级棒!!


    2. 手动下载远程库来pod install / pod update

    远程办公最怕的就是库版本发生变化,最近一拉最新代码经常就要pod install / pod repo update,每次都会卡死总要有各种问题,解决的方法大概就是github源就用4G,Google源就用代理+wifi,但是仍旧有的时候下不下来,于是我就在这里说一下如何直接从github下载然后让pod过去。

    首先,如果是卡在了pod repo update,也就是从远端拿这些库最新的地址tag之类的,这个时候可以让能够过这一步的同事把/Users/用户名/.cocoapods/repos/trunk/Specs整个文件夹压缩打包发给你,直接替换就好,这样就可以成功的过update那关啦。

    如果木有同事装好,可以直接从git上下载最新的:
    https://github.com/CocoaPods/Specs

    Specs里面存了哪些东西呢?我们以一会儿想要实验的库libwebp为例吧:


    libwebp的各个版本

    其实里面就是各个版本有个json以及json tag文件,我们打开json看一下:

    {
      "name": "libwebp",
      "version": "1.1.0",
      "summary": "Library to encode and decode images in WebP format.",
      "homepage": "https://developers.google.com/speed/webp/",
      "authors": "Google Inc.",
      "license": {
        "type": "BSD",
        "file": "COPYING"
      },
      "source": {
        "git": "https://chromium.googlesource.com/webm/libwebp",
        "tag": "v1.1.0"
      },
      "compiler_flags": "-D_THREAD_SAFE",
      "requires_arc": false,
      "platforms": {
        "osx": "10.8",
        "ios": "6.0",
        "tvos": "9.0",
        "watchos": "2.0"
      },
      "pod_target_xcconfig": {
        "USER_HEADER_SEARCH_PATHS": "$(inherited) ${PODS_ROOT}/libwebp/ ${PODS_TARGET_SRCROOT}/"
      },
      "preserve_paths": "src",
      "default_subspecs": [
        "webp",
        "demux",
        "mux"
      ],
      "prepare_command": "sed -i.bak 's/<inttypes.h>/<stdint.h>/g' './src/webp/types.h'",
      "subspecs": [
        {
          "name": "webp",
          "source_files": [
            "src/webp/decode.h",
            "src/webp/encode.h",
            "src/webp/types.h",
            "src/webp/mux_types.h",
            "src/webp/format_constants.h",
            "src/utils/*.{h,c}",
            "src/dsp/*.{h,c}",
            "src/dec/*.{h,c}",
            "src/enc/*.{h,c}"
          ],
          "public_header_files": [
            "src/webp/decode.h",
            "src/webp/encode.h",
            "src/webp/types.h",
            "src/webp/mux_types.h",
            "src/webp/format_constants.h"
          ]
        },
        {
          "name": "demux",
          "dependencies": {
            "libwebp/webp": [
    
            ]
          },
          "source_files": [
            "src/demux/*.{h,c}",
            "src/webp/demux.h"
          ],
          "public_header_files": "src/webp/demux.h"
        },
        {
          "name": "mux",
          "dependencies": {
            "libwebp/demux": [
    
            ]
          },
          "source_files": [
            "src/mux/*.{h,c}",
            "src/webp/mux.h"
          ],
          "public_header_files": "src/webp/mux.h"
        }
      ]
    }
    

    然后我们就可以去source的git下载相应tag的库啦

    "source": {
      "git": "https://chromium.googlesource.com/webm/libwebp",
      "tag": "v1.1.0"
    },
    
    git库

    下载tar.gz或者zip都OK,然后解压放到我们project的本地pod里面:


    工程里面的pod目录

    注意哦你放进去的就是你从库仓库下载下来的文件,可能和你看到本地这个库的内容不一样,没关系的,你放进去再pod install过了以后就一样啦。

    现在我们实现了下载需要的版本,并放入本地缓存,之后就可以来看如何pod install啦。在pod install的过程中,其实它就是比较了Podfile.lock文件和本地缓存文件版本差异,如果有区别就去远端下载,所以现在我们需要让本地缓存的库和podfile.lock里面一致,当然有一种方法是你把podfile.lock里面的库版本降低到你本地的版本,这样你也不用从远端下载就可以成功pod install啦,只是这样的话你需要经常stash着这个变化。

    那么本地缓存的版本由什么管理呢?其实就是项目/Pods/Manifest.lock文件,你打开会发现和Podfile.lock文件非常一致,只要把自己替换的库例如libwebp相关的内容都从Podfile.lock拷到项目/Pods/Manifest.lock即可,如果Podfile.lock里面的版本不是最新的,你就手动改一下版本号以及spec checksum即可。

    可参考:https://www.jianshu.com/p/06f507d2987d

    例如podfile.lock里需要改成酱紫,manifest.lock必须相关的和podfile.lock保持一致,包括checksum的编号:

      - libwebp (1.1.0):
        - libwebp/demux (= 1.1.0)
        - libwebp/mux (= 1.1.0)
        - libwebp/webp (= 1.1.0)
      - libwebp/demux (1.1.0):
        - libwebp/webp
      - libwebp/mux (1.1.0):
        - libwebp/demux
      - libwebp/webp (1.1.0)
    
    SPEC CHECKSUMS:
      ……
      KVOController: d72ace34afea42468329623b3379ab3cd1d286b6
      libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
    

    那么CHECKSUMS的编号是如何生成的呢?我最开始以为是远程仓库的commit号,后来查了一下看到上面的refer文章才知道是这么生成的:

    pod ipc spec /Users/用户名/.cocoapods/repos/trunk/Specs/1/9/2/libwebp/1.1.0/libwebp.podspec.json  | openssl sha1
    946cb3063cea9236285f7e9a8505d806d30e07f3
    

    然后你再pod install就可以成功了吼~ (P.S. 想要知道自己卡在哪里,加上--verbose就可以啦)


    3. podspec文件如果引入了其他模块引入的库也要声明

    如果很多人做好多个模块,可能我们自己的模块依赖了其他组引入的某个库,那么如果我们也需要在自己模块的podspec里面声明依赖了这个库哦,不能直接用了人家引入的不声明,因为如果他们团队之后不用了,那么我们也就没有啦~


    4. app在后台时keyboard监听也会被触发

    这个问题是我昨天晚上QA小姐姐发现的,就是如果我们注册了observer监听UIKeyboardDidHideNotification以及UIKeyboardWillShowNotification,那么即使我们的app在后台,当我们在其他app例如微信里面调用了键盘,自己app里面监听keyboard的方法仍旧会被触发,这样在两个app来回切换并且有微信键盘的时候就看着很奇怪。

    于是我Google了一下 (https://stackoverflow.com/questions/34409566/keyboardwillshow-gets-called-for-other-apps-keyboards),解决方案就是在监听触发的handler函数里面最好先判断自己的app是不是active的,如果不是就return不去处理这个监听:

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowNotificationHandler) name:UIKeyboardWillShowNotification object:nil];
    
    - (void) keyboardWillShowNotificationHandler {
      if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) {
        return;
      }
      ……
    }
    

    5. if define防止头文件重复引入

    我们创建一个.h文件会自动生成下面的代码:

    #ifndef HeaderName_h
    #define HeaderName_h
    // 这里面通常写各种宏定义、其他头文件的包含
    #endif 
    

    这样子其实为了防止该头文件被重复引用~

    被重复引用是指一个头文件在同一个cpp文件中被include了多次,这种错误常常是由于include嵌套造成的。

    比如:存在a.h文件#include "c.h",而b.cpp文件同时#include "a.h" 和#include "c.h",此时就会造成c.h被b.cpp重复引用。

    头文件被重复引用引起的后果:有些头文件重复引用只是增加了编译工作的工作量,不会引起太大的问题,仅仅是编译效率低一些。但是对于大工程而言,编译效率低下那将是一件多么痛苦的事情。

    而有些头文件重复包含,则会引起错误,比如:在头文件中定义了全局变量(虽然这种方式不被推荐,但确实是C规范允许的),这种头文件重复包含会引起全局变量的重复定义。

    在OC中#import保证了每个文件只会被import一次,即使我们没有加上面的ifdef也没有问题,但最好要加上啊,毕竟你不知道别人怎么import你的头文件,下面来尝试一下~

    可以编译过的版本:

    // Header1.h
    static const NSString * header1Name = @"header1";
    
    // Header2.h
    #import "Header1.h"
    static const NSString * header2Name = @"header2";
    
    // vc
    #import "Header1.h"
    #import "Header2.h"
    

    不可以编译过会报错redefine的版本:

    // Header1.h
    static const NSString * header1Name = @"header1";
    
    // Header2.h
    #include "Header1.h"
    static const NSString * header2Name = @"header2";
    
    // vc
    #include "Header1.h"
    #include "Header2.h"
    

    改一下让它可以编译过:

    // Header1.h
    #ifndef Header1_h
    #define Header1_h
    static const NSString * header1Name = @"header1";
    #endif /* Header1_h */
    
    // Header2.h
    #include "Header1.h"
    static const NSString * header2Name = @"header2";
    
    // vc
    #include "Header1.h"
    #include "Header2.h"
    

    6. 打包release debug证书区别

    这个真的不太熟,是偶尔看到的:https://blog.csdn.net/weixin_34221112/article/details/92325150

    所以Release版本如果"Debug executable"就可以断点,但无法使用Assert、Trace等功能。因此,Release版本下调试操作实际并没有任何意义,调试结果没有任何参考价值。

    development和distribution证书:development证书开发时使用,distribution证书发布时使用。它们跟Xcode的debug和release没有一毛钱关系。development证书允许开发者将app直接"Run"进真机,而distribution证书则不可以。这就是它们直接的主要区别。


    7. 淘口令

    各种复制口令到app然后会打开一个页面,最早感觉就是淘宝这么做的,它的是咋实现的呢?first step是从你的剪贴板拿到你复制的东西,second step我猜是把那串神奇的字符串发给后端,我们只要pop up一个webview,内容后端会来填充页面。

    所以我们先来看一下从剪贴板拿你的magic string:

    // 程序进入前台
    -(void)applicationWillEnterForeground:(UIApplication*)application {
      UIPasteboard* pasteboard= [UIPasteboard generalPasteboard];
      NSLog(@"pasteboard: %@", pasteboard.string);
    }
    

    我拿拼多多的app来fake了一下,然后复制了一个口令打开拼多多看到的页面层级是这样的:(神奇的发现陌陌的反fake比拼多多做的好。。。


    口令拼多多页面层级

    所以弹出的页面是原生的不是web哈~

    但是如果你无网复制粘贴进拼多多就没有反应,所以虽然不是直接pop一个web让后端做,但应该是从剪贴板拿到string以后发给了后端,后端会告诉前端弹哪种view,并把需要的填充数据返还。


    8. 内存泄漏的一些情况
    • 对象循环引用
      @class ,Strong,weak

    • block循环引用
      __weak typeof(self) weakself = self;

    • NSNotification的观察者忘记移除
      [[NSNotificationCenter defaultCenter] removeObserver:self];

    • delegate循环引用问题
      @property (nonatomic, weak) id delegate;

    • NSTimer循环引用
      使用GCD

    • 非OC对象内存处理
      CGImageRef类型变量非OC对象,其需要手动执行释放操作CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃。其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free等都需要注意

    • 使用过多的UIWebView
      参考:https://www.jianshu.com/p/9866294b39c6
      改为WKWebView

    • 大次数循环内存暴涨问题

    for (int i = 0; i < 100000; i++) {
      NSString *string = @“Abc”;
      string = [string lowercaseString];
      string = [string stringByAppendingString:@“xyz”];
      NSLog(@"%@", string);
    }
    
    改:
    for (int i = 0; i < 100000; i++) {
      @autoreleasepool {
        NSString *string = @“Abc”;
        string = [string lowercaseString];
        string = [string stringByAppendingString:@“xyz”];
        NSLog(@"%@", string);
      }
    }
    
    • 加载大图片或者多个图片
      [UIImage imageNamed:@""],次方法使用了系统缓存来缓存图像,会长时间占用内存,最好使用imageWithContentsOfFile方法

    • ANF的AFHTTPSessionManager
      参考:https://www.jianshu.com/p/922b043b244e

    • 地图类


    9. presentVC iOS13适配

    参考:https://www.cnblogs.com/guoshaobin/p/11167191.html

    其实就是presentViewController现在默认是一个叠在现有页面的感觉,iOS13改了好多0.0,如果想让他恢复全屏就酱紫:

    ViewController *vc = [[ViewController alloc] init];
    vc.title = @"presentVC";
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
    nav.modalPresentationStyle = UIModalPresentationFullScreen;
    [self.window.rootViewController presentViewController:nav animated:YES completion:nil];
    

    Refer: https://blog.csdn.net/heqiang2015/article/details/84575047

    相关文章

      网友评论

        本文标题:[iOS] 跟着大神们学习代码(8)

        本文链接:https://www.haomeiwen.com/subject/myckfhtx.html