Value and Reference Collections

值类型和引用类型的集合有所不同,看下图可知。

屏幕快照 2019-01-10 下午5.25.48

示例1      
let x = NSMutableArray()
x.add("🐵")
let y = x
y.add("🐶")    
print(x,y)
//结果
(
    "🐵",
    "🐶"
) (
    "🐵",
    "🐶"
)  
示例2     
var x:[String] = []
x.append("🐵")
var y = x
y.append("🐶")
    
print(x,y)
//结果  
["🐵"] ["🐵", "🐶"]

打印内存地址,探究 x 和 y 内存指向空间的关系。

示例3  
var x:[String] = []
x.append("🐵")
let xaddress = String(format: "%p", x)
print("xaddress===\(xaddress)")
var y = x
let yaddress = String(format: "%p", y)
print("yaddress===\(yaddress)")
y.append("🐶")
print("yaddress===\(yaddress)")

//结果
xaddress===0x600000434640
yaddress===0x6000004346a0
yaddress===0x6000004346a0
示例4  
let x = NSMutableArray()
x.add("🐵")
let xaddress = String(format: "%p", x)
print("xaddress===\(xaddress)")
let y = x
let yaddress = String(format: "%p", y)
print("yaddress===\(yaddress)")
y.add("🐶")
print("yaddress===\(yaddress)")

//结果
xaddress===0x6000004508c0
yaddress===0x6000004508c0
yaddress===0x6000004508c0

示例3中的 var y = x会使y开辟一个新的内存空间,内容是复制x的内容,y添加元素,从而不会影响到 x。

但是示例4对于 let y = x 没有开辟新的内存空间 还是指向同一块区域,所以当 y 发生改变的时候 x 的值也发生了改变。

结语

NS前缀的是引用类型,引用类型只存在一份内存,因此改变一个值就会引起另一个值的改变,这个需要引起注意。

2018/10/20 posted in  iOS

iOS开发一些小技巧

  • 去掉xcode黄色警告的方法

    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
    your code with warning
    #pragma clang diagnostic pop
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wunused-variable"
    your code with warning
    #pragma clang diagnostic pop
  • 控制器init方法执行时间

    _coverView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREENWIDTH, SCREENHEIGHT)];
    在控制器init方法中初始化一个大小为self.view.bounds的uiview必须等待视图出来的时候才会进行,要不然一直在主线程等待。
    如果直接给CGRectmake设置的话不用等界面加载就会执行,应该是一个主线程问题。
    这个时候如果直接去xib里面的控件,就会出现取不到的情况,取出来是nil。
  • 判断字符串为空

    今天遇到个问题,后台用java写的,给我传回一个空字符串,类型是NSNull,用
    stringWithFormat:转出来居然是@"<null>",
    注意,这不是空,NSNull和nil是不同的。需要用 xxx == [NSNull null]加以判断。
    去掉UITableView多余的空白行分割线
    有一种简单的方法
    self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
  • 如何将本地代码提交到远程git仓库

    * 第一步:建立git仓库 
    cd到你的本地项目根目录下,执行git命令 
    git init
    * 第二步:将项目的所有文件添加到仓库中
    git add .
    * 第三步:将add的文件commit到仓库
    git commit -m "注释语句"
    * 第四步:去github上创建自己的Repository
    * 第五步:重点来了,将本地的仓库关联到github上
    git remote add origin 仓库url
    后面的https链接地址换成你自己的仓库url地址,也就是上面红框中标出来的地址
    * 第六步:上传github之前,要先pull一下,执行如下命令:
    git pull origin master
    * 第七步,也就是最后一步,上传代码到github远程仓库
    git push -u origin master
    git push -u origin master -f //强制push
    每次如果有代码需要提交必须要add一下 并且在本地commit 打下log 再去拉取remote代码,
    然后push到remote的这样一个流程。
  • button 的文字显示不全

     [label setAdjustsFontForContentSizeCategory:YES]调节字体大小.
    
  • 从xib加载View

    // Instantiate the nib content without any reference to it.
    NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:@"EPPZPlainView" owner:nil options:nil];
    // Find the view among nib contents (not too hard assuming there is only one view in it).
    UIView *plainView = [nibContents lastObject];
    // Some hardcoded layout.
    CGSize padding = (CGSize){ 22.0, 22.0 };
    plainView.frame = (CGRect){padding.width, padding.height, plainView.frame.size};
    // Add to the view hierarchy (thus retain).
    [self.view addSubview:plainView];
  • 修改导航条的颜色

    self.navigationController.navigationBar.barTintColor = [UIColor redColor];```
    
  • 当某个UIView调用drawRect方法之后,它的默认背景就是黑色的

    解决办法有在初始化的时候设置backgroundColor

    - (void)awakeFromNib {
        [super awakeFromNib];
    self.backgroundColor = [UIColor clearColor];
    // Initialization code
    }
    - (id)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    // Initialization code
    self.backgroundColor = [UIColor blueColor]
    }
    return self;
    }
  • 或者在drawRect里面写下如下代码

    - (void)drawRect:(CGRect)rect
    {
    // Drawing code
    [[UIColor blueColor] setFill]; // changes are here
    UIRectFill(rect); // and here
    }
  • 主线程死锁

        NSLog(@"1---%@",[NSThread currentThread]);
        dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2");
    });
    NSLog(@"3");
    NSLog(@"1---%@",[NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"2");
    });
    NSLog(@"3");
    //打印顺序 1,3,2
    先加入的通知先收到通知
    先去执行通知的内容
  • 解决tableviewsection悬浮的问题

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        CGFloat sectionHeaderHeight = 50;
    if (scrollView.contentOffset.y<=sectionHeaderHeight&&scrollView.contentOffset.y>=0) {
    scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
    } else if (scrollView.contentOffset.y>=sectionHeaderHeight) {
    scrollView.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight, 0, 0, 0);
    }
    }
  • NSURLSession请求

     NSURLSession * session = [NSURLSession sharedSession];
        NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
    NSLog(@"%@",dic);
    }];
    [task resume];
  • 实现点击按钮出发一次震动,比如“即刻”app的点赞

    导入AudioToolBox.framework
    在按钮的点击方法中执行
    AudioServicesPlaySystemSound(kSystemSo undID_Vibrate);//上面是震动方法 不灵巧
    可以使用最新的
    UIImpactFeedbackGenerator * zhend = [[UIImpactFeedbackGenerator alloc]initWithStyle:UIImpactFeedbackStyleLight];
    [zhend impactOccurred];
  • getter方法中为何不能用self

    有经验的开发者应该都知道这一点,在getter方法中是不能使用self.的,比如:

    - (NSString *)name
    {
    NSLog(@"rewrite getter");
    return self.name; // 错误的写法,会造成死循环
    }

    原因代码注释中已经写了,这样会造成死循环。这里需要注意的是:self.name实际上就是执行了属性name的getter方法,getter方法中又调用了self.name, 会一直递归调用,直到程序崩溃。

2018/10/15 posted in  iOS

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