Skip to content

Files

Latest commit

c629fb1 · Dec 3, 2017

History

History
524 lines (340 loc) · 24.5 KB

11.md

File metadata and controls

524 lines (340 loc) · 24.5 KB

2017.11

RxSwift总结

作者: Lefe_x

ReactiveXReactive Extensions)是通过可观察的流实现异步编程的一种API,它结合了观察者模式、迭代器模式和函数式编程的精华。RxSwiftReactiveX编程思想的一种实现,几乎每一种语言都会有那么一个Rx[xxxx]框架,比如 RxJavaRxJS 等。Rx 可以概括为,对某些数据流(很广,可以是一些事件等)进行处理,使其变成可观察对象(Observable)序列,这样观察者(observer)就可以订阅这些序列【观察者模式】。然而对于订阅者来说(observer)某些选项(items)并不是自己需要的(需要过滤),某些选项(items)需要转换才能达到自己的目的【操作符 filter, map 等】。为了提升用户体验,或其它目的,有些操作需要放到特定的线程去执行,比如 UI 操作需要放到主线程,这就涉及到了调度器【调度器 Scheduler】。所以Rx可以这样概括,Rx = Observables + LINQ + Schedulers,其中 LINQ(Language Integrated Query)语言集成查询,比如那些操作符号。

下图是 RxSwift 的简单的应用,从数组中找出 Lefe_x 并显示到 Label 上。

override func viewDidLoad() {
  super.viewDidLoad()
  DispatchQueue.global().async {
      self.from()
  }
}

func from() {
  Observable.from(["Lefe", "Lefe_x", "lefex", "wsy", "Rx"])
     .subscribeOn(MainScheduler.instance)
     .filter({ (text) -> Bool in
	return text == "Lefe_x"
     })
     .map({ (text) -> String in
	return "我的新浪微博是: " + text
     })
     .subscribe(onNext: { [weak self] (text) in
	self?.nickNameLabel.text = text
     })
     .disposed(by: disposeBag)
}

流程如下图所示:

解决React Native引入realm导致编译卡顿问题

React Native项目中引入realm后,在编译阶段,网络状况不好的时候会卡在realm模块下载的地方。现象是如果在终端用react-native run-ios,则会卡在Download realm-sync-cocoa-${version}.tar.xz,如果是Xcode,则会卡在Build ${ProjectName}: RealmJS这一块。

这主要是因为realm的库太大(realm-sync-cocoa-${version}.tar.xz包大概是50+M)。所以我们可以考虑把包先下载到本地,以减少每次编译的时间。

  1. 我们可以从realm-cocoabuild.sh文件中找到realm-sync-cocoa-${version}.tar.xz的下载地址(注:${version}是具体的版本号),下载文件。
  2. 在命令行输入getconf DARWIN_USER_TEMP_DIR,获取文件下载的临时目录
  3. 将下载的文件拷贝到临时目录
  4. 这样基本就OK。

如果还不行,则可以考虑以下操作:

  1. 修改下载的脚本代码了。找到node_modules/realm/scripts/download_realm.js
  2. 找到download()函数,将其中的saveFile()函数作如下修改;
function saveFile() {
	if (process.stdout.isTTY) {
		printProgress(response.body, parseInt(response.headers.get('Content-Length')), archive);
	} else {
		console.log(`Downloading ${archive}`);
	}
	
	return new Promise((resolve) => {
		// const file = fs.createWriteStream(destination);
		// response.body.pipe(file).once('finish', () => file.close(resolve));
		resolve();
	}).then(() => fs.utimes(destination, lastModified, lastModified));
}

最后运行react-native run-ios

参考

  1. Core occasionally cannot be downloaded from China

判断代码在哪个队列中运行

之前分享了可以用dispatch_queue_set_specificdispatch_get_specific来判断代码是否运行在主队列上。除了这种方法外,还可以使用dispatch_queue_get_label获取当前队列的label,与主队列的label比较,如图代码所示:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"%d", [self isMainQueue]);       // 1
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"%d", [self isMainQueue]);   // 0
    });
}

- (BOOL)isMainQueue {
    return strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0;
}

主队列的labelcom.apple.main-thread。当然也可以用这种方式来判断其它队列。全局队列的label与其QOS类型相关,label值主要有以下几个:

com.apple.root.user-interactive-qos //
com.apple.root.user-initiated-qos   // DISPATCH_QUEUE_PRIORITY_HIGH
com.apple.root.default-qos          // DISPATCH_QUEUE_PRIORITY_DEFAULT
com.apple.root.utility-qos          // DISPATCH_QUEUE_PRIORITY_LOW
com.apple.root.background-qos       // DISPATCH_QUEUE_PRIORITY_BACKGROUND

我们可以使用dispatch_get_global_queue(qos_class_self(), 0)来获取代码所在的的全局队列,进而获取其label值。

参考

  1. Get current dispatch queue?

通过导入DSYM使用Instruments做性能分析

作者: Vong_HUST

通常情况下,使用 Instruments 做性能分析,看调用栈时,是能够直接看到对应方法调用堆栈的,而不是一串的地址,但这个前提是 Xcode 用编译过这个应用。如果是用同事或者打包服务器上的安装包做性能分析时,就无法看到对应的方法调用堆栈信息了,只能看到一串地址。这个时候可以通过导入 dSYM 的方式来查看对应的方法调用堆栈,方式如下(见图):打开 Instruments -> 运行一遍你的应用 -> 停止 -> File -> Symbols -> dSYM Path : Locate即可。如下图所示:

图一

但是有些时候可能应用中包含 extension,则直接 locate 会提示无法定位到 dSYM 文件

The specified path didn't locate a dSYM for any of the selected libraries.

这个时候需要把你的 dSYM.zip 解压,右键显示其内容将对应的 .app.dSYM 拷出,然后 locate 到这个拷出的 dSYM 文件即可或者按照下图操作也是可行的。具体流程见图:

Shell脚本在iOS中的应用

作者: Lefe_x

使用 Pod 的同学经常会遇到 "error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation." 错误,如下图所示。其实是 [CP] Check Pods Manifest.lock 这个脚本所起的作用。

Pod 中有 Manifest.lockPodfile.lock 这两个文件,只要这两个文件的内容不一样就会报错上面这个错误。Podfile.lock 是大家共用的文件(用来保证我们每个人的Pod库版本一样),而 Manifest.lock 是本地的文件(自己用)。下图中这个脚本正是做这样的事情。

diff "${PODS_PODFILE_DIR_PATH}/Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null
if [ $? != 0 ] ; then
    # print error to STDERR
    echo "error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation." >&2
    exit 1
fi
# This output is used by Xcode 'outputs' to avoid re-running this script phase.
echo "SUCCESS" > "${SCRIPT_OUTPUT_FILE_0}"

解释下这个脚本

shell 脚本总是以:#!/bin/bash 或者 #!/bin/sh 开头,它主要告诉系统执行这个文件需要哪个解释器,进入 /bin 目录下可以看到 bash 和 sh 解释器;

  • diff 命令:判断两个文件的不同,比如 diff /Users/lefe/Desktop/project/Kmart/Podfile.lock /Users/lefe/Desktop/project/Kmart/pods/Manifest.lock >~/Desktop/shell.log 比较两个文件的不同,并重定向到 shell.log 文件中;
  • > 重定向符号,可以把输出命令输出到某个文件中而不是控制台;
  • echo 是脚本的输出,相当于 printf
  • exit 1 退出,有了这个命令 Xcode 就会报错,你可以在 Xcode 中新建一个脚本,试试下面这个脚本:
echo "This is a test shell created by Lefe_x"
exit 1
  • $?: 指上条命令执行的结果,也就是 diff 执行的结果;

  • 下面是 shell 中的 if 语句:

if 条件 ; then
fi

如何在终端执行脚本

假如有个叫 podlgsk.sh 的脚本,只要给予它执行权限(chmod +x podlgsk.sh),注意只需要给一次执行权限就行,下次运行脚本时就不需要给予执行权限了,然后直接 ./podlgsk.sh 即可。

小结

其实 Shell 脚本就是把一些 linux 命令组合到一起,经过一些处理(比如:if 判断,循环等)后所形成的文件。(不知道总结的是否恰当)

iOS10之后多边形绘制

作者: Vong_HUST

众所周知,对于一些多边形的绘制,我们可以使用 CAShapeLayer 配合 UIBezierPath,然后再用这个 layerViewmask 即可。但是一种情况是不行的,对于 UIVisualEffectView,iOS10 之前 self.blurView.layer.mask = someShapeLayer 这一句是 ok 的,但是 iOS10 之后,这样设将无效,而应该使用 self.blurView.maskView = maskView。具体代码如下图所示:

详细解释可参考这里

iOS 11中隐藏section头尾的实现

作者: iOS_OneByte

iOS 中 UITableView 中有一种比较常用的样式 UITableViewStyleGrouped。有时我们要隐去 section 头尾的话,经常实现如下:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 0.1f;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
    return 0.1f;
}

如果只实现这2段代码的话,在 iOS 11 之前是不会出现问题的,但 iOS 11 之后需要同时实现如下:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    return nil;
}

-(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    return nil;
}

Base SDK 路径设置: Xcode < Build Settings < Base SDK

Base SDK:指得是当前编译应用的和构建 .ipaSDK 的版本,并且手机的 SDK 版本是向前兼容。

小程序和iOS数据绑定

作者: Lefe_x

在微信小程序中,开发 UI 功能模块,绝对比的上 iOS 原生开发。它已经提供了一套成熟的数据绑定方式,而不需要引入其它库。而在 iOS 中我们同样可以做数据绑定,然而需要花费一定的时间来学习一些数据绑定的框架,比如 RAC,RxSwift。这里主要以 RxSwift 为例说明。在小程序中,假如实现一个功能,点击按钮修改昵称,可以这样实现:

Page({
  data: {
    nickName: 'Lefe_x'
  },
  
  changeNickNameAction: function () {
    this.setData ({
      nickName: 'Lefe'
    })
  }
})

直接修改数据源 data 中的 nickName 即可让新的昵称(Lefe)显示到控件上。看看具体的数据绑定实现:

<view class="main-container">
   {{nickName}}
   修改昵称
</view>

nickName 与一个 Text 的控件绑定到了一起,这样只要 nickName 发生改变,就会显示到 Text 控件上。而在 iOS 中我们可以使用 RxSwift 做数据绑定。比如:当 nickNameTextField 输入内容后,将显示在 nickNameLabel 上。

class MiniViewController: UIViewController {
    
    let disposeBag = DisposeBag()

    @IBOutlet weak var nickNameTextField: UITextField!
    @IBOutlet weak var nickNameLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        nickNameTextField.rx.text.bind(to: nickNameLabel.rx.text).disposed(by: disposeBag)
    }
}

ViewController关联XIB文件时初始化方法的调用

作者: Vong_HUST

在新建一个 ViewController 文件时,如果同时也勾选了 Also create XIB file,那么ViewController *viewController = [[ViewController alloc] init] 得到的是和 initWithNibName:bundle: 初始化得到的 UI 一致的。原因是 init 方法最终都会调到指定构造器 initWithNibName:bundle:,但此时 nibNamenil,而且没有 VC 复写 loadView 的情况下,则系统会有一套自己的寻找机制来看是否有对应的 xib 文件,如果有,则加载 xib 文件。具体可参考苹果官方文档的解释

iOS 清理 cookies

在手机端开发 web 页面时,有时候我们可能需要删除一些 cookie 值。JS 删除 cookie 主要是将 cookieexpires 属性设置为一个早于当前时间的值。不过如果 cookieHttpOnly 的话,表示这个 cookie 值不能通过非 HTTP 方式来访问,而无法通过 JS 来访问,document.cookie 获取不到,所以也无法通过 JS 来删除这样的 cookie 值。

不过除了通过 server 端来处理外,我们也可以借助 native 端来执行删除操作。iOS 端使用 NSHTTPCookie 对象来表示一个 cookie ,并通过 NSHTTPCookieStorage 来管理当前应用的所有 cookie,包括 HttpOnly/secure 类型的 cookie 。所有想要删除 cookie ,只需要依据给定的条件(如域、path等属性)来找出对应的 NSHTTPCookie 对象,并删除就行。以下代码是清空当前应用程序所有 cookie 的操作:

NSArray<NSHTTPCookie *> *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies;
for (NSHTTPCookie *cookie in cookies) {
	[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
}

当然,如果要在 web 端触发操作,还需要提供 Hybrid 接口。

判断子类是否实现父类的方法

想在程序运行时判断一个子类是否实现了父类的某个方法,可以比较两个类对应方法的指针是否相等。以 -init 方法为例,如下代码所示:

@interface Test: NSObject
@end

@implementation Test
@end

BOOL check() {
    SEL sel = @selector(init);
    IMP baseInit = [NSObject instanceMethodForSelector:sel];
    IMP testInit = [Test instanceMethodForSelector:sel];
    
    return baseInit == testInit;
}

NSLog(@"%@", check() ? @"true" : @"false");			// true

按照 Objective-C 查找方法的规则,对象会先在自己的方法列表中查找,如果有,则调用;如果没有,则向上去父类里面查找,依此一直到 NSObject

需要注意的是,如果是多层继承体系,即使某个子类没有实现某个方法,也不能确定这个子类和某个父类是同一个实现,如下代码所示:

@interface Base: NSObject
@end

@implementation Base

- (id)init {
    return [super init];
}

@end

@interface Test: Base
@end

@implementation Test
@end

BOOL check() {
    SEL sel = @selector(init);
    IMP baseInit = [NSObject instanceMethodForSelector:sel];
    IMP testInit = [Test instanceMethodForSelector:sel];
    
    return baseInit == testInit;	
}

NSLog(@"%@", check() ? @"true" : @"false");         // false

ParsifalC童鞋之前也分享过相关的内容,提供了几种方法,具体可以参考这里

Git恢复被删除的Stash代码

作者: 高老师很忙

GitStash 功能很方便的帮我们存一些临时代码,如果不小心把 Stash 代码删除了如何恢复呢?使用 git fsck --lost-found 查看我们最近删除的 Stash ;找到你需要恢复的 dangling commit(只需关注 dangling commit );再使用 git merge 命令即可恢复。如下两图所示:

使用NSLog可能出现的安全问题

作者: iOS_OneByte

iOS 经常定义类似如下的输出宏:

#ifdef DEBUG
	#define ZJLog(fmt, ...) NSLog((fmt), ##__VA_ARGS__)
#else
	#define ZJLog(...)
#endif

但是大部分人可能只是遵循国际惯例,并不知道如果直接使用 NSLog 的危害或者如何去查看别人家的应用输出,其实很简单,只是 Xcode 隐藏的很深,路径如下

Xcode8 - Window - Devices
Xcode9 - Window - Devices And Simulators

如下图所示:

这个利用好的话,对于调试自己或者查(po)看(jie)别人家的应用都很有用,效果如下图所示:

pod install 和 pod update 的区别

作者: Vong_HUST

首先 podfile.lockpodfile 必须加入版本控制。

install 并不是第一次创建 podfile 时运行一次,后面就不再使用了。install 命令不仅在初始时使用,在新增或删除 repo 时也需要运行。每次添加或删除 repo 后应该执行 install 命令,这样其它的 repo 不会更新。update 仅仅在只需更新某一个 repo 或所有时才使用。每次执行 install 时,会将每个 repo 的版本信息写入到 podfile.lock,已存在于 podfile.lockrepo 不会被更新只会下载指定版本,不在 podfile.lock 中的 repo 将会搜索与 podfile 里面对应 repo 匹配的版本。即使某个 repo 指定了版本,如 pod 'A', '1.0.0',最好也是不要使用 update,因为 repo A 可能有依赖,如果此时使用 update 会更新其依赖。

参考:

  1. pod install vs. pod update

利用Attach方式调试推送、Today、3DTouch

作者: 高老师很忙

在调试推送、Today3DTouch 等唤起测试 App 的时候(此时App未启动),我们通常 Run 的方式是不行的,因为 RunApp 就启动了,不满足调试环境。Xcode 为我们提供了 Attach 的方式进行调试,使用起来也是超简单的。操作方式如下:

前提:已经加了断点(比如 application: didFinishLaunchingWithOptions: 方法里加断点)

  • Attach 之前需要把测试 App 的进程杀掉(如果不杀掉进程,这种方式是无法断点调试的);

  • 选择你要 Attach 的测试 App ,有两种方式:在 Debug 下拉菜单下面有 Attach to Process 选项(直接选择你的测试 App,如图1)和 Attach to Process by PID or Name 选项(输入名称,如图2);

15-1

15-2

  • 用推送、Today3DTouch 等方式唤起,就大功告成了,如下图所示

为什么我放弃使用系统自带的终端

作者: Lefe_x

用过 iTerm 后,我再也不想使用系统自带的终端了。 iTerm 是终端的一个替代品,而且开源免费。它支持非常棒的特性:

  • 设置一个可爱的背景图 Preference -> Profiles -> Window -> Background Image ,如下图所示;

  • Hotkey Window,让终端立刻出现在你的面前。比如当你使用 Xcode 正在编码时,这时需要执行 Pod install,使用快捷键 command+i(需要设置) 直接调出 iTerm

  • 选择即复制,当你选择终端中的文本后,它会自动复制;

  • 点击可跳转,如下图中,按住 command 并点击 /Users/apple/Desktop/project/lefeDemo 就会跳转到这个文件夹下。 按住 command 并点击 www.baidu.com,就会在浏览器中打开 www.baidu.com;

  • 自动提示你所输过的命令,这样可以快速输入你所敲过的命令;

  • 快照功能,可以回退到某一时刻你所输过的命令;

想了解 iTerm 更多特性,可以查看 官网

当 iTerm 遇上 ZSH,他们相爱了

作者: Lefe_x

Your terminal never felt this good before.

上一条 iOS知识小集 主要说了下使用 iTerm 可以改善我们的终端,评论中 @CrespoXiao@struggleend 提到了 iTerm + ZSH 使用起来更方便。搜了下 ZSH,发现 Star 数 62476 个,确实是好东西,和大家分享下,目前使用时间不长,姑且总结几条我认为比较好的,有熟悉的同学在评论中分享给大家吧 🤝,相信同学们不会吝啬的。安装看这里Oh My Zsh 它是基于 zsh 命令行的一个扩展工具集。

提示:根目录下有 ~/.zshrc 文件,可以修改配置,比如主题,插件等。

  • 设置一个主题,如果你有洁癖,选择一个你认为比较好的主题
  • 使用插件,比如有 Git 插件,当记不住命令的时候,按 Tab 键可以提示,比如 git p [tab] 将会提示,如下图所示;

  • 支持简写,比如省略 cd,回到上级目录直接输入:.. ;
  • ls *.jpeg 显示当前目录下所有 jpeg 文件;
  • cd <TAB> 简显示当前目录下的文件,继续按 可以选择你所要进入的目录;
  • git <UP> 可以找到最近输入的 Git 命令;

想了解 ZSH 更多特性,可以查看 官网

NSFetchedResultsController兼容性问题

作者: Vong_HUST

熟悉 CoreData 的都知道 NSFetchedResultsController 这个类,与 tableViewcollectionView 结合起来使用非常方便。

但是这个类在 iOS10 中有个比较坑爹的地方,如果使用的是下图的方式初始化

并且传入的 cacheName 不为 nil,那么在 iOS10 下面可能会产生各种莫名其妙的崩溃。详细原因如下图描述:

简单翻译一下就是:修改了 vcfetchRequest 之后,contextsave 操作会打开一个或多个文件描述符,当打开的文件描述符数目超过255(真机)后,后续的资源加载都会导致崩溃。

当初遇到这个问题的时候,整个应用都处于随机崩溃的情况,看日志都是加载 storyboard 或者 xib 等资源时的崩溃,但是死活复现不出来,看崩溃日志也找不到原因。然后无意间发现 Xcode console 中一直不断输出如下警告信息:

(NSFetchedResultsController): couldn't read cache file to update store info timestamps

然后 google 了一下,找到了一个相同的问题,并且有完整的复现 Demo 及复现步骤,然后全局代码搜索果然发现有一个地方初始化的时候传入了 cacheName,将其置 nil 后就再也没有那段 warning 同时上线之后也没有再收到资源加载的崩溃了。

所以 Xcodewarning 还是值的注意的。附上这个 radar链接,里面包含 Demo 地址。

今天再去复现的时候,试了两台 iOS10 的设备,Xcode 9.1iPhone5S iOS10.2 这个 bug 已经不存在了,但是 iPhone7 iOS10.0 还是存在的。