UISearchbar 自定义搜索图标失真

关于Searchbar设置搜索图标的问题
因为这个大小在iOS中是固定的14*14
如果UI给的切图不是这个尺寸就会出现失真的情况(ps:因为视图的默认contentMode是UIViewContentModeScaleToFill)
网上有的方法一般是设置一个继承自UITextfield的searchBar
去修改SearchBar的leftView。

UIImageView *searchIcon = [[UIImageView alloc] init];
searchIcon.image = [UIImage imageNamed:@"searchbar_textfield_search_icon"];
searchIcon.contentMode = UIViewContentModeCenter;        searchIcon.size = CGSizeMake(14, 14);
self.leftView = searchIcon;
self.leftViewMode = UITextFieldViewModeAlways;

但是其实完全可以便利UISearchBar的子视图拿到textField
再去设置textField.leftView.contendMode即可

其实系统方法
[xxxbar setImage:forSearchBarIcon:state];
内部就是设置了textField的leftView

2018/12/21 posted in  iOS

dispatch_semaphore_t 信号量

dispatch_semaphore_t 一种计算的信号量
dispatch_semaphore_create 创建一个数值的信号量
dispatch_semaphore_signal 对指定的信号量进行增加1

dispatch_semaphore_wait 会使传入的信号量减1,如果返回的值为0说明有任务进行中就阻碍当前的线程,如果返回的值大于0,就代表当前的线程当中没有任务再执行,就接着执行下面的任务。

用这个可以起到顺序执行异步任务的功效。
如果后一步的执行需要前一步执行完拿到结果才行的话,这也是一种异步顺序执行的方案。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self task:@"task one starting"];
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self task:@"task two starting"];
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self task:@"task three starting"];
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)task:(NSString *)taskName{
    NSLog(@"%@",taskName);
    sleep(1);
}

先执行dispatch_semaphore_wait 然后发现线程任务还在进行,就开始堵塞当前任务的线程,等待dispatch_async内部执行完成信号量+1才会继续下面的执行。

2018/12/15 posted in  iOS

隐藏导航栏底部的线条

有时候遇到一些特殊的要求,需要隐藏导航栏底部的线条。
两行代码就可以做到。

  • 方法一:
    设置导航栏的背景图(setBackgroundImage方法)
    设置导航栏的shadowImage (setShadowImage方法)
UINavigationBar *navigationBar = self.navigationController.navigationBar;    

//设置透明的背景图,便于识别底部线条有没有被隐藏

[navigationBar setBackgroundImage:[[UIImage alloc] init]
                       forBarPosition:UIBarPositionAny
                           barMetrics:UIBarMetricsDefault]; 

//此处使底部线条失效

[navigationBar setShadowImage:[UIImage new]];
  • 方法二:
self.navigationController.navigationBar.clipsToBounds = YES; 

导航栏全局属性设置

//全局设置导航栏主题

- (void)setNavigationControllerAppearance {
    [UINavigationBar appearance].barStyle  = UIBarStyleBlack;
    [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithWhite:0.1 alpha:0.5]];
    [[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
}

2018/12/11 posted in  iOS

RunLoop

Fundation 框架是对Core Fundation的封装。

Fundation

[NSRunLoop currentRunLoop];//返回当前线程的RunLoop 如果不存在就会创建一个RunLoop并返回。
//If a run loop does not yet exist for the thread, one is created and returned.

[NSRunLoop mainRunLoop]//获取主线程的RunLoop对象。

基础知识:每个线程上都对应有一个RunLoop对象,主线程RunLoop默认开启,子线程RunLoop要手动开启。
RunLoop在线程结束的时候退出循环。

Core Fundation

CFRunLoopGetCurrent()//
获取当前线程的RunLoop对象

CFRunloopGetMain()//获取主线程的RunLoop对象

CFRunLoopModeRef

runloop@3x
每个 RunLoop其实都是一个对象,可以包含多个不同的 Mode,每个Mode又由Source、Observer、 Timer组成,而且Mode中Source、Observer、 Timer都不存在的话,这个RunLoop也不存在。

系统默认注册了5个Mode

  • kCFRunLoopDeafultMode:App的默认mode。通常主线程是在这个mode下运行。

  • UITrakingRunLoopMode:界面跟踪mode 用于scrollview追踪触摸滑动,保证界面滑动时不受其他mode的影响。

  • UIInitalizeationRunloopMode:在刚启动app时进入的第一个mode 启动完成后就不再使用。

  • GSEventReceiveRunLoopMode:接受系统时间的内部mode 通常用不到

  • kCFRunLoopCommonModes:这是一个占位用的mode 不是一种真正的mode

CFRunLoopSourceRef

事件源(输入源)

  • source0 非基于port的

    • custom input sources
    • cocoa perform selector sources
  • source1 基于port的

    • port-based sources

CFRunLoopTimerRef

基于时间的触发器

基本上说的就是NSTimer
默认加到kCFRunLoopDeafultMode 但是滑动的时候定时器所在的mode会暂停,所以应该把定时器加到kCFRunLoopCommonModes中去。

//创建定时器并开启定时任务
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
//创建定时器但不开启定时任务
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

NSTimer中scheduledTimerWithTimeInterval方法创建的默认加到kCFRunLoopDeafultMode并开启,但是其他方法创建的定时器并没有开启,要去手动开启,或者直接指定一个mode,那么就会开启执行定时器任务。

CFRunLoopObserverRef

观察者,能够监听runloop的状态改变。

可以监听的时间点有以下几个。

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),即将进入runloop
    kCFRunLoopBeforeTimers = (1UL << 1),即将处理timer
    kCFRunLoopBeforeSources = (1UL << 2),即将处理source
    kCFRunLoopBeforeWaiting = (1UL << 5),即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),即将退出runloop
    kCFRunLoopAllActivities = 0x0FFFFFFFU监听runloop所有的情况
};
//添加runloop状态的监听
-(void)addobserver{
    
    CFRunLoopObserverRef observe = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        NSLog(@"%lu",activity);
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observe, kCFRunLoopDefaultMode);
}

CFRelease(observe);//记得要释放c中的对象


实战场景

Q:当滑动的时候对imageview设置图片并不会显示。

A:所以可以使设置image的操作在commonmode中执行。

比如使用方法

UIImageView * imageView = [[UIImageView alloc]init];

[imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"logo"] afterDelay:3 inModes:@[NSRunLoopCommonModes]];

Q:当滑动的时候Timer的定时任务不会执行。

A:把Timer放到commonmode中执行。

比如使用方法

 NSTimer * timer =   [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
    }];
    
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

创建子线程并执行事件,任务做完之后线程就销毁了。

-(void)creatThread{
    
    NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(testThread) object:nil];
    
    [thread start];
}

-(void)testThread{
      
    NSLog(@"%@",[NSThread currentThread]);
    
}

Q:如果想要让这个线程常驻的话应该怎么操作呢

A:创建一个带有mode的runloop并开启,runloop如果不指定mode的话就不能开启循环。

比如使用方法

-(void)creatThread{
    
    NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(testThread) object:nil];
    [thread start];
}

-(void)testThread{
    
    NSLog(@"%@",[NSThread currentThread]);
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
    [[NSRunLoop currentRunLoop] run];
    //这个代码不会往下执行,所以这个任务不会结束。
}

Q:如果子线程加一个定时器任务,定时器任务不执行

A:在主线程开启的定时器默认开启了runloop,但是子线程开启的定时器没有runloop需要手动开启。

-(void)creatThread{
    
    self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(testTimer) object:nil];
    [_thread start];
  
}
//修改前
-(void)testTimer{
    
    [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
       
        //code
    }];
}
//修改后
-(void)testTimer{
    
    [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
       
        //code
    }];
    1.[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
    //还可以使用下面这个方法,但是注意到如果是使用的schedue创建的方法默认是把timer加到runloop的只需要把当前的runloop run就可以了不需要再加1和2的方法,但是其他创建timer的方法没有默认把timer加到runloop就必须使用下面的这个方法把timer加到runloop当中。    
    2.[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    //必须的方法,因为子线程创建的runloop需要手动开启。
    [[NSRunLoop currentRunLoop] run];
}

没有开启runloop线程被销毁了,定时器也没有被加到runloop执行,所以解决办法就是把定时器加到runloop当中的mode中并执行run方法。


自动释放池

//当runloop在休眠之前会释放自动释放池,当runloop再启动时就会再创建池子
@autoreleasepool {
        [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
            //code
        }];
        
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    }

runloop休眠的时候自动释放池销毁,在启动的时候再次生成自动释放池。

2018/12/10 posted in  iOS

记录swift 当中超出以前认知的知识点

支持unicode编码的变量值

let a = [Int]()
let 🐶🐮 = "dogcow"
let 你好 = "你好世界"

错误处理

您使用错误处理来响应程序在执行期间可能遇到的错误情况。

与可以使用值的存在或不存在来传递函数成功或失败的选项相比,错误处理允许您确定失败的根本原因,并在必要时将错误传播到程序的另一部分。

当函数遇到错误条件时,它会抛出错误。然后该函数的调用者可以捕获错误并做出适当的响应。

func canThrowAnError() throws {
    // this function may or may not throw an error
}

函数表示它可以通过throws在其声明中包含关键字来引发错误。当您调用可以抛出错误的函数时,您可以将try关键字添加到表达式中。

Swift会自动将错误传播出当前作用域,直到它们被catch子句处理。

do {
    try canThrowAnError()
    // no error was thrown
} catch {
    // an error was thrown
}

一个do语句创建一个新的包含范围,允许误差传播到一个或多个catch条款。

以下是如何使用错误处理来响应不同错误条件的示例:

func makeASandwich() throws {
    // ...
}

do {
    try makeASandwich()
    eatASandwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

在此示例中,makeASandwich()如果没有可用的干净菜肴或缺少任何成分,该函数将抛出错误。因为makeASandwich()可以抛出错误,函数调用包含在try表达式中。通过将函数调用包装在do语句中,抛出的任何错误都将传播到提供的catch子句。

如果没有抛出错误,eatASandwich()则调用该函数。如果抛出错误并且它与SandwichError.outOfCleanDishes大小写匹配,则将washDishes()调用该函数。如果抛出错误并且它与SandwichError.missingIngredients大小写匹配,则buyGroceries(_:)调用该函数,并使用模式[String]捕获的关联值catch。

错误处理中更详细地介绍了抛出,捕获和传播错误。

一元减号算子

可以使用前缀-(称为一元减号运算符)来切换数值的符号:

let three = 3
let minusThree = -three       // minusThree equals -3
let plusThree = -minusThree   // plusThree equals 3, or "minus minus three"

一元减号运算符(-)直接位于它运行的值之前,没有任何空格。

一元加运算符

在一元加运算(+)只返回其所操作的价值,没有任何变化:

let minusSix = -6
let alsoMinusSix = +minusSix  // alsoMinusSix equals -6

虽然一元加运算符实际上没有做任何事情,但是当使用一元减运算符作为负数时,您可以使用它来为代码提供正数的对称性。

闭区域操作员

的封闭范围操作符(a...b)限定了从运行范围a来b,并且包括这些值a和b。值a不得大于b。

当在您希望使用所有值的范围内进行迭代时,闭环范围运算符非常有用,例如使用for- in循环:

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

有关for- in循环的更多信息,请参阅控制流。

半开放式操作员

所述半开区间运算符(a..<b)限定了从运行范围a到b,但不包括b。它被认为是半开放的,因为它包含它的第一个值,但不是它的最终值。与闭区域运算符一样,值a不得大于b。如果值a等于b,则结果范围将为空。

当您使用基于零的列表(如数组)时,半开范围特别有用,其中计算列表的长度(但不包括)非常有用:

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
    print("Person \(i + 1) is called \(names[i])")
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack

请注意,该数组包含四个项目,但0..<count只计算3(数组中最后一项的索引),因为它是半开放范围。有关数组的更多信息,请参阅数组。

单面范围

闭区域运算符有一个替代形式,用于在一个方向上尽可能继续的范围 - 例如,包括从索引2到数组末尾的数组的所有元素的范围。在这些情况下,您可以省略范围运算符一侧的值。这种范围称为单侧范围,因为操作员仅在一侧具有值。例如:

for name in names[2...] {
    print(name)
}
// Brian
// Jack

for name in names[...2] {
    print(name)
}
// Anna
// Alex
// Brian

半开放范围操作符也具有单侧形式,仅使用其最终值编写。就像在两侧都包含值一样,最终值不是范围的一部分。例如:

for name in names[..<2] {
    print(name)
}
// Anna
// Alex

单边范围可以在其他上下文中使用,而不仅仅在下标中使用。您不能迭代忽略第一个值的单侧范围,因为不清楚迭代应该从哪里开始。您可以迭代忽略其最终值的单侧范围; 但是,因为范围无限期地继续,请确保为循环添加显式结束条件。您还可以检查单侧范围是否包含特定值,如下面的代码所示。

let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

数组元素下标和值的对应关系

如果需要每个项的整数索引及其值,请使用该enumerated()方法迭代数组。对于数组中的每个项,该enumerated()方法返回由整数和项组成的元组。整数从零开始,每个项目加1; 如果枚举整个数组,则这些整数与项目的索引相匹配。您可以将元组分解为临时常量或变量,作为迭代的一部分:

for (index, value) in shoppingList.enumerated() {
    print("Item \(index + 1): \(value)")
}

布尔值类型不能比较大小,元祖也是可以比较大小的。

创建和初始化空集

您可以使用初始化程序语法创建某个类型的空集:

var letters = Set<Character>()

Swift集的类型写为Set,Element允许集存储的类型在哪里。与数组不同,集合没有等效的简写形式

Set是一个无序的不重复元素的集合

Swift的Set类型没有定义的顺序。要以特定顺序迭代集合的值,请使用该sorted()方法,该方法将集合的元素作为使用<运算符排序的数组返回。

for genre in favoriteGenres.sorted() {
    print("\(genre)")
}
// Classical
// Hip hop
// Jazz

使用Array Literal创建集合

您还可以使用数组文字初始化集合,作为将一个或多个值写为集合集合的简写方式。

下面的示例创建一个名为favoriteGenres存储String值的集合:

var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items

Set基本集合运算

  • 使用该intersection(_:)方法创建一个仅包含两个集合共有值的新集合。
  • 使用该symmetricDifference(_:)方法创建一个新集合,其中包含任一集合中的值,但不能同时创建两者。
  • 使用该union(_:)方法创建包含两个集中所有值的新集。
  • 使用此subtracting(_:)方法创建一个值不在指定集中的新集。
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]

oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
  • 使用“is equal”运算符(==)来确定两个集合是否包含所有相同的值。
  • 使用此isSubset(of:)方法确定集合的所有值是否都包含在指定的集合中。
  • 使用此isSuperset(of:)方法确定集合是否包含指定集合中的所有值。
  • 使用isStrictSubset(of:)或isStrictSuperset(of:)方法确定集合是否是指定集合的​​子集或超集,但不等于。
  • 使用该isDisjoint(with:)方法确定两个集合是否没有共同的值。
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]

houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true

创建一个空字典

与数组一样,您可以Dictionary使用初始化程序语法创建某个类型的空:

var namesOfIntegers = [Int: String]()
// namesOfIntegers is an empty [Int: String] dictionary

使用Dictionary Literal创建字典

var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

迭代字典

您可以用字典遍历键值对for- in环。字典中的每个项都作为元组返回,您可以将元组的成员分解为临时常量或变量,作为迭代的一部分:(key, value)

for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}
// LHR: London Heathrow
// YYZ: Toronto Pearson

Switch区间匹配

switch可以检查案例中的值是否包含在间隔中。此示例使用数字间隔为任何大小的数字提供自然语言计数:

let approximateCount = 62
let countedThings = "moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
    naturalCount = "no"
case 1..<5:
    naturalCount = "a few"
case 5..<12:
    naturalCount = "several"
case 12..<100:
    naturalCount = "dozens of"
case 100..<1000:
    naturalCount = "hundreds of"
default:
    naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
// Prints "There are doze

元组

您可以使用元组在同一switch语句中测试多个值。可以针对不同的值或值的间隔来测试元组的每个元素。或者,使用下划线字符(_)(也称为通配符模式)来匹配任何可能的值。

下面的示例采用(x,y)点,表示为类型的简单元组,并将其分类在示例后面的图表上。(Int, Int)

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("\(somePoint) is at the origin")
case (_, 0):
    print("\(somePoint) is on the x-axis")
case (0, _):
    print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
    print("\(somePoint) is inside the box")
default:
    print("\(somePoint) is outside of the box")
}
// Prints "(1, 1) is inside the box"

Switch价值绑定

一个switch情况下,可以将其命名为临时常量或变量,在案件的身体使用相匹配的一个或多个值。此行为称为值绑定,因为值绑定到案例正文中的临时常量或变量。

下面的示例采用(x,y)点,表示为类型的元组,并将其分类在下面的图表上:(Int, Int)

let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}
// Prints "on the x-axis with an x value of 2"


Switch-where

一个switch情况下可以使用where子句来检查附加条件。

以下示例对下图中的(x,y)点进行了分类:

let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
    print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    print("(\(x), \(y)) is just some arbitrary point")
}
// Prints "(1, -1) is on the line x == -y"

Switch复合案例

共享相同主体的多个开关盒可以通过case在每个模式之间用逗号后写几个模式来组合。如果任何模式匹配,则认为该情况匹配。如果列表很长,则可以在多行上写入模式。例如:

let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
    print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
     "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
    print("\(someCharacter) is a consonant")
default:
    print("\(someCharacter) is not a vowel or a consonant")
}
// Prints "e is a vowel"

Switch复合案例包括值绑定

复合案例的所有模式都必须包含同一组值绑定,并且每个绑定必须从复合案例中的所有模式中获取相同类型的值。这确保了,无论复合案例的哪个部分匹配,案例正文中的代码总是可以访问绑定的值,并且值始终具有相同的类型。

let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
    print("On an axis, \(distance) from the origin")
default:
    print("Not on an axis")
}
// Prints "On an axis, 9 from the origin"

Swift-fallthrough

在Swift中,switch语句不会落入每个案例的底部并进入下一个案例。也就是说,switch一旦第一个匹配的案例完成,整个语句就完成了它的执行。相反,C要求您break在每个switch案例的末尾插入一个明确的语句,以防止通过。避免默认的下降意味着Swift switch语句比C中的对应语句更简洁和可预测,因此它们避免switch错误地执行多个案例。

如果您需要C样式的直通行为,则可以使用fallthrough关键字逐个选择加入此行为。以下示例fallthrough用于创建数字的文本描述。

let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " a prime number, and also"
    fallthrough
default:
    description += " an integer."
}
print(description)
// Prints "The number 5 is a prime number, and also an integer."

检查API可用性

Swift内置支持检查API可用性,这可确保您不会意外使用在给定部署目标上不可用的API。

编译器使用SDK中的可用性信息来验证代码中使用的所有API是否在项目指定的部署目标上可用。如果您尝试使用不可用的API,Swift会在编译时报告错误。

您可以在or 语句中使用可用性条件来有条件地执行代码块,具体取决于您要使用的API是否在运行时可用。当编译器验证该代码块中的API可用时,编译器将使用可用性条件中的信息。ifguard

if #available(iOS 10, macOS 10.12, *) {
    // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
    // Fall back to earlier iOS and macOS APIs
}

具有隐含回报的函数

如果函数的整个主体是单个表达式,则该函数隐式返回该表达式。例如,下面的两个函数都具有相同的行为:

func greeting(for person: String) -> String {
    "Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// Prints "Hello, Dave!"

func anotherGreeting(for person: String) -> String {
    return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))
// Prints "Hello, Dave!"

可变参数

函数可以具有至多一个可变参数。

可变参数参数接受具有指定类型的零倍或更多的值。您可以使用variadic参数指定在调用函数时可以向参数传递不同数量的输入值。通过...在参数的类型名称后面插入三个句点字符()来编写可变参数。

传递给可变参数的值在函数体内可用作适当类型的数组。例如,在函数体内可以使用名称numbers和类型的可变参数Double...作为名为numberstype 的常量数组[Double]。

下面的示例计算任意长度的数字列表的算术平均值(也称为平均值):

func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers

进出参数

默认情况下,函数参数是常量。尝试从该函数体内更改函数参数的值会导致编译时错误。这意味着您无法错误地更改参数的值。如果希望函数修改参数的值,并且希望在函数调用结束后这些更改仍然存在,请将该参数定义为输入输出参数。

您只能将变量作为输入输出参数的参数传递。您不能传递常量或文字值作为参数,因为不能修改常量和文字。&当您将变量名称作为参数传递给输入输出参数时,可以将变量名称直接放在变量名称之前,以指示它可以被函数修改。

输入输出参数不能具有默认值,并且不可变参数不能标记为inout。

这是一个名为的函数示例swapTwoInts(_:_:),它有两个输入输出的整数参数,a并且b:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

该swapTwoInts(_:_:)函数简单地交换binto a的值和ainto 的值b。该函数通过将值存储a在一个被调用的临时常量temporaryA,指定bto 的值a,然后分配给它temporaryA来执行此交换b。

您可以swapTwoInts(_:_:)使用两个类型的变量调用该函数Int来交换它们的值。请注意,传递给函数时,someInt和的名称anotherInt前缀为&符号swapTwoInts(_:_:):

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"

上面的示例显示函数的原始值someInt和anotherInt由swapTwoInts(_:_:)函数修改,即使它们最初是在函数外部定义的。

输入输出参数与从函数返回值不同。swapTwoInts上面的示例没有定义返回类型或返回值,但它仍然修改了someInt和的值anotherInt。输入输出参数是函数在其函数体范围之外产生效果的另一种方法。

嵌套函数

到目前为止,您在本章中遇到的所有函数都是全局函数的示例,这些函数在全局范围内定义。您还可以在其他函数体内定义函数,称为嵌套函数。

默认情况下,嵌套函数对外界是隐藏的,但它们的封闭函数仍然可以调用它们。封闭函数也可以返回其嵌套函数之一,以允许嵌套函数在另一个范围内使用。

您可以重写chooseStepFunction(backward:)上面的示例以使用和返回嵌套函数:

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!

2018/12/10 posted in  iOS

Masonry布局“Unable to simultaneously satisfy constraints”

出现这个问题是设置约束优先级错误,设置一下约束的优先级就行。

[self.doctorTitle mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.title.mas_left);
            make.right.equalTo(self.title.mas_right);
            make.top.equalTo(self.topline.mas_bottom).offset(10).priorityHigh();
            make.height.mas_equalTo(@14);
}];

2018/12/9 posted in  iOS

iOS开发代码规范

针对我的开发经验ViewController应该是这样的。

#pragma mark - Life Cycle|生命周期
#pragma mark - UITableViewDataSource and UITableViewDelegate|主要是一些代理的方法
#pragma mark - Private Method|私有方法(内部可调用)
#pragma mark - Public  Method|公有方法
#pragma mark - Set & Get|getter 和 setter方法
#pragma mark - Notification|接收通知之后执行的方法
#pragma mark - Event|一些视图的点击事件 比如button
#pragma mark - DelegeteHandler|代理的实现时间

1️⃣代码命名基础

①一般原则

②前缀

前缀是编程接口中名称的重要部分。它们区分了软件的功能区域。前缀可防止第三方开发人员定义的符号与Apple定义的符号(以及Apple自己的框架中的符号之间)发生冲突。

  • 前缀具有规定的格式。它由两个或三个大写字母组成,不使用下划线或“子前缀”。以下是一些示例:

  • 在命名类,协议,函数,常量和typedef结构时使用前缀。千要不能在命名方法时,使用前缀; 方法存在于由定义它们的类创建的名称空间中。另外,不要使用前缀来命名结构体的字段。

2️⃣命名方法

①通用规则

在命名方法时,请记住以下几条一般准则:

  • 使用小写字母开始名称,并将嵌入单词的第一个字母大写。不要使用前缀。
  • 对于表示对象采取的操作的方法,使用动词开始名称(禁止使用“do”或“does”)。
  • 如果方法返回接收者的属性,则在属性后面命名方法。除非间接返回一个或多个值,否则不必使用get

    code comment
    - (NSSize)cellSize; 正确
    - (NSSize)getCellSize; 错误
  • 在所有参数之前使用关键字

    code comment
    - (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; 正确
    - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; 错误
  • 在参数之前创建单词描述参数。

    code comment
    - (id)viewWithTag:(NSInteger)aTag; 正确
    - (id)taggedView:(int)aTag; 错误
  • 在创建比继承方法更具体的方法时,在现有方法的末尾添加新关键字。

    code comment
    - (id)initWithFrame:(CGRect)frameRect; 初始化方法
    - (id)initWithFrame:(CGRect)frameRect backgroundColor:(UIColor*)backgroundColor; 自定义初始化方法
  • 不要使用“and”来链接作为接收者属性的关键字。

    code comment
    - (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes; 正确
    - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; 错误
  • 如果该方法描述了两个单独的操作,请使用“和”链接它们。

    - (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; NSWorkspace

②访问方法

访问器方法是设置和返回对象属性值的方法

  • 如果属性表示为名词,则格式为:

    - (type)noun;
    - (void)setNoun:(type)aNoun ;
    
  • 如果属性表示为形容词,则格式为:

    在声明属性的时候可以这么写
    @property(nonatomic,assign,getter=isAdjective)BOOL Adjective;

    - (BOOL)isAdjective;
    - (void)setAdjective:(BOOL)flag;
    
  • 如果属性表示为动词,则格式为:

    - (BOOL)verbObject;
    - (void)setVerbObject:(BOOL)flag;
    
  • 不要使用分词将动词转换为形容词:

    - (void)setAcceptsGlyphInfo:(BOOL)flag;
    - (BOOL)acceptsGlyphInfo;
    - (void)setGlyphInfoAccepted:(BOOL)flag;
    - (BOOL)glyphInfoAccepted;
  • 可以使用模态动词(动词前面加上“can”,“should”,“will”等)来澄清含义,但不要使用“do”或“does”。

    - (void)setCanHide:(BOOL)flag;
    - (BOOL)canHide;
    - (void)setShouldCloseDocument:(BOOL)flag;
    - (BOOL)shouldCloseDocument;
    - (void)setDoesAcceptGlyphInfo:(BOOL)flag;
    - (BOOL)doesAcceptGlyphInfo;
  • 仅对间接返回对象和值的方法使用“get”。并且当需要返回多个项目时,才应将此表单用于方法。

    - (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase; NSBezierPath

③代理方法

委托方法(或委托方法)是在某些事件发生时对象在其委托中调用的方法(如果委托实现它们)。它们具有独特的形式,同样适用于在对象的数据源中调用的方法:

  • 方法的开始使用通过标识发送消息的对象的类(类名省略前缀,第一个字母为小写):

    - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
    - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
    
  • 冒号追加到类名前(参数是对委托对象的引用),并且该方法只有一个参数,即发送者。

    - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
    
  • 对此的一个例外是由于发布通知而调用的方法。在这种情况下,唯一的参数是通知对象。

    - (void)windowDidChangeScreen:(NSNotification *)notification;
    
  • 对于被调用以通知委托已发生或即将发生某些事情的方法,请使用“did”或“will”。

    - (void)browserDidScroll:(NSBrowser *)sender;
    - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
    
  • 虽然您可以使用“did”或“will”来调用要求委托代表另一个对象执行某些操作的方法,但首选“should”。

    - (BOOL)windowShouldClose:(id)sender;
    

④集合方法

对于管理对象集合的对象(每个对象称为该集合的元素),约定是具有以下形式的方法:

- (void)addElement:(elementType)anObj;

- (void)insertElement:(elementType)anObj atIndex:(int)index;

- (void)removeElement:(elementType)anObj atIndex:(int)index;

- (void)removeElement:(elementType)anObj;

- (NSArray *)elements;

有一点需要主要如果集合是真正无序的,那么返回NSSet而不是NSArray

⑤方法参数

有一些关于方法参数名称的一般规则:

  • 与方法一样,参数以小写字母开头,连续单词的第一个字母大写(例如,removeObject:(id)anObject)。
  • 不要在名称中使用“pointer”或“ptr”。使用参数的类型而不是它的名称声明它是否是指针。
  • 避免使用单字母和双字母名称作为参数。
  • 避免只保存几个字母的缩写。

常用的方法参数

...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString

⑥私有方法

在大多数情况下,私有方法名称通常遵循与公共方法名称相同的规则。但是,常见的约定是为私有方法提供前缀,以便很容易将它们与公共方法区分开来。即使采用这种约定,私有方法的名称也可能导致一种特殊类型的问题。当您设计Cocoa框架类的子类时,您无法知道您的私有方法是否无意中覆盖了具有相同名称的私有框架方法。

Cocoa框架中大多数私有方法的名称都有一个下划线前缀(例如_fooData),以将它们标记为私有。从这个事实可以看出两个建议。

  • 不要使用下划线字符作为私有方法的前缀。
  • 如果您是一个大型Cocoa框架类(如NSView或UIView)的子类,并且您希望绝对确定您的私有方法的名称与超类中的名称不同,则可以将自己的前缀添加到私有方法中。前缀应尽可能唯一,可能基于您的公司或项目以及“XX_”形式。因此,如果您的项目名为Byte Flogger,则前缀可能是 BF_addObject:

虽然给私有方法名称加一个前缀的建议似乎与先前声称方法存在于其类的命名空间中的说法相矛盾,但这里的意图是不同的:防止无意覆盖超类私有方法。

3️⃣命名函数

Objective-C允许您通过函数和方法表达行为。当底层对象总是单例或者处理明显功能的子系统时,你应该使用函数而不是类方法。

函数有一些通用的命名规则,您应该遵循:

  • 函数名称与方法名称类似,但有几个例外:

    • 它们以您用于类和常量的相同前缀开头。
    • 前缀后面的单词的第一个字母是大写的。
  • 大多数函数名称以描述函数具有的效果的动词开头:

    NSHighlightRect
    NSDeallocateObject
    

查询属性的函数具有另一组命名规则:

  • 如果函数返回其第一个参数的属性,则省略动词。

    unsigned int NSEventMaskFromType(NSEventType type)
    float NSHeight(NSRect aRect)
    
  • 如果通过引用返回值,请使用“get”。

    const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
    
  • 如果返回的值是布尔值,则该函数应以变形动词开头。

    BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)
    

4️⃣命名属性和数据类型

①声明的属性和实例变量

一个属性声明有效地声明了访问器的属性方法,如果属性表示为名词或动词,则格式为:@property (…) type nounOrVerb;

@property (strong) NSString *title;
@property (assign) BOOL showsAlpha;

但是,如果声明的属性的名称表示为形容词,则属性名称将省略“is”前缀,但指定get访问器的常规名称,例如:

@property (assign, getter=isEditable) BOOL editable;

②常量

常量的规则根据常量的创建方式而有所不同。

2.1 枚举常量
  • 对具有整数值的相关常量组使用枚举。
  • 枚举常量和它们分组的typedef遵循函数的命名约定。以下示例来自NSMatrix.h:

    typedef enum _NSMatrixMode {
        NSRadioModeMatrix           = 0,
    NSHighlightModeMatrix = 1,
    NSListModeMatrix = 2,
    NSTrackModeMatrix = 3
    } NSMatrixMode;

    ⚠️注意,typedef标签是不必要的。

  • 可以用位掩码等内容创建未命名的枚举,例如:

    enum {
        NSBorderlessWindowMask      = 0,
    NSTitledWindowMask = 1 << 0,
    NSClosableWindowMask = 1 << 1,
    NSMiniaturizableWindowMask = 1 << 2,
    NSResizableWindowMask = 1 << 3
    };
2.2 用const创建的常量
  • const创建浮点值常量。如果常量与其他常量无关,则可以用来const创建整数常量; 否则,使用枚举。
  • const常量的格式由以下声明举例说明:

    const float NSLightGray;
    
2.3其他类型的常量
  • 通常,不要使用#define预处理器命令来创建常量。对于整数常量,使用枚举,对于浮点常量,使用const限定符,如上所述。
  • 对预处理器在确定是否将处理代码块时评估的符号使用大写字母。例如:#ifdef DEBUG

  • 请注意,编译器定义的宏具有前导和尾随双下划线字符。例如:__MACH__

  • 定义用于通知名称和字典键等用途的字符串的常量。通过使用字符串常量。如:APPKIT_EXTERN NSString * NSPrintCopies;

    #import <UIKit/UIKitDefines.h>
    UIKIT_EXTERN  NSString * NSPrintCopies;
    

③通知和异常

通知和例外的名称遵循类似的规则。但两者都有自己推荐的使用模式。

  • 通知

如果一个类有一个委托,那么它的大部分通知都可能由委托通过一个已定义的委托方法接收。这些通知的名称应反映相应的委托方法。例如,全局NSApplication对象的委托自动注册,以便applicationDidBecomeActive:在应用程序发布时接收消息NSApplicationDidBecomeActiveNotification。

通知由全局NSString对象标识,其名称以这种方式组成:
[关联类的名称] + [Did | will] + [UniquePartOfName] +通知
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

  • 异常

用来处理抛出的NSException使用异常。

异常由全局NSString对象标识,其名称以这种方式组成:

[Prefix] + [UniquePartOfName] + Exception
比如

NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException

5️⃣框架开发人员的技巧和技巧

开发框架有他们如何编写自己的代码比其他开发商更加谨慎。许多客户端应用程序可以在其框架中链接,并且由于这种广泛的暴露,框架中的任何缺陷可能会在整个系统中被放大。以下各项讨论了可以采用的编程技术,以确保框架的效率和完整性。

  • 类初始化

在调用类的任何其他方法之前,initialize 类方法为您提供了一个懒惰地执行一次代码的地方。

运行时发送initialize到继承链中的每个类,即使它没有实现它; 因此它可能initialize不止一次地调用一个类的方法(例如,如果一个子类没有实现它)。通常,您希望初始化代码只执行一次。确保这种情况发生的一种方法是使用dispatch_once():

+ (void)initialize {
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        // the initializing code
    }
}

⚠️注意: 因为运行时将initialize发送到每个类,所以可能initialize会在子类的上下文中调用 - 如果子类没有实现initialize,那么调用将落入超类。如果您特别需要在相关类的上下文中执行初始化,则可以执行以下检查而不是使用dispatch_once():

if(self == [NSFoo class]){
    //初始化代码
}
  • 指定的初始化器

指定的初始值设定init项是调用init超类方法的类的方法。(其他初始值设定项调用init类定义的方法。)每个公共类都应该有一个或多个指定的初始值设定项。

当您实现一个框架类时,您通常还必须实现其归档方法:initWithCoder:和encodeWithCoder:。注意不要在初始化代码路径中执行在取消归档对象时不会发生的事情。实现这一目标的一个好方法是,initWithCoder:如果您的类实现了归档,则从您指定的初始化程序和(它本身是指定的初始化程序)调用一个公共例程。

  • 初始化期间的错误检测

    • 通过调用重新分配自我super的指定初始化。
    • 检查返回值nil,这表示超类初始化中发生了一些错误。
    • 如果在初始化当前类时发生错误,请释放该对象并返回nil。

下面是🌰代码

- (id)init {
    self = [super init];  // Call a designated initializer here.
    if (self != nil) {
        // Initialize object  ...
        if (someError) {
            [self release];
            self = nil;
        }
    }
    return self;
}

参考

[1]Apple Guideline
[2]iOS Coding Style Guide 代码规范

2018/12/1 posted in  iOS

Enumerate枚举遍历数组

let arr: NSArray = [1,2,3,4,5]
var result = 0
arr.enumerateObjectsUsingBlock { (num, idx, stop) -> Void in
    result += num as! Int
    if idx == 2 {
        stop.memory = true
    }
}
print(result)
// 输出:6

虽然说使用 enumerateObjectsUsingBlock: 非常方便,但是其实从性能上来说这个方法并不理想 (这里有一篇四年前的星期五问答阐述了这个问题,而且一直以来情况都没什么变化)。另外这个方法要求作用在 NSArray 上,这显然已经不符合 Swift 的编码方式了。在 Swift 中,我们在遇到这样的需求的时候,有一个效率,安全性和可读性都很好的替代,那就是快速枚举某个数组的 EnumerateGenerator,它的元素是同时包含了元素下标索引以及元素本身的多元组:

var result = 0
for (idx, num) in [1,2,3,4,5].enumerate() {
    result += num
    if idx == 2 {
        break
    }
}
print(result)

基本上来说,是时候可以和 enumerateObjectsUsingBlock: 说再见了。

2018/12/1 posted in  iOS