[iOS] 跟着大神们学习代码(4)

目录:

  1. 让屏幕不因为设置灭屏事件而熄灭
  2. cell生命周期
  3. masonry数组布局
  4. 用二进制Option代表进行到哪一步
  5. 调试看到都是nil,print告诉你variable not available
  6. category覆盖原方法
  7. category不安全?performselector可以直接调用?
  8. @selector为啥用assign
  9. &取址
  10. tableview中如何让行高自动被撑起来
  11. scrollView缓慢滑动到边界松手不走scrollViewDidEndDecelerating

1. 让屏幕不因为设置灭屏事件而熄灭

设置的“显示与亮度”里面可以设置自动灭屏时间,但是有的时候不希望app灭屏,那么可以用下面的方式设置来disable idle timer:
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];

但有的时候AVPlayer播放会导致你想让它灭屏他也不灭,据说视频播放完才会正常响应灭屏。参考https://stackoverflow.com/questions/22164936/ios-app-prevents-device-from-sleep-mode

如果不想让视频播放妨碍灭屏可以:

if (@available(iOS 12.0, *)) {
    self.player.preventsDisplaySleepDuringVideoPlayback = NO;
} else ([self.player respondsToSelector:NSSelectorFromString(@"_preventsSleepDuringVideoPlayback")]) {
    [self.player setValue:@(NO) forKey:@"preventsSleepDuringVideoPlayback"];
}

但self.player.preventsDisplaySleepDuringVideoPlayback只支持12以上系统,以下用kvc改不太规范可能不过审慎用哦。

P.S. 连着debugger默认不灭屏记得断开关闭重新打开app尝试哦。

原理同事说是好像是AVPlayer生效是在视频播放完成后才响应这个属性设置,类似视频播放结束的时候才让idle timer run起来,所以生效时间是视频播放时间+锁屏时间。


2. cell生命周期

cell的生命周期可以参考https://www.jianshu.com/p/59b8bb25f16a

对于cell而言- (void)prepareForReuse是先于tableview的cellForIndexPath delegate的,当tableview第一次出现没有任何滑动的时候,prepareForReuse是不会被调用的,但是当你稍微滑动一丢丢,就会有prepareForReuse的调用了,但是这个时候没有任何cell出屏幕啊,所以其实tableview是有准备出来一两个备用cell在屏幕外池子里面的~

当tableview第一次出现的时候分别给cell的prepareForReuse以及delegate的三个方法(如下)打断点发现:

  • prepareForReuse没有被调用
  • cellForRowAtIndexPath和willDisplayCell成对调用(多余屏幕内个数的次数,例如12次,但屏幕只放的下9个)
  • didEndDisplayingCell也会被调用多次,屏幕内放得下9个cell的话,会回调didEndDisplayingCell给indexpath给10-12行的,所以其实tableview有提前准备cell

当cell要展示的时候,会回调table view的delegate willDisplayCell,然后当cell完全出屏幕的时候会回调didEndDisplayingCell

// delegate
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self.tableview dequeueReusableCellWithIdentifier:@"Table1ViewCell"];
    return cell;
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
}

我打断点看了一下didEndDisplayingCell的object会接着用于prepareForReuse,然后再willDisplayCell,地址都是一致的。


3. masonry数组布局

masonry可以给NSArray的view数组布局,让他们沿着一个轴均匀分布:

@implementation NSArray (MASAdditions)

- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;

- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;

但是这种只能view的宽度/高度都一样,看源码可以看到:

MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
    if (axisType == MASAxisTypeHorizontal) {
        MAS_VIEW *prev;
        for (int i = 0; i < self.count; i++) {
            MAS_VIEW *v = self[I];
            [v mas_makeConstraints:^(MASConstraintMaker *make) {
                if (prev) {
                    make.width.equalTo(prev);

所以其实上面的方法主要适用于固定spacing然后几个view平分空间,或者view的宽度(水平分布)/高度(竖直分布)固定,平分spacing,反正得是宽度/高度一致。

虽然上面的不是很实用,不过这个分类里面还有可以给数组加一样或者更新一样的constraint哦~
mas_makeConstraints、mas_updateConstraints、mas_remakeConstraints

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block {
    NSMutableArray *constraints = [NSMutableArray array];
    for (MAS_VIEW *view in self) {
        NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
        [constraints addObjectsFromArray:[view mas_makeConstraints:block]];
    }
    return constraints;
}

我们经常出现的场景其实是spacing固定,宽度/高度不一样的沿着某个轴布局,我们可以借鉴masonry数组的思想统一布局改写一个分类:

- (void)mas_live_updateDistributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
    MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
    if (!tempSuperView) {
        return;
    }
    if (axisType == MASAxisTypeHorizontal) {
        MAS_VIEW *prev;
        for (int i = 0; i < self.count; i++) {
            MAS_VIEW *v = self[I];
            [v mas_updateConstraints:^(MASConstraintMaker *make) {
                if (prev) {
                    make.left.equalTo(prev.mas_right).offset(fixedSpacing);
                } else {//first one
                    make.left.equalTo(tempSuperView).offset(leadSpacing);
                }
                
                make.top.bottom.equalTo(tempSuperView);
            }];
            
            if (i == self.count - 1) {
                [tempSuperView mas_updateConstraints:^(MASConstraintMaker *make) {
                    make.right.equalTo(v.mas_right).offset(tailSpacing);
                }];
            }
            prev = v;
        }
    }
    else {
        MAS_VIEW *prev;
        for (int i = 0; i < self.count; i++) {
            MAS_VIEW *v = self[I];
            [v mas_updateConstraints:^(MASConstraintMaker *make) {
                if (prev) {
                    make.top.equalTo(prev.mas_bottom).offset(fixedSpacing);
                } else {//first one
                    make.top.equalTo(tempSuperView).offset(leadSpacing);
                }
                
                make.right.left.equalTo(tempSuperView);
            }];
            
            if (i == self.count - 1) {
                [tempSuperView mas_updateConstraints:^(MASConstraintMaker *make) {
                    make.bottom.equalTo(v.mas_bottom).offset(tailSpacing);
                }];
            }
            prev = v;
        }
    }
}

这样的话,你可以给每个item不一样的宽高限制,这个方法只会给他们加spacing的限制,不会改变他们原有的宽高,并且这个方法还能用子view撑起这个view array的共同父view容器。
P.S. 其实有道算法题目就是找最近共同父view,思想和找链表最近节点是一样的,找到最上面然后算差,错开出发就可以了

  • 其实最开始代码不是这么写的,是给最后一个item加right contraint为父view right - tailSpacing,但是这样会有一个cell复用的bug哦~

假设上一个cell有两个item view A & B,即将出现的cell内有三个view A & B & C;之前的constraint加上的是viewB.right = superView.right - tailing,然后复用的时候viewB.right = viewC.left - spacing,这里viewB木有问题可以正常显示,但是viewC加了一个viewC.right = superView.right - tailing,于是superView现在有两个right的限定,viewB显示正常viewB.right = superView.right - tailing是生效的,那么由于viewC.right = superView.right - tailing也存在,算了一下以后viewC被挤到宽度为0了,于是就不显示了。

其实这个问题就是要复用的时候把superView的tailing约束清掉就可以了,如果是给最后一个item加约束,由于superView是被动加了约束,是不能用remake清掉别人给它的约束的,所以改成给superView加约束,然后cellForRowAtIndexPath的时候把superView除了tailing的约束remake一下,这样tailing就被清掉了于是就木有问题啦~


4. 用二进制Option代表进行到哪一步

需求是酱紫的,加入我写好作业这个事儿是需要先写数学,在写语文,最后写英语,然后才能退出写作业流程。

如果我中间不小心退出了,之后仍旧要继续之前的过程,例如我之前到了语文,回来还要开始写语文、英语、退出。

于是如果都用completion block一层套一层的话,有多少种可能性就要写几种,例如如果进行到数学,就要数学finish后语文finish后英语;如果进行到语文,就要语文finish后英语,如果进行到英语,就要写英语;如果都写完了就退出吧。

if (state == mathNotFinish) {
  [self writeMathWithCompletion:^(Bool finished){
    if (!finished) {
      return;
    }
    
    [self writeChineseWithCompletion:^(Bool finished){
      if (!finished) {
        return;
      }

      [self writeEnglishWithCompletion:^(Bool finished){
        if (!finished) {
          return;
        }
        
        // all done
       }];
    }];
  }];
} else if (state == ChineseFinish) {
// 照着上面来一遍。。。
} else {
……
}

如果像上面这么嵌套+枚举可能性我自己都想打自己了。。代码实在太丑了,所以可以通过Option来实现对过程的管理,也就是到了哪一步

然后continue的时候根据当前状态进行下一步,有点儿类似数据库升级:

typedef NS_OPTIONS(NSUInteger, StageOption) {
    StageOptionMathDone = 1 << 0,
    StageOptionChineseDone = 1 << 1,
    StageOptionEngDone = 1 << 2,
};

- (void)doHomework {
    if (self.workStage & StageOptionEngDone) {
        return;
    }
    
    if (self.workStage & StageOptionChineseDone) {
        [self writeEnglishWithCompletion:^(Bool finished){
            if (!finished) {
                return;
            }
            
            self.workStage = self.workStage | StageOptionEngDone;
            [self doHomework];
        }];
        return;
    }
    
    if (self.workStage & StageOptionMathDone) {
        [self writeChineseWithCompletion:^(Bool finished){
            if (!finished) {
                return;
            }
            
            self.workStage = self.workStage | StageOptionChineseDone;
            [self doHomework];
        }];
        return;
    }

    [self writeMathWithCompletion:^(Bool finished){
        if (!finished) {
            return;
        }
        
        self.workStage = self.workStage | StageOptionChineseDone;
        [self doHomework];
    }];
}

5. 调试看到都是nil,print告诉你variable not available

把优化等级改为none就好啦~


[iOS] 跟着大神们学习代码(4)_第1张图片
image

6. category覆盖原方法

参考:https://www.jianshu.com/p/87cfbdda0a68

如果category覆盖了类原来的方法,一定会category的,这是为啥呢?先看看category什么时候改的method list~

// ClassB.h
#import 

NS_ASSUME_NONNULL_BEGIN

static const NSInteger kNumss = 10;
@interface ClassB : NSObject

- (void) print1;
- (void) print2;

@end

NS_ASSUME_NONNULL_END

// ClassB.m
#import "ClassB.h"

@implementation ClassB

- (void) print1 {
    NSLog(@"Print 1");
}

- (void) print2 {
    NSLog(@"Print 2");
}

@end

然后我们加个分类:

// .h
#import "ClassB.h"

NS_ASSUME_NONNULL_BEGIN

@interface ClassB (error)

@end

NS_ASSUME_NONNULL_END

//.m
#import "ClassB+error.h"

@implementation ClassB (error)

- (void)print3 {
    NSLog(@"Print 3");
}

@end

然后在不import这个分类的情况下打印method list:

ClassB *bItem = [[ClassB alloc] init];
    
Class objectRuntimeClass = object_getClass(bItem);
NSLog(@"object method list \n");
unsigned int count;
Method *methodList = class_copyMethodList(objectRuntimeClass, &count);
for (NSInteger i = 0; i < count; i++) {
    Method method = methodList[I];
    NSString *methodName = NSStringFromSelector(method_getName(method));
    NSLog(@"method Name = %@\n", methodName);
}

// 输出:
2019-12-19 22:28:46.484988+0800 Example1[845:15117] object method list
2019-12-19 22:28:46.485120+0800 Example1[845:15117] method Name = print1
2019-12-19 22:28:46.485191+0800 Example1[845:15117] method Name = print2
2019-12-19 22:28:46.485254+0800 Example1[845:15117] method Name = print3

虽然category没有import,但是方法在load的时候就加到了method list的尾部,import只是为了编译通过可以调用,但不是import分类的时候才把分类里面的方法加到method list的哦,方法执行的时候会从第0个往后找。


现在把category改成酱紫:

#import "ClassB+error.h"

@implementation ClassB (error)

- (void)print2 {
    NSLog(@"Print 2 error");
}

@end

然后在不import它的情况下:

ClassB *bItem = [[ClassB alloc] init];
    
Class objectRuntimeClass = object_getClass(bItem);
NSLog(@"object method list \n");
unsigned int count;
Method *methodList = class_copyMethodList(objectRuntimeClass, &count);
for (NSInteger i = 0; i < count; i++) {
    Method method = methodList[I];
    NSString *methodName = NSStringFromSelector(method_getName(method));
    NSLog(@"method Name = %@\n", methodName);
}

[bItem print2];

输出:

2019-12-19 22:46:51.893565+0800 Example1[947:25334] object method list
2019-12-19 22:46:51.893683+0800 Example1[947:25334] method Name = print2
2019-12-19 22:46:51.893750+0800 Example1[947:25334] method Name = print2
2019-12-19 22:46:51.893838+0800 Example1[947:25334] method Name = print1
2019-12-19 22:46:51.893906+0800 Example1[947:25334] Print 2 error

重复方法可以同时存在在method list里面,的确是category中的方法执行的Print 2 error而非本来定义的print 2。可以看到其实category的print2是在method list的第一个的,比原方法靠前。

据说因为类加载会先加载父类>子类>子类的category的顺序,所以加载的时候会先有Class B的方法,然后追加category的方法到index[0]之后的位置,原来的方法会后移。

这里我是有一点疑问的,因为之前class B category内方法为print 3的时候,方法并没有追加到顶部,这个是为啥呢?准备开个类加载的探究啦


下面试试如果两个分类实现了同一个方法会调用哪一个?

如下图两个ClassB+notright以及ClassB+right都有print2方法,然后调用一下print出来的是right:

[iOS] 跟着大神们学习代码(4)_第2张图片
build phrase里面的compile sources

输出:

2019-12-20 22:37:24.804598+0800 Example1[1351:17279] Print 2 right

如果我们把compile sources里面顺序换一下试试:

[iOS] 跟着大神们学习代码(4)_第3张图片
截屏2019-12-20下午10.46.38.png

输出:

2019-12-20 22:40:54.251458+0800 Example1[1391:20863] Print 2 notright

所以在compile sources里面靠后的category的方法会覆盖之前的哦~


7. category不安全?performselector可以直接调用?

如果用上面的demo,改写一下category:

#import "ClassB+right.h"

@implementation ClassB (right)

- (void)print3 {
    NSLog(@"Print 2 right");
}

@end

在不引入ClassB的category的情况下,可以直接执行下面的语句,只有warning不会报错:

ClassB *b = [[ClassB alloc] init];
[b performSelector:@selector(print3)];

也就是说,即使不import category,也可以通过performSelector执行category内方法,所以category一旦添加,整个项目是可见的,可以随地调用,不是非常安全。

那么什么时候应该加category呢?如果你希望模块内对这个对象添加一些方法,类似于给player加一个计算它停过多少次,如果这个功能很业务,只能or只应该这个模块用,最好不要category。category最好是其他地方也可以用的,和这个类绑死,只要是这个类的对象都有这个功能,无论这个对象在哪个模块哪个场景。


8. @selector为啥用assign

可以参考https://my.oschina.net/u/2340880/blog/396367 & https://www.cnblogs.com/cocoajin/p/3249824.html

@selector是什么?SEL并不是一种对象类型,它是一个关键字,就像int,long一样,它声明了一种类型:类方法指针。其实就可以理解为一个函数指针

Objective-C在编译的时候,会根据方法的名字(包括参数序列),生成一个用 来区分这个方法的唯一的一个ID(一个整数),这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么它们的ID都是相同的。就是说,不管是超类还是子类,不管是有没有超类和子类的关系,只要名字相同那么ID就是一样的。

// 方法存在
SEL sel = @selector(testBlock);
NSLog(@"SEL: %d", sel);

输出:
2019-12-20 23:08:40.031479+0800 Example1[1523:37824] SEL: 1388698754

// 方法不存在
SEL sel = @selector(testBlockfhdsfhdsf);
NSLog(@"SEL: %d", sel);

输出:
2019-12-20 23:09:25.391055+0800 Example1[1545:38986] SEL: 115122353

这里我试了一下如果输出用%@替代%d是会crash的,所以其实sel就是基本数据类型类似long这种。而且每次执行,sel的数值都是一样的,他也不会管这个函数存不存在,直接类似把String转为了int,可能是看hash值吧每次都是一样的,也就不分子类父类了,全靠名称。

大概因为其实SEL就是一串数字ID,不是OC对象,所以就assign啦,类似基本数据类型,系统会管理它的内存哒。

这样的机制大大的增加了我们的程序的灵活性,我们可以通过给一个方法传递SEL参数,让这个方法动态的执行某一个方法;我们也可以通过配置文件指定需要执行的方法,程序读取配置文件之后把方法的字符串翻译成为SEL变量然后给相应的对象发送这个消息。

从效率的角度上来说,执行的时候不是通过方法名字而是方法ID也就是一个整数来查找方法,由于整数的查找和匹配比字符串要快得多,所以这样可以在某种程度上提高执行的效率

我们可以方便的通过方法的名字,获取到方法的ID也就是我们所说的SEL,反之亦然。具体的使用方法如下:

SEL 变量名 = @selector(方法名字); 
SEL 变量名 = NSSelectorFromString(方法名字的字符串); 
NSString *变量名 = NSStringFromSelector(SEL参数); 

至于SEL的应用,我相信最广泛的便是target——action设计模式了。我们来简单模拟一下系统button的工作原理:

.h文件:

#import 

@interface Mybutton : UIButton
-(void)addMyTarget:(id)target action:(SEL)action;
@end
.m文件

#import "Mybutton.h"

@implementation Mybutton
{
    SEL _action;
    id _target;
}
-(void)addMyTarget:(id)target action:(SEL)action{
    _target=target;
    _action=action;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [_target performSelector:_action];
}


@end

9. &取址

可参考https://www.jianshu.com/p/1dc7c31fa06f 以及 https://www.jianshu.com/p/00c2c189f403

  • int p1表示创建了一个指针变量p1,表示p1是一个指针变量,int表示该指针变量指向的对象的类型
  • (*p1)表示寻址,找到p1指向的区域
  • &表示取a的内存地址。
  • ** 代表指向地址的指针

即使是基础变量也可以用指针指向哦,因为任何变量都有地址~

NSString *test = @"test";
NSLog(@"test:%p, test的地址为:%p", test, &test);

NSString *test2 = test;
NSLog(@"test2:%p, test2的地址为:%p", test2, &test2);

输出:
2019-12-21 09:13:23.423677+0800 Example1[2594:140413] test:0x100605cd0, test的地址为:0x7ffeef60e118
2019-12-21 09:13:23.423802+0800 Example1[2594:140413] test2:0x100605cd0, test2的地址为:0x7ffeef60e110

首先其实我们创建的类型 *变量名形式的都是指针,什么是指针嘞?就是指向了实际存储对象的地址,类似下面酱紫:

[iOS] 跟着大神们学习代码(4)_第4张图片
指针

当我们用NSString *test2 = test;的时候,其实就是把test里面装的地址拷贝了一份也装入了test2,但是这俩地址存储位置是不一样的哦(&test != &test2)。

那为什么OC里面的对象都是直接使用的呢?并没有*变量名这样取址嘞?

OC里面的对象其实都是结构体哦,对象其实是个指针变量,所有指针都是结构指针,结构指针是指向一种struct类型的指针变量,它是结构体在内存中的首地址。

结构指针说明的一般形式是:

struct (结构类型名称) * (结构指针变量名);

例如:struct date * pdate, today;
说明了两个变量,一个是指向结构date的结构指针pdate,today是一个date结构变量。

语句:

struct date{
  int year;
  int month;
  int day;
};
pdate = &today;  

通过结构变量today访问其成员的操作,也可以用等价的指针形式表示:

today.year = 2001;  等价于  (*pdate).year = 2001;

由于运算符""的优先级比运算符"."的优先级低,所以必须有"( )"将pdate括起来。若省去括号,则含义就变成了"*(pdate.year)"。

在C语言中,通过结构指针访问成员可以采用运算符"->"进行操作,对于指向结构的指针,为了访问其成员可以采用下列语句形式:结构指针->成员名;

这样,上面通过结构指针pdate访问成员year的操作就可以写成:
pdate->year = 2001;。如果结构指针p指向一个结构数组,那么对指针p的操作就等价于对数组下标的操作。


所以其实我们看到色NSString就类似下面的结构体:

typedef struct Object {
    char *string;
} *Object; 

当我们调用变量指针的时候内部转换为了结构指针的样子:

NSString *str = @"abc";
NSLog("str的值为:%@",str);

转为:
Object obj = malloc(sizeof(Object));
obj->string = "abc";
NSLog(@"%s", obj->string); 

然后我们看下参数传递的时候如何处理指针~

NSString *test = @"test";
NSLog(@"test:%p, test的地址为:%p", test, &test);
[self testArgu:test];

- (void)testArgu:(NSString *)test {
    NSLog(@"testArgu");
    NSLog(@"test:%p, test的地址为:%p", test, &test);
}

输出:
2019-12-21 13:31:25.501729+0800 Example1[3100:228725] test:0x105393cd0, test的地址为:0x7ffeea880118
2019-12-21 13:31:25.501851+0800 Example1[3100:228725] testArgu
2019-12-21 13:31:25.501934+0800 Example1[3100:228725] test:0x105393cd0, test的地址为:0x7ffeea8800d8

方法内部会自行生成一个指针变量,来指向传入的指针变量指向的对象。(小扩展下:这个局部变量由系统自行管理,存放在栈区)

所以这里传入的参数和函数内的参数的内容是一样,但是参数自身的地址是不一样的。

  • 那么如果我们传入的是指向地址的地址呢?即使方法内部copy了这个数据放入了新的地址,我们仍旧不会丢失参数指针的真实地址。
NSString *test = @"test";
NSLog(@"test:%p, test的地址为:%p", test, &test);
[self testArgu:&test];

- (void)testArgu:(NSString **)testPtr {
    NSLog(@"testArgu");
    NSLog(@"test:%p, test的地址为:%p", *testPtr, testPtr);
}

输出:
2019-12-21 13:43:15.367530+0800 Example1[3131:233734] test:0x108edfcd0, test的地址为:0x7ffee6d34118
2019-12-21 13:43:15.367649+0800 Example1[3131:233734] testArgu
2019-12-21 13:43:15.367746+0800 Example1[3131:233734] test:0x108edfcd0, test的地址为:0x7ffee6d34110

为什么test的地址还是不对呢?我们传入的就是NSString **了吖?其实是因为自动添加的autoreleasing~


autoreleasing

编译器优化会默认对传入的对象加一个__autoreleasing修饰符。
__autoreleasing:将其修饰的对象加入autoreleasepool,也就会起到延缓释放的作用。

那么OC编译器为什么要添加这个呢?因为本着“谁创建谁释放”内存管理原则,在方法内部创建的对象(指向传入的&str),在方法结束后,就会自动释放

因此我们就可以推测:编译器的优化不只是在方法的参数前多加一个__autoreleasing,还使用了一个__autoreleasing修饰的临时变量来承接我们想要传入的变量。

类似酱紫:

NSString *test = @"test";
__autoreleasing NSString *tempStr = str;
[self testArgu:&tempStr];

既然是由于修饰符不一致的原因造成的隐式的优化,那么我们可不可以自己创建传入方法的对象时就使用满足要求的__autoreleasing修饰呢?

__autoreleasing NSString *str = @"test";
[self testArgu:&str];

这样就可以避免编译器再使用一个临时变量来转接我们自己的变量了。而且通过验证可以发现,这次的log地址也完全符合预期了。

再想想,可以让我们创建的对象“适应”编译器给方法自动生成的__autoreleasing,那么能不能让方法来“适应”我们创建的对象呢?

-(void) testArgu:(NSString * __strong *)str;

一般对付隐式的方式就是用显式来覆盖他。

酱紫试一下发现输出就符合预期啦:

- (void)testArgu:(NSString * __strong *)testPtr {
    NSLog(@"testArgu");
    NSLog(@"test:%p, test的地址为:%p", *testPtr, testPtr);
}

输出:
2019-12-21 13:46:08.288290+0800 Example1[3161:235411] test:0x10cedacd0, test的地址为:0x7ffee2d39118
2019-12-21 13:46:08.288413+0800 Example1[3161:235411] testArgu
2019-12-21 13:46:08.288501+0800 Example1[3161:235411] test:0x10cedacd0, test的地址为:0x7ffee2d39118

10. tableview中如何让行高自动被撑起来

可以参考:https://www.jianshu.com/p/5f5c550d61a0

懒杨杨大概说下步骤吧~

  • 设置tableView.rowHeight = UITableViewAutomaticDimension
    如此设置之后,就不必要写heightForRowAtIndexPath方法了。(默认值就是UITableViewAutomaticDimension)

  • 设置tableView.estimatedRowHeight = 100
    设置一个预估的行高,为了代码的易读性,还是尽量要设置一个跟cell的高差不多的值

  • 让内容自动撑开cell的contentView

// cell .m
#import "Table1ViewCell.h"
#import "Masonry.h"

@implementation Table1ViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.contentLabel = [[UILabel alloc] init];
        self.contentLabel.numberOfLines = 0;
        self.contentLabel.font = [UIFont systemFontOfSize:15.0];
        self.contentLabel.textColor = [UIColor blackColor];
        [self.contentView addSubview:self.contentLabel];
        [self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.contentView).offset(10);
            make.bottom.equalTo(self.contentView).offset(-10);
            make.left.equalTo(self.contentView).offset(10);
            make.right.equalTo(self.contentView).offset(-10);
        }];
    }
    return self;
}

@end

// vc.m

#import "Table1ViewController.h"

#import "Table1ViewCell.h"

@interface Table1ViewController () 

@property (nonatomic, strong) NSArray *objects;

@property (weak, nonatomic) IBOutlet UITableView *tableview;

@end

@implementation Table1ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableview.dataSource = self;
    self.tableview.delegate = self;
    self.tableview.rowHeight = UITableViewAutomaticDimension;
    self.tableview.estimatedRowHeight = 100;
    
    self.objects = @[@"美商务部对中兴通讯暂时部分解禁,中兴新掌门李自学:尽快恢复生产",
    @"公告称:从公告发布之日起至8月1日,在有限条件下解除对中兴通讯公司的出口禁令。美国商务部公共事务总监丽贝卡·格洛弗在接受CGTN记者问询时表示,对中兴通讯的7年禁售令并未正式取消。",
    @"这份公告的授权对象是已经与中兴通讯开展业务的公司,期限是一个月。这些公司销售给中兴通讯的产品必须用于以下方面。第一是支持现有网络和设备的持续运行,其次是支持现有的手机,第三是用于网络安全研究和漏洞披露,另外还有一个条件是交易资金必须在美国商务部授权的机构间转移。",
    @"在6月29日上午举行的中兴通讯股东大会上,中兴新掌门人李自学发言时强调:“我们的任务还是提振整个公司的信心,包括公司的员工(信心),在拒绝令解除之后,尽快恢复生产,在这之后再做一些工作。”",
    @"美国芯片巨头美光部分产品在华遭禁售",
    @"7 月 3 日福州中级法院裁定对美国芯片巨头美光(Micron)发出“诉中禁令”,美国部分闪存 SSD 和内存条 DRAM 将暂时遭禁止在中国销售,虽不是最终判决,但似乎暗示美光确实有侵权之嫌。",
    @"美光方面周二表示,尚未收到竞争对手台联电早些时候提到的在中国大陆销售芯片的临时禁令。美光表示,在评估来自福州中级人民法院的文件之前,该公司不会对此置评",
    @"小米或成史上最快纳入恒生指数的港股,一手中签率100%",
    @"自小米宣布IPO以来,这家超级独角兽就在不停地创造纪录。",
    @"先是将成首家同股不同权的港股上市公司;招股书又披露在全球营收超千亿且盈利的互联网公司中,小米增速排名第一;认购结束后,小米录得近10倍超购,打破2011年以来大盘股认购倍数纪录,成为全球最大散户规模的IPO,是香港有史以来规模最大的民营公司IPO,全球第三的科技公司IPO",
    @"业内人士表示,小米上市后或将在10个交易日后被纳入恒生综合指数,创下港股史上最快纪录",
    @"此外,香港信报消息显示援引市场消息显示,小米一手中签率100%,逾2.5万人申请一手;至于获分派2手有1.5万人,认购9手稳获2手。至于B组的大户方面,最大一张飞为认购1000万股,获分97.2万股,即4860手。小米将于7月9日挂牌"];
}

#pragma mark - UITableViewDelegate
//- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//    return 60;
//}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.objects.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @"Table1ViewCell";
    Table1ViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if (cell == nil) {
        cell = [[Table1ViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
    }
    
    cell.contentLabel.text = self.objects[indexPath.row];
    
    return cell;
}

以上代码跑出来是酱紫的~


[iOS] 跟着大神们学习代码(4)_第5张图片
不实现heightForRowAtIndexPath

如果你把注释调的heightForRowAtIndexPath放开就是酱紫的了:


[iOS] 跟着大神们学习代码(4)_第6张图片
实现heightForRowAtIndexPath

所以如果想自适应就不要实现heightForRowAtIndexPath哦~~

如果你有些cell想固定高度,有些想自适应就酱紫吧:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    BaseItem *item = [self.dataManager cellItemAtIndexPath:indexPath];
    if (item.cellHeight > 0) {
        return item.cellHeight;
    }
    return UITableViewAutomaticDimension;
}

11. scrollView缓慢滑动到边界松手不走scrollViewDidEndDecelerating

可参考:https://blog.csdn.net/abc649395594/article/details/46780065/

场景就是如果你缓慢滑动,一直滑到该停下的位置,那么其实你停下是被迫的,没有减速过程,不会调用scrollViewDidEndDecelerating,所以其实如果你想在停止滚动的时候做点什么,需要同时实现scrollViewDidEndDragging:

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    if(!decelerate){
        //这里复制scrollViewDidEndDecelerating里的代码
    }
}

你可能感兴趣的:([iOS] 跟着大神们学习代码(4))