iOS_一些小知识点(持续更新・・・)

本文介绍了iOS开发中的几个关键技巧,包括初始化方法的返回值类型选择、NSInteger与int及long的区别、单例模式的最佳实践、self.name与_name的区别、ARC中的strong与copy特性以及Object Literals的高效使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有两年没有开发iOS了,最近又开始接触iOS开发,发现好多旧知识忘记了,好多新知识还不知道。

最近正在学习中,一些心得做下memo,也可以和大家分享一下。


初始化方法的返回值类型(类名,id,instancetype)

初始化方法的返回值一般设成id。为什么呢?为什么不设成类名呢?如果你设成类名,子类就不好处理了。比如说子类想覆盖父类的初始化方法,但想返回自己的类型,就不好处理了。所以一般返回id类型。

但是id类型不是type safe的。比如说,NSString *str = [NSArray array]; 编译器是检查不是来的,等到运行的时候就会崩溃的。

后来LLVM编译器出来之后,建议使用instancetype来代替id。凡是返回值是instancetype的方法,编译器都会检查返回值,如果没有返回本类或者子类,都会报编译错误。


NSInteger和int,long

NSInteger类型可以代表一个int类型或者一个long类型,我们推荐使用NSInteger,是因为它会根据手机的处理器来决定到底用int还是long,如果处理器是32位的,那么就用32位的int,如果处理器是64位的,那么就用64位的long。从A7处理器(iPhone 5S)开始,苹果开始采用64位的处理器。

iOS中的单例模式

从iOS4.0开始,GCD横空出世,不仅方便了多线程开发,也引入了一个适合实现单例模式的函数dispatch_once。一下是具体代码:

+ (instancetype)sharedInstance {
    static MyClass *shared = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shared = [[self alloc] init];
    });
    return shared;
}

onceToken是一个检查代码块是否调用过的一个谓词。

dispatch_once不仅能保证代码只调用一次,还是线程安全的,所以就不需要用@synchronized了。确实很方便。


关于self.name和_name的区别

name是属性,通过self.name来调用,调用的是get方法,可以在类外使用。
_name是成员变量,只能在.m文件中使用。

一般来说,.m文件中推荐使用_name。因为使用self.name有时候会带来不必要的麻烦。举个例子:
代码1
self.name = [[NSString alloc] init];

代码2
_name = [[NSString alloc] init];
self.name = @"name";

关于_name的引用计数,代码2是正常的,引用计数为1,代码1时不正常的,引用计数是2。
我们来分析一下:
self.name = 会调用set方法,set方法如下:
- (void)setName:(NSString *)name {
    if (_name != name) {
        [_name release];
        _name = [name retain];
    }
}
代码1,alloc了引用计数+1,调用set方法,[_name release]的时候_name还是nil,所以不起作用,最后有retain了,引用计数变为2。
代码2,alloc了引用计数+1,调用set方法,[_name release]的时候_name还是不为nil,所以起了作用,所以,最后引用计数还是为1。
所以,代码1是会造成内存泄漏的。

关于ARC中的strong和copy

@property (copy, nonatomic) NSString *copyName;
@property (strong, nonatomic) NSString *strongName;

上面的NSString到底用stong还是copy呢?
ARC中的stong其实就是retain,就是引用计数+1。而copy会拷贝一个副本出来。
但是,具体的区别是什么呢?
NSMutableString *name = [NSMutableString stringWithFormat:@"name"];
self.copyName = name;
self.strongName = name;

@"name"存在于堆上面的某个地址中,假设它的地址是0xB1。name存在于栈上面的某个地址中,假设它的地址是0xA1。
同样,我们假设strongName的栈地址是0xA2,copyName的栈地址是0xA3。
strong只是引用计数+1,不会重新分配内存。copy会拷贝副本,所以会重新分配内存,假设分配到0xB2。所以存在以下指向关系:

由于NSMutableString是NSString的子类,子类可以直接赋给父类。
当NSMutableString类型的name在另外一个类里面,通过stringName或copyName所在类的实例来赋值的时候,如果是strongName,会有潜在的风险。因为name和strongName是指向同一块内存的,而name又是可变类型的,所以当外面的name的值变化了,里面的strongName会跟着变化,而我们往往是不希望strongName变化的。要解决此问题,copy就起到了作用,因为copyName又拷贝了一份内存地址,所以和原来的name是相互独立的,不管name改成什么值,copyName永远是@“name”。
所以,如果一个类它的子类有可变类型的,那么我们推荐用copy而不是strong。这样的类有NSArray, NSDictionary, NSSet, NSString, NSData等等。
有人担心从外部传进来的如果不是可变类型的,那么岂不是每次都copy,会存在性能问题?其实ARC会进行判断,如果赋的值时可变类型的,那么就会执行copy,如果赋的值时不可变类型的,那么就不会执行copy,而是让引用计数+1,就和strong一样了。

Object Literals

Object Literals可以帮助你效率的定义数字,数组和字典对象。类似于Java的Auto Boxing功能。在WWDC2012上提出,Xcode4.4中就可以使用了。
那么到底Object Literals怎么使用呢?
首先让我们先来看一下以前定义数字,数组和字典对象的方法:
NSNumber * number = [NSNumber numberWithInt:1];
NSArray * array = [NSArray arrayWithObjects:@"one", @"two", nil];
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:@"value1", @"key1", @"value2", @"key2", nil];
再来看一下Object Literals的写法:
NSNumber * number = @1;
NSArray * array = @[@"one", @"two"];
NSDictionary * dict = @{@"key1":@"value1", @"key2":@"value2"};
是不是发现简单了许多?再也不用加nil了,而且字典的key,value也不用倒着写了。
更多事例如下:
  // 整数
  NSNumber *fortyTwo = @42;             // 等价于 [NSNumber numberWithInt:42]
  NSNumber *fortyTwoUnsigned = @42U;    // 等价于 [NSNumber numberWithUnsignedInt:42U]
  NSNumber *fortyTwoLong = @42L;        // 等价于 [NSNumber numberWithLong:42L]
  NSNumber *fortyTwoLongLong = @42LL;   // 等价于 [NSNumber numberWithLongLong:42LL]

  // 浮点数
  NSNumber *piFloat = @3.141592654F;    // 等价于 [NSNumber numberWithFloat:3.141592654F]
  NSNumber *piDouble = @3.1415926535;   // 等价于 [NSNumber numberWithDouble:3.1415926535]

  // 布尔值
  NSNumber *yesNumber = @YES;           // 等价于 [NSNumber numberWithBool:YES]
  NSNumber *noNumber = @NO;             // 等价于 [NSNumber numberWithBool:NO]

  // 空数组
  NSArray * array = @[];                // 等价于 [NSArray array]
  // 空的字典
  NSDictionary * dict = @{};            // 等价于 [NSDictionary dictionary]

Xcode为了方便开发者将旧代码快速的转换为新代码,提供了转换工具(Edit –> Refactor –> Convert to Modern Objective-C Syntax)。


用block遍历元素

遍历数组的两种方式:
NSArray *array = ...;
for (int i = 0; i < array.count; i++) {
    ...;
}

for (NSString *str in array) {
    ...;
}

遍历字典的方式:
NSDictionary * dict = …;
NSArray * keys = [dict allKeys];
for (NSString * key in keys) {
        NSString * value = [dict objectForKey:key];
}

在iOS4.0之后,可以用block方便的遍历数组和字典:
[array enumerateObjectsUsingBlock:^(NSString * obj, NSUInteger idx, BOOL *stop) {

}];

[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {

}];




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值