作者: 高老师很忙
mach_absolute_time()
这个函数大家应该比较了解:返回的是CPU已经运行的时钟周期数,可以转化为秒数,这个是不会受本地时间影响的,不过当手机重启会重新开始计数,锁屏休眠后会暂停计数。今天看文档惊喜的发现了 iOS10
之后提供了 mach_continuous_time()
方法:
注释写的是:
like mach_absolute_time, but advances during sleep
突然感觉好惊喜,查看了一下源码:
通过休眠测试(使用 iPhone8+
, iOS11
,删除所有 APP
, 关掉网络和蓝牙等,先运行 demo
获取一个时间,锁屏过了几个小时,再次获取时间),结果果然可喜:
不过重启后仍然会重置。顺便再介绍一下 mach_approximate_time()
,是获取一个大约时间,暂时没有用过,可以简单看下源码:
比较好理解。希望能帮助到大家!
作者: 南峰子_老驴
昨天打开好久没开的公众号后台,看到一个小伙伴 @阳光下的小泡沫星人002 给我留言,指正我之前写的关于Runtime的文章 中一个错误之处,并为此写了一篇文章 在此感谢。
这个错误是关于获取 NSObject
的元类的 isa
指针的问题。在我的文章中,错误的用 objc_getClass((__bridge void *)[NSObject class])
这种方式来获取对象的指针。代码如下:
void TestMetaClass(id self, SEL _cmd) {
NSLog(@"This objcet is %p", self);
NSLog(@"Class is %@, super class is %@", [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 0; i < 4; i++) {
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = objc_getClass((__bridge void *)currentClass);
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", objc_getClass((__bridge void *)[NSObject class])); // 0x0
}
实际上 objc_getClass
的参数是类名的字符串,获取指定类的类对象。上述代码中 (__bridge void *)[NSObject class])
的结果并不是类名的字符串,而是一个对象的指针,所以 objc_getClass
函数返回值的相当于是一个 nil
,打印指针的值就是 0x0
。所以正确使用 objc_getClass
的方式应该是objc_getClass("NSObject")
,其效果与 [NSObject class]
是一样的。
要想获取 NSObject
类对象的元类,可以使用 object_getClass
函数。这个函数参数是 id
类型,即一个对象,返回对象的 Class
信息,即对象的 isa
指针;如果传入的是一个类对象,获取的就是元类信息。
所以正确的代码如下,for
循环中的输出结果也印证了 NSObject
元类的isa指向的是其本身。
void TestMetaClass(id self, SEL _cmd) {
NSLog(@"This objcet is %p", self);
NSLog(@"Class is %@, super class is %@", [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 0; i < 4; i++) {
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = object_getClass(currentClass);
}
// Following the isa pointer 0 times gives 0x10048e1f0
// Following the isa pointer 1 times gives 0x10048e220
// Following the isa pointer 2 times gives 0x7fff8c8e50f0
// Following the isa pointer 3 times gives 0x7fff8c8e50f0
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
// NSObject's class is 0x7fff8c8e5140
// NSObject's meta class is 0x7fff8c8e50f0
}
由于博客已停更,错误之处可以在微博上私信。还请阅读的时候不要盲从。
作者: Lefe_x
当我们提交的代码需要 Review 的时候,需要用到 Gerrit,具体关于 Gerrit 的介绍可以看 [这里]
(https://gerrit-review.googlesource.com/Documentation/) 。使用 Gerrit 后,执行 push
操作的时候,不能直接使用 git push
命令,也就说你不能使用 SourceTree
的 Push
功能,只能在终端乖乖的输入 git push origin HEAD:refs/for/dev
。有些同学可能会问,我特别想使用 SourceTree
,不想使用终端命令,有没有好的方法?其实,SourceTree
提供了一个功能:【自定义操作】(SourceTree -- 偏好设置 -- 自定义操作 -- 添加),导入事先写好的脚本。还可以设置一个快捷键(这里设置了 cmd+p)。脚本如下:
#!/bin/bash
cd /Users/wangsuyan/Desktop/iOS
git push origin HEAD:refs/for/Dev0.0.1
备注:记得给脚本执行权限。 如果你没有给自定义的操作设置快捷键,可以通过【动作 -- 自定义操作】选择执行你的 Action。
这样,当你 push 的时候直接 cmd+p
即可提交你的代码,是不是很爽。当然你可以写一些其它的脚本,并自定义为 Action ,来提高你的工作效率。你可以看我以前写的 [脚本教程] (http://www.jianshu.com/p/8a975f358de8) 。
作者: Lefe_x
调试的时候,往往底层库会埋一些 NSLog
来调试,使用下面这种方式打印出来的函数名称 __PRETTY_FUNCTION__
是底层库的函数名。
# define LEFLog(fmt, ...) NSLog((@"%s (%d) => " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
打印是这样的:+[Network post] 中打印了 I am a log
,而不清楚是谁调用了。
+[Network post] (22) => I am a log
但是,我想要的是我在最顶层调用时的函数名,这样我可以很容易的看到是那个方法调用了底层库。
不太理解?举个例子吧:
每个 APP 都会有一个网络层,业务层会直接与网络层进行交互。调试的时候,我想知道 A 请求是在哪个页面中的哪个函数被调用了,咋么办?前提是 NSLog
在底层库。我们可以这样实现:
@implementation LEFLog
+ (NSString *)lastCallMethod
{
NSArray *symbols = [NSThread callStackSymbols];
NSInteger maxCount = symbols.count;
NSString *secondSymbol = maxCount > 2 ? symbols[2] : (maxCount > 1 ? symbols[1] : [symbols firstObject]);
if (secondSymbol.length == 0) {
return @"";
}
NSString *pattern = @"[+-]\\[.{0,}\\]";
NSError *error;
NSRegularExpression *express = [NSRegularExpression regularExpressionWithPattern:pattern options:kNilOptions error:&error];
if (error) {
NSLog(@"Error: %@", error);
return @"";
}
NSTextCheckingResult *checkResult = [[express matchesInString:secondSymbol options:NSMatchingReportCompletion range:NSMakeRange(0, secondSymbol.length)] lastObject];
NSString *findStr = [secondSymbol substringWithRange:checkResult.range];
return findStr ?: @"";
}
@end
然后定义一个宏:
# define LEFLog(fmt, ...) NSLog((@"%@, %s (%d) => " fmt), [LEFLog lastCallMethod], __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__
打印结果是这样的:在 LefexViewController
中的 viewDidLoad
调用了 Network
的 post
方法,并打印 I am a log
.
-[LefexViewController viewDidLoad], +[Network post] (22) => I am a log
作者: Vong_HUST
我们都知道,如果我们想要自定义指定构造器时,应该要遵循以下3个原则(图1):
1、子类指定构造器必须调用父类指定构造器
2、便捷构造器只能通过调用自身指定构造器来完成初始化
3、指定构造器必须要用 NS_DESIGNATED_INITIALIZER
标示
但是如果你继承了 UITableViewController,并且自定义了指定构造器,而你的项目刚好要支持 iOS8 的话,在 iOS8 下就会出现一个必崩的 bug。示例代码及简单解释见图2。
有人提了对应的 radar, stackoverflow 上也有对应的详尽解释 更多内容可查看
目前唯一的解决方案就是不继承 UITableViewController,而是继承自 UIViewController 然后持有一个 UITableView😂
作者: 高老师很忙
今天主要给大家介绍一个如何给断点加条件和命令来提高我们的调试效率。例如:一个for循环,在第n次循环的时候有一个bug,这个时候条件断点就有了用武之地,通过右击断点,选择编辑断点;
我们可以设置Condition:i= n
,直接断到我们想要调试的那次循环;
或者也可以用Ignore
功能来设置忽略前面不关心的循环;
通过设置Action功能还可以在断点处执行一些简单的命令、打印日志、播放声音、执行脚本等,
让调试变的欢乐起来吧!
平时在 Gitlab
、GitHub
、SourceTree
上进行 CodeReview
的时候,只能看到发生改动的地方,想要查看改动点对应的上下文时非常麻烦(网页上要不断的点展开)。今天给大家介绍一个我们小组内 CodeReview
时用到的工具及其配置,如果你有其它方式,欢迎一起交流探讨。确保配置前已安装好 SourceTree
以及 Kaleidoscope
,配置方式如下
- 点击
Kaleidoscope
菜单 –> 点击Integration
–> 下载ksdiff
(点击Read More
)
-
安装完成之后,删除
~/.gitconfig
文件中difftool
与mergetool
相关配置(删之前最好备份一下原文件) -
打开
SourceTree
的偏好设置,按下图的方式配置。两处Command
都填/usr/local/bin/ksdiff
,Argument
分别为--partial-changeset --relative-path "$MERGED" -- "$LOCAL" "$REMOTE"
、--merge --output "$MERGED" --base "$BASE" -- "$LOCAL" --snapshot "$REMOTE" --snapshot
。这两个参数也可以按自己的需求来配置。
- 以上设置完成之后可以给
SourceTree
加一个自定义动作,快捷键按自己的喜好设置,参数项填difftool -y -t sourcetree $SHA HEAD
即可。
以上配置完成之后就大功告成了,使用方式就是在 SourceTree
中选中一个非 HEAD
的 commit
,然后按下快捷键,就会在 Kaleidoscope
打开所有改动过的文件。
在Swift 4.0中,使用Codable
来编码一个Dictionary
时,某些情况下得到的可能并不是类似于字典/对象结构的字符串,而可能是一个类似数组结构的字符串,如下代码所示:
let dict: [Float : String] = [
18.0: "ff0000",
20.0: "00ff00",
21.0: "0000ff"
]
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let encoded = try! encoder.encode(dict)
let jsonText = String(decoding: encoded, as: UTF8.self)
print(jsonText)
//[
// 21,
// "0000ff",
// 18,
// "ff0000",
// 20,
// "00ff00"
//]
这里的dict
以Float
类型为key
,结果是输出一个类似数组的字符串。查看源码中Dictionary
对Encodable
协议的扩展实现,如下代码所示:
extension Dictionary : Encodable /* where Key : Encodable, Value : Encodable */ {
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Key.self, in: type(of: self))
assertTypeIsEncodable(Value.self, in: type(of: self))
if Key.self == String.self {
// Since the keys are already Strings, we can use them as keys directly.
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in self {
let codingKey = _DictionaryCodingKey(stringValue: key as! String)!
try (value as! Encodable).__encode(to: &container, forKey: codingKey)
}
} else if Key.self == Int.self {
// Since the keys are already Ints, we can use them as keys directly.
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in self {
let codingKey = _DictionaryCodingKey(intValue: key as! Int)!
try (value as! Encodable).__encode(to: &container, forKey: codingKey)
}
} else {
// Keys are Encodable but not Strings or Ints, so we cannot arbitrarily convert to keys.
// We can encode as an array of alternating key-value pairs, though.
var container = encoder.unkeyedContainer()
for (key, value) in self {
try (key as! Encodable).__encode(to: &container)
try (value as! Encodable).__encode(to: &container)
}
}
}
}
可以看到当 key
的类型为 String
和 Int
时,使用 _DictionaryCodingKey
来编码 key
,进而编码 key-value
对,最后以类似字典的结构输出(依赖于 keyed container
),这是因为在 Codable
系统中,这两个类型是有效的可编码 key
类型;而其它类型则不是,由于 Dictionary
无法告诉其它类型如何编码自身,所以将这些值 key
存储在一个 unkeyed container
中,最终处理成一个数组。
参考:
最近在 Medium
上看到一篇《Should you use POP?》的文章,文章主要通过以下几个方面来对比了 pop
和 Core Animation
。
-
Core Animation
工作原理:每次添加动画时QuartzCore
会打包其参数,然后通过进程间通信的方式传递给一个名为backboardd
的后台进程。然后该进程通过OpenGL
渲染和处理layer
的层级以及layer
上的动画。最重要的一点就是该进程完全独立于你的应用,应用只会拿到动画开始和结束的回调(CAAnimationDelegate
),不负责动画的渲染(显式动画除外)。也就是主线程和CoreAnimation
不会互相影响,也就是即使主线程阻塞了,CoreAnimation
依旧在执行。 -
pop
的工作原理:使用CADisplayLink
来开启一个fps=60
的渲染工作。每当CADisplayLink
回调触发时,更新一下动画的进度。也就是每一帧发生改变时都需要通知backboardd
来渲染,因为它对于layer
的变化并不知情。 -
由于
pop
必须在主线程上处理动画,所以pop
动画很有可能发生卡顿。作者写了一个Demo
来演示对应效果,效果如下图,左边为Core Animation
的方式,右边为pop
的方式。
- 作者对比了
Core Animation
和pop
的性能。同样动画效果情况下(运行10s),使用Time Profiler
看backboardd
(CoreAnimation
) 和应用(pop
)CPU
消耗,iPhone4 iOS7.1.2
和iPhone6 iOS8.1.1
的对比结果下图所示(左边iPhone4
,右边iPhone6
)。可以看出两者backboardd
进程的CPU
耗时差别在100ms
左右,但是应用的CPU
耗时差距十分明显,Core Animation
应用CPU
耗时接近于0。
- 结论如下图所示。
Core Animation
优点为:①单独进程运行 ②不会阻塞主线程。缺点为:①复杂动画效果要写冗长的代码 ②手势驱动动画比较复杂。pop
的优点为:①丰富的 API ②内置很多的动画 缺点为:①在主线程上执行 ②动画过程可能卡顿 ③消耗更高的CPU
作者: Lefe_x
在宏的定义中,我们也许会遇到过 ##
,比如下面是一些第三方库中 ##
使用场景:
微信 WCDB 中的宏定义:
#define __WCDB_BINDING(className) _s_##className##_binding
唱吧 KTVHTTPCache 定义不同类中是否可以打印的例子:
#define KTVHCLogEnableValueConsoleLog(target) KTVHCLog_##target##_ConsoleLogEnable
那 ##
有什么用呢?
##
在宏中的作用就是先分隔,然后进行强制连接。我们可能会定义不同的函数名或变量时就可以使用这样的宏定义。
那 ##
是如何工作的呢?
-
__WCDB_BINDING(className)
,首先_s_##className##_binding
会拆分成_s
,className
,_binding
。__WCDB_BINDING(ViewController) 将会被替换成_s_ViewController_binding
; -
KTVHCLogEnableValueConsoleLog(target)
,首先KTVHCLog_##target##_ConsoleLogEnable
会被拆分为KTVHCLog_
,target
和_ConsoleLogEnable
。KTVHCLogEnableValueConsoleLog(Lefex) 会被替换成KTVHCLog_ Lefex_ConsoleLogEnable
;
3.当使用 KTVHCLogEnable(HTTPServer, YES) ,将会定义一个名为 KTVHCLog_ HTTPServer_ConsoleLogEnable
静态常量,初始值为 YES。
#define KTVHCLogEnable(target, console_log_enable) \
static BOOL const KTVHCLog_##target##_ConsoleLogEnable = console_log_enable; \
比如我们使用不同的 View 名字创建不同的 View:
#define Name(target) weibo_##target##_name
#define View(target) view##target##Label
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString * Name(lefex) = @"Lefe_x";
// 打印:You weibo name is: Lefe_x
NSLog(@"You weibo name is: %@", weibo_lefex_name);
UILabel *View(1) = [UILabel new];
view1Label.backgroundColor = [UIColor redColor];
UIView *View(2) = [UIView new];
view2Label.backgroundColor = [UIColor yellowColor];
}
作者: Vong_HUST
平时我们拿到一份崩溃日志,需要解析,一般操作是取到对应的 dSYM 和对应的二进制文件,然后拿到相应的崩溃日志 uuid 、二进制的 uuid、dSYM 的 uuid 通过命令行来解析。这些操作感觉比较耗时。下面介绍几种情况下的快速解析的方法:
- 1、自己设备上 Xcode 编译的包发生闪退:连上手机打开 Xcode,cmd+shift+2 呼出 Device 的 Window,如图1所示,然后点击 View Device Logs,然后选中对应时间段自己 app 的崩溃日志。如果此时对应的调用栈还没有符号化,可以选中日志后右键如图2所示 Re-Symbolicate Log 即可。
-
2、如果是打包服务器或者 Appstore 的包发生闪退:拷贝对应的包和 dSYM 到任意文件夹下,注意将 dSYM 解压以及 .ipa 里面的 .app 取出。然后按照情况1的方式处理即可,Xcode 会自动索引二进制及 dSYM。
-
3、如果拿到别的设备导出的未符号化的崩溃日志,可以将日志拖至图2所示的列表中,注意此时上面 tab 记得选 All Logs 而不是 This Device,然后参考情况2,找到崩溃日志对应的二进制包和 dSYM 文件,按照情况2处理即可。可能会遇到系统库的一些方法无法符号化的问题,只需要找到对应的设备连上电脑,让 Xcode 读取一遍该设备(同机型和系统版本的也可以)的符号表,然后再 Re-Symbolicate 一遍就行。
-
4、遇到线上用户崩溃,无法拿到完整崩溃日志,可以让用户到【设置->分析->分析数据】里面找到对应时间点的崩溃日志,然后截图,根据一个开源工具 dSYMTools,把崩溃栈的关键地址输入到文本框中即可解析出崩溃的那个方法,具体使用方法参考 ReadMe。