iOS常见知识问答

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 *)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;

而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常见知识问答)