美文网首页
iOS常见知识问答

iOS常见知识问答

作者: supersmalltalk | 来源:发表于2018-12-06 18:15 被阅读37次

    iOS常见知识问答

    from http://wintelsui.github.io/pages/WTBlogs/htmls/iOSBasicKnowledgeQA.html


    ❏ 锁

    http://www.cocoachina.com/cms/wap.php?action=article&id=22402

    https://github.com/bestswifter/blog/blob/master/articles/ios-lock.md

    ===================================
    OSSpinLock :

    OSSpinLock lock = OS_SPINLOCK_INIT;
    OSSpinLockLock(&lock);
    OSSpinLockUnlock(&lock);
    

    ===================================
    synchronized :

    @synchronized(self) {}
    

    ===================================

    dispatch_semaphore :
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 
    dispatch_semaphore_signal(semaphore); 
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    

    ===================================

    pthread_mutex :
    pthread_mutex_t lock;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&lock, &attr);
    pthread_mutexattr_destroy(&attr);
    pthread_mutex_lock(&lock);
    pthread_mutex_unlock(&lock);
    

    ===================================

    NSLock:遵循协议 @protocol NSLocking

    - (void)lock;
    - (void)unlock;
    - (BOOL)tryLock;
    - (BOOL)lockBeforeDate:(NSDate *)limit;
    

    ===================================
    os_unfair_lock:iOS 10+ 推荐替换不再安全的OSSpinLock

    os_unfair_lock_t unfairLock;
    unfairLock = &(OS_UNFAIR_LOCK_INIT);
    os_unfair_lock_lock(unfairLock);
    os_unfair_lock_unlock(unfairLock);
    

    ===================================


    ❏ KVO

    键值监听,是观察者模式,用于监听属性的改变,主要方法包括:
    添加监听

    - (void)addObserver:(NSObject *)observer 
             forKeyPath:(NSString *)keyPath 
                options:(NSKeyValueObservingOptions)options 
                context:(nullable void *)context;
    

    移除监听

    - (void)removeObserver:(NSObject *)observer 
                forKeyPath:(NSString *)keyPath 
                   context:(nullable void *)context;
    

    监听回调

    - (void)observeValueForKeyPath:(nullable NSString *)keyPath 
                          ofObject:(nullable id)object 
                          change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change 
                          context:(nullable void *)context;
    

    原理
    KVO 通过isa-swizzling(类型混合指针)机制来实现,对象在被addObserver后,isa 指针指向了派生出的新类“NSKVONotifying_X元对象名X”,这个类继承自原类,并重写类被观察对象的的 Set 方法(原对象必须有该属性的 Set 方法),在新 Set 方法中添加了

    - (void)willChangeValueForKey:(NSString *)key;
    - (void)didChangeValueForKey:(NSString *)key;
    

    等方法以回调给观察者。
    手动触发回调,则需要调用

    - (void)willChangeValueForKey:(NSString *)key;
    - (void)didChangeValueForKey:(NSString *)key;
    

    ❏ KVC

    通过Key名直接访问对象的属性,或者给对象的属性赋值.

    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    

    接受者会按照一定顺序搜索设置属性方法(搜索 setKey, _key, _isKey, key, isKey);
    如果未找到设置方法,则会调用-setValue:forUndefinedKey:方法返回NSUndefinedKeyException异常;
    如果找到方法,但是设置参数为 nil 或者类型不匹配,则会调用setNilValueForKey:方法并返回NSInvalidArgumentException异常。

    - (nullable id)valueForKey:(NSString *)key;
    

    接受者按照getKey:、key、isKey顺序搜索获取属性值方法;
    如果未找到方法,则调用+accessInstanceVariablesDirectly方法查看类是否允许按照_key, _isKey, key, isKey顺序读取;
    如果不允许或者最终未找到,则调用-valueForUndefinedKey方法,并返回NSUndefinedKeyException异常。


    ❏ 响应者链,事件传递

    一系列直接或间接继承自UIResponder的类组成响应者链,用于在接收到触摸事件后沿着响应者链传递事件,响应者链始于 AppDelegate,UIWindow。

    当系统检测到触摸事件后,被打包为 UIEvent 加入到UIApplication 的事件队列,UIApplication将事件传递给 UIWindow 处理,
    调用

    - (BOOL)pointInside:(CGPoint)point 
              withEvent:(nullable UIEvent *)event;
    

    方法由层级下而上遍历,确定视图是否在点击区域内,以缩小传递范围;

    - (UIView *)hitTest:(CGPoint)point 
              withEvent:(nullable UIEvent *)event;
    

    方法自上而下遍历以确定响应者链,最终将事件传递给触摸视图。


    ❏ weak原理

    weak 对象弱引用
    Runtime 维护了一个weak哈希表,用于存储指向某个对象的所有weak指针,Key是所指对象的地址,Value是weak指针的地址数组。

    阶段:
    1:objc_initWeak,初始化新的weak指针指向对象的地址。
    2:objc_storeWeak,添加引用时,更新指针指向,创建对应的弱引用表。
    3:clearDeallocating,根据对象地址获取weak指针地址的数组,遍历数组将其中的数据设为nil,将对象地址从weak表中删除。


    ❏ atomic

    atomic原子属性

    Set 方法:——reallySetProperty(…)

    objc_retain(newValue);
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    oldValue = newValue;
    slotlock.unlock();
    objc_release(oldValue);
    

    Get 方法:——objc_getProperty(…)

    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(oldValue);
    slotlock.unlock();
    return objc_autoreleaseReturnValue(value);
    

    而其中
    spinlock_t锁实际为

    using spinlock_t = mutex_tt<LOCKDEBUG>;
    

    而mutex_tt为

    class mutex_tt : nocopy_t {
        os_unfair_lock mLock;
    }
    

    其内部是os_unfair_lock,iOS 10之后苹果推荐使用os_unfair_lock来代替不在安全的OSSpinLock

    Atomic 是否线程安全?
    Atomic 并不能保证线程安全,它只能提升正确率,atomic只是在属性的 Get/Set方法中赋值时添加了锁;
    A,B,C三个线程同时发起修改访问属性时,并不能完全保证 A 线程写后读取到的值是 A 写入的值,
    也无法保证直接使用 _xxx方式 访问实例变量,与使用 self. 的 Get/Set方法访问属性间获得正确的值。


    ❏ block 种类,copy 用途

    参考:https://www.jianshu.com/p/f0870fa95aac

    Block根据存储位置分为:

    NSGlobalBlock:(全局block),位于数据区。
    NSStackBlock:(栈block),位于栈区。
    NSMallocBlock:(堆block),位于堆区。

    全局block的特点是未引用外部变量(auto变量),它不会受到copy的影响;
    栈block,一般作为函数的参数,它引用了外部变量(auto变量),编译器会把它创建在栈上;
    堆block,一般作为对象属性,由于栈block 只属于创建时的作用域,作用域结束,栈block 可能随时会被系统释放,
    所以对栈block 进行copy 操作后将栈block 拷贝到堆上,变成了堆block,已防止 block 被提前释放,
    所以在声明 block 作为属性的时候要使用 copy。


    ❏ weak, retain, strong, copy...

    Assign:只是简单赋值,不更改引用计数,适用于基础数据类型和C数据类型,主要存在于栈上;
    Retain/Strong:指向并拥有该对象,引用计数+1。
    Copy:创建一个引用计数为1的对象,主要用于拥有可变类型的不可变对象,以及用于 block;
    Weak:弱引用,不更改引用计数,对象销毁后,访问对象得到nil,详见原理。

    参考:https://clang.llvm.org/docs/AutomaticReferenceCounting.html#id10 中 4.1.1 Property declarations 介绍

    assign implies __unsafe_unretained ownership.
    copy implies __strong ownership, as well as the usual behavior of copy semantics on the setter.
    retain implies __strong ownership.
    strong implies __strong ownership.
    unsafe_unretained implies __unsafe_unretained ownership.
    weak implies __weak ownership.

    With the exception of weak, these modifiers are available in non-ARC modes.


    ❏ 消息转发

    • (BOOL)resolveInstanceMethod:(SEL)sel;
      是否要为对象处理调用(添加方法)
    • (id)forwardingTargetForSelector:(SEL)aSelector;
      将 Selector 转发给其他已实现该方法的对象
    • (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;
    • (void)forwardInvocation:(NSInvocation *)anInvocation;
      生成方法签名,并转发

    //类方法

    • (BOOL)resolveClassMethod:(SEL)sel;
    • (id)forwardingTargetForSelector:(SEL)aSelector;
    • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    • (void)forwardInvocation:(NSInvocation *)anInvocation;

    ❏ 浅拷贝,深拷贝

    深复制:内容拷贝。源对象和副本指向的是不同的两个对象,源对象引用计数器不变,副本计数器设置为1

    浅复制:指针拷贝,源对象和副本指向的是同一个对象,对象的引用计数器+1,相当于retain。

    copy和mutableCopy:mutableCopy过对象类型改变都是深拷贝


    ❏ synthesize dynamic

    @synthesize:
    编译时,会自动生成 Get/Set方法。但手动添加方法优先。

    @dynamic:
    编译时,不会自动生成Get/Set方法。编译时无影响,但是如果未手动实现Get/Set,在调用Get/Set方法时,会由于为实现方法而崩溃。(unrecognized selector sent to instance)


    ❏ NSInvocation

    - (void)runLoginInvocation{
        NSString *account = @"lily";
        NSString *password = @"123123";
        
        NSMethodSignature  *signature = [[self class] instanceMethodSignatureForSelector:@selector(loginAccount:password:)];
    //    NSMethodSignature  *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@@"];//NSInvocation.h:_NSObjCValueType
        
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        invocation.target = self;
        invocation.selector = @selector(loginAccount:password:);
        
        [invocation setArgument:&account atIndex:2];//Index从2开始 0 为 Target,1 为 selector
        [invocation setArgument:&password atIndex:3];
        
        [invocation invoke];//执行方法
    //    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    //        [invocation invoke];//执行方法
    //    });
    }
    
    - (void)loginAccount:(NSString *)account password:(NSString *)password{
        NSLog(@"account:%@ password:%@",account,password);
    } 
    

    ❏ __block与__weak

    __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,也可以修饰基本数据类型。
    __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。 __block对象可以在block中被重新赋值,__weak不可以。


    ❏ isa 元类

    在Objective-C中,任何类的定义都是对象。任何类与实例都有isa指针,指向类的元类(meteClass),元类中保存了类的类方法列表;
    对象的isa指针指向所属的类,类的isa指针指向了所属的元类,元类的isa指向了根元类,根元类指向了自身。
    获取 isa 指针指向使用 Class object_getClass(id obj)


    isa.png

    ❏ 多线程

    1:NSThread:简单的面向对象的线程管理类

    常用方法

    - (void)testThread{
    //创建线程-selector
    NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(runThreadA) object:nil];
    [threadA setName:@"threadA"];
    //线程启动
    [threadA start];
    // [threadA cancel];//线程取消
    
    //创建线程-block
    NSThread *threadB = [[NSThread alloc] initWithBlock:^{
    NSLog(@"runThreadB run:%@", [NSThread currentThread]);
    }];
    [threadB start];
    
    //创建线程并执行线程-selector
    [NSThread detachNewThreadSelector:@selector(runThreadA) toTarget:self withObject:nil];
    [NSThread detachNewThreadWithBlock:^{
    NSLog(@"runThreadD run:%@", [NSThread currentThread]);
    }];
    
    //隐式创建线程并执行线程-selector
    [self performSelectorInBackground:@selector(runThreadA) withObject:nil];
    }
    
    - (void)runThreadA{
    NSLog(@"runThreadA run:%@", [NSThread currentThread]);
    //暂停当前线程几秒
    [NSThread sleepForTimeInterval:2];
    // [NSThread sleepUntilDate:(nonnull NSDate *)];
    
    //线程结束后在主线程调用方法 runThreadAEnd()
    [self performSelectorOnMainThread:@selector(runThreadAEnd) withObject:nil waitUntilDone:YES];
    }
    
    - (void)runThreadAEnd{
    NSLog(@"runThreadAEnd run:%@", [NSThread currentThread]);
    }
    
    2:NSOperation与NSOperationQueue,面向对象,可以继承重写满足不同需求
    //创建 NSOperation -selector
    NSInvocationOperation *opis = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(runThreadA) object:nil];
    //设置线程优先级
    opis.queuePriority = NSOperationQueuePriorityVeryHigh;
    //[opis start];//NSInvocationOperation单独使用时不会l开启新线程,将会在当前线程执行
    
    //创建 NSOperation -block
    NSBlockOperation *opbs = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"runThread Block run:%@", [NSThread currentThread]);
    // 回到主线程
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    NSLog(@"runThread Block->Main run:%@", [NSThread currentThread]);
    }];
    }];
    //[opbs start];//NSBlockOperation单独使用时不会l开启新线程,将会在当前线程执行
    
    // 获取主线程队列
    //NSOperationQueue *queue = [NSOperationQueue mainQueue];
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 设置最大并发操作数,默认值-1,无限制并发;
    [queue setMaxConcurrentOperationCount:2];
    
    // 设置依赖
    [opbs addDependency:opis];
    
    //添加线程
    [queue addOperation:opis];
    [queue addOperation:opbs];
    [queue addOperationWithBlock:^{
    NSLog(@"runThread addBlock run:%@", [NSThread currentThread]);
    }];
    
    // 阻塞当前线程,等待队列中所有线程执行完后在继续执行,千万不能在主线程使用
    // [queue waitUntilAllOperationsAreFinished];
    // NSLog(@"因为waitUntilAllOperationsAreFinished而等待了一会儿");
    
    
    // 取消单个操作
    // [opbs cancel];
    
    
    //取消所有队列
    // [queue cancelAllOperations];
    
    3:GCD
    // 创建串行队列
    dispatch_queue_t queueSerial = dispatch_queue_create("com.sfs.queue", DISPATCH_QUEUE_SERIAL);
    // 创建并行队列
    dispatch_queue_t queueConcurrent = dispatch_queue_create("com.sfs.queue", DISPATCH_QUEUE_CONCURRENT);
    // 获取主队列
    dispatch_queue_t queueMain = dispatch_get_main_queue();
    // 获取全局并发队列
    dispatch_queue_t queueGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_sync(queueSerial, ^{
    sleep(2);
    NSLog(@"run queueSerial end");
    });//
    dispatch_async(queueConcurrent, ^{
    sleep(1);
    NSLog(@"run queueConcurrent end");
    });
    dispatch_async(queueGlobal, ^{
    sleep(0.5);
    NSLog(@"run queueGlobal end");
    dispatch_async(queueMain, ^{
    NSLog(@"run queueMain end");
    });
    });
    

    同步执行 + 串行队列
    异步执行 + 串行队列
    同步执行 + 并发队列
    异步执行 + 并发队列

    group

    dispatch_group_t groupTestBlock = dispatch_group_create();
    dispatch_group_async(groupTestBlock, queueConcurrent, ^{
    NSLog(@"run groupTestBlock queueConcurrent end");
    });
    dispatch_group_async(groupTestBlock, queueGlobal, ^{
    NSLog(@"run groupTestBlock queueGlobal end");
    });
    dispatch_group_notify(groupTestBlock, queueMain, ^{
    NSLog(@"run groupTestBlock queueMain end");
    });
    // 等待Group内任务全部完成后,才继续执行(会阻塞当前线程)
    // dispatch_group_wait(groupTestBlock, DISPATCH_TIME_FOREVER);
    
    dispatch_group_t groupTestSpecial = dispatch_group_create();
    dispatch_group_enter(groupTestSpecial);
    [NSThread detachNewThreadWithBlock:^{
    sleep(2);
    NSLog(@"run groupTestSpecial threadA end");
    dispatch_group_leave(groupTestSpecial);
    }];
    dispatch_group_enter(groupTestSpecial);
    [NSThread detachNewThreadWithBlock:^{
    sleep(1);
    NSLog(@"run groupTestSpecial threadB end");
    dispatch_group_leave(groupTestSpecial);
    }];
    dispatch_group_notify(groupTestSpecial, queueMain, ^{
    NSLog(@"run groupTestSpecial queueMain end");
    }); 
    

    ❏ runtime


    相关文章

      网友评论

          本文标题:iOS常见知识问答

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