iOS面试题+基础知识iOS面试资料搜集

iOS 面试题及答案20道61~80(四)

2019-07-28  本文已影响73人  struggle3g

iOS 面试题及答案20道61~80(三)

61:谈谈iOS内存

61.1: 内存区域划分

iOS进程内存布局从高地址往低地址分位几个区块:

1.栈区域
2.堆区域
3.未初始化静态、全局数据       (存放程序中未初始化)
4.已初始化静态、全局数据       (存放程序静态分配的变量和全局变量)
5.常量区                    (存放程序常量)
6.代码区                    (存放程序代码)
。。。

61.2: 内存管理

61.2.1: 引用计数

在iOS中使用了"引用计数"来管理内存,指将资源的被引用次数保存起来,当被引用次数变味0的时候将其释放的过程。

当创建一个对象A的实例并在堆上申请完内存,对象A的引用计数为1,在其他对象B持有这个对象A时,就需要把对象A的引用计数+1,持有者需要释放对象A的时候,引用计数-1,当最会对象A的引用计数为0时,对象的内存会被释放。

iOS在xcode4.2版本之前一直是使用的是MRC(手动引用计数),当xcode4.2版本之后就使用了ARC(自动引用计数)

61.2.2 MRC(Manual Retain Count)

在MRC中管理内存的引用计数,全部都是手动完成的,所以我们需要知道哪些是初始化为1,哪些是+1,哪些是-1

61.2.2.1 影响引用计数的方式:
对象操作 对应方法 引用计数变化
生成并持有对象 alloc、new、copy、mutablecopy 1
持有对象 retain +1
释放对象 release -1
延迟释放对象 autorelease -1
61.2.2.2 MRC管理的四法则

1 . 自己生成并自己持有

/*
 * 自己生成并持有该对象
 */
 id obj0 = [[NSObeject alloc] init];
 id obj1 = [NSObeject new];

2 . 非自己生成的对象,自己也能持有

/*
 * 持有非自己生成的对象
 */
id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有
[obj retain]; // 自己持有对象

3 . 不再需要自己持有对象的时候,释放

/*
 * 不在需要自己持有的对象的时候,释放
 */
id obj = [[NSObeject alloc] init]; // 此时持有对象
[obj release]; // 释放对象
/*
 * 指向对象的指针仍就被保留在obj这个变量中
 * 但对象已经释放,不可访问
 */
 [obj method];  //崩溃 

4 . 非自己持有的对象无法释放

/*
 * 非自己持有的对象无法释放
 */
id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有
[obj release]; // 此时将运行时crash 或编译器报error

//非自己生成的对象,且该对象存在,但自己不持有,这个特性是使用autorelease实现的
- (id) getAObjNotRetain {
    id obj = [[NSObject alloc] init]; // 自己生成并持有该对象
    [obj autorelease]; // 取得的对象存在,但自己不持有该对象
    return obj;
}

61.2.3 ARC(Automatic Retain Count)

ARC是自动引用计数,也就是说编译器在编译的时候自动在已有的代码中插入合适的内存管理代码

目前iOS开发基本上都是基于ARC的编程,开发人员在大部分情况下不需要考虑内存管理,因为编译器已经帮你做了。

在ARC中会经常出现的一个问题循环引用问题。

61.2.3.1 ARC中的所有权修饰符

所有权修饰符对应属性修饰符

对应关系 所有权修饰符 属性修饰符
- __strong strong
- __strong copy
- __strong retain
- __unsafe_unretaied unsafe_unretaied
- __weak weak
- __unsafe_unretained assign
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

__autoreleasing需要注意的地方1

当定义了一个方法:

- (void)doWithError:(NSError **)outError;

上述方法,ARC模式下会自动编程如下代码

- (void)doWithError:(NSError * __autoreleasing *)outError;

error指向的对象在创建出来后,被放入到了autoreleasing pool中,等待使用结束后的自动释放,函数外error的使用者并不需要关心*error指向对象的释放。

__autoreleasing需要注意的地方2

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){

          // do stuff  
          if (there is some error && error != nil)
          {
                *error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
          }

    }];
}

会隐式地创建一个autorelease pool,上面代码实际类似于:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){

          @autoreleasepool  // 被隐式创建
      {
              if (there is some error && error != nil)
              {
                    *error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
              }
          }
    }];

    // *error 在这里已经被dict的做枚举遍历时创建的autorelease pool释放掉了 :(  
}  

为了能够正常的使用*error,我们需要一个strong型的临时引用,在dict的枚举Block中是用这个临时引用,保证引用指向的对象不会在出了dict的枚举Block后被释放,正确的方式如下

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
  __block NSError* tempError; // 加__block保证可以在Block内被修改  
  [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
  { 
    if (there is some error) 
    { 
      *tempError = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil]; 
    }  

  }] 

  if (error != nil) 
  { 
    *error = tempError; 
  } 
}

在ARC与MRC当中使用autorelease

//ARC 下  释放了内存
NSString *__autoreleasing str1;
@autoreleasepool {
    str1 = [[NSString alloc] initWithFormat:@"hehe2321321312213"]; // ARC
}
NSLog(@"%@",str1);  //崩溃

//MRC 下  释放了内存
NSString * str1;
@autoreleasepool {
    str1 = [[[NSString alloc] initWithFormat:@"hehe2321321312213"]autorelease]; // MRC
}
NSLog(@"%@",str1);  //崩溃

61.2.3.2 ARC中的属性标示符
@property(assign/retain/strong/weak/unsafe_unretained/copy)NSObject *object;
61.2.3.3 ARC模式下对象何时被释放
  1. 局部变量会在作用域完成以后全部使用release一遍,也就是说过了作用域,这个临时变量将被释放
  2. ARC模式下变量的默认所有权修饰符是__strong,当变量的强引用指向一个都没有时,该对象被释放(也可以说是它的引用计数为0时)
  3. ARC模式下在非Alloc、new、copy、mutablecopy创建的对象时,在这些工厂方法的返回值都会自动添加到autoreleasepool中
  4. Autorelease返回值的快速释放机制,在ARC模式下:
    1. 在工厂方法生成的对象,返回值会调用objc_autoreleaseReturnValue方法,代替我们调用autorelease
    2. 调用该工厂方法的对象中,会实现objc_retainAutoreleasedReturnValue(工厂方法)代替retain
    3. 在使用完成以后系统会自动添加objc_storeStrong(&创建的对象,nil)代理实现release
    4. 通过pthread的线程存储技术,以工厂方法为key返回值为value存储,最终objc_autoreleaseReturnValue存value,objc_retainAutoreleasedReturnValue获取值,最后得到的数值没有进行内存管理。

61.2.4 AutoreleasePool(自动释放池)

61.2.4.1 AutoreleasePool基本概念
61.2.4.2 @autoreleasepool底层实现

首先从中间代码入手,使用下面命令来得到C++代码

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return 0;
    }
}
这里我直接用main.m文件进行分析
clang -rewrite-objc main.m

得到的C++代码:

只看有关代码

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
int main(int argc, char * argv[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        return 0;
    }
}

从上面内容我们可以看到main函数中声明了一个__AtAutoreleasePool对象,相当于调用了objc_autoreleasePoolPush()函数,该函数的作用就是向堆栈中压入一个"自动释放池",当main函数执行完成以后,则执行__AtAutoreleasePool的析构函数objc_autoreleasePoolPop(atautoreleasepoolobj),用于释放"自动释放池",

如上述所说,拼装成一个伪代码

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ {
        //创建自动释放池
        __AtAutoreleasePool __autoreleasepool = objc_autoreleasePoolPush();
        //TODO 执行各种操作,将对象加入自动释放池
        
        //释放自动释放池
        objc_autoreleasePoolPop(__autoreleasepool)
    }
}
61.2.4.3 AutoreleasePool本质

在苹果开放源码中我们可以找到AutoreleasePool的底层实现
objc_autoreleasePoolPush()以及objc_autoreleasePoolPop(__autoreleasepool)的实现如下:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

从源码中我们可以发现,AutoreleasePoolPage才是AutoreleasePool的实现对象所在,找到AutoreleasePoolPage,如下代码:

class AutoreleasePoolPage 
{
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  //4096  必须是多个vm页面大小
#else
        PAGE_MAX_SIZE;  //4096   大小和对齐,功率为2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;    //数据校验
    id *next;               //栈顶地址,默认创建一个新的AutoreleasePoolPage对象的时候next是nil
    pthread_t const thread; //所在线程
    AutoreleasePoolPage * const parent;  //父对象
    AutoreleasePoolPage *child;          //子对象
    uint32_t const depth;                
    uint32_t hiwat;
    ...
}

去除不必要的代码我们可以看到,通过这个结构体我们可以了解到AutoreleasePool实质上是若干个AutoreleasePoolPage组成的双向链表

61.2.4.4 AutoreleasePoolPage工作原理

创建AutoreleasePoolPage

void *objc_autoreleasePoolPush(void)中实际上调用了AutoreleasePoolPage::push()函数,代码如下

static inline void *push() 
{
    id *dest;
    if (DebugPoolAllocation) {
          //当自动释放池出现顺序错误时停止,并允许堆调试器跟踪自动释放池的时候调用
        // 每个自动释放池都从一个新的池页面开始。
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        //代码正常运行时调用
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
static inline id *autoreleaseFast(id obj)
{
    AutoreleasePoolPage *page = hotPage(); //得到hotPage对象
    if (page && !page->full()) { 
    //这个对象存在 且没有满的时候调用
        return page->add(obj);
    } else if (page) {
    //这个AutoreleasePoolPage对象存在  AutoreleasePoolPage已经满了以后调用
        return autoreleaseFullPage(obj, page);
    } else {
        //这个AutoreleasePoolPage对象不存在时调用
        return autoreleaseNoPage(obj);
    }
}


/*
 *  得到当前线程中所使用的AutoreleasePoolPage
 **/
 
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
 
static inline AutoreleasePoolPage *hotPage() 
{
     //根据线程为key找到这个线程中的AutoreleasePoolPage对象
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
        tls_get_direct(key);
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    if (result) result->fastcheck();
    return result;
}


/*
 * 当前hotPage存在并且没有满,
 **/
id *add(id obj)
{
    assert(!full());
    unprotect();
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;
    protect();
    return ret;
}

/*
 *  这个AutoreleasePoolPage对象存在  AutoreleasePoolPage已经满了以后调用
 **/

static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    //如果hotpage满了以后查找下一个page直到找不到,没有满的page那么就新建一个page,然后将对象添加到该页,并设置其为hotPage
    assert(page == hotPage());
    assert(page->full()  ||  DebugPoolAllocation);

    do {
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    setHotPage(page);
    return page->add(obj);
}



/*
 * //这个AutoreleasePoolPage对象不存在时调用
 **/
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
    // We are pushing an object or a non-placeholder'd pool.

    // Install the first page.
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);
        
    // Push the requested object or pool.
    return page->add(obj);
}

大致逻辑:

  1. 本质上AutoreleasePool是底层是调用了AutoreleasePoolpage结构体中的push方法,而push方法调用的是Autoreleasefast。

  2. 如果第一次hotPage()为NULL,那么调用autoreleaseNoPage(obj)新建一个parent=NULL的AutoreleasePoolPage对象做为自动释放池加入栈中,并将其设置为hotPage,最后返回给main()函数中的 __AtAutoreleasePool __autoreleasepool变量,

  3. hotPage()找到了正在运行的page,并且当前page并没有满那么调用add(obj)添加这个page进入到链表,并将hotpage返回给__AtAutoreleasePool __autoreleasepool变量

  4. hotPage()如果当前page满了,那么就会在child当中查找是否有不满的page,如果找到不满的就将这个child设置成hotPage,并调用add(obj),如果在child中没有找到不满的page,那么就创建一个新的page然后在设置hotPage,并调用add(obj)最后将并将hotpage返回给__AtAutoreleasePool __autoreleasepool变量

销毁AutoreleasePoolPage

销毁这个自动释放池的方法是:

static inline void pop(void *token)

token 就是push()的返回值,通过该地址我们可以找到对应的page,pop()代码如下:

static inline void pop(void *token) 
{
    AutoreleasePoolPage *page;
    id *stop;

    if (token) {
        page = pageForPointer(token); //找到所在的page地址
        stop = (id *)token; //POOL_SENTINEL的地址,从栈顶释放对象直到这个位置
    } else {
        // Token 0 is top-level pool
        page = coldPage();
        stop = page->begin();
    }

    page->releaseUntil(stop); //对自动释放池中对象调用objc_release()进行释放

    // memory: delete empty children
    // hysteresis: keep one empty child if this page is more than half full
    // special case: delete everything for pop(0)
    if (!token) {
        page->kill();
        setHotPage(NULL);
    } else if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

过程分为两部分:

  1. page->releaseUntil(stop),对栈顶(page->next)到stop地址(POOL_SENTINEL)之间的所有对象调用objc_release(),进行引用计数减1;
  2. 清空page对象page->kill()
61.2.4.5 AutoreleasePoolPage总结

释放
objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参,

  1. 根据传入的哨兵对象地址找到哨兵对象所处的page
  2. 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次release消息,并向回移动next指针到正确位置,从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page

嵌套的AutoreleasePool
嵌套的AutoreleasePool,pop的时候总会释放到上次push的位置为止,多层的pool就是多个哨兵对象而已,就像剥洋葱一样,每次一层,互不影响。

最后简单概括

autoreleasepool由若干个autoreleasePoolPage类以双向链表的形式组合而成, 当程序运行到@autoreleasepool{时, objc_autoreleasePoolPush()将被调用, runtime会向当前的AutoreleasePoolPage中添加一个nil对象作为哨兵,
在{}中创建的对象会被依次记录到AutoreleasePoolPage的栈顶指针,
当运行完@autoreleasepool{}时, objc_autoreleasePoolPop(哨兵)将被调用, runtime就会向AutoreleasePoolPage中记录的对象发送release消息直到哨兵的位置, 即完成了一次完整的运作.

62:iOS动态添加属性

62.1 iOS动态添加属性

62.1.1 使用场景

  1. 为现有的类添加私有变量以帮助实现细节。
  2. 为现有的类添加公有属性。
  3. 为KVO创建一个关联的观察者。

62.1.2 动态加添属性的相关函数

在runtime源码的runtime.h文件中,找到它们的声明:

//设置关联属性
void objc_setAssociatedObject(id object,const void *key,id value,objc_AssociationPolicy policy);
//获取关联属性
id objc_getAssociatedObject(id object, const void *key);
//删除所有关联属性
void objc_removeAssociatedObjects(id object);

参数:object对象的实例,voidkey类似对象级别唯一的一个常量,value值,关联策略*

关键字 关联策略 对应属性修饰符
OBJC_ASSOCIATION_ASSIGN @property (assign) or @property (unsafe_unretained) 弱引用关联对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (strong, nonatomic) 强引用关联对象,且为非原子操作
OBJC_ASSOCIATION_COPY_NONATOMIC @property (copy, nonatomic) 复制关联对象,且为非原子操作
OBJC_ASSOCIATION_RETAIN @property (strong, atomic) 强引用关联对象,且为原子操作
OBJC_ASSOCIATION_COPY @property (copy, atomic) 复制关联对象,且为原子操作

62.1.3 动态加添属性的实现

//UserModel对象
@interface UserModel : NSObject
@end

@implementation UserModel
@end

//在类扩展中的动态添加属性
#import <objc/runtime.h>
@interface UserModel(my)
@property (nonatomic,copy)NSString *str;
@end

@implementation UserModel(my)
- (NSString *)str{
    return objc_getAssociatedObject(self, _cmd);
}
- (void)setStr:(NSString *)str{
    objc_setAssociatedObject(self, @selector(str), str, OBJC_ASSOCIATION_COPY);
}
@end

62.2 iOS动态添加属性的实现原理

总结:每一个对象本身都存在一个AssciationsManager,并维护着AssociationsHashMap,AssociationsHashMap中以对象地址为key维护者一个ObjectAssociationMap,ObjectAssociationMap以key为key维护者一个objcAssociation对象,这个对象就是我们关联属性的值以及关联策略

63: 为什么要使用taggedPointer

假设要存储一个NSNumber对象,其值是一个整数。正常情况下,如果这个整数只是一个NSInteger的普通变量,在64位CPU下是占8个字节的。1个字节有8位,如果我们存储了一个很小的值,会出现很多位没有使用的情况,这样就造成了内存浪费,苹果为了解决这个问题,引入了taggedPointer的概念。

首先来上一段代码:

NSString *str1              =   @"1";
NSString *str2              =   [NSString stringWithString:@"1"];
NSString *str3              =   [NSString stringWithFormat:@"1"];
NSString *str4              =   [[NSString alloc]initWithString:@"1"];
NSString *str5              =   [[NSString alloc]initWithFormat:@"%@",str1];
NSMutableString * str6      =   [str1 copy];
NSMutableString * str7      =   [str1 mutableCopy];
NSMutableString * str8      =   [[NSMutableString alloc]initWithString:@"123456"];
[str7 appendString:@"2"];
NSMutableString * str9      =   [str8 mutableCopy];
[str9 appendString:@"23"];
NSMutableString * str10     =   [str8 copy];

打印数据:

str1:__NSCFConstantString,0x102500060,1
str2:__NSCFConstantString,0x102500060,1
str3:NSTaggedPointerString,0xd11716512661ffd9,1
str4:__NSCFConstantString,0x102500060,1
str5:NSTaggedPointerString,0xd11716512661ffd9,1
str6:__NSCFConstantString,0x102500060,1
str7:__NSCFString,0x2831d82d0,12
str8:__NSCFString,0x2831d8540,123456
str9:__NSCFString,0x2831d80f0,12345623
str10:NSTaggedPointerString,0xd11475026552dfde,123456

根据上述内容我们可以发现几种字符串类型:

  1. NSTaggedPointerString
  2. __NSCFConstantString
  3. __NSCFString

63.1 NSTaggedPointerString(taggedPointer)

objc750中的taggedPointer源码分析


extern uintptr_t objc_debug_taggedpointer_obfuscator;

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
    if (tag <= OBJC_TAG_Last60BitPayload) {
        uintptr_t result =
            (_OBJC_TAG_MASK | 
             ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
             ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    } else {

        uintptr_t result =
            (_OBJC_TAG_EXT_MASK |
             ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
             ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    }
}

上述源码:_objc_encodeTaggedPointer是对TaggedPointer进行编码,而_objc_decodeTaggedPointer对其解码
所以我们在iOS中只需要将NSTaggedPointerString类型的值带入到_objc_decodeTaggedPointer

NSString *str1              =   @"1";
NSString *str5              =   [[NSString alloc]initWithFormat:@"%@",str1];
NSMutableString * str10     =   [str8 copy];

NSLog(@"str5:%@,0x%lx,%@", [str5 class],_objc_decodeTaggedPointer(str5),str5);
NSLog(@"str10:%@,0x%lx,%@", [str10 class],_objc_decodeTaggedPointer(str10),str10);

打印结果:

str5:NSTaggedPointerString,0xa000000000000611,a
str10:NSTaggedPointerString,0xa000000000000611,a

63.1.1 如何判断是否是Tagged Pointer

判断一个指针是否是Tagged Pointer的源码:

static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) {
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

63.1.2 关于Tagged Pointer的面试题

1.下面这段代码执行后, 会发生什么

@property (nonatomic, copy) NSString *name;

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
    });
}

会崩溃,原因是在setName:方法中,实际的实现是:

- (void)setName:(NSString *)name
{
    if (_name != name) {
        [_name release];
        _name = [name copy];
    }
}

原因:

  1. 因为使用了多线程赋值,所以会有多个线程同时调用[_name release], 所以才发触发上面的崩溃
  2. 解决办法是开锁,或者是设置属性的原子属性atomic

2. 下面的代码为什么可以正常运行, 不会崩溃

@property (nonatomic, copy) NSString *name;

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        self.name = [NSString stringWithFormat:@"abc"];
    });
}

运行程序, 上面的代码确实不会发生崩溃

63.2 __NSCFConstantString

63.2 __NSCFString

64 计算机位运算

含义 运算符 例子
左移 << 1 0011 => 0110
右移 >> 1 0110 => 0011
按位或(两位只要有一个为1就为1) | 0011 | 1011 => 1011
按位与(两位只要有一个为0就为0) & 0011 & 1011 => 0011
按位取反 0011 => 1100
按位异或(相同为0不同为1) ^ 0011 ^ 1011 => 1000

65 initWithString、stringWithString、initWithFormat、stringWithFormat的区别

65.1 initWithString和stringWithString

65.1.1 NSString调用

initWithString,stringWithString生成一个常量字符串,只读取数据
使用[[NSString alloc]initWithString:]、[NSString stringWithString:]方式获取的是一个不可变的字符串常量,而这个常量是存放到内存中的常量区。不用引用计数管理内存,代码如下

NSString *str1 = @"1";
NSString *str2 = [[NSString alloc]initWithString:@"1"];
NSString *str3 = [NSString stringWithString:@"1"];

打印内容:
str1:__NSCFConstantString,0x7ffee9bb3868,0x10604b060,1,retaincount = -1
str2:__NSCFConstantString,0x7ffee9bb3860,0x10604b060,1,retaincount = -1
str3:__NSCFConstantString,0x7ffee9bb3858,0x10604b060,1,retaincount = -1

65.1.2 NSMutableString调用

initWithString,stringWithString生成一个可变对象,会另外申请空间存放后面的常量字符串,这时其retaincount为1,代码如下:

NSMutableString *str4 = [[NSMutableString alloc]initWithString:@"1"];
NSMutableString *str5 = [NSMutableString stringWithString:@"1"];
//打印
str4:__NSCFString,0x7ffee67c3850,0x6000038dde90,1,retaincount = 1
str5:__NSCFString,0x7ffee67c3848,0x6000038de1c0,1,retaincount = 1

65.1.2 总结

65.2 initWithFormat和stringWithFormat

65.2.1 NSString调用

代码如下:

//当字符串不超过字符串的内存空间,那么就使用NSTaggedPointerString
NSString *str1 = @"sa";
NSString *str2 = [[NSString alloc]initWithFormat:@"%@",str1];
NSString *str3 = [NSString stringWithFormat:@"%@",str1];

打印如下:
str1:__NSCFConstantString,0x7ffee6628868,0x1095d6060,sa,retaincount = -1
str2:NSTaggedPointerString,0x7ffee6628860,0xe5343da0d0e38890,sa,retaincount = -1
str3:NSTaggedPointerString,0x7ffee6628858,0xe5343da0d0e38890,sa,retaincount = -1

//当字符串超过字符串的内存空间,那么就使用NSTaggedPointerString
NSString *str1 = @"sa321312312312";
NSString *str2 = [[NSString alloc]initWithFormat:@"%@",str1];
NSString *str3 = [NSString stringWithFormat:@"%@",str1];

打印如下:
str1:__NSCFConstantString,0x7ffee412d868,0x10bad1060,sa321312312312,retaincount = -1
str2:__NSCFString,0x7ffee412d860,0x600000939060,sa321312312312,retaincount = 1
str3:__NSCFString,0x7ffee412d858,0x600000939080,sa321312312312,retaincount = 1

65.2.2 NSMutableString调用

代码如下:

NSMutableString *str4 = [[NSMutableString alloc]initWithFormat:@"%@",str1];
NSMutableString *str5 = [NSMutableString stringWithFormat:@"%@",str1];

打印如下:
str4:__NSCFString,0x7ffeef95a850,0x6000024c93e0,sa3213123123121,retaincount = 1
str5:__NSCFString,0x7ffeef95a848,0x6000024c96e0,sa3213123123121,retaincount = 1

65.2.3 总结

65.3 总结

在不可变字符串中:

在可变字符串中

66 Autorelease返回值的快速释放机制

ARC下,runtime有一套对autorelease返回值的优化策略。
比如在一个工厂方法中:

+ (instancetype)CreateObject {
    return [self new];
}
Sark *sark = [Sark createSark];

谁创建谁释放的原则,返回值需要是一个autorelease对象才能配合调用方正确管理内存,于是乎编译器改写成了形如下面的代码:

+ (instancetype)createSark {
    id tmp = [self new];
    return objc_autoreleaseReturnValue(tmp); // 代替我们调用autorelease
}
// caller
id tmp = objc_retainAutoreleasedReturnValue([Sark createSark]) // 代替我们调用retain
Sark *sark = tmp;
objc_storeStrong(&sark, nil); // 相当于代替我们调用了release

Thread Local Storage(线程局部存储 TLS)通过

Thread Local Storage(TLS)线程局部存储,目的很简单,将一块内存作为某个线程专有的存储,以key-value的形式进行读写,比如在非arm架构下,使用pthread提供的方法实现:

void* pthread_getspecific(pthread_key_t);
int pthread_setspecific(pthread_key_t , const void *);

在返回值身上调用objc_autoreleaseReturnValue方法时,runtime将这个返回值object储存在TLS中,然后直接返回这个object(不调用autorelease);同时,在外部接收这个返回值的objc_retainAutoreleasedReturnValue里,发现TLS中正好存了这个对象,那么直接返回这个object(不调用retain)。
于是乎,调用方和被调方利用TLS做中转,很有默契的免去了对返回值的内存管理。

67 block循环引用

typedef void(^MyBlock)();
@property (copy, nonatomic) MyBlock myBlock;
@property (copy, nonatomic) NSString *blockString;

- (void)testBlock {
    self.myBlock = ^() {
        NSLog(@"%@",self.blockString);//产生了循环引用
    };
}

上述代码:由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用。解决方案就是添加__weak

__weak typeof(self) weakSelf = self;

self.myBlock = ^() {
        NSLog(@"%@",weakSelf.blockString);//没有产生循环引用
 };

并不是所有block都会造成循环引用,只有被强引用了的block才会产生循环引用,比如dispatch_async(dispatch_get_main_queue(), ^{}),[UIView animateWithDuration:1 animations:^{}]这些系统方法等或者block不是强引用而是临时变量,简单来说栈block不会产生循环引用。

typedef void(^MyBlock)(void);
- (void)testWithBlock:(MyBlock)block {
    block();
}
[self testWithBlock:^{
    NSLog(@"%@",self);//不会产生循环引用
}];
    

还有一种情况:在一个实例类A中强引用了一个block和一个实例B,这个block执行过程中调用了实例B中的属性,且在执行过程中实例B释放了,此时访问实例B是没有任何反应的。

typedef void(^MyBlock)();
@property (copy, nonatomic) MyBlock myBlock;
@property (copy, nonatomic) NSString *blockString;
@property (strong,nonatomic)UserModel *user;
self.user = [[UserModel alloc]init];
self.user.nama = @"wode";
    
__weak typeof(self.user)weakuser = self.user;
    
self.myBlock = ^{
    //self.user 再别的地方玩释放了它,所以应该保持住它
    __strong typeof(weakuser)stronguser = weakuser;
    
    NSLog(@"%@",stronguser.nama);
};
    self.myBlock();

68 NSTimer循环引用属于相互循环使用

在对象实例中,创建一个NSTimer做为一个属性,由于定时器创建后会请引用这个实例对戏那个,该实例对象和NSTimer互相引用,形成循环引用。
解决方式:

69 代理(delegate)循环引用属于相互循环引用

delegate 是iOS中开发中比较常遇到的循环引用,一般在声明delegate的时候都要使用弱引用 weak,或者assign,当然怎么选择使用assign还是weak,MRC的话只能用assign,在ARC的情况下最好使用weak,因为weak修饰的变量在释放后自动指向nil,防止野指针存在

70 __block的作用

还可以使用__block来修饰变量
在MRC下,__block不会增加其引用计数,避免了循环引用
在ARC下,__block修饰对象会被强引用,无法避免循环引用,需要手动解除。

71 UITableViewCell如何优化

简单优化:

  1. 正确的处理Cell的复用
  2. 尽量少或不设置cell及子类的透明度
  3. cell中内容设计到延迟内容的代码,尽量使用异步操作,主线程通知结果或刷新UI
  4. 尽量少的在heightForRowAtIndexPath:中去增加一些计算量操作,可以直接在请求结果以后在直接计算出高度在通知UI刷新
  5. 尽可能的在cell中addView,尽量减少subviews

进一步优化

  1. 滑动过程中按需要加在数据(与SDWebImage异步加载会更好)
  2. 避免大量的图片缩放,最好是大小刚刚好的资源,如果必须缩放也必须是图片等比缩放
  3. 避免使用CALayer特效

高级优化

72 什么是image

  1. Executable:应用的主要二进制(比如.0文件) 可执行文件
  2. Dylib:动态链接库
  3. Bundle: 资源文件

73 App启动性能优化

73.1 (Main函数加载之前的调用优化)Total pre-main time

在Xcode->Edit scheme->Arguments->Environment Variables中添加key = DYLD_PRINT_STATISTICS,value = YES,就可以打印出来pre-main的内容

Total pre-main time: 910.97 milliseconds (100.0%)
         dylib loading time: 126.46 milliseconds (13.8%)
        rebase/binding time: 697.11 milliseconds (76.5%)
            ObjC setup time:  59.21 milliseconds (6.5%)
           initializer time:  28.00 milliseconds (3.0%)
           slowest intializers : 
             libSystem.B.dylib :   3.04 milliseconds (0.3%)

从上面可以看到过程

  1. dylib loading time 动态库加载过程花费的时间
  2. rebase bind time 重定绑定,
    • 因为iOS引入了ASLR技术和代码签名
    • 镜像会在随机的地址上进行加载,和之前指针指向的地址会有一个偏差,dyld需要修正这个偏差
  3. Objc setup time 对象设置时间
    • 注册声明过的Objc类
    • 将分类插入类的方法列表
    • 检查selector的唯一性
  4. initializer time 初始化器时间
    • 调用每个Objc类和分类的+load方法
    • 调用C/C++中的构造器函数
    • 创建非基本类型全局变量

73.1.1 dylib loading加载优化

73.1.2 rebase bind 优化

73.1.3 Objc setup 优化

73.1.4 Initializers

73.2 (Main调用之后优化)main After

满足业务需要的前提下,didFinishLaunchingWithOptions在主线程里做的事情越少越好:

74 swift和oc的区别

74.1 Protocol不同:

    OC:    主要用来传值,传递事件
    Swift: 不仅仅可以传值跟传递事件,
            可以在协议中定义属性以及方法,
            并且还能对其进行扩展,

74.2 类型推断

    OC:    在定义属性的多时候必须添加你的类型(id是一个指向所有类的指针)
    swift: 当我添加一个变量的时候,我无需定义他的类型,会根据初始化的值,推断出来适合这个值的一个类型

74.3 结构体和类

    OC:    结构体值类型、类指针类型,
            简单谈一下结构体,因为这个是跟swift的最大的差别:
                1. 通常情况下,我们在初始化结构体完成以后如果需要修改结构体中的成员变量,直接使用点语法就能直接修改成员变量,
                2. 当一个对象持有一个结构体变量,如果不使用属性property,默认是受保护的,所以你并不能修改这个结构体
                3. 当一个对象中有一个结构体属性的时候,点语法其实就是setter、getter方法,当我获取这个属性的时候,如果是类对象,那么就是这个属性的引用,如果是结构体getter返回的值就是一个新的结构体的值,你在怎么修改也不会影响到这个对象中的结构体属性。
                
                
    swift: 结构体值类型、类指针类型,这个规则并没有改变
            对比OC,Swift中的结构体的功能被扩大了,基本上拥有了类差不多的功能
                1.定义属性
                2.定义方法
                3.定义getter和setter
                4.可以定义初始化器来设置初始状态
                5.实现扩展的功能
                6.遵守协议,并实现协议的方法
                7.结构总是被复制,并不使用引用计数
            不过也有一些结构体不具备的类的功能
                1.允许一个类继承另一个类的特征
                2.能够在运行时检查和解释类实例的类型
                3.初始化器使一个类的实例能够释放它所分配的任何资源,可以使用deinit来释放资源
                4.引用计数允许多个引用到一个类实例
            总体来讲struct的优势在于:
                    1.结构较小,适用于复制操作,相比一个class的实例被多次引用,struct更加安全
                    2.更不需要担心内存泄漏以及多线程冲突问题
            可以定义inout的关键字来让值类型可以进行引用传递
            swift之所以讲Dictionary、array、String设计成值类型:
                    1.值类型在栈上面操作,而引用类型在堆上面操作,栈上的操作仅仅是单个指针的移动,而堆上面的操作牵扯到合并、移动、重新链接等问题,这样做大幅度减少了堆上内存分配和回收的次数,从而讲开销降到最低。
                    2.解决并发,多线程、、循环引用、内存泄漏

74.4 函数式编程

    OC只是面向对象的编程,而Swift既面向对象又是面向函数式的编程
    特点:
            1. 没有附加作用, 函数保持独立,所有功能就是返回一个值,没有其他行为,尤其是不得修改外部的变量
            2. 不修改状态,也不会修改系统的变量,后者函数内部对象的变量,只是单纯的算法
            3.  引用透明,函数运行不依赖于外部变量和状态,只依赖于传递的参数。任何情况下,只要传递的参数相同,那么得到的返回值都是一样的。
            4. 函数式编程可以看作成表达式,而并不是执行语句,表达式是一个单独的计算公式,总是有一个返回值,而执行语句,只是执行过程并没有返回值。
    举例子说明:
    Swift中拥有 map,reduce、filter、flatmap这类数学函数式的方法,而OC并没有    

74.5 可选类型

    1.在oc中可选类型是为了表达一个变量的值是否为空的情况,如果为空,那么它就是nil,在Swift中这一概念被扩大化了,无论是引用变量还是值类型,都可以是可选类型。
    2.在Swift当中明确提出可选类型的概念,当我的变量(不管是值还是引用类型),可以是空值,我就在变量类型的后面加上?如果这个变量类型后面没有加任何修饰符号,或者加了!那么这个值是必须有一个值的,

74.6 属性观察以及setter、getter

    1.在OC中,属性观察需要KVO,Setter、getter方法可以是有property、或者自定义setter、getter方法
    2.而在swift中,属性观察增加一个初始化时就可以直接观察的方式,willset、以及didSet

74.7 swift有一个copy-on-write功能

当数组A copy生成数组B,两个数组是同样的地址,当我修改数组B中的元素的,在打印会发现,数组B完全变成了另外一个数组,而数组A不变,这就是copy-on-write

75 SDWebImage怎么实现的

75.1 角色划分

  1. UIimageView+WebCache 提供给客户端的一个API
  2. SDWebImageManager 这个是管理中心
  3. SDWebImageDownLoader 这个是下载中心 有operationQueue
  4. SDWebImageDownLoaderOperation 抽象的网络操作
  5. SDImageCache
  6. SDImageCachesManager 图片缓存类 主要做缓存操作
  7. SDImageCachesManagerOperation 单独的缓存类
  8. SDDiskCache 磁盘缓存类
  9. SDWebImagePrefetcher 批量缓存器‘
  10. SDWebImageDecoder 解码器 Argb专程rgb
  11. SDWebImageCompat 平台兼容性类

75.1 优点:

  1. 提供很多分类,这些分类都可以使用图片缓存 如:ImageView、UIbutton、MKAnnotationView、UIview
  2. 异步下载图片
  3. 异步缓存、内存+磁盘缓存
  4. 后台解压缩
  5. 同一个URL不会重复下载
  6. 自动是被无效的URL,不重复系在
  7. 不阻塞主线程
  8. 高性能
  9. 使用GCD和ARC结合
  10. 支持多种图片格式
  11. 支持动态图片

75.1 缓存过程

UIimageView->管理器->下载器->缓存器
2.3版本

75.2 URL不变怎么知道图片更新

NSURLRequestUseProtocolCachePolicy:对特定的URL请求使用网络协议(如HTTP)中实现的缓存逻辑。这是默认的策略。该策略表示如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response中的Cache-Control字段判断 下一步操作,如: Cache-Control字段为must-revalidata, 则 询问服务端该数据是否有更新,无更新话 直接返回给用户缓存数据,若已更新,则请求服务端

75.3 为啥要解码

图片格式转换, jpg->png,gif->jpg,iOS不支持透明通道 不支持arpg

75.4 设计模式

下载重载,如果当我去下载图片的时候,将这下载对象缓存在内存当中,如果这个图片没有下载成功是不会清理的。

75.5 为什么添加图片类型

为了区分 gif跟普通图片
因为根据规则,图片数据的第一个字节是图片的类型,

76 子视图随父视图动画

76.1 设置clipsToBounds

YES剪裁超出父视图范围的子视图部分。当设置成NO的时候,不剪裁超出父视图范围的子视图,

77 项目开发中业务分层

四层:

  1. 网络层 Networklayer
  2. 业务层 Business
  3. 数据层 Data
  4. 工具层 Utils

项目结构
网路基础库结构
网络层和业务层
业务层分层结构
UI展示层结构
UI展示层和业务层

78 项目开发->存在的疑惑

  1. 层与层之间的关系?
  1. 主程序如何引用静态库的?
    • 引用4个组件:
    • 1.导入静态库到项目中,(出现错误需要关闭静态库项目,重新打开就好)
    • 2.项目配置setting target dependencies 添加静态库
    • 3.配置我们头文件 user header User Header search Paths 添加静态库的路径
    • 第三方库的引用链接:哪些需要哪些不需要
    • 1.主程序依赖:UI层
    • 2.UI层之外的不需要
  2. 静态库引用静态库?
    • 静态库配置setting 搜索search paths User Header search Paths 添加静态库的路径
  3. 静态库如何引用pods第三方库?
    • 安装一个pods 添加pod库,搜索search paths User Header search Paths 添加pod中header中的路径
  4. 各层之间为什么要通过组建化的方式来设计?
  5. 为什么要使用静态库来做?

79 新建的静态库在哪存放?

静态库全部在根目录单独的文件夹

80 C与OC的桥接(Core Foundation和Foundation的桥接)

上一篇下一篇

猜你喜欢

热点阅读