iOS基础细节

一 、为什么会存在堆空间

堆空间的存在主要是为了延长对象的生命周期,并使得对象的生命周期可控。

  • 如果试图用栈空间取代堆空间,显然是不可行的。栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,如果申请的空间超过栈的剩余空间时,将出现栈溢出,发生未知错误。因此,能从栈获得的空间较小。而堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 但是栈空间比堆空间响应速度更快,所以一般类似int、NSInteger等占用内存比较小的通常放在栈空间,对象一般放在堆空间。
  • 如果试图用数据区(全局区)取代堆空间,显然也是不可行的。因为全局区的生命周期会伴随整个应用而存在,比较消耗内存,生命周期不向在堆空间那样可控,堆空间中可以随时创建和销毁。
  • 代码区就不用想了,如果能够轻易改变代码区,一个应用就无任何安全性可言了。
二 、Tagged Pointer 是什么?

从 64bit 开始,iOS 引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储。在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值;使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中。当指针不够存储数据时,会使用动态分配内存的方式来存储数据。

三 、iOS平台跨域访问漏洞?

UIWebView 默认开启了WebKitAllowUniversalAccessFromFileURLs 和 WebKitAllowFileAccessFromFileURLs 属性。利用这个漏洞给某个 App 下发一个 HTML 文件,当 UIWebView 使用 file 协议打开这个 HTML 文件, HTML 文件中含有一段窃取用户数据的 JS 代码,就会导致用户数据泄露。

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
_webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath]]];

 <script>
            // 这个可以是手机任意一个文件地址
            var localfile = "/etc/passwd"
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4) {
                    alert(xhr.responseText);
                }
            }
           try {
              xhr.open("GET", localfile, true);
              xhr.send();
           } catch (ex) {
              alert(ex.message);
           }
        </script>

上面代码可以读取出手机端 /etc/passwd 的文件。这个漏洞访问其他应用的数据,而不必需要用户的许可。但WKWiebView的 WebKitAllowUniversalAccessFromFileURLs 和 WebKitAllowFileAccessFromFileURLs 默认是关闭的(可以手动控制),不会存在这样的风险。
补充:针对 https 请求UIWebView需要做额外处理,借助NSURLConnection做证书验证,而WKWebView无需做过多额外处理。

四 、iOS 9 以后通知不再需要手动移除

通知 NSNotification 在注册者被回收时需要手动移除,是一直以来的使用准则。原因是在 MRC 时代,通知中心持有的是注册者的 unsafe_unretained 指针,在注册者被回收时若不对通知进行手动移除,则指针指向被回收的内存区域,变为野指针。此时发送通知会造成 crash 。而在 iOS 9 以后,通知中心持有的是注册者的 weak 指针,这时即使不对通知进行手动移除,指针也会在注册者被回收后自动置空。因为向空指针发送消息是不会有问题的。

五 、NSUserDefaults 存储字典的一个坑
NSDictionary *dict = @{@1: @"1",
                           @2: @"2",
                           @3: @"3",
                           @4: @"4"};
                            
[[NSUserDefaults standardUserDefaults] setObject:dict forKey:@"key"];
[[NSUserDefaults standardUserDefaults] synchronize];

执行上述代码会报如下错误:

[User Defaults] Attempt to set a non-property-list object {
    3 = "3";
    2 = "3";
    1 = "1";
    4 = "4";
} as an NSUserDefaults/CFPreferences value for key `key`

The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
......
And although NSDictionary and CFDictionary objects allow their keys to be objects of any type, if the keys are not string objects, the collections are not property-list objects.

苹果官网有上述这样一段话,能往 NSUserDefaults 里存储的对象只能是 property list objects,包括 NSData,NSString, NSNumber, NSDate, NSArray, NSDictionary,且对于 NSArray 和 NSDictionary 这两个容器对象,它们所包含的内容也必需是 property list objects。重点看最后一句话,虽然 NSDictionary 和 CFDictionary 对象的 Key 可以为任何类型(只要遵循 NSCopying 协议即可),但是如果当Key 不为字符串 string 对象时,此时这个字典对象就不能算是property list objects了,所以不能往 NSUserDefaults 中存储,不然就会报错。

六 、performSelector:afterDelay:的坑
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:self withObject:@selector(test) afterDelay:.0];
        NSLog(@"3");
    });
- (void)test{
    NSLog(@"2");
}

上述代码的执行结果并非 1 2 3 ,而是 1 3。原因是performSelector: withObject: afterDelay:的本质是往 RunLoop中添加定时器,而子线程默认是没有启动RunLoop。performSelector: withObject: afterDelay:接口虽然和performSelector:系列接口长得很类似。但前者存在于RunLoop相关文件,后者存在于NSObject相关文件。
image.png
image.png
七 、@autoreleasepool
autoreleasepool 使用

每次遍历的时候生成了很多占内存大的对象,如果交于默认的 autoreleasepool 去管理生命周期,会有因为内存飙升产生crash的风险,遍历过程中,可在适当的位置上去使用@autoreleasepool,一旦出了@autoreleasepool作用域,该作用域内的变量会立马释放。如:

for(int i = 0; i < 10000; i++){
        @autoreleasepool {   
        }
  }

但并不是所有的遍历方法都要加上@autoreleasepool,比如enumerateObjectsUsingBlock:方法,仔细阅读苹果官方文档,可发现该方法内部已经添加过@autoreleasepool处理。

autoreleasepool 底层

autoreleasepool 底层是个C++结构体,创建和销毁的时候分别会调用构造函数和析构函数。

struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    ~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    void * atautoreleasepoolobj;
 };
系统默认 autoreleasepool

iOS 中有个默认的autoreleasepool,主线程的 Runloop 中注册了 2 个 Observer:

  • 第1个Observer监听kCFRunLoopEntry事件,会调系统默认autoreleasepool的 objc_autoreleasePoolPush() ;
  • 第2个Observer:
    监听kCFRunLoopBeforeWaiting事件,会调系统默认autoreleasepool的 objc_autoreleasePoolPop()、objc_autoreleasePoolPush();
    监听了kCFRunLoopBeforeExit事件,会调系统默认autoreleasepool的objc_autoreleasePoolPop();
autorelease和autoreleasepool

内存管理中调用alloc、new、copy、mutableCopy方法返回对象,在不需要这个对象时,要调用 release 或autorelease 来释放它,MRC 中通常会使用 release 和 autorelease。autorelease 一般是仅用在 MRC 中。

八 、如何对 NSMutableArray 进行 KVO

一般情况下只有通过调用 set 方法对值进行改变才会触发 KVO。但是在调用NSMutableArray的 addObject或removeObject 系列方法时,并不会触发它的 set 方法。所以为了实现NSMutableArray的 KVO,官方为我们提供了如下方法:

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key

在增删元素时,使用上述方法来获取要操作的可变数组,然后再执行添加或删除元素的操作,便能实现 KVO 机制。如:

@property (nonatomic, strong) NSMutableArray *arr;
//添加元素操作
[[self mutableArrayValueForKey:@"arr"] addObject:item];
//移除元素操作
[[self mutableArrayValueForKey:@"arr"] removeObjectAtIndex:0];
九、被忽略的UIViewController两对API

如何判断一个页面的viewWillAppear方法是 push 或 present 进来是调用的,还是 pop 或 dismiss 是调用的?一种比较笨拙的方法是通过添加属性标记是进入还是返回调用viewWillAppear方法。还有一种最简单的方法,是直接调用苹果提供的两对 API 。
针对 Push 和 Pop 或 add childViewController 和 remove childViewController 的 API:

@property(nonatomic, readonly, getter=isMovingToParentViewController) BOOL movingToParentViewController NS_AVAILABLE_IOS(5_0);
@property(nonatomic, readonly, getter=isMovingFromParentViewController) BOOL movingFromParentViewController NS_AVAILABLE_IOS(5_0);

针对 Present 和 Dismiss 的 API

@property(nonatomic, readonly, getter=isBeingPresented) BOOL beingPresented NS_AVAILABLE_IOS(5_0);
@property(nonatomic, readonly, getter=isBeingDismissed) BOOL beingDismissed NS_AVAILABLE_IOS(5_0);
十、抗压缩优先级

两个水平布局的label,两边间隔分别是12,中间间隔为8(懂意思就行)。如果两个label 都不设置宽度,则左边 label 会拉长,右边 label 自适应。

UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectZero];
    label1.backgroundColor = [UIColor redColor];
    label1.text = @"我是标题";
    [self.view addSubview:label1];
    [label1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.view);
        make.left.equalTo(@(12));
    }];
    UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectZero];
    label2.backgroundColor = [UIColor redColor];
    label2.text = @"我是描述";
    [self.view addSubview:label2];
    [label2 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(label1);
        make.left.equalTo(label1.mas_right).offset(8);
        make.right.equalTo(self.view).offset(-12);
    }];

如果想让左边 label 自适应,右边 label 拉升,可以设置控件拉升阻力(即抗拉升),拉升阻力越大越不容易被拉升。所以只要 label1 的拉升阻力比 label2 的大就能达到效果。

//UILayoutPriorityRequired = 1000 
    [label1 setContentHuggingPriority:UILayoutPriorityRequired
                              forAxis:UILayoutConstraintAxisHorizontal];
//    //UILayoutPriorityDefaultLow = 250
    [label2 setContentHuggingPriority:UILayoutPriorityDefaultLow
                              forAxis:UILayoutConstraintAxisHorizontal];

Content Hugging Priority:拉伸阻力,即抗拉伸。值越大,越不容易被拉伸。
Content Compression Resistance Priority:压缩阻力,即抗压缩。值越大,越不容易被压缩。

十一、为什么会有深拷贝和浅拷贝之分
image.png

上图中观察可知只有不可变 + 不可变组合的时候才出现浅拷贝,其他三种情况都是深拷贝。原因在于,两个不可变对象内容一旦确定都是不可变的,所以不会彼此干扰,为了节省内容空间,两个对象可以指向同一块内存。而其他三种情况,都有可变对象的存在,为了避免两个对象之间的彼此干扰,所有会开辟额外的空间。

十一、为什么交叉方法出现"死循环"

因为交换了方法的实现 IMP ,如果alert_replaceInitWithString方法内部调用initWithString会出现真正的死循环。下面代码的死循环只是一个假象。

@implementation NSAttributedString (Exception)
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
            [objc_getClass("NSConcreteAttributedString") swizzleMethod:@selector(initWithString:) swizzledSelector:@selector(alert_replaceInitWithString:)];
        }
    });
}
-(instancetype)alert_replaceInitWithString:(NSString*)aString{
    if (!aString) {
        NSString *string = [NSString stringWithFormat:@"[%s:%d行]",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String],__LINE__];
        [[[ExceptionAlert alloc]init]showAlertWithString:string];
        ;
        return nil;
    }
    return [self alert_replaceInitWithString:aString];
}
@end
十二、为什么交叉方法出现"死循环"
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 最近一朋友正准备跳槽,就从各处搜索整理一些基础,便于朋友复习,也便于自己复习查看. 1. 回答person的ret...
    smile丽语阅读 1,710评论 0 7
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,065评论 1 32
  • 1.设计模式是什么? 你知道哪些设计模式,并简要叙述?设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型...
    龍飝阅读 2,113评论 0 12
  • 看到窗外鹅毛般的大雪,我这小姐姐似乎有些清凉,一看到雪就让人觉得浪漫,情侣漫步在雪地里,一不小心就白了头,可惜在上...
    Ly芽儿阅读 217评论 0 1
  • 关于我和我老婆当初恋爱的故事,我在很多场合与很多人都讲过。简单说就是我看到我同事有一张我老婆的照片,然后我就认定她...
    放心之语阅读 1,075评论 0 2