Autorelease Pool 的用处

在 ARC 下,我们并不需要手动调用 autorelease 有关的方法,甚至可以完全不知道 autorelease 的存在,就可以正确管理好内存。因为 Cocoa Touch 的 Runloop 中,每个 runloop circle 中系统都自动加入了 Autorelease Pool 的创建和释放。

当我们需要创建和销毁大量的对象时,使用手动创建的 autoreleasepool 可以有效的避免内存峰值的出现。因为如果不手动创建的话,外层系统创建的 pool 会在整个 runloop circle 结束之后才进行 drain,手动创建的话,会在 block 结束之后就进行 drain 操作。详情请参考苹果官方文档。一个普遍被使用的例子如下:

for (int i = 0; i < 100000000; i++)
{
    @autoreleasepool
    {
        NSString* string = @"ab c";
        NSArray* array = [string componentsSeparatedByString:string];
    }
}

如果不使用 autoreleasepool ,需要在循环结束之后释放 100000000 个字符串,如果
使用的话,则会在每次循环结束的时候都进行 release 操作。

Autorelease Pool 进行 Drain 的时机

如上面所说,系统在 runloop 中创建的 autoreleaspool 会在 runloop 一个 event 结束时进行释放操作。我们手动创建的 autoreleasepool 会在 block 执行完成之后进行 drain 操作。需要注意的是:

当 block 以异常(exception)结束时,pool 不会被 drain

Pool 的 drain 操作会把所有标记为 autorelease 的对象的引用计数减一,但是并不意味着这个对象一定会被释放掉,我们可以在 autorelease pool 中手动 retain 对象,以延长它的生命周期(在 MRC 中)。

2018/10/11 posted in  iOS

Runtime运行时

  1. runtime调用方法

    - (void)test {
    TestModel *objct = [[TestModel alloc] init];
    ((void (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("country"));
    ((void (*) (id, SEL, NSString *)) objc_msgSend) (objct, sel_registerName("getProvince:"), @"广东省");
    ((void (*) (id, SEL, NSString *, NSString *)) objc_msgSend) (objct, sel_registerName("getCity:station:"), @"深圳市", @"世界之窗");
    NSString *weather = ((NSString* (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("getWeather"));
    NSLog(@"%@", weather);
    }
  2. 给分类添加属性

    #import <objc/runtime.h>
    static void *TestStringKey = &TestStringKey;
    @implementation TestModel (String)
    - (void)setTestString:(NSString *)testString {
    objc_setAssociatedObject(self, TestStringKey, testString, OBJC_ASSOCIATION_COPY);
    }
    - (NSString *)testString {
    return objc_getAssociatedObject(self, TestStringKey);
    }
    @end
  3. 获取属性

    使用class_copyIvarList获得的属性数组是Ivar类型 
    可以使用ivar_getName将ivar类型转换为char
    再使用NSString 的stringWithUTF8String方法转化为NSString类型。
2018/10/10 posted in  iOS

对Block的一点点认识

如果使用weak修饰block,那么将有可能出现野指针的错误问题

那么strong 和 copy 有什么区别呢?

block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.
使用retain也可以,但是block的retain行为默认是用copy的行为实现的,
因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。

Block防止内存泄漏问题

另外还需要注意的是使用 block 的时候注意到内存泄露的情况,必要的时候要使用 weak 中断两个引用。

关于Block的一点注意 特别是网络请求这块

Block捕获外界变量销毁
我们再进一步想想,Block有个最大的特点是可以访问当前的作用域,我们随便创建一个数组,重复上面操作,是否能够销毁...

- (void)blockDemo {
    NSArray *outsideArray = @[@1, @2, @3];
    
    [self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
        // 处理业务逻辑
        // ...
        
        NSLog(@"Result from block:%@", data);
        NSLog(@"outsideArray :%@", outsideArray);
    }];
}

打印结果:
2018-03-25 19:55:40.997535+0800 NetworkCallback[4970:3641450] NextPageViewController has been dealloc!
2018-03-25 19:55:44.831721+0800 NetworkCallback[4970:3641450] outsideArray :(
    1,
    2,
    3
)

注意,vc先销毁了,但是5s后,这个临时变量竟然还没有销毁。那么这个变量存储在哪里呢?
考虑一下,如果你在Block代码里访问了一个超大的文件,这个文件必然是保存在内存的,然后此时你遇上了网络慢,接口好久没有返回,那么这个超大的文件就会一直占用内存

小结

使用Block无论是否有循环引用的可能,都要使用weakself,来防止vc被持有,而延迟释放
Block会导致对象的生命周期被延长,特别是当某些大文件被Block访问时,有几率导致内存不足

2018/10/7 posted in  iOS

Filter, Map, Reduce Functions

Filter

Filter 函数是过滤出所有符合过滤条件的元素

比如下面的方法过滤出数组所有元素等于3的元素

func filter() {
        let numbers = [1,2,3,3,3,4]
        //过滤出所有元素为3的元素 并返回过滤之后的数据
        let filtedNumbers = numbers.filter({return $0 == 3})
        print(filtedNumbers)//[3,3,3]
    }

Map

使用map函数把数组中的所有元素使用同一个规则,并返回一个相同数量的数组

func map() {
        let numbers = [1,2,3,3,3,4]
        //过滤出所有元素为3的元素 并返回过滤之后的数据
        let filtedNumbers = numbers.map({return $0 * 2})
        print(filtedNumbers)//[2,4,6,6,6,8]
    }

Reduce

使用reduce函数实现函数的前后累加,并返回一个元素

func reduce(){
        let numbers = [1,2,3,3,3,4]
      
        let sum = numbers.reduce(0, {sum,number in sum + number})

        print(sum)
        
    }
2018/10/5 posted in  iOS

设置完约束之后一定要激活约束才生效

-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        
        UILabel * messageLabel = [[UILabel alloc]init];
        messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
        [self addSubview:messageLabel];
        NSArray * constrains = @[
        [messageLabel.topAnchor constraintEqualToAnchor:self.topAnchor],
        [messageLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
        [messageLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
        [messageLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor]];
        
        [NSLayoutConstraint activateConstraints:constrains];
        
        
    }
    
    return self;
    
}

translatesAutoresizingMaskIntoConstraints属性要注意。

官方注释

/* By default, the autoresizing mask on a view gives rise to constraints that fully determine
the view's position. This allows the auto layout system to track the frames of views whose
layout is controlled manually (through -setFrame:, for example).
When you elect to position the view using auto layout by adding your own constraints,
you must set this property to NO. IB will do this for you.
*/

默认情况下,视图上的自动调整蒙版会产生完全确定的约束
视图的位置。这允许自动布局系统跟踪视图的框架
布局是手动控制的(例如,通过-setFrame:)。
当您选择通过添加自己的约束来使用自动布局来定位视图时,
必须将此属性设置为NO。IB会为你做这件事的。

另外还有一个比较注意的属性就是intrinsicContentSize

intrinsicContentSize,也就是控件的内置大小。比如UILabel,UIButton等控件,他们都有自己的内置大小。控件的内置大小往往是由控件本身的内容所决定的,比如一个UILabel的文字很长,那么该UILabel的内置大小自然会很长。控件的内置大小可以通过UIView的intrinsicContentSize属性来获取内置大小,也可以通过invalidateIntrinsicContentSize方法来在下次UI规划事件中重新计算intrinsicContentSize。如果直接创建一个原始的UIView对象,显然它的内置大小为0。

2018/9/30 posted in  iOS

Mach-O

查看mach-o文件

使用machoview打开编译之后生成的.app文件中的xxx 。
可以找到__objc_selrefs __objc_classrefs__objc_superrefs

__objc_selrefs 里的方法一定是被调用了的。
__objc_classrefs 里是被调用过的类,__objc_superrefs 是调用过 super 的类。通过 __objc_classrefs__objc_superrefs,我们就可以找出使用过的类和子类。

2018/9/27 posted in  iOS

消除CocoaPods引入第三方SDK的警告

有些强迫症不能看到xcode编译报出的警告⚠️,所以在podfile中添加一行
inhibit_all_warnings!
如果没有多个target或者需要所有都隐藏这个黄色警告就在最外层写

# platform :ios, '9.0'
inhibit_all_warnings!

target 'MVVM' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!
   pod 'ReactiveObjC', '~> 3.1.0'
   pod 'MBProgressHUD', '~> 1.1.0'
   pod 'MJRefresh', '~> 3.1.15.7'
  # Pods for MVVM

end

如果有多个target的话就可以在各个需要隐藏的target当中写上这句话

# platform :ios, '9.0'

target 'MVVM' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!
  inhibit_all_warnings!
   pod 'ReactiveObjC', '~> 3.1.0'
   pod 'MBProgressHUD', '~> 1.1.0'
   pod 'MJRefresh', '~> 3.1.15.7'
  # Pods for MVVM

end

target 'RAC' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!
   pod 'Masonry', 
   pod 'AFNetworking',   # Pods for RAC

end
2018/9/20 posted in  iOS

swift 中的 selector

首先,@selector方法选择器是OC中很常用的一个关键字,无论是计时器相应方法,通知效应方法,按钮相应方法,自省判断是否能相应某个方法,都会用到@selector,OC中常用生成@selector的格式如下:

- (void)func1{
}
 
- (void)func1With:(id )obj{
}
 
//生成选择器方法如下
SEL selector1 = @selector(func1);
SEL selector2 = NSSelectorFromString(@"func1");
SEL selector3 = @selector(func1With:);
SEL selector4 = NSSelectorFromString(@"func1With:");

在swift中没有@selector了,从swift2.2开始,我们开始用#selector来从暴露给OC的代码中,获取一个选择器。类似的,swift中类似于SEL的是一个叫做Selector的结构体,上述生成SEL的代码,在Swift中类似的实现方式如下:

@objc func func1() {
}

@objc func func2(with obj:Any ) {
}

let selector1 = #selector(func1)
let selector2 = #selector(func2(with:))

在swift4.0中,所有的swift方法都默认是OC不可见得。如果想让OC调用某些swift方法,需要在方法前面添加@objc关键字,以此暴露给OC。swift3.0及其之前,只要是NSObject的子类,编译器都会在非private得方法前默认加上@objc,此举会恶化编译器编译速度。如果此对象中有大量的OC调用,可以在类型前加@objcMembers关键字,所有非private方法都默认暴露给OC

最后,如果swift中两个相同命名,参数不同方法,直接生成Selector会产生二义性报错。遇此情况,添加上参数和返回值类型,强转即可。

@objc func func1() {
}

@objc func func1(with obj:Any ) {
}

//如此生成即可
let selector1 = #selector(func1 as ()->Void)

2018/9/20 posted in  iOS