1、记录代码片段运行时间
double begin = mach_absolute_time();
[self refreshSongImageInNewLogic];
double end = mach_absolute_time();
NSLog(@"time cost = %f",MachTimeToMilliSecond(end - begin)); //毫秒
double MachTimeToMilliSecond(uint64_t time)
{
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
return (double)time * (double)timebase.numer / (double)timebase.denom / 1e6;
}
以上是一种方法,今天看 YYCache 源码的时候发现另外一种记录代码片段运行时间的方法,也顺带记录一下吧:
NSTimeInterval begin, end, time;
printf("\n===========================\n");
printf("Memory cache set 200000 key-value pairs\n");
begin = CACurrentMediaTime();
@autoreleasepool {
for (int i = 0; i < count; i++) {
[nsDict setObject:values[i] forKey:keys[i]];
}
}
end = CACurrentMediaTime();
time = end - begin;
printf("NSDictionary: %8.2f\n", time * 1000);
好奇点击 CACurrentMediaTime 进去看了一下,结果,哈哈.......它的其实也是调用 mach_absolute_time() 来计时的,同根同源啊!
2、关于视频播放,网上查找了一下资料主要有 MPMoviePlayerController (注意和 MPMoviePlayerViewController 的区别 )和 AVPlayer 这两个类来播放,如果对 UI 的自定义需求不大的话建议用 MPMoviePlayerController ; AVPlayer 是对 MPMoivePlayerController 的进一步封装,可提供更加个性化的定制,当然,使用起来就相对就没有 MPMoviePlayerController 那么方便了。具体的可以参考苹果的开发文档,两者的比较可以看这里:AVPlayer and MPMoivePlayerController difference
另外,在 github 上找到 2 个 star 比较多的开源库,也可参考一下:
ALMoviePlayerController - 使用的是 MPMoviePlayerController
VKVideoPlayer - 使用的是 AVPlayer
VideoPlayerKit - 封装了挺多功能的
3、手机一不小心升级 ios 9.1,然后 XCode还是7.0 ,导致真机 debug 不了了。想着算了那就升级一下 XCode 到 7.1 呗,结果 XCode 7.1 最低要求系统是 10.10.5, 尼玛,我又要升级系统。最后我一整天啥都没干成,就在那里升级升级......
好不容易升级完了,结果XCode 7.2 beta ,ios 9.1 真机调试的时候报出 “could not find developer disk image” 的错误,泪崩~ 好吧,Google一下喽,最后的解决办法是:
把 XCode 7.1 /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/9.1 这个目录下(包括9.1这个文件夹)的所有内容 copy 到 XCode 7.2 beta 版本的
/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/ 这个目录下,然后重启一下 XCode 就好了。
4、设置 UIButton 的 hidden 属性不起作用,很多情况下都是因为你在后台线程操作的原因。改为在主线程设置就好了。
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.toMVButton.hidden = YES;
weakSelf.toKTVButton.hidden = YES;
});
今天发现在 Block 中修改 button 的位置(frame)的时候看到 log 打印出来的位置明明对了,界面却显示不正确,setNeedsLayout 也没有用,后来改为在主线程设置就好了。
5、用宏定义检测block是否可用!
#define BLOCK_EXEC(block, ...) if (block) { block(__VA_ARGS__); };
// 宏定义之前的用法
/*
if (completionBlock)
{
completionBlock(arg1, arg2);
}
*/
// 宏定义之后的用法
BLOCK_EXEC(completionBlock, arg1, arg2);
6、对于逆向工程的目的,但是这是可以看的对象实例变量。它通常很容易用valueForKey这样获取。
还有一个情况下,它不能用valueForKey获取,虽然:当这个变量是void *类型。
1 2 3 4 |
|
用底层方式来访问
1 |
|
不要使用这段代码,它的非常危险的。仅使用于逆向工程
7、获取沙盒路径的2种方式,第一种是比较常用的
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
第二种是
char* home = getenv("HOME");
strcat(home, "/Documents/recorder.pcm");
对于第二种,如果你需要在 C++ 文件里面读写文件又需要到沙盒路径的时候再适合不过了。
8、UILabel 设置透明字体
9、
一个基于runtime的开源仓库"libextobjc",集合多种功能,例如可以给一个protocol中声明的方法增加默认实现,遵守此协议的类如果自己没有实现协议中声明的方法,就会调用到默认实现中。
10、事件的传递和响应的区别:
事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件)
<1>、当一个事件发生后,事件会从父控件传给子控件,也就是说由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件的传递,也就是寻找最合适的view的过程。
<2>、接下来是事件的响应。首先看initial view能否处理这个事件,如果不能则会将事件传递给其上级视图(inital view的superView);如果上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller,首先判断视图控制器的根视图view是否能处理此事件;如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传 递;(对于第二个图视图控制器本身还在另一个视图控制器中,则继续交给父视图控制器的根视图,如果根视图不能处理则交给父视图控制器处理);一直到 window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃
<3>、在事件的响应中,如果某个控件实现了touches…方法,则这个事件将由该控件来接受,如果调用了[supertouches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches….方法
11、主界面卡死的问题,除了考虑主线程阻塞的问题外还要看看是否是死锁导致的,项目中遇到的一个问题就是死锁的导致的界面卡死。切记。
12、预编译的宏一般写在 xxx.pch 文件中,如果不希望加载预编译.pch文件的话可以在项目 - Build Settings 里面找到.pch文件并把它删除就好了。
13、合并 arm64 和 x86 的 .a 文件可用 lipo 命令,如下:
分别选择iOS设备和模拟器进行编译,最后找到相关的.a进行合包,使用lipo -create 真机库.a的路径 模拟器库.a的的路径 -output 路径/合成库的名字.a
查询 .a 包含的架构用 lipo -info xxx.a
有合并就有分离,分离 fat file arch 可以使用下面命令:
lipo libCKFFT_bin.a -thin x86_64 -output libCKFFT_x86_64.a
13.1、Xcode 打开 bitcode 有 2 个地方要设置:
13.2、查看 .a 是否包含 bitcode
参考 .a 是否包含bitCode
otool -arch armv7 -l xxx.a | grep __bitcode | wc -l
输出结果大于 0 的为包含,否则为不包含!(这个只是查看 armv7 的,查看所有的架构的话去掉 -arch armv7 就好了。)
14、利用 association 防止多次调进一个方法,总觉得为这种事情去加一个成员变量会让一段逻辑的代码过于分散,喜欢能 self-managed 的函数
参考链接:这里
15、因为是c/c++ 和 oc 混编,需要用到内核里面一个结构体成员的变量,所以就加了一个接口返回该结构体,胶水层也做了一个接口调用内核的这个接口,返回 void * 给上层调用,结果问题来了,不知道如何做 struct 类型和 void * 的转换,stackoverflow 上找了一下没找到答案;后来,想了一想,内核的接口不返回该结构体变量了,直接返回该结构体变量的地址,胶水层就能够直接转换为 void* 了。总结:还是思路问题,遇到死胡同,换一下思路和角度看问题,也许棘手的问题一下子就能迎刃而解。
16、设置锁屏界面图片用到的主要两个类:MPMediaItemArtwork 和 MPNowPlayingInfoCenter 。
17、Objective-C 中的 Shallow Copies, Deep Copies, mutableCopyWithZone, copyWithZone 的详解。
18、断点之后打印 runtime 的所有调用函数
参考链接:这里(强烈推荐看一下)
19、私有化某个方法,不让其他人使用,例如在单例模式下,你要求别人使用 [MyClass shareInstance] 这个,而不希望别人自己另外创建一个实例去使用,这时候你就可以把 init 方法设置为不可用,如下
- (instancetype)init __attribute__((unavailable("Disabled. Use +sharedInstance instead")));
20、Debug 技巧:查看出错地址信息 LLDB 命令:
image lookup –address 0x0000000104c25550
拓展连接:LINUX 内核开发与调试
21、AudioQueue 音量淡入淡出设置
void AudioQueuePlayer::fade(float sec, float volume)
{
if (audioQueue) {
mVolume = volume;
AudioQueueSetParameter(audioQueue, kAudioQueueParam_VolumeRampTime, sec);
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, volume);
}
}
关于 kAudioQueueParam_VolumeRampTime 这个参数的用法苹果的解释如下:
For example, to fade from unity gain down to silence over the course of 1 second, set this parameter to 1 and then set the kAudioQueueParam_Volume parameter to 0.
22、关于调试
lldb 调试命令:
添加断点:
b NSLog
breakpoint set --name "+[NSUserDefaults standarUserDefaults]"
删除断点:
br delete
23、开发过程中遇到了[self performSelector:@selector(timeout) withObject:nil afterDelay:10];这个不执行的情况,查完资料后发现当在子线程时才会出现这种情况,主线程是不会的,所以把它修改到主线程上来执行就好了。
dispatch_async(dispatch_get_main_queue(), ^{
[selfperformSelector:@selector(timeout)withObject:nilafterDelay:10];
});
24、分类中调用回主类里面的方法:
+ (void)invokeOriginalMethod:(id)target selector:(SEL)selector {
// Get the class method list
uint count;
Method *list = class_copyMethodList(target, &count);
// Find and call original method .
for ( int i = count - 1 ; i >= 0; i--) {
Method method = list[i];
SEL name = method_getName(method);
IMP imp = method_getImplementation(method);
if (name == selector) {
((void (*)(id, SEL))imp)(target, name);
break;
}
}
free(list);
}
但一般不建议这么做 !!!@Dave DeLong 给出的理由是:
You can do it, but not using a category. A category replaces a method. (Warning, car analogy) If you have a car, and you destroy that car and replace it with a new car, can you still use the old car? No, because it is gone and does not exist anymore. The same with categories.
正确的做法是使用 method swizzling 。
参考链接:
24、使用 runtime 的方式实现 copyWithZone 对变量的赋值,这样就不用一个一个写了。
-(id)copyWithZone:(NSZone *)zone {
id tmpCopy = [[[self class] allocWithZone:zone] init];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([tmpCopy class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSString *key = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
id value = [self valueForKey:key];
if (value == nil) {
[tmpCopy setValue:[NSNull null] forKey:key];
} else {
[tmpCopy setValue:value forKey:key];
}
}
if (properties) {
free(properties);
}
return tmpCopy;
}
25、关于死锁的情况:
1、在加了锁的方法里面再调用含有同一把锁的方法时会造成死锁。典型的是在回调方法里面又调用了加了锁的方法。
2、死锁是在同一个线程的情况下才会发生的。
�� :
//
// ViewController.m
// Test
//
// Created by aaron on 2017/9/12.
// Copyright © 2017年 aaron. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
{
NSLock *_streamLock;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_streamLock = [[NSLock alloc] init];
[_streamLock lock];
NSLog(@"lock content inside...");
//情况1:只有在同一线程,并且 lock 里面再调用了同一个锁的 lock 才会造成死锁
// [self test];
//情况二:不同线程,就算是在 lock 里面再调用了同一个锁的 lock 也不会导致死锁
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[thread start];
[_streamLock unlock];
NSLog(@"unlock content outside...");
}
- (void)test {
[_streamLock lock];
NSLog(@"lock test content inside...");
[_streamLock unlock];
NSLog(@"unlock test content outside...");
}
@end
同一线程,并且 lock 里面再调用了同一个锁的 lock 才会造成死锁
// [self test];
//情况二:不同线程,就算是在 lock 里面再调用了同一个锁的 lock 也不会导致死锁
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[thread start];
[_streamLock unlock];
NSLog(@"unlock content outside...");
}
- (void)test {
[_streamLock lock];
NSLog(@"lock test content inside...");
[_streamLock unlock];
NSLog(@"unlock test content outside...");
}
@end
输出:
2017-11-09 11:20:01.267286+0800 Test[76242:1001255] lock content inside...
2017-11-09 11:20:01.267511+0800 Test[76242:1001255] unlock content outside...
2017-11-09 11:20:01.267613+0800 Test[76242:1001510] lock test content inside...
2017-11-09 11:20:01.268113+0800 Test[76242:1001510] unlock test content outside...
26、关于内存泄露
一般比较可能出现内存泄露的地方
既然有内存泄露,就应该有响应的检查办法:
比较常用的是使用 Instrument 去检查,详细参考:Session 311 - Advanced Memory Analysis with Instruments,以及苹果的开发者文档:Finding Abandoned Memory 。
另外还有一个就是使用开源库去自动检测,业内比较常用的是使用 MLeaksFinder + FBRetainCycleDetector 这一套组合方案。MLeaksFinder 本身已经集合了FBRetainCycleDetector,所以直接用 MLeaksFinder 就可以了。
27、设置队列的优先级可以用 dispatch_set_target_queue
参考:GCD中的dispatch_set_target_queue的用法及作用
28、如何在一个透明视图上添加不透明的子控件
相信很多同学都会遇到过这个问题, 当我们弹出一个半透明的遮盖层时, 又想在遮盖层上加一些子视图, 这个时候如果你的遮盖层设置了alpha属性, 你会惊讶的发现, 加载遮盖层上的所有子控件都是透明了, 错误做法如下:
view.backgroundColor = [UIColor clearColor];
view.alpha = 0.8;
想解决这个问题重点是不要设置view全局透明, 只需要将其北京设置透明就可以了, 正确做法如下:
view.backgroundColor = [[UIColor whiteColor]colorWithAlphaComponent:0.7f];
29、浮点数比较不要用 == 或者 != ,这样会由于精度问题导致判断失效。正确的做法是取绝对值,误差在一定范围内判定为相等或者不等。
参考链接:判断两个 float 变量是否相等以及和 0 值比较方法