美文网首页中高级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