iOS的一些知识总结

1:strong weak copy assign retain区别

  • strongcopy的区别
#import <Foundation/Foundation.h>

@interface Model : NSObject
@property(strong,nonatomic) NSString * str1;
@property(copy,nonatomic) NSString * str2;
@end

@implementation Model

@end

int main() {
    Model *model = [[Model alloc] init];
    NSMutableString *str = [[NSMutableString alloc] initWithString:@"123"];
    model.str1 = str;
    model.str2 = str;
    [str appendString:@"123"];
    NSLog(@"%@",model.str1);
    NSLog(@"%@",model.str2);
    return 0;
}

输出
在这里插入图片描述
声明为copy的对象被复制了

  • assginweak的区别:

通常来说,assgin用于声明基础类型,weak用于声明对象,两者都不会使引用计数器+1,但是用于声明代理的时候,两者是有区别的:
一旦指向的对象被销毁
weak会将delegate置为nil
assign还指向被释放的对象,这样就变成了野指针

  • retainstrong的区别:
    一般来说,两者没有区别,都是让引用计数器+1,但是用于声明block的时候,两者是有区别的,据说strong在声明block的时候,等同于copy,没有验证,但还是使用copy吧。

2:block的使用

block的本质是一个匿名函数,iOS引入block,使iOS拥有面向函数编程的能力。在面向函数编程模型中,函数就像普通变量一样,可以作为参数、成员变量、返回值,也可以像js那样实现链式编程。

//这样就定义了一个输入值为int类型,返回值也为int类型的block
int (^block)(int val);

//将block定义为一种变量名
typedf int (^Block)(int val);

@interface Model:NSObject

//这两种定义block为成员变量的方法都可以
@property(copy,nonatomic) Block b;
@property(copy,nonatomic) void(^block)(void);

//也可以将block作为函数的参数和返回值
-(void(^)(int val))method:(int(^)(void))block;
@end

@implementation Model
- (instancetype)init{
    if (self = [super init]) {
    	//block作为局部变量
        int (^localBlock)(int val) = ^int(int val){
			return val;
		}
		localBlock(1);
    }
    return self;
}
-(void(^)(int val))method:(int(^)(void))block{
	int val = block();
	return ^(int val){
		NSlog(@"这是返回的block");
	}
}
@end

3:block对变量进行截取

函数中的局部变量,会存入栈中,当block作为局部变量时,可以对栈中的变量进行 截取,也就是将栈中的局部变量复制一份。
但是,block不能修改截取的局部变量,因为捕获的是const类型。
但是可以修改全局变量和局部变量。

int main() {
    int a = 2;
    void(^block)(void) = ^{
        NSLog(@"%d",a);
    };
    a = 10;
    block();
    return 0;
}

在这里插入图片描述
这里输出的还是2,因为block对变量进行了复制。
当然,如果是对象就不一样了,block复制了指向对象的指针,但如果存于堆中的对象发生了改变,block的访问还是会变的。

int main() {
    NSMutableString * str = [NSMutableString stringWithFormat:@"123"];
    void(^block)(void) = ^{
        NSLog(@"%@",str);
    };
    [str appendString:@"456"];
    block();
    return 0;
}

在这里插入图片描述
block由于复制了指向对象的指针,按理来说,是会让对象的引用计数器+1,所以这个时候就需要用 __block , 直接使用原指针,避免对象引用计数器的增加。
__block 的意思是不复制,直接使用截取的变量,因此使用 __block修饰的变量,可以在block内进行更改。

@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, charchar *argv[]) {  
    @autoreleasepool {  
        MyClass* obj = [[[MyClass alloc] init] autorelease];  
        [obj test];  
        return 0;  
    }  
}  

执行结果为1 1 1 2 1。

  • static 和 global类型的变量由于在静态区,和进程的生命周期一致,所以引用计数不会增加。
  • 成员变量,self的引用计数+1,而变量本身不会增加。
  • 局部变量,引用计数器+1,为2
  • __block类型的变量,block不会复制__block类型的变量,而是使用了和外面同一个,自然也不会使引用计数器+1.

4:关于block的循环引用

这样就引起了循环引用,self持有block,block持有self,谁都释放不掉。

    self.block = ^{
        NSLog(@"%lu",self.val);
    };

这样解决了循环引用,block中使用self不会使引用计数器+1,但是不够完美,万一self被释放掉了,block中的用到weakSelf的代码又没有执行到,就会有异常。

__weak ViewController * weakSelf = self;
    self.block = ^{
        NSLog(@"%lu",weakSelf.val);
    };

完美的写法
block中声明的strongSelf和原先的self不同,当block中的代码执行完毕,strongSelf会被弹出栈中,不会引起循环引用。如果block中的代码还没有执行,self也不会被提前释放。

__weak ViewController * weakSelf = self;
    self.block = ^{
        __strong typeof(self) strongSelf = weakSelf;
        NSLog(@"%lu",strongSelf.val);
    };

5:GCD

1:使用group同步进程
dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("group.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务1完成");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2完成");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"请求完成");
    });

输出
在这里插入图片描述
网上有教程在queue中发起网络异步请求,发现不能够成功同步,这是因为NSUrlSession在发起网络请求的时候,又另外开启了一个异步线程,所以queue中,不会等待session的网络请求完成
然后,坑爹的是!好像现在iOS不支持同步网络请求了,所有请求都是异步的,只能使用信号量了。

2:使用信号量和PV操作同步网络请求

关于信号量的基础,可以看看北大的操作系统视频(陈向群老师讲的),讲的很棒。
信号量主要有如下几个操作

//创建一个信号量,参数为信号量的值,互斥情况,一般为1
dispatch_semaphore_t mutex = dispatch_semaphore_create(1);

//p操作(信号量减1),第一个参数是信号量,第二个参数是等待时间
dispatch_semaphore_wait(mutex, 1000);

//v操作(信号量加1),参数为信号量
dispatch_semaphore_signal(mutex);

解决多异步请求同步的问题:

__block int n = 0;
    dispatch_semaphore_t mutex = dispatch_semaphore_create(1);
    
    NSURLSessionTask *task1 = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"任务1完成");
        dispatch_semaphore_wait(mutex, 1000);
        n++;
        dispatch_semaphore_signal(mutex);
    }];
    [task1 resume];
    
    NSURLSessionTask * task2 = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"任务2完成");
        dispatch_semaphore_wait(mutex, 1000);
        n++;
        dispatch_semaphore_signal(mutex);
    }];
    [task2 resume];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (TRUE) {
            if (n == 2) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"任务完成,刷新界面");
                });
                break;
            }
        }
    });

但是总觉得使用while循环等待有点浪费资源。。。不知道有没有更好的办法。

6:iOS 内存管理基础

虽然现在都是ARC机制,但是面试还是喜欢问MRC相关的东西,引用计数就不说了,默认都会。这里主要介绍 autorelesepool
autorelesepool代码块结束后,会对其内部的对象统一release一次,从而达到延迟释放的目的。
比如,但循环生产大内存对象时,如果不适

…未完

7:iOS单例模式

懒汉式(第一次使用的时候创建)

@implementation Single
static id instance;
+ (instancetype)sharedInstance{
    dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    dispatch_once_t token;
    dispatch_once(&token, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

- (id)copyWithZone:(NSZone *)zone{
    return [Single sharedInstance];
}

- (id)mutableCopyWithZone:(nullable NSZone *)zone{
    return [Single sharedInstance];
}

@end

饿汉式(类初始化的时候创建,由于这个时候只有一个线程,所以不需要考虑临界资源访问的问题)

#import "Single.h"

@implementation Single
static id instance;

+ (void)load{
    instance = [[Single alloc] init];
}

+ (instancetype)sharedInstance{
    return instance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    if (instance == nil) {
        instance = [super allocWithZone:zone];
    }
    return instance;
}

- (id)copyWithZone:(NSZone *)zone{
    return instance;
}

- (id)mutableCopyWithZone:(nullable NSZone *)zone{
    return instance;
}

@end

这里注意instancetype和id的区别

  • id 可以作为参数类型和返回值类型,但 instancetype 只能作为返回值类型
  • instancetype 作为返回值时,编译器会自动检测返回值类型,因此做返回值的时候,一律使用 instancetype

8:iOS copy和mutableCopy

网络上,关于这两个的区别,很多都是错的,比如copy是浅拷贝,mutableCopy是深拷贝。。。扯淡。
iOS中,对象要具备拷贝能力,需要继承两个协议,NSCopyNSMutableCopy,当我们调用

[object copy];
[object mutableCopy]

的时候,具体结果如何,关键看我们是如何实现这两个协议的
对于内置对象来说,以NSString为例子,对NSString调用copy方法

NSString *str = @"123";
NSString *str2 = [str copy];
NSLog(@"%p %p",str,str2);

输出

 0x100001030 0x100001030

内存地址是一样的,这说明cocoa的作者在实现NSString的时候,copyWithZone中并没有真的对对象进行了拷贝,因此str2只是复制了str1的地址,因为NSString作为不可变字符串,是不能够修改的,既然不能够修改,为什么要拷贝呢?所以copyWithZone中没有对对象进行拷贝。
对NSString调用mutableCopy就不一样了,由于要复制一个可变对象,自然要进行深拷贝:

NSString *str = @"123";
NSMutableString *str2 = [str mutableCopy];
NSLog(@"%p %p",str,str2);

输出:

0x100001030 0x100582510

str2为新的可变字符串,可以进行字符串的增加
对于作为成员变量声明的对象,如:

@property(copy,nonatomic) NSString *str;

str的set方法中,会调用copy方法,根据上面的分析,当使用的使用NSString对象的时候,copy和strong不会有什么区别,因为并不会真正的拷贝对象。
但是如果将NSMutableString赋值给str就不一样了。
NSMutableString由于是可变数组,copy方法会对对象进行深拷贝,拷贝出来的是不可变对象,如果要拷贝可变对象,就要用mutableCopy1

9:当我们点击一个app图标后,发生了什么

mach-0文件:包括

  • 动态库(dylib)
  • bundle:info.plist 图片等 [NSBundle mainBundle] 调用
  • 可执行文件
  • Framework(动态库对应的头文件和资源文件的集合)
main函数调用之前
  1. 加载dyld(dynamic loader)到内存中,
  2. dyle加载app启动所需的资源文件(bundle)和动态链接库(dylib)
  3. Rebase && Bind:当应用程序执行时,对应的进程会被加载入虚拟地址空间,ARSL技术,使得这个进程对应的虚拟地址空间的起始是随机的,为了让应用程序能够找到指令的正确位置,dyld使用
    Rebase: 修正内部(指向当前mach-o文件)的指针指向
    Bind: 修正外部指针指向
  4. objective-c/Initializers: oc作为动态语言,在执行之前,会把类的信息注册到一个全局的table中,之前的iOS版本中,接下来,就会对类进行加载,调用 +load 方法,单例的饿汉式和method-swizzing就是在里面实现的。但是目前+load方法已经弃用了,官方建议使用initialize,该方法会在类第一次收到消息前调用

更新的dyld3有所不同
在这里插入图片描述

  • 分析Mach-o Headers
  • 分析依赖的动态库
  • 查找需要Rebase & Bind之类的符号
  • 把上述结果写入缓存
    所以,新的iOS版本中,上面的步骤会加快,可以直接从缓存中读入数据。
main函数调用之后

didFinishLaunchingWithOptions
加载UIWindow
加载rootViewController
有博客算上了 applicationDidBecomeActive,这其实是有问题的,因为这一步在根视图控制器之后。

优化启动时间

main之前:

dylibs(动态库)

系统的动态库由于有缓存,其实加载速度非常快,内嵌的动态库加载速度往往较慢,这个时候可以减少动态库的数量或者将多个动态库合并为一个。

Rebase和bind

  • 删除无用的方法和类
  • 将功能类似的category合并
initialize

用initialize替代load

main之后

优化这些初始化的核心思想就是:

能延迟初始化的尽量延迟初始化,不能延迟初始化的尽量放到后台初始化。

这些工作主要可以分为几类:

  • 三方SDK初始化,比如Crash统计; 像分享之类的,可以等到第一次调用再出初始化。
  • 初始化某些基础服务,比如WatchDog,远程参数。
  • 启动相关日志,日志往往涉及到DB操作,一定要放到后台去做
  • 业务方初始化,这个交由每个业务自己去控制初始化时间。

比如根视图控制器通常是 UITabarController,里面的视图控制器可以只初始化一个,其他的使用占位符,等用户点击的时候,再初始化。

10:button的事件响应流程是什么

凡是继承于 UIResponder 或者 UIResponder 的子类的对象都可以作为响应对象,比如UIApplication、UIViewController和UIView。
在响应用户触摸等事件中,APP具体会通过下面三步来完成操作:

  1. 生成事件 。当用户点击屏幕时,会产生一个触摸事件,并放入由Application管理的事件队列中,然后在队列中取出最前面的事件交给Window处理。
  2. 查找第一响应对象 。Window收到事件后会在视图层次结构中找到最适合的一个视图来处理事件,通常一个窗口中最适合处理当前事件的对象称为第一响应对象。
  3. 处理事件 。通常最后是第一响应对象处理事件,如果第一响应对象无法处理事件,就会把事件传递给下一个响应对象,直到Application。如果Application也无法处理,那就丢弃掉此事件。

    在上述系列操作中,所参与到的UIApplication、UIViewController和UIView就作为响应对象构成这次事件的响应链。

    举个例子:
    在这里插入图片描述
    这里View A是window的根视图
    1:调用A的 hit-test:withEvent,返回nil,调用 pointInside:withEvent: ,查看是否还有子视图,返回true。
    2:对子视图 (View B,View C)进行 hit-Test
    3:View B调用 pointInside:withEvent:,返回false,对应 hitTest:withEvent: 返回nil。
    4:View C调用 pointInside:withEvent:,返回true,对View C所有子视图进行 hit-test
    5:以此类推,一直到View E,调用 pointInside:withEvent: 方法,返回true,对应的hitTest:withEvent: 返回View E,回溯,View C返回View E,View A返回View E,这样Application就知道View E是第一响应对象。

如果第一响应对象能处理时间,则响应结束,否则就会沿着响应链一直回溯
在这里插入图片描述

11:weak的使用原理

weak是通过runtime初始化并维护的,本质是一个 weak表结构

struct weak_table_t {
    weak_entry_t *weak_entries; //保存了所有指向指定对象的weak指针,weak_entries的对象
    size_t    num_entries;              // weak对象的存储空间
    uintptr_t mask;                      //参与判断引用计数辅助量
    uintptr_t max_hash_displacement;    //hash key 最大偏移值
};
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
    DisguisedPtr referent;  //范型
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    }
}
  1. weak_table_t(weak 全局表):采用hash(哈希表)的方式把所有weak引用的对象,存储所有引用weak对象。
  2. weak_entry_t(weak_table_t表中hash表的value值,weak对象体):用于记录hash表中weak对象。
  3. objc_object(weak_entry_t对象中的范型对象,用于标记对象weak对象):用于标示weak引用的对象。

weak的初始化方法:

id objc_initWeak(id *addr, id val)
{
    *addr = 0;
    if (!val) return nil;
    return objc_storeWeak(addr, val); // 存储weak对象
}

储存weak对象的方法

id
objc_storeWeak(id *location, id newObj)
{
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
    spinlock_t *lock1;
#if SIDE_TABLE_STRIPE > 1
    spinlock_t *lock2;
#endif

 retry:
    oldObj = *location;

    oldTable = SideTable::tableForPointer(oldObj);
    newTable = SideTable::tableForPointer(newObj);

    lock1 = &newTable->slock;
#if SIDE_TABLE_STRIPE > 1
    lock2 = &oldTable->slock;
    if (lock1 > lock2) {
        spinlock_t *temp = lock1;
        lock1 = lock2;
        lock2 = temp;
    }
    if (lock1 != lock2) spinlock_lock(lock2);
#endif
    spinlock_lock(lock1);

    if (*location != oldObj) {
        spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
        if (lock1 != lock2) spinlock_unlock(lock2);
#endif
        goto retry;
    }

    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    newObj = weak_register_no_lock(&newTable->weak_table, newObj, location);
    // weak_register_no_lock returns nil if weak store should be rejected

    // Set is-weakly-referenced bit in refcount table.
    if (newObj  &&  !newObj->isTaggedPointer()) {
        newObj->setWeaklyReferenced_nolock();
    }

    // Do not set *location anywhere else. That would introduce a race.
    *location = newObj;

    spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
    if (lock1 != lock2) spinlock_unlock(lock2);
#endif

    return newObj;
}
  • 旧对象解除注册操作 weak_unregister_no_lock

该方法主要作用是将旧对象在 weak_table 中解除 weak 指针的对应绑定。根据函数名,称之为解除注册操作。从源码中,可以知道其功能就是从 weak_table 中接触 weak 指针的绑定。而其中的遍历查询,就是针对于 weak_entry 中的多张弱引用散列表。

  • 新对象添加注册操作 weak_register_no_lock

这一步与上一步相反,通过 weak_register_no_lock 函数把心的对象进行注册操作,完成与对应的弱引用表进行绑定操作。

  • 初始化弱引用对象流程一览

弱引用的初始化,从上文的分析中可以看出,主要的操作部分就在弱引用表的取键、查询散列、创建弱引用表等操作,可以总结出如下的流程图:
在这里插入图片描述
3.weak释放为nil过程

weak被释放为nil,需要对对象整个释放过程了解,如下是对象释放的整体流程:

  • 调用objc_release
  • 因为对象的引用计数为0,所以执行dealloc
  • 在dealloc中,调用了_objc_rootDealloc函数
  • 在_objc_rootDealloc中,调用了object_dispose函数
  • 调用objc_destructInstance
  • 最后调用objc_clear_deallocating。

对象准备释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
详细见:http://www.cocoachina.com/ios/20181210/25761.html

12:NSTimer循环引用问题(等mac修好后试试)

timer作为VC的属性,被VC强引用,创建timer对象时VC作为target被timer强引用,即循环引用。
在这里插入图片描述
用于解决block循环引用的 weakself,无法用于NSTimer中,因为无论是weak还是strong,NSTimer中都会重新生成一个新的强引用指针指向self,导致循环引用。
解决办法:
创建一个继承NSObject的子类TempTarget,并创建开启计时器的方法。
.h文件

@property (nonatomic, assign) SEL selector;	//VC中要调用的方法
@property (nonatomic, weak) NSTimer *tempTimer;
@property (nonatomic, weak) id tempTarget;	//指向VC

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)tempTarget selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats;

.m文件

@implementation TempTarget

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)tempTarget selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
    TempTarget *target = [[TempTarget alloc] init];
    target.tempTarget = tempTarget;
    target.selector = selector;
    target.tempTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:target selector:@selector(timerSelector:) userInfo:userInfo repeats:repeats];
    return target.tempTimer;
}

- (void)timerSelector:(NSTimer *)tempTimer {
    if (self.tempTarget && [self.tempTarget respondsToSelector:self.selector]) {
        [self.tempTarget performSelector:self.selector withObject:tempTimer.userInfo];
    }else {
        [self.tempTimer invalidate];
    }
}
@end

在这里插入图片描述

13:arc下什么时候release?

arc是编译器特性:

  1. 对于方法内的对象,在方法结束时添加release
  2. 对于成员变量,在dealloc中添加release
14: category和extentsion原理

category原理:
(1)在编译期间生成category对象
(2)在运行时动态的将普通方法添加到类对象中,将类方法添加到元类中
(3)catogory结构体中有属性,因而可以使用runtime添加set和get方法,但成员变量时在编译的时候确定的,因此category无法添加成员变量。
(4)如果category和原类中有同名方法,那么会优先调用category中的方法。

extension:
extension就是类的一部分,只有知道类的源码才可以为它添加extension,因此它在编译期决定。在ViewController中看到的这个其实就是一个extension。

@interface ViewControlelr()
@end
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值