Object-C 零碎知识点笔记

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

@interface MPMoviePlayerController : NSObject {

    void *_internal;    // 4 = 0x4

    BOOL _readyForDisplay;  // 8 = 0x8

}

用底层方式来访问

1

id internal = *((const id*)(void*)((uintptr_t)moviePlayerController + sizeof(Class)));

不要使用这段代码,它的非常危险的。仅使用于逆向工程

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 个地方要设置:

(1)

(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、关于内存泄露

一般比较可能出现内存泄露的地方

  1. 定时器,ViewController 里面开始定时器,然后退出界面的时候没有销毁,或者是写在了 - dealloc 里面了,导致定时器没有机会执行到;
  2. block 和 delegate 的循环引用 —— 可以使用 XCode 自带的可视化工具 Debug Memory Graph 查看,参考文章:iOS — Identifying Memory Leaks using the Xcode Memory Graph Debugger
  3. Associations 也可能导致内存泄露,比如你把它的内存所有权方式设置为 OBJC_ASSOCIATION_RETAIN_NONATOMIC 。资料来源:FBRetainCycleDetector

既然有内存泄露,就应该有响应的检查办法:

比较常用的是使用 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 值比较方法

发布了171 篇原创文章 · 获赞 333 · 访问量 141万+

猜你喜欢

转载自blog.csdn.net/chaoyuan899/article/details/49178615