RE0_OC_2

54 阅读17分钟

【知识点】对象在内存中的存储,new的作用?

Persion *p1 = [Person new]

new做了哪些事?

1、在堆内存中申请一块合适大小的空间

2、根据类的模板在空间中创建对象(创建类模板的属性和isa指针,isa指针指向代码中的类)

3、初始化对象的属性(如果对象属性的数据类型是C数据类型,初始化为Null,如果是OC数据类型,初始化为Nil)

4、返回创建对象的地址

【知识点】nil与Null的相同点与不同点?

相同点:

1、都只能作为指针变量来使用,都表示不指向内存中的任何空间;

2、在值上是等价的,都是0

不同点:

Nil:用于表示指向 Objective-C 对象的空指针

Null:用于表示普通的 C 指针的空值。

(10)异常处理

什么是异常?

程序可以编译 链接 执行.

当程序在执行的时候 处于某种特定条件下 程序的执行就会终止.

异常的后果: 程序会立即崩溃.程序立即终止运行.并且后面的代码不会执行了

处理异常:try……catch

  @try
 
  {
 
  }
 
  @catch(NSException *ex)
 
  {
 
  }

避免异常最常用的还是逻辑判断:

如:判断某个对象中是否包含某个方法

// [对象 respondsToSelector:@selector(方法)]
[xiaoMing respondsToSelector:@selector(showFist)];

包含回返回1,不包含返回0

(11)类方法

分类:

OC中的方法分为两类:

1、对象方法/实例方法;(调用时,必须先创建对象,然后通过对象名调用)

2、类方法;(调用不依赖对象,可直接通过类名调用)

声明:

对象方法的声明:

-(返回值类型)方法名;
-(void)showFist;

类方法的声明:

+(返回值类型)方法名;
+(void)saiHi;

类方法和对象方法的声明和实现,除了-和+,其他一致;

调用:

但调用时有区别;

对象方法,必须通过实例化对象调用;即之前用的[Persopn *p1] = [Person new] [p1 showFist]

但类方法不依赖对象,可直接通过类名调用:[Person sayHi]

类方法的特点:

类方法因为可以直接调用方法,因此较对象方法来讲更节省空间且更高效

缺点:

1、类方法中不能像对象方法一样直接访问类的属性;

因为类调用方法,是直接在内存中找到存储该类的代码段,然后执行方法;而类的属性,是在实例化的时候才会创建(参考知识点new的作用),所以类方法无法直接访问类的属性;

//---------------------接口
@interface Person : NSObject{
    @public
    NSString *_name;
}
​
//类方法声明
+(void)sayHi;
​
@end//---------------------实现
@implementation Person
​
+(void)sayHi{
    _name;  //不能直接访问或修改类的属性
    NSLog(@"我是类方法");
}
​
@end

2、类方法无法通过self调用同一个类的对象方法(因为没有创建对象);

注意:

虽然类方法无法直接调用类的属性和对象方法,但对象方法可以直接调用类方法

什么时候用类方法:

在这个方法中,不需要直接访问类的属性和对象方法时,可以使用类方法,以此节省空间,提升效率;

类方法的规范:

1、如果要实现一个类,则要求为这个类创建一个和类同名的类方法,然后这个方法创建一个纯洁的对象并返回;

例如:我们要创建一个Person类,则按照规范,我们需要创建一个和Person类同名的类方法,即:

//---------------------接口
@interface Person : NSObject{
    @public
    NSString *_name;
}
​
//类方法声明
+(void)sayHi;
​
//纯洁的类方法
+(Person *)person;
​
@end//---------------------实现
@implementation Person
​
+(void)sayHi{
    _name;  //不能直接访问或修改类的属性
    NSLog(@"我是类方法");
}
​
//纯洁的类方法,创建一个对象,然后返回
+(Person *)person{
  Person *p1 = [Person new];
  return p1;
}
​
@end

这样,在为这个类创建对象时,有以下两种方式:

Person *p1 = [Person new];
​
//或
Person *p2 = [Person person];

如果你希望对象一创建就携带了类的属性,那么可以定义一个带参数的类方法:

//---------------------接口
@interface Person : NSObject{
    @public
    NSString *_name;
    int _age;
}
​
//类方法声明
+(void)sayHi;
​
//与类同名的类方法
+(Person *)person;
​
//带参数的类方法
+(Person *)personWithNameAge:(NSString *)userName andAge:(int)userAge;
​
@end
​
  
//---------------------实现
@implementation Person
​
+(void)sayHi{
    NSLog(@"我是类方法");
}
​
//与类同名的类方法
+(Person *)person{
    Person *p1 = [Person new];
    return p1;
}
​
//带参数的类方法
//方法名规范:类名Withxxxx
+(Person *)personWithNameAge:(NSString *)userName andAge:(int)userAge{
    Person *p1 = [Person new];
    p1->_name = userName;
    p1->_age = userAge;
    return p1;
}
​
@end

调用时,创建出的对象就直接带上了一些属性值;

Person *p1 = [Person personWithNameAge:@"小米" andAge:18];
    
NSLog(@"%d",p1->_age);  //18

与原来的先创建对象,再给对象属性赋值的写法更简便:

Person *p1 = [Person new];
p1->_name = @"小米";
p1->_age = 20;
NSLog(@"%d",p1->_age);  //20

(12)NSString

定义:

NSString是一个Foundation框架提供的类,用来存储OC中的字符串,因此本质上来讲创建出的字符串其实是一个NSString对象;

标准的创建方法:
NSString *str1 = [NSString new];
//按照苹果的规范,苹果还提供了一个同名类方法来创建对象,即:
NSString *str2 = [NSString string];

通过上述的标准创建方式,可以创建出一个空的NSString字符串:@""

简便的创建方法:
NSString *str = @"";
NSString *str1 = @"测试";
最常用的类方法:

(1)将C语言字符串转换为OC字符串:stringWithUTF8String

char *c1 = "rose";
NSString *ns = [NSString stringWithUTF8String:c1];
NSLog(@"%@",ns);

(2)格式化字符串:stringWithFormat

NSString *ns = [NSString stringWithFormat:@"格式化字符串,测试人员:%@,年龄:%d",@"大王",20];   //格式化字符串,测试人员:大王,年龄:20
NSLog(@"%@",ns);

(3)将字符串写入指定的文件中:writeToFile

- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile encoding:(NSStringEncoding)enc error:(NSError **)error;
​
BOOL res =[str writeToFile:@"/Users/babylon/Desktop/code/day9_oc/day09/day09/haowai.txt" atomically:NO encoding:NSUTF8StringEncoding error:&err];
​
//第一种判断方式:
NSLog(@"%@",res==YES?@"写入成功!":@"写入失败!");

path:要写入的路径

atomically:保证原子性,如果传入yes,会先创建一个临时文件,如果写入成功之后,再将临时文件拷贝到指定目录;安全不会出错,但效率极低;反之,如果传NO,则直接写入指定目录,有可能会出错,但效率高。推荐使用NO;

encoding:以哪种编码形式将数据写入文件,如:NSUTF8StringEncoding

error:如果方法执行成功.则方法执行完毕之后. 这个 NSError 的指针的值为 nil 如果方法执行失败.则方法执行完毕之后 这个 NSErro 指针会指向 1 个错误对象. 可以打印这个错误对象得到发生错误的详细信息

(4)将文件中内容读取到字符串:stringWithContentsOfFile

+ (nullable instancetype)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;
​
NSError *err;
​
//将文件中的内容读取到字符串中.NSString * str =[NSString stringWithContentsOfFile:@"/Users/babylon/Desktop/code/day9_oc/day09/day09/haowai.txt" encoding:NSUTF8StringEncoding error:&err];
​
if (err != Nil) {
    NSLog(@"%@",err.localizedDescription);
}
else{
    NSLog(@"%@",str);
}

(5)从URL中读取内容到字符串:stringWithContentsOfURL

字符串不仅可以从本地读取文件,也可以从URL中读取文件

详细参见目录:NSURL

常用的对象方法:

(1)获取字符串长度:length

返回值类型为:NSUInteger,long型

NSString *ns = @"test this string how long";
NSUInteger len = [ns length]; //或直接ns.length
NSLog(@"字符串长度为:%ld",len);

(2)获取字符串指定下标的字符:characterAtIndex

返回值类型为:unichar,字符串型,输出时使用%C

NSString *ns = @"test this string how long";
unichar ch = [ns characterAtIndex:6];
NSLog(@"%C",ch);  //h

(3)比较两个字符串是否相等:isEqualToString

返回值类型为:BOOL,输出时可用%d

NSString *str1 = @"哈哈哈哈";
NSString *str2 = @"嘻嘻嘻嘻";
​
BOOL res = [str1 isEqualToString:str2];
NSLog(@"%d",res); //0

不能使用==来比较,不严谨。

(4)比较字符串大小:compare

返回值类型为NSComparisonResult,枚举类型,-1小于,0等于,1大于

NSString *str1 = @"China";
NSString *str2 = @"China";
​
NSComparisonResult res = [str1 compare:str2];
NSLog(@"%ld",res);  //0

忽略大小写比较:传入compare的第二个参数options:NSCaseInsensitiveSearch

NSString *str1 = @"Jack";
NSString *str2 = @"jack";
​
NSComparisonResult res1 = [str1 compare:str2 options:NSCaseInsensitiveSearch];
NSLog(@"%ld",res1); //0 相等

options的其他传参:

NSCaseInsensitiveSearch:忽略大小写

NSNumericSearch:适合字符串格式相同时,其中数字的比较,如比较pic00872.png pic00873.png

(5)判断字符串是否以指定的字符串开头:hasPrefix

NSString *str1 = @"Jackhhhh";
BOOL include = [str1 hasPrefix:@"Jack"];
NSLog(@"是否以Jack开头:%d",include); //是为1,否为0

(6)判断字符串是否以指定的字符串结尾:hasSuffix

NSString *str1 = @"Jackhhhh";
BOOL include = [str1 hasSuffix:@"hhh"];
NSLog(@"是否以hhh结尾:%d",include); //是为1,否为0

(7)在主串中搜索字串,从前往后:rangeOfString

NSString *str1 = @"Jackhhhh";
NSRange range = [str1 rangeOfString:@"ack"];
​
//返回值是1个NSRange类型的结构体变量.
typedef struct _NSRange {
    NSUInteger location; //代表子串在主串出现的下标.
    NSUInteger length; //代表子串在主串中匹配的长度.
} NSRange;
​
NSLog(@"子串在主串中匹配的长度:%ld", range.length);  //3
NSLog(@"子串在主串中首次出现的下标:%ld", range.location);  //1

如果没有找到,则lengh为0,location为NSUInteger的最大值,也就是NSNotFound

从后往前:NSBackwardsSearch

如果需要从后往前查寻,则需要传入第二个参数:options:NSBackwardsSearch

NSString *str1 = @"Jackhhhjjjhhh";
NSRange range = [str1 rangeOfString:@"hhh" options:NSBackwardsSearch];
​
NSLog(@"子串在主串中匹配的长度:%ld", range.length);    //3
NSLog(@"子串在主串中首次出现的下标:%ld", range.location);    //10

(8)字符串的截取

从哪个位置开始截取到最后:- (NSString *)substringFromIndex:(NSUInteger)from

从开始截取到哪个位置:- (NSString *)substringToIndex:(NSUInteger)to

截取一个范围内的字符串:- (NSString *)substringWithRange:(NSRange)range;

NSString *str1 = @"Jackhhhjjjhhh";
​
NSString *res1 = [str1 substringFromIndex:2];
NSString *res2 = [str1 substringToIndex:8];
NSString *res3 = [str1 substringWithRange:NSMakeRange(2,5)];
​
NSLog(@"从索引2截取到尾部:%@",res1);    //ckhhhjjjhhh
NSLog(@"从开头截取到索引8:%@",res2);    //Jackhhhj
NSLog(@"从2开始,截取5个字符:%@",res3);  //ckhhh

NSMakeRange创建一个range结构体:第一个值location,第二个值length

(9)字符串的替换:stringByReplacingOccurrencesOfString

NSString *str1 = @"Jackhhhjjjhhh";
​
NSString *res1 = [str1 stringByReplacingOccurrencesOfString:@"Jack" withString:@"Trip"];
​
NSLog(@"替换后的字符串:%@",res1);    //Triphhhjjjhhh

将原有字符替换为空,就是删除;

(10)字符串类型转换

转换注意. 从头开始转换,能转换多少就是多少. 到遇到不能转换的时候就停止转换.

@property (readonly) double doubleValue; 
@property (readonly) float floatValue;
@property (readonly) int intValue;
@property (readonly) NSInteger integerValue
@property (readonly) long long longLongValue
@property (readonly) BOOL boolValue

转换为double

NSString *str1 = @"123123.413asdaf124g";
double db1 = str1.doubleValue;
printf("转换后的:%lf",db1); //123123.413000

转换为int

NSString *str1 = @"123123.413asdaf124g";
int db1 = str1.intValue;
printf("转换后的:%d",db1); //123123

(11)去掉字符串前后空格

仅首尾,无法去除中间

NSString *str1 = @"   123123.413asdaf124g   ";
​
NSString *res = [str1 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
​
NSLog(@"去除首尾空格后的字符串:%@",res); //123123.413asdaf124g

去掉字符串首尾小写字母:lowercaseLetterCharacterSet

NSString *str1 = @"abcabc  I love U abcabc";
​
NSString *res = [str1 stringByTrimmingCharactersInSet:[NSCharacterSet lowercaseLetterCharacterSet]];
​
NSLog(@"去除小写字母后的字符串:%@",res); //  I love U

去掉字符串前后的大写字母:uppercaseLetterCharacterSet

NSString *str1 = @"ABC I love U ABC";
​
NSString *res = [str1 stringByTrimmingCharactersInSet:[NSCharacterSet uppercaseLetterCharacterSet]];
​
NSLog(@"去除大写字母后的字符串:%@",res); //  I love U

去掉字符串前后指定的字符串:characterSetWithCharactersInString

NSString * str = @"(_@ I love U @_)";
//- (NSString *)stringByTrimmingCharactersInSet:(NSCharacterSet *)set;//characterSetWithCharactersInString 去掉字符串前后特定字符(设置字符串内包含的字符)
NSString * newStr =
[str stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"@/:();()¥「」"、[]{}#%-*+=_\|~<>$€^•'@#$%^&*()_+'""]];
​
​
NSLog(@"处        理          前:[%@]",str);
NSLog(@"删除字符串前后特定字母处理后:[%@]",newStr);

将字符串转换为大写/小写字符串

字符串全大写:uppercaseString

字符串全小写:lowercaseString

每个单词首字母大写:capitalizedString

【知识点】%p和%@

NSString(@"%p",str);
NSString(@"%@",str);

%p:打印出的是指针变量的值;

%@:打印出的是指针变量的对象;

(13)匿名对象

普通实例化对象:

Person *p1 = [Person new];

那么p1就是对象的名称,

我们后续调用类中属性和方法,也是使用p1来执行的;

如:

p1->_name= @"小明";
p1->_age=18;
[p1 saiHi];

那么匿名对象,顾名思义,就是没有名字,实例化完成后直接用;

[Person new] ->_name = @"小橘"; //第一个对象
[Person new]->_age = 20;  //第二个对象
[[Person new] sayHi]; //第三个对象

与p1这种有名字的对象不同,p1为_name _age赋值,以及调用sayHi方法,都是操作的p1这一个对象 但匿名对象只能使用一次,即,每次new都是一个新的对象,因此[[Person new] sayHi]执行完并不能得到赋值后的值,而还是默认值;

因此,匿名对象的使用场景是:这个对象创建之后只用一次

(14)属性的封装

方法的getter和setter

一般情况下,需要修改类的属性或读取时,都需要用getter和setter,而不是用@public

(详细例子待完善)

(15)static

用来修饰局部变量,将这个属性变为静态变量,当方法执行完毕后,变量不会被回收;

如:学生的学号自增长:

+ (instancetype)studentWithName:(NSString *)name andAge:(int)age{
    static int num = 1;
    Student *s1 = [Student new];
    s1->_number = num++;
    s1->_name = name;
    s1->_age = age;
    return s1;
}
int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        Student *s1= [Student studentWithName:@"小李" andAge:19]; //number:1
        Student *s2 = [Student studentWithName:@"小红" andAge:19];//number:2
    }
    return  0;
}

因为在num中使用了static修饰符。所以在每次创建student对象时,number并没有每次都从1开始,而是保存上一个状态;也就是会将这个局部变量存储在常量区,当方法执行完毕之后不会回收,下次再执行这个方法的时候,直接使用;

(16)self

self可用在对象方法和类方法中;

用在对象方法中:

1、在方法中声明了和类属性同名的变量,但需要访问类属性时,使用self,self表示当前对象;

(属性要求以下划线开头,但局部变量没有要求,因此尽量避免同名属性出现;)

2、在方法中调用同一对象的其他方法时;

3、在对象方法中,不能调用当前类的类方法;

用在类方法中:

1、类方法中的self表示当前这个类;

2、在类方法中,可以调用当前类的其他类方法,但不能调用当前类的对象方法以及不能访问类的属性;

+ (instancetype)studentWithName:(NSString *)name andAge:(int)age{
    static int num = 1;
    Student *s1 = [self student]; //  在类方法中创建对象,写法1
    Student *s1 = [Student new];  // 写法2
    Student *s1 = [Studeng student];  //写法3
    [s1 setName:name];
    [s1 setAge:age];
    [s1 setNumber:num];
    return s1;
}

(17)super关键字

1、可以用在类方法和对象方法中,不能用来访问属性

2、在对象方法中,如果子类想在自己的对象方法中调用父类的对象方法,可以使用super关键字;但同时,因为是继承,父类的就是自己的,所以也可以使用self关键字;

3、在类方法中,子类也可以使用super调用父类的类方法,同样的,继承的就是自己的,也可以使用self,同样类方法可以直接使用父类名子类名调用;

4、super特指从父类继承过来的方法,增加可读性

Father.h

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Father : NSObject@property (nonatomic, strong) NSString * name;
@property (nonatomic, strong) NSNumber * age;
​
-(void)eat;
+(void)smile;
​
@endNS_ASSUME_NONNULL_END

Father.m

#import "Father.h"@implementation Father
​
-(void)eat{
    NSLog(@"I am %@, I am %@ yeas old, I can eat",self.name, self.age);
}
+(void)smile{
    NSLog(@"I can Smile");
}
@end

Son.h

#import "Father.h"NS_ASSUME_NONNULL_BEGIN@interface Son : Father
​
-(void)learn;
+(void)Shout;
​
​
@endNS_ASSUME_NONNULL_END

Son.m

#import "Son.h"@implementation Son
​
- (void)learn{
    [self eat];     //调用父类对象方法,写法一
    [super eat];    //调用父类对象方法,写法二
    NSLog(@"我会学习");
}
​
+ (void)Shout{
    [super smile];  //调用父类类方法,写法一
    [self smile];   //调用父类类方法,写法二
    [Father smile]; //调用父类类方法,写法三
    [Son smile];    //调用父类类方法,写法四
}
​
@end

main.m

#import <Foundation/Foundation.h>
#import "Son.h"int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        //调用父类对象方法
        Son *s1 = [Son new];
        [s1 setName:@"小明"];
        [s1 setAge:@20];
        [s1 learn];
        
        //调用父类对象类方法
        [Son Shout];
    }
    return  0;
}

(18)属性访问修饰符

修饰符的类型

@private

私有的,被@private修饰的属性只能在本类的内部访问

@protected

受保护的,被@protected修饰的属性只能在本类和本类的子类中访问.

****@package

被@package修饰的属性 可以在当前框架中访问。(了解即可)

@public

公共的,被@public修饰的属性可以在任意的地方访问。

默认

如果部位属性指定修饰符,则默认是@protected

继承

如果父类有私有属性,并在一个方法中使用了私有属性,那么子类可以通过调用父类的方法间接访问父类的私有属性;

修饰符的作用域

从写访问修饰符的地方开始往下,直到遇到另外1个访问修饰符或者结束大括弧为止。中间的所有的属性都属于上面的访问修饰符。

image-20250624200057528.png

使用建议

1、@public:无论什么时候都不要暴露给外界;

2、@private:如果属性只想在本类中使用,不想在子类中使用;

3、@protected:如果你希望属性只在本类和本类的子类中使用;

推荐使用默认的@protected。

(19)私有属性和私有方法

私有属性:

使用@private修饰的属性就是私有属性,虽然外界不能访问,但外界还是可以看到;

若要实现外界无法看到且无法访问这个属性,则可以不声明属性,直接写在实现中;

@implementation Student{
  NSString *_name;
}
私有方法:

方法只写实现,不写声明,就是私有方法,只能在本类中调用;

(20)里氏替换原则(LSP)

定义

子类可以替换父类的位置,且程序功能不受影响;

如:子类Student继承父类Person,虽然*p1需要一个Person类,但是我们返回了一个Student类,这样是允许的;因为子类本身本身继承于父类,拥有父类的所有属性和方法,所以替换后,不影响程序的功能;

Person *p1 = [Student new];
表现形式

父类指针指向子类对象

作用

1、1个指针中不仅可以存储本类对象的地址还可以存储子类对象的地址。Person *p1 = [Student new]

2、 如果1个指针的类型是NSObject类型的,那么这个指针中可以存储任意OC对象的地址,因为NSObject是所有类的基类。NSObject *obj = [任意对象 new]

3、如果1个数组的元素的类型是1个OC指针类型的,那么这个数组中不仅可以存储本类对象还可以存储子类对象。

Person *p1[3];
p1[0] = [Person new];
p1[1] = [Student new];

4、如果1个数组的元素是NSObject指针类型,那么意味着任意的OC对象都可以存储到这个数组之中。

NSObject *obj[3];
obj[0] = [Person new];
obj[1] = [Student new];
obj[3] = @"jack";

5、如果1个方法的参数是1个对象,那么我们在为这个参数传值的时候,可以传该对象和子类对象;

#God.m
#import "God.h"@implementation God
​
-(void)killWithPerson:(Person *)p1{
    NSLog(@"凡人,%@",[p1 name]);
}
@end
God *god = [God new];
        
Person *p1 = [Person new];
[p1 setName:@"小明"];
​
Student *s1 = [Student new];
[s1 setName:@"小李"];[god killWithPerson:p1];  //既能传本类
[god killWithPerson:s1];  //又能传子类

6、如果一个父类指针指向一个子类对象时,这个父类指针只能调用子类对象的父类成员,不能调子类独有的方法

大致就是:因为需要的是父类对象,虽然传子类对象也能说的通,因为子类是继承父类的,拥有父类的成员,但是本质上需要的还是父类对象,既然是父类对象,那么只能调父类自己的方法;(回归本质了属于是)