美文网首页
iOS 开发Tip2

iOS 开发Tip2

作者: 我是Damo | 来源:发表于2019-03-26 18:29 被阅读0次

    51.封装KVO

    使用(注意:对UITextfiled使用在真机上不可以使用KVO,KVO是基于KVC,因为通过键盘输入不走setter方法):

        self.handle = [ObserverManager addNonRetainObserverToObserject:self keypath:@"name" block:^(NSDictionary *change) {
            NSLog(@"%@",[change valueForKey:NSKeyValueChangeNewKey]);
        }];
    
    //为什么这个方法?
    //应为. vc ->  handle ->vc ->observer,会导致vc释放不了,如果不是vc,强引用,防止提箱提前释放,倒是移除观察者时崩溃
    

    实现:

    
    #import <Foundation/Foundation.h>
    @class ObserverHander;
    
    @interface ObserverManager : NSObject
    
    + (ObserverHander *)addObserverToObserject:(NSObject *)obj
                                       keypath:(NSString *)keypath
                                         block:(void (^)(NSDictionary *))block;
    
    + (ObserverHander *)addNonRetainObserverToObserject:(NSObject *)obj
                                                keypath:(NSString *)keypath
                                                  block:(void (^)(NSDictionary *))block;
    
    @end
    
    
    #import "ObserverManager.h"
    #import "ObserverHander.h"
    #import <mach/mach.h>
    
    @implementation ObserverManager
    
    
    + (ObserverHander *)addObserverToObserject:(NSObject *)obj
                                       keypath:(NSString *)keypath
                                         block:(void (^)(NSDictionary *))block {
        MyObserver *observer = [[MyObserver alloc] init];
        [obj addObserver:observer forKeyPath:keypath options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];
        ObserverHander *handle = [[ObserverHander alloc] initWithObserver:observer target:obj keypath:keypath block:block];
        return handle;
    }
    
    + (ObserverHander *)addNonRetainObserverToObserject:(NSObject *)obj
                                                keypath:(NSString *)keypath
                                                  block:(void (^)(NSDictionary *))block {
        MyObserver *observer = [[MyObserver alloc] init];
        [obj addObserver:observer forKeyPath:keypath options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];
        ObserverHander *handle = [[ObserverHander alloc] initWithNonRetainObserver:observer target:obj keypath:keypath block:block];
        return handle;
    }
    
    - (void)dealloc {
        NSLog(@"\n\n ObserverManager dealloc");
    }
    
    
    #import <Foundation/Foundation.h>
    
    @interface MyObserver: NSObject
    
    @property (nonatomic,   copy) void (^handleBlock)(NSDictionary *change);
    
    @end
    
    @interface ObserverHander : NSObject
    
    @property (nonatomic, strong) MyObserver *observer;
    @property (nonatomic, strong) NSObject *obj;
    @property (nonatomic,   weak) NSObject *weakObj;
    @property (nonatomic,   copy) NSString *keypath;
    
    - (instancetype)initWithObserver:(MyObserver *)observer
                              target:(NSObject *)obj
                             keypath:(NSString *)keypath
                               block:(void (^)(NSDictionary *))block;
    
    - (instancetype)initWithNonRetainObserver:(MyObserver *)observer
                                       target:(NSObject *)obj
                                      keypath:(NSString *)keypath
                                        block:(void (^)(NSDictionary *))block;
    @end
    
    #import "ObserverHander.h"
    
    @interface ObserverHander()
    
    
    @end
    
    
    @implementation ObserverHander
    
    - (instancetype)initWithObserver:(MyObserver *)observer
                              target:(NSObject *)obj
                             keypath:(NSString *)keypath
                               block:(void (^)(NSDictionary *))block {
        if (self = [super init]) {
            self.obj = obj;
            self.observer = observer;
            self.keypath = keypath;
            self.observer.handleBlock = block;
        }
        return self;
    }
    
    - (instancetype)initWithNonRetainObserver:(MyObserver *)observer
                              target:(NSObject *)obj
                             keypath:(NSString *)keypath
                               block:(void (^)(NSDictionary *))block {
        if (self = [super init]) {
            self.weakObj = obj;
            self.observer = observer;
            self.keypath = keypath;
            self.observer.handleBlock = block;
        }
        return self;
    }
    
    - (void)removeObserver {
        [self.obj removeObserver:self.observer forKeyPath:self.keypath];
        self.obj = nil;
    }
    
    - (void)dealloc {
        NSLog(@"\n\n ObserverHandle dealloc \n\n");
        [self removeObserver];
    }
    
    @end
    
    @implementation MyObserver
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        NSLog(@"观察");
        if (self.handleBlock) self.handleBlock(change);
    }
    
    - (void)dealloc {
        NSLog(@"\n\n MyObserver dealloc");
    }
    
    @end
    
    

    52.栈空间

    主线程的栈控件大小为1M,而且无法修改,所有二级线程默认大小为512KB

    注意:完整的线程不会被立刻创建出来,实现的栈控件大小会随着使用而增长,即使主线程有1M的栈空间,某个时间节点的实际栈空间可能要小很多

    在线程启动前,栈空间的大小可以被改变(属性:stackSize),栈空间的最小值为16KB,且必须为16KB的倍数.

    扩:创建耗时
    在 iphone6P iOS 8.4上,线程的创建时间在4-5毫秒,启动线程的耗时区间为5 - 100毫米,平均大约为29毫秒

    53 block

    Block被另一个Block使用时,另一个Block被copy到堆上时,被使用的Block也会被copy。但作为参数的Block是不会发生copy的。

    下面为MRC

    
    void foo() {
      int base = 100;
      BlkSum blk = ^ long (int a, int b) {
        return  base + a + b;
      };
      NSLog(@"%@", blk); // <__NSStackBlock__: 0xbfffdb40>
      bar(blk);
    }
    
    void bar(BlkSum sum_blk) {
      NSLog(@"%@",sum_blk); // 与上面一样,说明作为参数传递时,并不会发生copy
    
      void (^blk) (BlkSum) = ^ (BlkSum sum) {
        NSLog(@"%@",sum);     // 无论blk在堆上还是栈上,作为参数的Block不会发生copy。
        NSLog(@"%@",sum_blk); // 当blk copy到堆上时,sum_blk也被copy了一分到堆上上。
      };
      blk(sum_blk); // blk在栈上
    
      blk = [[blk copy] autorelease];
      blk(sum_blk); // blk在堆上
    }
    
    @interface MyClass : NSObject {
        NSObject* _instanceObj;
    }
    @end
    
    @implementation MyClass
    
    NSObject* __globalObj = nil;
    
    - (id) init {
        if (self = [super init]) {
            _instanceObj = [[NSObject alloc] init];
        }
        return self;
    }
    
    - (void) test {
        static NSObject* __staticObj = nil;
        __globalObj = [[NSObject alloc] init];
        __staticObj = [[NSObject alloc] init];
    
        NSObject* localObj = [[NSObject alloc] init];
        __block NSObject* blockObj = [[NSObject alloc] init];
    
        typedef void (^MyBlock)(void) ;
        MyBlock aBlock = ^{
            NSLog(@"%@", __globalObj);
            NSLog(@"%@", __staticObj);
            NSLog(@"%@", _instanceObj);
            NSLog(@"%@", localObj);
            NSLog(@"%@", blockObj);
        };
        aBlock = [[aBlock copy] autorelease];
        aBlock();
    
        NSLog(@"%d", [__globalObj retainCount]);
        NSLog(@"%d", [__staticObj retainCount]);
        NSLog(@"%d", [_instanceObj retainCount]);
        NSLog(@"%d", [localObj retainCount]);
        NSLog(@"%d", [blockObj retainCount]);
    }
    @end
    
    int main(int argc, char *argv[]) {
        @autoreleasepool {
            MyClass* obj = [[[MyClass alloc] init] autorelease];
            [obj test];
            return 0;
        }
    }
    

    执行结果为1 1 1 2 1。

    __globalObj和__staticObj在内存中的位置是确定的,所以Block copy时不会retain对象。

    _instanceObj在Block copy时也没有直接retain _instanceObj对象本身,但会retain self。所以在Block中可以直接读写_instanceObj变量。

    localObj在Block copy时,系统自动retain对象,增加其引用计数。

    blockObj在Block copy时也不会retain。ARC下会被retain

    在Block中使用成员变量,retain的不是这个变量,而会retain self。

    54生产者-消费者

    #import "Product.h"
    
    @interface Product()
    
    @property (nonatomic, strong) NSCondition *condition;
    @property (nonatomic, strong) NSMutableArray *collectorArr;
    @property (nonatomic, assign) BOOL shouldProduce;
    
    
    @end
    
    @implementation Product {
    }
    
    - (instancetype)initWithCondition:(NSCondition *)condition collectorArr:(NSMutableArray *)collector {
        if (self  = [super init])  {
            self.condition = condition;
            self.collectorArr = collector;
        }
        return self;
    }
    
    - (void)setShouldProduce:(BOOL)shouldProduce {
        _shouldProduce = shouldProduce;
        NSLog(@"shouldProduct = %d",_shouldProduce);
    }
    
    - (void)product {
        self.shouldProduce = YES;
        while (self.shouldProduce) {
            [self.condition lock];
            if (self.collectorArr.count > 0)  {
                [self.condition wait];
            }
            sleep(4);
            NSLog(@"%s",__FUNCTION__);
            [self.collectorArr addObject:[[Product alloc] init]];
            [self.condition signal];
            [self.condition unlock];
        }
    }
    
    #import "Consumer.h"
    
    @interface Consumer()
    
    @property (nonatomic, strong) NSCondition *condition;
    @property (nonatomic, strong) NSMutableArray *collectorArr;
    @property (nonatomic, assign) BOOL shouldCustom;
    
    @end
    
    @implementation Consumer
    
    - (instancetype)initWithCondition:(NSCondition *)condition collectorArr:(NSMutableArray *)collector {
        if (self  = [super init]) {
            self.condition = condition;
            self.collectorArr = collector;
        }
        return self;
    }
    
    - (void)setShouldCustom:(BOOL)shouldCustom {
        _shouldCustom = shouldCustom;
        NSLog(@"shouldConstom = %d",shouldCustom);
    }
    
    - (void)consum {
        self.shouldCustom = YES;
        while (self.shouldCustom) {
            [self.condition lock];
            if (self.collectorArr.count == 0) {
                [self.condition wait];
            }
            NSLog(@"%s",__FUNCTION__);
            [self.collectorArr removeObjectAtIndex:0];
            [self.condition signal];
            [self.condition unlock];
        }
    }
    @end
    
     NSMutableArray *array = [NSMutableArray array];
        NSCondition *condition = [[NSCondition alloc] init];
        Product *product = [[Product alloc] initWithCondition:condition collectorArr:array];
        Consumer *consumer = [[Consumer alloc] initWithCondition:condition collectorArr:array];
        [NSThread detachNewThreadSelector:@selector(product) toTarget:product withObject:nil];
        [NSThread detachNewThreadSelector:@selector(consum) toTarget:consumer withObject:nil];
    
        [condition broadcast];
    

    55定时器的另一种写法

    - (void)dispatchTaskAction
    {
        [self loadLabelsWithIndex:timerIndex];
        
        timerIndex++;
        
        if (timerIndex < verticalCount)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                
                [self dispatchTaskAction];
            });
        }
    }
    

    56 int objc_getClassList(Class *buffer, int bufferCount)

    分析:该函数的作用是获取已经注册的类,它需要传入两个参数,第一个参数 buffer :已分配好内存空间的数组,第二个参数 bufferCount :数组中可存放元素的个数,返回值是注册的类的总数。
    当参数 bufferCount 值小于注册的类的总数时,获取到的是注册类的集合的任意子集
    第一个参数传 NULL 时将会获取到当前注册的所有的类,此时可存放元素的个数为0,因此第二个参数可传0,返回值为当前注册的所有类的总数。

    57.Class *objc_copyClassList(unsigned int *outCount)

    该函数的作用是获取所有已注册的类,和上述函数 objc_getClassList 参数传入 NULL 和 0 时效果一样,代码相对简单:

    unsigned int outCount;
    Class *classes = objc_copyClassList(&outCount);
    for (int i = 0; i < outCount; i++) {
    NSLog(@"%s", class_getName(classes[i]));
    }
    free(classes);

    58.解析dYSM

    • 查找symbolicatecrash位置
      find /Applications/Xcode.app -name symbolicatecrash -type f
      将symbolicatecrash,crash,dYSM放在同一个文件下

    解析导出symbol.crash
    ./symbolicatecrash ./FlycoIT.crash ./FlycoIT.app.dSYM > symbol.crash

    查看三者的UUID是否一致
    1.查看 xx.app 文件的 UUID,terminal 中输入命令 :
    dwarfdump --uuid xx.app/xx (xx代表你的项目名)
    2.查看 xx.app.dSYM 文件的 UUID ,在 terminal 中输入命令:
    dwarfdump --uuid xx.app.dSYM
    3.crash :grep "AppName arm64" xxx.crash
    或grep --after-context=2 "Binary Images:" crash
    0x102abc000 - 0x10350bfff FlycoIT arm64 <
    4b3956d9508f31d5942041ffb5a44b05*>

    • 在achive中导出dSYM和App到同一个文件夹下
      xcrun atos -arch arm64 -o XXX.app/XXX错误地址
      XXX是app名称

    59.蓝牙数据传输

    像所有的网络传输协议一样,应用层能感知到的丢包有可能产生于应用层之下的所有层。对于蓝牙BLE而言,进一步细化从机到主机的数据传输场景(假设使用的是Notify方式传输),其数据传输路径为:
    1、从机的应用层产生数据。
    2、从机将数据从Host上位机转交给Controller下位机。
    3、从机Controller通过调制器,将数据在连接事件的射频Tx阶段发出。
    4、主机Controller同时开启射频Rx阶段,接收无线电波并通过解调器还原数字信号。
    5、主机将数据从Controller下位机转交给Host上位机。
    6、主机的应用层接收数据。根据上面的流程,进一步分析一下可能产生数据丢包的环节。

    在步骤2中,Host调用HCI函数将数据提交给Controller发送之前,会先将数据存放在Controller的数据缓冲区中,不同的蓝牙芯片这个缓冲区的大小不同,比如Ti CC2541的射频缓冲区只有4个PDU(4*20字节)的大小。在缓冲区存满后继续调用HCI的Notify等函数会返回缓存区满状态位标识。这是第一个有可能产生丢包的位置,若缓冲区满且开发者没有对此状态位进行校验,则应用层产生的数据会在传递给链路层之前丢失。

    在步骤3、4中,从机的Controller在连接事件的Tx阶段,会通过射频依次发送缓冲区数据,并在下一个Rx阶段根据捕获和解析对端主机的数据包,从其链路层报头中读取NESN来确认其在上一个Tx阶段发送的数据包是否被对端主机链路层正确接收。由于链路层数据包序列号校验机制(SN和NESN)和重传机制的存在,基本可以保证数据在物理层和链路层阶段不会产生丢包,这跟互联网的传输层TCP协议校验数据包序列和完整性基本类似。之所以上面用“基本”二字,是因为这里还是存在第二个有可能产生丢包的位置。由于BLE中使用的CRC校验是弱CRC,其只能保障传输字节的全部偶数位和部分奇数位不产生错误,并无法保证所有数据在传输过程中不出错。那么一个可能性极小的场景是:CRC弱校验通过,但实际上NESN出现了错误,而将本来应该是0的位识别成了1,或者相反。而这个位差错让本来应该是错误的序列号交替变成了正确的。但这个只是我的臆断,也有可能SN跟NESN都在可被确信的CRC校验偶数位上,所以最多只是负载数据有偏差而不会对丢包产生误判,这里需要查阅更多的资料来确认。

    在步骤5中,主机将数据从Controller向上派发到Host,并转到应用层触发回调通知。这里是第三个可能产生丢包的地方。原因同步骤2,但这里还有一点小区别,就是notify跟indicate的区别(反向传输中的write with/without response也是同理),其本质区别是数据接收成功的判断标志是以链路层确认为准还是以应用层确认为准。前者是notify采用的方式,而后者是indicate采用的方式。可见如果采用notify做数据传输,链路层到应用层这一步依然是有丢包的可能,比如链路层的数据传输速率大于应用层的回调函数中对数据处理的速率,导致Host中数据缓冲区持续增长,当缓冲区满后,主机的Controller将丢弃无法继续保存的数据,并不会通知对端设备重传。

    而indicate方式下,每一个数据包的成功确认都是以应用层回调处理执行完成为标志,而通常数据的逻辑处理是一个耗时的操作,并且由于数据是在连接事件内接受到,但同一个连接事件在开始的时候主从机两端的数据就已经准备好,由于连接事件的持续事件极短,在这段时间内Controller的所有工作都集中在射频的发射上,而不会从Host上位机接受数据和响应,甚至有的蓝牙芯片会有选项在这段时间内直接挂起CPU,以防止其他中断信号同步到达,干扰到射频数据的发送效率。所以在indicate方式下,一个数据包至少要横跨两个甚至更多的连接事件时长才可以确认到达,从而发送下一个数据包,这就是有响应的数据发送方式比无响应要慢的原因。所以一般若采用indicate的方式发送,则从机端应用层的数据产生配速,应该控制在一个连接间隔的时间段中,只产生一个蓝牙数据包的速度,以保持与发送速率一致,避免在应用层缓冲区持续的增加堆积数据等待发送。因为链路层的数据传输是线性的,在得到前一个indicate需要确认的包回执之前,controller不会认为该数据包发送完成,所以这之间只能不断发送空包作为前一个任务的延续数据包维持等待状态,而无法再发送应用层产生的新的有效数据。

    一、BLE数据包分析
    BLE包的结构是


    20160524230602226.png
    preamble(1 Byte)+ AccessAddress(4 Bytes)+ PDU + CRC(3 Bytes)
    
    1)preamble = 10101010 or 01010101
    
    2)Access Address = 0x8e89bedd6
    
    3)连接以后的数据传输的PDU是:Header(2 Bytes)+ Payload (27 Bytes max)+MIC(4bytes),所以传输一个包的   最大长度是1+4+33+3=41字节,有效数据最大是27字节。
    

    二、自定义characteristic的最大长度

       从上面分析可见,通过WRITE或者NOTIFY characteristic发送一个包的最大长度是27字节。但是这个27字节指的是BLE底层的协议包格式。对于profile层的characteristic,其最大的长度是20字节,也即是每个包最大的长度是20字节。
    
       这是因为profile所用的characteristic处于GATT协议层,而GATT到ATT层需要增加一个字节的op code,两个自己的attribute handle。然后ATT到L2CAP适配层需要增加4个字节的L2CAP头。所以从GATT到底层需要封装7个字节的额外协议数据,因此GATT层最大是20字节。
    
       其实,按照蓝牙BLE的协议,L2CAP协议层自动对数据包进行拆包和封装,理论上是支持ATT/GATT层更大的数据包的。但一般SDK默认ATT_MTU=23,这样L2CAP的工作会变得很简单,事实上,BLE的应用场景也不需要定义太长的数据包,20个字节足够了。
    

    60 videoZoomFactor

    • 。尝试使用更高分辨率的预置,但避免使用AVCaptureSessionPresetPhoto分辨率——这会导致曝光时间长,在低光情况下会增加模糊。

    • 尝试在1.0和videoZoomFactorUpscaleThreshold之间设置一个videoZoomFactor。这提供了一个更高的分辨率窗口,在较小的图像部分,这意味着有更多的条码像素的解码器工作。

    -(void)changeDevicePropertySafety:(void (^)(AVCaptureDevice *captureDevice))propertyChange{
        AVCaptureDevice *captureDevice= _captureDevice;
        
        if ([captureDevice lockForConfiguration:nil]) {
            propertyChange(captureDevice);
            [captureDevice unlockForConfiguration];
        }
    }
    
    //缩放的实现点击
    - (void)focalLength{
        NSLog(@"调整焦距");
        @weakify(self)
        [self changeDevicePropertySafety:^(AVCaptureDevice *captureDevice) {
            @strongify(self)
            if (captureDevice.videoZoomFactor == 1.0) {
                //缩放倍数
                
                CGFloat current = 1.5;
                if (current < captureDevice.activeFormat.videoZoomFactorUpscaleThreshold) {
                    [captureDevice rampToVideoZoomFactor:current withRate:10];
                    self->_isFocal = YES;
    //                captureDevice.activeVideoMaxFrameDuration
                }
            } else {
                self ->_isFocal = NO;
                //1.0 表示缩放回到原来的大小(复原)
                [captureDevice rampToVideoZoomFactor:1.0 withRate:20];
            }
        }];
        
    }
    

    61.改变栈底控制器,需要在viewdidAppear中修改

    NSMutableArray *temArr = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];

    if (temArr.count >= 4)
    {
        [temArr removeObjectAtIndex:1];
    
        self.navigationController.viewControllers = temArr.copy;
    }
    

    62.- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait

    如果创建的子线秤没有runloop不会执行方法

    - (void)creatNewThread {
        [[NSThread currentThread] setName:@"myThread"]; // ①给线程设置名字
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];    // ②给线程添加runloop
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];   //③给runloop添加数据源
        while (![[NSThread currentThread] isCancelled]) {           //④:检查isCancelled
            [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];  //⑤启动runloop
        }
    }
    

    63 class_conformsToProtocol和conformsToProtocol区别

    1.class_conformsToProtocol是只检查当前类符不符合协议,和其父类无关。
    conformsToProtocol: 其父类符合协议,则其亦符合。
    2.[NSObject conformsToProtocol:] 仅限于NSObject(满足绝大部分需求) ,而class_conformsToProtocol 适用于所有Class

    64. UIDelayedAction

    UIScrollView 中默认情况下对TouchesBegan进行了延迟(150ms)用于判断是否进行滑动如果滑动就不将事件传递给子view;由于延迟导致如果点击过快,这时候touchesBegan 和 touchesEnd就紧接着发生。
    如果是在touchesBegan和touchesEnd里面都进行了 setNeedsDisplay 操作,标记需要绘制;而两次的间隔时间极小,导致Runloop 只执行一次绘制。换句话说同时标记了两次只有一次标记绘制生效。

    65.NSTimer释放

    #import "SecondViewController.h"
    #import <objc/runtime.h>
    #import "TimerProxy.h"
    
    @interface SecondViewController ()
    
    @property (nonatomic, strong) NSTimer *timer;
    @property (nonatomic, strong) id target;
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, strong) TimerProxy *proxy;
    
    @end
    
    static void *kNameKey = @"kNameKey";
    
    @implementation SecondViewController
    
    void logName(id self,SEL _cmd) {
        SecondViewController *vc = objc_getAssociatedObject(self, kNameKey);
        [vc fire];
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        //方法一:
    //    self.name = @"jack";
    //    self.target = [NSObject new];
    //    objc_setAssociatedObject(_target, kNameKey, self, OBJC_ASSOCIATION_ASSIGN);
    //    class_addMethod([self.target class], @selector(logName), (IMP)logName, "V@:");
    //    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(logName) userInfo:nil repeats:YES];
        
        //方法二:
        _proxy = [TimerProxy alloc];
        _proxy.target = self;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_proxy selector:@selector(fire) userInfo:nil repeats:YES];
    }
    
    - (void)fire {
        NSLog(@"%@",self.name);
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    - (void)dealloc {
        [self.timer invalidate];
        self.timer = nil;
        NSLog(@"%s",__FUNCTION__);
    }
    
    #import <Foundation/Foundation.h>
    
    @interface TimerProxy : NSProxy
    
    @property (nonatomic,   weak) id target;
    
    @end
    
    
    #import "TimerProxy.h"
    
    @implementation TimerProxy
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        return [_target methodSignatureForSelector:sel];
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation {
        return [invocation invokeWithTarget:_target];
    }
    
    @end
    
    

    66.使用NSProxy实现伪多继承

        id mutipleInheritProxy = [MultipleInheritProxy allocWithInheritOne:[InheritOne new] inheritTwo:[InheritTwo new]];
        [mutipleInheritProxy run];
        [mutipleInheritProxy eat];
        NSLog(@"%d",[mutipleInheritProxy respondsToSelector:@selector(run)]);
        NSLog(@"%d",[mutipleInheritProxy respondsToSelector:@selector(eat)]);
    
    
    #import "MultipleInheritProxy.h"
    
    @interface MultipleInheritProxy()
    
    @property (nonatomic, strong) id inheritOne;
    @property (nonatomic, strong) id inheritTwo;
    
    @end
    
    @implementation MultipleInheritProxy
    
    + (instancetype)allocWithInheritOne:(id)inheritOne inheritTwo:(id)inheritTwo {
        MultipleInheritProxy *proxy = [MultipleInheritProxy alloc];
        proxy.inheritOne = inheritOne;
        proxy.inheritTwo = inheritTwo;
        return proxy;
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        id signalinheritOne = [_inheritOne methodSignatureForSelector:sel];
        if (signalinheritOne) {
            return signalinheritOne;
        }
        id signalinheritTwo = [_inheritTwo methodSignatureForSelector:sel];
        if (signalinheritTwo) {
            return signalinheritTwo;
        }
        return [self methodSignatureForSelector:sel];
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation {
        SEL sel = invocation.selector;
        id signalinheritOne = [_inheritOne methodSignatureForSelector:sel];
        if (signalinheritOne) {
            return [invocation invokeWithTarget:_inheritOne];
        }
        id signalinheritTwo = [_inheritTwo methodSignatureForSelector:sel];
        if (signalinheritTwo) {
            return [invocation invokeWithTarget:_inheritTwo];
        }
    }
    
    - (BOOL)respondsToSelector:(SEL)aSelector {
        BOOL result1 = [_inheritOne respondsToSelector:aSelector];
        if (result1) {
            return result1;
        }
        BOOL result2 = [_inheritTwo respondsToSelector:aSelector];
        if (result2) {
            return result2;
        }
        return NO;
    }
    
    @end
    

    67.布尔值

    由于BOOL的类型实际上是char,它的行为方式与C _Bool值或c++ BOOL值不同。例如,下面代码中的条件在i386上为假(PPC上为真):

    - (BOOL)value {
        return 256;
    }
    

    相比之下,下面代码中的条件在所有平台上都是正确的(即使sizeof(bool) = 1):

    - (bool)value {
        return 256;
    }
    

    68.NSHashTable和NSMapTable

    NSHashTable是更广泛意义的NSSet,区别于NSSet/NSMutableSet,NSHashTable有如下特性:

    • NSHashTable是可变的;
    • NSHashTable可以持有weak类型的成员变量;
    • NSHashTable可以在添加成员变量的时候复制成员;
    • NSHashTable可以随意的存储指针并且利用指针的唯一性来进行hash同一性检查(检查成员变量是否有重复)和对比操作(equal);
        Model *otherM = [Model new];
        NSHashTable *hashTabel = [[NSHashTable alloc] initWithOptions:NSPointerFunctionsWeakMemory capacity:3];
        self.hashTable = hashTabel;
        [hashTabel addObject:self.target];
        [hashTabel addObject:otherM];
        [hashTabel addObject:self.model];
        NSLog(@"%@",_hashTable.allObjects);
        
        NSMapTable *mapTable = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:4];
        [mapTable setObject:otherM forKey:@"model"];
        [mapTable setObject:self.model forKey:@"model"];
        [mapTable setObject:self.target forKey:@"target"];
        id obj       = [mapTable objectForKey:@"model"];
        NSArray *arr = mapTable.keyEnumerator.allObjects;
        arr          = mapTable.objectEnumerator.allObjects;
    
    

    NSHashTable是根据一个option参数来进行初始化的,因为从OSX平台上移植到iOS平台上,原来OSX平台上使用的枚举类型被放弃了,从而用option来代替,命名也发生了一些变化:

    • NSHashTableStrongMemory
      等同于NSPointerFunctionsStrongMemory。对成员变量进行强引用,这是一个默认值,如果采用这个默认值,NSHashTable和NSSet就没什么区别了。
    • NSHashTableWeakMemory
      等同于NSPointerFunctionsWeakMemory。对成员变量进行弱引用,使用NSPointerFunctionsWeakMemory,object引用在最后释放的时候会被指向NULL。
    • NSHashTableZeroingWeakMemory
      已被抛弃,使用NSHashTableWeakMemory代替。
    • NSHashTableCopyIn
      在对象被加入集合之前进行复制(NSPointerFunction-acquireFunction),等同于NSPointerFunctionsCopyIn。
    • NSHashTableObjectPointerPersonality
      用指针来等同代替实际的值,当打印这个指针的时候相当于调用description方法。和NSPointerFunctionsObjectPointerPersonality等同。

    NSMapTable

    NSMapTable是对更广泛意义的NSDictionary。和NSDictionary/NSMutableDictionary相比具有如下特性:

    • NSDictionary/NSMutableDictionary会复制keys并且通过强引用values来实现存储;
    • NSMapTable是可变的;
    • NSMapTable可以通过弱引用来持有keys和values,所以当key或者value被deallocated的时候,所存储的实体也会被移除;
    • NSMapTable可以在添加value的时候对value进行复制;

    和NSHashTable类似,NSMapTable可以随意的存储指针,并且利用指针的唯一性来进行对比和重复检查。
    用法:假设用NSMapTable来存储不用被复制的keys和被若引用的value,这里的value就是某个delegate或者一种弱类型。

    69.NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END

    如果需要每个属性或每个方法都去指定nonnull和nullable,是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable的指针。

    NS_ASSUME_NONNULL_BEGIN
    @interface TestNullabilityClass ()
    @property (nonatomic, copy) NSArray * items;
    - (id)itemWithName:(nullable NSString *)name;
    @end
    NS_ASSUME_NONNULL_END
    

    70. SFSafariViewController

    iOS 9引入,更加年轻,也是用来显示网页内容的

    这是一个特殊的View Controller,而不是一个单独的 View,和前面两个的区别
    在当前App中使用Safari的UI框架展现Web内容,包括相同的地址栏,工具栏等,类似一个内置于App的小型Safari

    共享Safari的一些便利特性,包括:相似的用户体验,和Safari共享Cookie,iCloud Web表单数据,密码、证书自动填充,Safari阅读器(Safari Reader)

    可定制性比较差,甚至连地址栏都是不可编辑的,只能在init的时候,传入一个URL来指定网页的地址

    只能用来展示单个页面,并且有一个完成按钮用来退出

    - (IBAction)onButtonClick:(id)sender {
        NSString *urlString = @"http://www.kdanmobile.com";
        
        SFSafariViewController *sfViewControllr = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:urlString]];
        sfViewControllr.delegate = self;
        [self presentViewController:sfViewControllr animated:YES completion:^{
            //...
        }];
    }
    

    71.url元素组成

    scheme://host.domain:port/path/filename

    72默认的launchScreen

    1.将Default-568h@2x.png,Default.png,Default@2x.png命名格式的图片放入项目中
    2.将General ->Launch Screen File 设为空

    将icon-72.png,icon-Small-50.png,iconSmall(@2x).png,icon(@2x).png
    同理可设置

    73.NS_FORMAT_FUNCTION(F,A)

    #if !defined(NS_FORMAT_FUNCTION)
        #if (__GNUC__*10+__GNUC_MINOR__ >= 42) && (TARGET_OS_MAC || TARGET_OS_EMBEDDED)
        #define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
        #else
        #define NS_FORMAT_FUNCTION(F,A)
        #endif
    #endif
    

    attribute((format)) 标示一个可变参函数使用了 Format string ,从而在编译时对其进行检查,其定义为 format (archetype, string-index, first-to-check),其中archetype代表 format string 的类型,它可以是 printf,scanf,strftime或者strfmon,Cocoa开发者还可以使用NSString来指定其使用和 [NSString stringWithFormat:] 与 NSLog() 一致的 Format string 规则。string-index 代表 Format string是第几个参数,first-to-check 则代表了可变参数列表从第几个参数开始

    F 就是 Format string 是第F个参数,A 就是代表可变参数列表是从第A个参数开始

    用法:

    - (NSString *)append:(NSString *)format,...NS_FORMAT_FUNCTION(1,2){
        va_list ap;
        va_start(ap, format);
        NSString *information = [[NSString alloc] initWithFormat:format arguments:ap];
        va_end(ap);
        return information;
    }
    
    

    其他的宏

    74.制作静态库和动态库

    静态库:.a和.framework
    动态库.dylib和.framework(iOS9改为.tbd)
    区别:
    静态库:链接时,静态库中的文件会被完整拷贝到可执行文件中
    动态库:不复制,程序运行时由系统动态加载到内存,只加载一次多个程序共用
    制作静态库时
    1.图片放在bundle文件中,避免资源冲突(测试会使用项目中的同名文件)
    在编译之前,需要把bundle添加到build phases中copy files
    2.将Build setting中的Build Active Architecture Only(只生成当前架构的库)关闭,
    运行模拟器,生成i386和x86_64架构的库1
    运行真机,生成armv7和arm64架构的库2
    3.lipo -create 库1 库2 -output 合成库名
    lipo -info 库文件 显示当前库文件的架构
    4.在target下点击加号添加单元测试调试,link frame合成库

    制作framework文件
    1.在build phases中将其他头文件从Header ->Project拖到Public
    2.在build setting-> Link ->Mach-O Type 选择static Library
    3.将bundle包拖进工程,
    4.剩下同静态库
    注意:
    1.在模拟器运行项目,闪退,报错:image not found
    使用file 库名,查看到Mach-O 64-bit dynamically linked shared library x86_64,默认是动态链接库
    2.直接拖framework,bundle包不会进去

    将下面.sh文件放在MyFrameWork的根目录下
    注:如果有多个Xcode,使用sudo xcode-select --switch /Application/Xcode名.app
    使用xcode-select -p查看运行的xcode版本

    #!/bin/bash
    
    #打包发布脚本
    
    # 设置编译目标
    targetName="编译目标名称"
    
    # 设置编译模式通常 Debug Release
    compileMode="Release"
    
    # 设置需要的编译架构
    distribute_x86_archs=("i386" "x86_64")
    distribute_arm_archs=("armv7" "armv7s" "arm64")
    
    # 设置需要的sdk
    iOS_iPhoneSimulator_sdk="iphonesimulator"
    iOS_iPhoneOS_sdk="iphoneos"
    
    
    # 设置各种版本编译后的目录
    target_distribution_sdk="distribution_sdk"
    
    # 设置发布的SDK名称
    target_sdk_name="${targetName}.framework"
    
    #编译各种arch版本的库文件
    if [ -d "${target_distribution_sdk}" ]  
    then  
    rm -rf "${target_distribution_sdk}"  
    fi
    
    #清理build文件
    if [ -d "build" ]  
    then  
    rm -rf "build"  
    fi
    
    #删除老的framework
    if [ -d "${target_sdk_name}" ]  
    then  
    rm -rf "${target_sdk_name}"  
    fi
    
    mkdir "${target_distribution_sdk}"
    
    for distribute_arch in ${distribute_x86_archs[@]};do
        xcodebuild -target "$targetName" -configuration "${compileMode}"  -sdk "${iOS_iPhoneSimulator_sdk}" -arch "${distribute_arch}" build
        mkdir "${target_distribution_sdk}/${distribute_arch}"
        mv "build/${compileMode}-iphonesimulator/${target_sdk_name}" "${target_distribution_sdk}/${distribute_arch}/${target_sdk_name}"
        rm -rf "build" 
    done
    
    for distribute_arch in ${distribute_arm_archs[@]};do
        xcodebuild -target "$targetName" -configuration "${compileMode}"  -sdk "${iOS_iPhoneOS_sdk}" -arch "${distribute_arch}" build
        mkdir "${target_distribution_sdk}/${distribute_arch}"
        mv "build/${compileMode}-iphoneos/${target_sdk_name}" "${target_distribution_sdk}/${distribute_arch}/${target_sdk_name}"
        rm -rf "build" 
    done
    
    
    if [ -d "distribution_target" ]  
    then  
    rm -rf "distribution_target"  
    fi
    mkdir "distribution_target"
    
    mkdir "./distribution_target/${target_sdk_name}"
    cp -R "${target_distribution_sdk}/arm64/${target_sdk_name}" "./distribution_target"
    rm -rf "./distribution_target/${target_sdk_name}/${targetName}"
    
    lipo -create "${target_distribution_sdk}/i386/${target_sdk_name}/${targetName}" "${target_distribution_sdk}/x86_64/${target_sdk_name}/${targetName}" "${target_distribution_sdk}/armv7/${target_sdk_name}/${targetName}" "${target_distribution_sdk}/armv7s/${target_sdk_name}/${targetName}" "${target_distribution_sdk}/arm64/${target_sdk_name}/${targetName}" -output "distribution_target/${target_sdk_name}/${targetName}"
    
    
    rm -rf "${target_distribution_sdk}"
    #rm -rf "distribution_target"
    
    echo "构建完成"
    
    

    75.swift 值类型和引用类型

    array 这个 struct 中存储了指向这个 array 真正 storage 的 reference, 而 array 这个 struct 只是其内部 storage 的一个 owner。

    当多个 owner 同时指向这个 storage,其中任意一个 owner 要修改这个 storage 内部的内容时, 就会先复制这个 storage 的内容, 再在复制出来的 storage 内进行真正的修改。

    var c: [Int] = [ ... ]
    var d = c        //这里没有拷贝
    d.append(2)      //这里 *有* 拷贝
    

    Swift 标准库提供可一个函数 isKnownUniquelyReferenced ,用来检查某个实例是不是唯一的引用,如果是,说明该实例没有被共享,我们可以直接修改当前的实例,如果不是,说明实例被共享,这时对它进行更改就需要先复制。

    76.optional结构解析

    public enum Optional<Wrapped> : ExpressibleByNilLiteral {
        case none
        case some(Wrapped)
            
        public init(_ some: Wrapped)
        public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
        public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
        public init(nilLiteral: ())
        public var unsafelyUnwrapped: Wrapped { get }
    }
    
    

    包含 none 和 some(Wrapped) 两个 case,分别代表可选类型“没有值”和“有值”两种情况
    既然是枚举类型,那么我们就可以通过判断 case 是否匹配来读取可选变量的值,代码如下:

    77 多个网络任务请求完成后执行操作

    i)使用GCD的dispatch_group_t

    NSURLRequest *request = [NSURLRequest requestWithURL:url];
      NSURLSession *session = [NSURLSession sharedSession];
      
      dispatch_group_t downloadGroup = dispatch_group_create();
      for (int i=0; i<10; i++) {
          dispatch_group_enter(downloadGroup);
          
          NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
              
              NSLog(@"%d---%d",i,i);
              dispatch_group_leave(downloadGroup);
              
          }];
          
          [task resume];
      }
      
      dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
          NSLog(@"end");
      });
    
    

    ii)使用GCD的信号量dispatch_semaphore_t 用时 300+ms

    NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSession *session = [NSURLSession sharedSession];
        
        dispatch_semaphore_t sem = dispatch_semaphore_create(0);
        for (int i=0; i<10; i++) {
            
            NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                
                NSLog(@"%d---%d",i,i);
                count++;
                if (count==10) {
                    dispatch_semaphore_signal(sem);
                    count = 0;
                }
                
            }];
            
            [task resume];
        }
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"end");
        });
    

    网络请求顺序回调。

    
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSession *session = [NSURLSession sharedSession];
        
        NSMutableArray *operationArr = [[NSMutableArray alloc]init];
        for (int i=0; i<10; i++) {
            NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
                NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                    
                    NSLog(@"%d---%d",i,i); //乱序
                    
                }];
                
                [task resume];
    
                //非网络请求
                NSLog(@"noRequest-%d",i);//顺序
            }];
            
            [operationArr addObject:operation];
            if (i>0) {
                NSBlockOperation *operation1 = operationArr[i-1];
                NSBlockOperation *operation2 = operationArr[i];
                [operation2 addDependency:operation1];
            }
        }
        
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        [queue addOperations:operationArr waitUntilFinished:NO];  //YES会阻塞当前线程
    #warning - 绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。
    
    

    78 GCD VS NSOperation

    GCD:调度组
    NSOperation:
    1.操作队列最大数目
    2.优先级
    3.依赖
    4.取消操作

    79 cocopod中resource_bundles vs resources

    resource_bundles 允许定义当前 Pod 库的资源包的名称和文件。用 hash 的形式来声明,key 是 bundle 的名称,value 是需要包括的文件的通配 patterns。

    CocoaPods 官方强烈推荐使用 resource_bundles,因为用 key-value 可以避免相同名称资源的名称冲突

    使用 resources 来指定资源,被指定的资源只会简单的被 copy 到目标工程中(主工程)。

    
    spec.resource = 'Resources/HockeySDK.bundle'
    spec.resources = ['Images/*.png', 'Sounds/*']
    ----------------------------------------------------------------------------------
    spec.ios.resource_bundle = { 'MapBox' => 'MapView/Map/Resources/*.png' }
    spec.resource_bundles = {
        'MapBox' => ['MapView/Map/Resources/*.png'],
        'OtherResources' => ['MapView/Map/OtherResources/*.png']
      }
    

    80 cocoapod static_framework

    1.4.0推出了static framework,,唯一要注意的是依赖的其他pod也要保证是静态库或加static_framework为ture

    bitcode

    Bitcode,干的事情是指令集优化。根据你设备的状态去做编译优化,进而提升性能,但这对包的大小优化起不到什么本质上的作用

    LLVM的前端可以理解为C/C++/OC/Swift等编程语言,LLVM的后端可以理解为各个芯片平台上的汇编指令或者可执行机器指令数据,那么,BitCode就是位于这两者直接的中间码,Bitcode仅仅只是一个中间码不能在任何平台上运行,但是它可以转化为任何被支持的CPU架构,包括现在还没被发明的CPU架构,也就是说现在打开Bitcode功能提交一个App到应用商店,以后如果苹果新出了一款手机并CPU也是全新设计的,在苹果后台服务器一样可以从这个App的Bitcode开始编译转化为新CPU上的可执行程序,可供新手机用户下载运行这个App.

    包含bitcode配置的程序将会在App store上被编译和链接。bitcode允许苹果在后期重新优化程序的二进制文件,而不需要重新提交一个新的版本到App store上。

    注: ipa 变小的事情是 iOS 9 的 feature。

    1. 如果想给 ipa 减肥,Bitcode 乃至 App Thinning 都对 iOS 9.0.2 以下用户无效。
    2. 其实Bitcode 对 ipa 减肥起不到什么作用。

    81 cocoapod指定版本

    '~> 0.1.2' :0.2以下(不含0.2),0.1.2以上(含0.1.2)
    '~> 0' 等于没有此约束
    >,>=,<,<=显而易见

    82 profile.lock

    pod install只会将Podfile的信息写入到Podfile.lock, 但是不修改Pods已安装的依赖库的版本信息。pod update不但会将Podfile的信息写入到Podfile.lock文件中, 还会更新Pods已安装的依赖库的版本信息。

    83 设置阴影边框

    设置阴影边框时,如果子视图背景颜色为空,会发现子视图中的控件也出现阴影效果

    84 lldb观察熟悉变化

    (lldb) watchpoint set variable model->_name

    Watchpoint created: Watchpoint 1: addr = 0x600001676e88 size = 8 state = enabled type = w
        declare @ '/Users/yuntai/Desktop/Target/Test/Test/ViewController.m:130'
        watchpoint spec = 'model->_name'
        new value: 0x0000000000000000
    
    Watchpoint 1 hit:
    old value: 0x0000000000000000
    new value: 0x000000010bee42c8
    
    Watchpoint 1 hit:
    old value: 0x000000010bee42c8
    new value: 0x0000000000000000
    
    Watchpoint 1 hit:
    old value: 0x0000000000000000
    new value: 0x00000000000002e7
    
    Watchpoint 1 hit:
    old value: 0x00000000000002e7
    new value: 0x0000000000000000
    

    85 iOS包大小优化

    http://www.hgsem.com/xmt/2018-01-16/2326.html

    86 读写效率预估


    九万条基础上

    方式 单条插入万次(ms) 查询万次(ms) 100Block查询1w条(ms) 备注
    SQlite3 1462 331 150 经常重复的数据建立索引再对其检索后,反而比不建立索引查询要慢一倍多
    Realm 32851 699 205 如果在一次事务中插入一万条,耗时735ms

    数据来源:https://blog.csdn.net/Cloudox_/article/details/75012746


    10万个key值的基础,写入10万条记录键值对或包含记录的文件

    方式 写入键值对万条平均(ms) 写入包含文件万条平均(ms) 查询单条(ms)
    NSUserDefault 1000 100 100

    数据来源:https://blog.csdn.net/wwwang89123/article/details/46401579


    87 使用Autolayout设置两个动态宽度的lable之间的约束

    |L R|
    第一步:设置L的上左下和距离R的约束,设置R的上下右的约束

    第二步:设置R的Content Hugging Priority(抗拉伸优先级) 为<251,值越小,视图越容易被拉伸;这时L的宽度会自适应宽度,R的左侧会拉伸至L的右侧,约束报错会消失
    注意:这时如果L和R的总宽度的大于屏幕宽度的时候还是会出现错误,报这个错的原因是,当屏幕宽度不足时,不知该切掉哪一个

    第三步:设置Content Compression Resistance Priority< 750,也叫内容压缩阻力优先级,该优先级越高,则越晚轮到被压缩

    i)如果没有超过屏幕宽度,Content Hugging Priority起主要作用
    ii)如果超过了屏幕宽度Content Compression Resistance Priority起主要作用,设置>750方会根据自身内容调整宽度,如果内容超过屏幕,则会将另一方挤压的不显示.


    屏幕快照 2019-01-10 上午9.33.31.png

    88宏定义中的特殊字符

    符号把一个符号直接转换为字符串,例如:

    define STRING(x) #x

    const char *str = STRING( test_string ); str的内容就是"test_string",也就是说#会把其后的符号
    直接加上双引号。

    符号会连接两个符号,从而产生新的符号(词法层次),例如:

    define SIGN( x ) INT_##x

    int SIGN( 1 ); 宏被展开后将成为:int INT_1;

    89 pod 'XXXXX ', :modular_headers => false

    如果true,可以用@import XXXXX;导入第三方可
    反之则不可以

    90 多个target在一个工程空间中

    workspace 'hebing.xcworkspace'
    target 'hebing' do
      # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
      # use_frameworks!
    pod 'SDWebImage', :modular_headers => true
      # Pods for hebing
    
    end
    
    
    target 'hebing2' do
        # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
        xcodeproj 'hebing2/hebing2.xcodeproj'
        # use_frameworks!
        pod 'AFNetworking'
        # Pods for hebing
        
    end
    
    

    91.第三方库pod install速度慢的问题.例如ZXingObjC

    偷懒的方法:
    在 资源库⁩ ▸ ⁨Caches⁩ ▸ ⁨CocoaPods⁩▸pods 的release中放入对应版本的第三方
    在specs->Release中放入第三方库的 x.x.podsepec.json文件(可在已经pod的电脑上拷贝)

    92.自动化打包时build settings 中PRODUCT_BUNDLE_IDENTIFIER的修改

    1.找到project.pbxproj的路径
    2.sed -i ' ' 's/原bundle id/替换的bundle id/g' project.pbxproj
    原理:就是替换project.pbxproj中的所有原bundle id的值.

    93.The request cannot be processed at this time. Please try again later

    苹果开发者账号添加的手机号只能是没有appid的手机号,一个手机号只能对应一个appleid

    94.bundleForClass:

    如果在动态库中定义的class,则返回动态库的bundle,如果不是在动态加载的class或是在动态库中定义的class,返回main bundle

    95.attribute((constructor))

    attribute((constructor))应该是在main函数之前,执行一个函数,便于我们做一些准备工作.

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            printf("main function");
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    __attribute__((constructor)) static void beforeFunction()
    {
        printf("beforeMain");
    }
    
    beforeMain
    main function
    

    attribute((destructor))让系统在main()函数退出或者调用了exit()之后,调用我们的函数

    还可以给属性设置优先级.这些函数并不非要写到main.m文件中,无论写到哪里,结果都是一样的.但是,为了更显式的让阅读者看到这些定义,至少,还是在main.m文件中留个声明.

    优先级设置,最好从100之后开始用,执行顺序从低到高

    static  __attribute__((constructor(101))) void before1()
    {
        printf("1");
    }
    

    96.红板报实现效果

    原理:
    将屏幕上下等分,每边一个view上覆盖一层view,将最上层的两个view的锚点定在屏幕中心.
    向上滑动时旋转z轴,使view看起来有切斜的效果,当90度后页面的背视图是颠倒,所以需要一次转化,使背视图可以正常显示.此时下层的底视图显示的是下一页底部的内容

    当滑动到180度后,更换上面顶层视图为刚刚下层翻转时的视图,下层顶部视图切换为下层底视图,实现上下翻页效果
    推荐研究PageOverturnAnimation

    97 WKWebview在iphoneX上底部留白

    原因是WKContentView在安全区域之内

    if (@available(iOS 11.0, *)) {
                _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
     }
    

    98 dispatch_set_target_queue

    i)指定优先级

      // 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。
      dispatch_set_target_queue(serialDiapatchQueue, dispatchgetglobalqueue);
    

    ii)队列执行

    //1.创建目标队列,这里是串行队列就串行执行,如果是并行队列,则并发
        dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
        
        //2.创建3个并行队列,这里串行和并行队列的影响不大,主要是targetQueue的类型,
        dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_CONCURRENT);
    
        //3.将3个并行队列分别添加到目标队列
        dispatch_set_target_queue(queue1, targetQueue);
        dispatch_set_target_queue(queue2, targetQueue);
        dispatch_set_target_queue(queue3, targetQueue);
        
        
        dispatch_async(queue1, ^{
            NSLog(@"1 in");
            [NSThread sleepForTimeInterval:3.f];
            NSLog(@"1 out");
        });
        
        dispatch_async(queue2, ^{
            NSLog(@"2 in");
            [NSThread sleepForTimeInterval:2.f];
            NSLog(@"2 out");
        });
        dispatch_async(queue3, ^{
            NSLog(@"3 in");
            [NSThread sleepForTimeInterval:1.f];
            NSLog(@"3 out");
        });
    
    2019-03-13 10:01:32.786307+0800 Test[71807:7774127] 1 in
    2019-03-13 10:01:35.790011+0800 Test[71807:7774127] 1 out
    2019-03-13 10:01:35.790565+0800 Test[71807:7774127] 2 in
    2019-03-13 10:01:37.793784+0800 Test[71807:7774127] 2 out
    2019-03-13 10:01:37.793973+0800 Test[71807:7774127] 3 in
    2019-03-13 10:01:38.794962+0800 Test[71807:7774127] 3 out
    
    

    99.Git冲突:commit your changes or stash them before you can merge.

    i)先保存备份当前的工作区的内容,将当前的工作区内容保存到Git栈中
    将工作区与上次的提交保持一致,拉取新的提交
    从Git栈中读取最近一次保存的内容,恢复工作区的相关内容。由于可能存在多个Stash的内容,所以用栈来管理,pop会从最近的一个stash中读取内容并恢复。

    git stash
    git pull
    git stash pop
    

    git stash list: 显示Git栈内的所有备份,可以利用这个列表来决定从那个地方恢复。
    git stash clear: 清空Git栈。此时使用gitg等图形化工具会发现,原来stash的哪些节点都消失了。

    ii)放弃本地修改,直接覆盖之

    git reset --hard
    git pull
    

    100.fatal: cannot do a partial commit during a merge.
    git commit -i myfile 将指定的文件添加到提交的索引中
    git commit -am 'Conflicts resolved' 提交所有更改的文件

    相关文章

      网友评论

          本文标题:iOS 开发Tip2

          本文链接:https://www.haomeiwen.com/subject/hrmnvqtx.html