During my recent iOS app development, I am working on a project that needs to send large images to our server for image processing.
The data transferring could be slow if our customers are under a pool quality internet environment. We wish this boring process could be pleasing for our customers, so we focused a little bit more on our UI designs.
If you like it, you can find it on Cocoapods.org and my GitHub page.
https://cocoapods.org/pods/JCCloudProgressView
https://github.com/JasonHan1990/JCCloudProgressView
Feel free to point out any coding errors. I love to learn from you guys.
Thanks!!
Recently, I am working on a project about training a model to predict the result of college admission. Me and my teammate, we are searching for data from https://www.collegedata.com. Unfortunately, the website doesn’t offer data downloading. So I started to write a program to scrape the data.
My first implementation was using Cheerio (https://github.com/cheeriojs/cheerio), a NodeJS framework that is good at parsing and searching HTML nodes.
However, I need to hand typing a bunch of variables as inputs to my program. And for my teammate, it was not convenient for him to use. He needed to install NodeJS and know some basic knowledge of how NodeJS works.
My second implementation was to create a chrome extension and use jQuery to scrape the data. Then, I packed the extension and sent it to my teammate to install and use it.
Since it is not a commercial extension. I was keeping things simple.
1 | { |
For “js” section, the order is important. Chrome loads them sequentially. So, the libs need to load first.
Go to chrome -> extension. Toggle the developer mode. Then load unpacked extention.
After installing the extension, in the https://www.collegedata.com website, a floating bar appears on the top left corner.
The extension works when there are data on the page. Otherwise, it just alerts you “No data found”.
So, go the admission tracking page, and type in the university and years that you want to look at. Here, I typed in Stanford, and year from 2010 to 2020.
Once the data are retrieved from server. Click “Download” button. Then, save the data.csv file to your desktop.
Open with Office Excel.
Link to the code: https://bitbucket.org/JunchengHan/collegedata-scraper-extension/src
]]>Drag your fonts into XCode project, and make sure each of them is included in the target
Double check that your fonts are in the Resource Bundle
Select your target -> Build Phases -> Copy Bundle Resources
Add your fonts to application plist
Check the fonts names
The font file downloaded from internet might be differ from the font name itself. You can check the font name with following codes:
Objective-C
1 | for (NSString * fontFamilyName in UIFont.familyNames) { |
Swift1
2
3
4for family in UIFont.familyNames.sorted() {
let names = UIFont.fontNames(forFamilyName: family)
print("Family: \(family) Font names: \(names)")
}
Finally, use your custom fonts.
]]>When the unsafe_unretained
object is recycled by ARC, the property is still pointing to the object. If we are calling the property, the system will crash.
However, when the weak
object is recycled by ARC, the property will be set to nil. If we are calling the property, of course nothing will happen, but the system won’t crash.
That is why weak
is safer than unsafe_unretained
.
Pasta example from https://www.hackingwithswift.com/articles/77/whats-new-in-swift-4-2
1 | enum Pasta: CaseIterable { |
Print out:
1 | cannelloni |
Then you can easily generate data for table view.
1 | enum Pasta: CaseIterable { |
Here is the pasta table:
-[AVCapturePhotoOutput capturePhotoWithSettings:delegate:] Settings may not be re-used'
.From apple’s documents.
Important
It is illegal to reuse a AVCapturePhotoSettings instance for multiple captures. Calling the capturePhoto(with:delegate:) method throws an exception (invalidArgumentException) if the settings object’s uniqueID value matches that of any previously used settings object.
To reuse a specific combination of settings, use the init(from:) initializer to create a new, unique AVCapturePhotoSettings instance from an existing photo settings object.
The correct way to do it is to make a new AVCapturePhotoSettings object from copying your pre-made object:
1 | AVCapturePhotoSettings *uniqueSetting = [AVCapturePhotoSettings photoSettingsFromPhotoSettings:self.outputSettings]; |
Thanks.
]]>我在这里就按照书中的顺序来做个笔记和添加我所知道的更多知识。
对于Class和Struct,一个是引用类型,一个是值类型我就不赘述了,书里写的很清楚。
默认下Struct内部的函数是不能修改Struct内部的属性(properties)的,但是我们可以在func
前面加mutating
关键字来打破默认。例如下面的doSomethingToInternalProperties()。
1 | struct TV { |
更多:
上面的例子里一共有三种不同的属性(properties)。
static
关键字。即不需要创建实例就能获得的关于这个类的属性。let tvTag = TV.Tag
个人感觉Swift的Enum真的很强大啊,可以有不同的case,也可以有func。Enum和Struct很相似,就不赘述了。我们体验Enum的强大吧,下面是用Enum做一个数学表达式,然后用Enum内部的函数来求值。
1 | // example |
解释一下,这里定义了一个数学表达式Expression的Enum。数学表达式有很多很多中,这里我写了:
然后内部有一个求值的方程,他会判断,如果这个表达式是一个整数表达式,那么就返回整数的值,如果是加法表达式,这里用到递归,左边表达式求值后再加上右边表达式的值。
你看的没错,Enum里面可以用递归嵌套。这是因为有递归嵌套,所以Enum前面需要加上indirect
关键字。
对比与Struct的区别,一个是传递的值得类型不同,另一个是Struct中的属性(properties)是可以不初始化的,就像上面的列子里,height和width我都没有初始化,甚至这两个属性我都没有定义为optional。
但是Class中的属性是要定义的,如果你不定义,那么你多半会得到Class ‘XXX’ has no initializers这样的错误警告。例子:
1 | // Compiler error: Class 'Address' has no initializers |
Class作为对象当然是可以继承的。一个类继承另一个类后,可以覆盖(override)它父类func的实现方法。如果你不希望子类获得这样的能力,你可以在父类的func前面加上final
关键字。Swift不允许类的多继承。
在Swift里面,比较推荐用protocal来代替继承,因为继承会增加程序见的耦合度,增加程序出错的可能性。用protocal的话,就是底耦合度,protocal类似于Java的interface,protocal提供所需的属性和方法,但不实现他们。这些属性和方法由遵循(comfirm)该协议的类来实现。
我看书的目录有关于面向协议的编程一节,这里就不多说了,看到那一节在说。
简单理解,就是定义一个value能不能是空的(nil)。一般用if let else或是guard let else来判断。
用guard比if let的好处在于,可以减少if else的嵌套。
Swift中的协议真的用法很灵活,大家做好多看看官方Swift教科书。这里列出一些:
protocal
前面加@objc
或是在protocal
后加class
来强制只有Class能用这个Protocal1 | protocol SomeProtocal { |
require
。1 | protocol SomeProtocal { |
1 | protocol Vehicle { |
1 | protocol SomeProtocal { |
书里面简单介绍的Swift泛型怎么用。在实际运用的时候,我们也可以限制泛型值的类型。看个例子,下面的方程返回泛型数组中一个特定值的下标。
1 | func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? { |
这段程序是有问题的,因为不是所有类型的值都能使用==
来判断相等,特别是工程师自己写的类。只有满足实现了Equatable协议的Class和Struct,程序才能正常运行。所以我们可以对泛型T做限制。
1 | func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? { |
open
public
Swift将很多ObjC中的数据类型从引用类(reference type)改成了值类(value type)。这样及降低了内存泄露的风险,也提高了内存效率。书中给了一个例子:
1 | // arrayA 是一个数组,为值类型 |
然后说到,当arrayB没有改变的时候,arrayB和arrayA指向同一个内存。意思就是他们应该有相同的address。
然后我就像去证明一下,首先通过看官方文献我找到了这样一个方程来返回地址withUnsafePointer(to:_:)。
1 | var array1: [Int] = [0, 1, 2, 3] |
结果输出地址不一样,我当时就很怀疑人生,然后翻看一些书籍和别人的博客寻求答案。我看到的各种东西都证明《iOS面试之道》里是正确的,但是证明呢?
接着我看到了这个方法,用UnsafeRawPointer
。
1 | func print(address o: UnsafeRawPointer ) { |
终于得到了相同的地址。那么UnsafeRawPointer和UnsafePointer的区别是什么,我的理解是,UnsafeRawPointer是指向没有类型(untyped)的数据,可以理解为存在内存中裸的数据,例子中就是0在内存中所在位置,而UnsafePointer是指向有类型(typed)的数据,例子中应该是指向我们建立的Array在内存中的地址。
这个属于Swift的类型转换(type cast)。
加了些我知道的,但是书里没有提及的知识,先这么多吧,我以前的笔记挺乱的,整理好了有什么要加的在加进来。
Reference:
https://xiaozhuanlan.com/ios-interview 故胤道长和唐巧两位大神的书《iOS 面试之道》
https://swiftdoc.org/
https://developer.apple.com/documentation/swift/unsaferawpointer
单向链表节点:
1 | class ListNode { |
双向的话就多加一个prev。
解决链表问题是常用的技巧:
栈Stack,后进先出(LIFO)。Swift没有现成的Stack,但是可以用Array来轻松实现。
在iOS面试之道里面,所有不同数据类型的Stack的创建是遵循一个Stack Protocol,然后用Associated Types
来设定不同的数据类型。这非常Swifty。这里也可以用Swift自带的Generic来做:
1 | struct Stack<Element> { |
队列Queue,先进先出(FIFO)。同样也可以用Array来实现。
1 | struct Queue<Element> { |
简单解释用两个Stack实现一个Queue。假设我们有StackA和StackB,StackA用来存Enqueue进来的元素,StackB用来处理Dequeue的元素。
用两个Queue实现Stack要稍微难想一点点,要点事两个Queue要shift他们的功能。
更多:
如何实现一个线程安全的Stack。实现线程安全在iOS中有多种方法,主要分为运用锁(lock)相关的技术和运用线程先关的技术。在阅读苹果关于多线程编程的文档后,可以得知,苹果官方推荐工程师用线程相关的技术,特别是GCD,来做线程安全。原因是因为:iOS系统分为应用层(application level)和内核层(kernel level)。锁的处理是在内核层,当程序每一次创建锁和开锁的时候,它都要从应用层转到内核层进行操作,这个过程的消耗是巨大的,更不用说在用锁实现线程安全的时候会有大量的建锁和开锁的程序。而苹果的GCD则不同,它基本上是只运行在应用层的,只有真正有需要的时候,才会进去内核层。
再来说说CGD做线层安全,我所知道的有两个方法:
我以前一直认为第二种方法更高效,毕竟是并发嘛。但是在和一个苹果工程师交谈后和阅读苹果文档后,我发现了问题所在。在用第二种方法的时候,程序会创建Barrier和删除Barrier,情形类似于锁,这个过程会消耗很多性能。如果这个线程安全的Stack有大量的数据操作的话,自然就慢了。
看了这么多,其实苹果官方文档中有这个一句话:
Serial dispatch queue offer a more efficient alternative to locks and other synchronization primitives.
意思就是串行最棒棒。所以我为什么之前要讲这么多。
给个例子吧,这个是我以前写的Thread Safe Stack,虽然是用ObjC,但是可以看看。这里我是用LinkList来实现Stack。所有的程序都是ObjC,不过更优秀的做法是用C或C++来写LinkList。
ListNode.h
1 |
|
ListNode.m
1 |
|
ThreadSafeStack.h
1 |
|
ThreadSafeStack.m
1 |
|
Reference:
https://xiaozhuanlan.com/ios-interview 《iOS 面试之道》
Concurrency programming / GCD
https://developer.apple.com/documentation/dispatch
https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html\#//apple\_ref/doc/uid/TP40008091-CH1-SW1
关于Swift的数组,有三种不同的形式:
ContiguousArray
: 其实我之前也一直没有注意这个形式的数组,因为平时更多在写Objc,很少写Swift。ContiguousArray就是强制规划一个连续的空间来储存Elements。当用ContiguousArray来储存对象数据(class
或@objc
)时,其性能优于Array。Array
: 当Array储存值类型的数据的时候,内存空间是连续的。但是当储存类型是对象(class
或@objc
)的时候,Array会自动桥接到NSArray上,内存可能不连续。其实可以看出,当Array和ContiguousArray都储存值类型(struct
或enumeration
)的话,他们的性能相当。ArraySlice
: 数组片段,它的作用就是让我们更快和更高效地对一个大数组的其中一部分最处理。ArraySlice是不会创建新的存储空间的,它和原本的Array在内存上共享同一区域。但是,这并不意味修改ArraySlice中的元素会影响到原来的Array。更多:
ContiguousArray
的创建:1 | var cArray = ContiguousArray<Any>(repeating: 0, count: 3) |
count
和capacity
:我们知道,当Array中的元素增加的时候,如果数组的内存不足,就需要新建一个更大的数组,然后从旧的数组中复制所有元素到新的数组里。现在的高级语言的Array都是Dynamic Array,你不需要自己去给数组扩容。为了提高数组扩的容的效率,底层代码都是成倍数的增加数组的大小。如Swift的Array的增长因子是2(阅读)。所以Capacity和Count是不同的,Capacity是指这个数组的总大小,Count是现在所有存在元素的个数。例子:
1 | var cArray = ContiguousArray<Any>(repeating: 0, count: 3) |
reserveCapacity(_:)
:正如上面所说,当数组扩容的时候是有性能损耗的。当你大概知道需要多大内存容量的数组的时候,就可以用reserveCapacity(_:)
来创建和保持数组的大小,省去数组Capacity
变化带来的性能损耗。
ArraySlice会保留在原本数组中相同元素的index。
1 | let absences = [0, 2, 0, 4, 0, 3, 1, 0] |
这意味着可能存在的内存溢出。在Swift文档中有如下建议:
Important: Long-term storage of ArraySlice instances is discouraged. A slice holds a reference to the entire storage of a larger array, not just to the portion it presents, even after the original array’s lifetime ends. Long-term storage of a slice may therefore prolong the lifetime of elements that are no longer otherwise accessible, which can appear to be memory and object leakage.
Set和Dictionary在查找上都是O(1),这是源于他们都采用了hash。他们在存储时都是无序的。如果你有需求将一个自定义的类放入Set中或作为Dictionary的Key,那么这个类需要满足Hashable Protocal。
更多:
可以的。当你在创建一个dictionary的时候,如果value为nil,则这个字典中所有的value都会转化为optional。
另外,我们知道在Swift中去删除一个Key-Value pair,我们只需要给这个key所对应的value赋值nil。那么当value为nil时,这个方法会不会失效?答案是不会。
最后,当一个value是nil的时候,用if let判断时会怎样?答案是if let会通过,value为nil。例子:
1 | var dic = ["a": 1, |
对比与Objective-C,swift把很多类都变成了struct,字符串也不例外。这样的好处是增加了代码的安全,因为Swift为你最好了Copy-on-write,你也不用再像写ObjC那样要特别留意用copy关键字和动不动就要copy一下。当然,通过用inout关键字也是能实现传递引用的。
更多:
用一个Array来获得一个所有字符的数组。
1 | let string = "abc" // "abc" |
String和Array很像,当你计算出一个range后,你就可以获得子字符串。
1 | let fqdn = "useyourloaf.com" |
Reference:
https://xiaozhuanlan.com/ios-interview 《iOS 面试之道》
https://useyourloaf.com
https://swiftdoc.org/
AVFoundation provides a controller class AVPlayer
to play timed audio-visual media. The AVPlayer can handle playback of local, progressively downloaded and streamed media conforming HLS protocol.
AVPlayer is just a controller. It can play, pause, stop and jump to a certain time in a video, but it can’t show a video to users’ eyes. In order to display the video in user interface, we need use AVPlayerLayer
.
AVPlayerLayer
is built on the top of Core Animation. AVPlayerLayer
extends the CALayer
class, so it can be used like any other CALayer and can be set as the backing layer for UIView
or NSView
or can be added into an existing layer hierarchy.AVPlayerLayer
has a property videoGravity
to specifies how the video is displayed within a player layer’s bounds.
1 | typedef NSString * AVLayerVideoGravity NS_STRING_ENUM; |
AVPlayerItem
stores the reference to AVAsset
objects. AVPlayerItem
is a dynamic object. Many of its property values can be changed during the item’s preparation and playback. We can use KVO to observe these changes as they occur. For instance, the AVPlayerItem
has a property status to indicate if the item is ready for playback. When you first create a player item, the status is AVPlayerItemStatusUnknown
, meaning it’s not ready for playback. We need to wait until its status changes to AVPlayerItemStatusReadyToPlay
before it’s ready to use.
1 | // Example for create an avplayer |
CMTime
is the structure describing media time. A CMTime is represented as a rational number, with a numerator (an int64_t value), and a denominator (an int32_t timescale).
1 | typedef struct{ |
AVPlayerItem
has a duration property which is a object of CMTime. We can get the total duration in seconds by
1 | double seconds = playerItem.duration.value / playerItem.duration.timescale; |
We can create a CMTime by CMTimeMake(int64_t value, int32_t timescale)
1 | CMTime t1 = CMTimeMake(1, 10); // 0.1s |
AVPlayer
has this functions to manage the time.
1 | /* Returns the current time of the current player item. */ |
AVPlayer
also provides methods for us to observing time.
1 | /* Requests the periodic invocation of a given block during playback to report changing time. */ |
For example, we can add update the player transport UI by adding a periodic time observer.
1 | // update transport UI |
AVPlayerItem
has several notifications for different activities.
AVPlayerItemTimeJumpedNotification
AVPlayerItemDidPlayToEndTimeNotification
AVPlayerItemFailedToPlayToEndTimeNotification
AVPlayerItemPlaybackStalledNotification
AVPlayerItemNewAccessLogEntryNotification
AVPlayerItemNewErrorLogEntryNotification
We can use these to track current playback. For handling playback end:
1 | // Add Observer |
When AVPlayer got interrupted, current playback will pause. We can listen to the notification comes from AVAudioSession, but don’t forget to activate AVAudioSession.
1 | // Configure AVAudioSession and activate it when you init your avplayer controller |
We will use the notification comes from AVAudioSession
again to handle route change.
1 | // Add Observer |
AVPlayerItemFailedToPlayToEndTimeNotification
. In most cases, we will get this notification when we play streaming video and you got network problem.
1 | // Add Observer |
Here is the Test App
]]>AVAsset
is an abstract, immutable class providing a composite representation of a media resource, modeling the static attributes of the media as a whole, such as its title, duration, and metadata. AVAsset is not the media itself, but acts as a container for timed media. It is composed of one or more media tracks along with metadata describing its contents.AVAsset
confirms the AVAsynchronousKeyValueLoading
protocol. We can querying an asset’s properties asynchronously.
1 | // check for status for a key |
1 | // example |
Note that:
The completionHandler block will be called only once per invocation of loadValuesAsynchronouslyForKeys:completionHandler:, no matter how many keys you pass to this methods.
We need to call status statusOfValueForKey:error: on each property you requested.
https://developer.apple.com/documentation/avfoundation/avasset
]]>AVAudioRecorder
is also built on top of Audio Queue Services. It provides capability to:For recording and playback, we should choose AVAudioSessionCategoryPlayAndRecord
for AVAudioSession
category.
In order to use the microphone on our device, we need add microphone usage key to application.plist file.
1 | <key>NSMicrophoneUsageDescription</key> |
To create an AVAudioRecorder
, we need a url to specify a location to record to, a dictionary of settings for the recording session and output error.
1 | - (instancetype)initWithURL:(NSURL *)url settings:(NSDictionary<NSString *,id> *)settings error:(NSError * _Nullable *)outError; |
We can configure several different things for our recording session. The general settings are audio data format, sampling rate and number of channels.
1 | NSMutableDictionary *setting = [NSMutableDictionary dictionary]; |
1 | // example for creating a AVAudioRecorder |
1 | /* when we call this method, the program creates an audio file at the location specified by the url we created */ |
Like AVAudioPlayer, the metering is not enabled by default.
1 | @property(getter=isMeteringEnabled) BOOL meteringEnabled; |
Pause the recording by your audio session got interrupted.
1 | [[NSNotificationCenter defaultCenter] addObserver:self |
AVAudioRecorder
needs a place to store the recording audio file. It is a good practice that store the audio file in the temporary directory in iOS file system during recording, and then copy the file to documents directory for permanent saving.
1 | /* Create temperary url */ |
Test app is Here.
]]>AVAudioPlayer
makes easy to playback the audio data from local files and memory. It is built on top of Core Audio’s C-based Audio Queue Service. It provides all core functions we can find in Audio Queue Service.AVAudioPlayer
can’t do:The AVAudioPlayer
can be created by NSData
or NSURL
.
1 | // Initializer for AVAudioPlayer |
AVAudioPlayer
provides ways for us to modify the player’s volume, panning, playback rate, number of looping and preform audio metering.
Calling play()
will play a sound asynchronously. It will implicitly call the prepareToPlay()
if the audio player is not already prepared to play. We can call the prepareToPlay()
method after we created a audio player to minimize the lag between calling the play()
method. Calling stop()
method will stop the playback and undo the setup from prepareToPlay()
.
1 | /* set the audio player’s stereo pan position, value from -1.0 to 1.0. A value of -1.0 is full left, 0.0 is center, 1.0 is full right. */ |
AVAudioPlayer
doesn’t meter the audio-level by default. We can turn it on and get average sound decibel and peak decibel.
1 | /* default is not enabled */ |
In order to make our app playback sound when we lock the screen or we turn our cellphones to silent mode, we need to choose the correct AVAudioSessionCategory
to AVAudioSession
.AVAudioSession
is a global singleton instance. We can configure it after app launched. There are several categories. https://developer.apple.com/documentation/avfoundation/avaudiosession/audio_session_categories
Category | Allows Mixing | Audio Input | Audio Output | When Screen Lock | When Silent |
---|---|---|---|---|---|
Ambient | yes | no | yes | off | off |
Solo Ambient | no | no | yes | off | off |
Playback | optional | no | yes | on | on |
Record | no | yes | no | on | on |
Play and Record | optional | yes | yes | on | on |
Multi-Route | no | yes | yes | on | on |
1 | // Configure AVAudioSession |
In order to make the app play in the background, we also need change the info.plist file. Add following code to the file.
1 | <key>UIBackgroundModes</key> |
Adding this setting specifies that the application is now allowed to play audio in the background.
When we got a call or alarm, our app will automatically pause the sound, but it will not play the sound after the interruption. We can listen the AVAudioSessionInterruptionNotification
notification and code the behavior we want.
1 | // add notification for audio player controller |
When we switch the audio output route, iOS will send a route change notification. Suppose we want to stop the playback when users unplug their headphone, we can check the notification’s userInfo dictionary for the route change reason and for a description of the previous audio route. If previous portType is AVAudioSessionPortHeadphones
, we stop the playback.
1 | // Note This is not calling from main thread |
1 | // Called when audioplayer finished playing. |
Here is the link for the test app: AVAudioPlayer Test App
Bool
, Int
, String
, Character
… or MyType
Double(10)
as
keyword: let doubleVal = 3 as Double
When to use a tuple? For shortly operation, like sending multiple values to a function or return multiple values from a function. Otherwise, using struct or class.
1 | let coordinate: (Stirng, Int, Int) = ("Coordinate" , 3, 5) |
1 | var count = 0 |
1 | // if you want to fall through the switch case, use fallthrough keyword |
By default, methods in struct can’t mutate the internal variables, because the struct is immutable. But we can label the method to mutating to do the job.
1 | mutating func doSomethingToInternalVal() { |
Properties:
1 | struct TV { |
When we created an object with “let”, we assigned a constant reference to that variable. You can not assign a new object to it, but you can still change its properties.
Class need initializer when at least one of its properties is not initialed or nil.
1 | // Compiler error: Class 'Address' has no initializers |
final
keyword can prevent one class or function been inherited.
super.init()
. In swift, we call parent’s initializer after we init all properties in child’s class. This prevents a property been used before it is ready. And more important, we can only call the parent’s designated initializer but not convenience initializer.Property Requirements
A protocol can require any confirming type to provide an instance property or type property. It only specifies the required property name and type and also specifies whether each property must be gettable or gettable and settable.
1 | protocol SomeProtocol { |
Always prefix type property requirements with the static
keyword when you define them in protocol.
1 | protocol AnotherProtocol { |
Method Requirements
Always prefix type method requirements with the static keyword when you define them in protocol.
1 | protocol SomeProtocol { |
Always prefix mutating method requirements with the mutating keyword when you define them in protocol to indicate that method is allowed to modify the instance it belongs and to any properties of that instance.
When a class confirmed the protocol with mutating method, we don’t need to put mutating
keyword before the func
. The mutating keyword is only used by structures and enumerations.
1 | protocol Togglable { |
Protocol can require specified initializer to be implemented by confirming types. When confirming types implement the initializer, put required
keyword before init
. If a subclass overrides a designated initializer from a superclass, and also implements a matching initializer requirement from a protocol, mark the initializer implementation with both the required and override modifiers.
1 | protocol SomeProtocal { |
Protocol as type
Delegation:
Delegation is a design pattern that enable a class or structure to hand off some of its responsibilities to an instance of another type. The delegation pattern is implemented by:
Delegation can respond to a particular action, or retrive data from an external source without understanding the type of the source.
Protocol Inheritance:
Protocol can inherit one or more other protocols and add further requirements.
Class-only Protocol
We can limit the protocol adoption to class type by adding class keyword after colon.
Optional requirements
Both the protocol and the optional requirement must be marked with the @objc
attribute.
Note that @objc
protocols can be adopted only by classes that inherit from Objective-C classes or other @objc
classes.
1 | protocol CounterDataSource { |
Overloading an existing operator: To overloading an operator, define a new function for the operator symbol.
1 | // Use * to repeat a string a specified number of times |
Custom a new operator: A custom operator needs three things.
1 | // create exponential operator ** |
Define generics:
To make your function or type to be generic, add placeholder type name inside angle brackets.
1 | // example |
Extending a generic type
When we extend a generic type, we don’t need to provide the type parameter in extension definition.
1 | // follow by the generic stack |
Type constraint syntax
1 | func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { |
Example: we have a function which finds the index number for a generic item in an array.
1 | func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? { |
We will see one error in the console, because not every Swift type confirmed the protocol Equatable.
1 | error: binary operator '==' cannot be applied to two 'T' operands |
To solve this problem, we can make a constraint to the generic type T by adding portable Equatable after T:
.
1 | func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? { |
If you want your custom types to use this function, you should make your custom types confirm the protocol Equatable. For example, we make a full name structure, and we have an array of names, find one in it.
1 | // make a FullName structure confirming Equatable protocol |
We can also use where
clause to define constraints for type.
1 | // The code above |
Associated Types
An associated type gives a placeholder name to a type that is used as part of the protocol. Assign actual type to that associated type when the protocol is adopted. associatedtype
keyword.
1 | // Example we made an associated type Item in protocol container |
More for the where clauses we talked above, it can do more jobs for type constraints. This is a function for checking two containers are equal.
1 | func allItemsMatch<C1: Container, C2: Container> |
In the where clauses:
C1.Item == C2.Item
makes sure C1 and C2 contains same type of item.C1.Item: Equatable
makes sure C1’s item confirming equatable protocol, also C2’s item since we checked two containers has same type of items.Extension with generic where clause
We can use extension with generic where clause to require the element to confirm to a protocol and also require the element to be specific type.
1 | // Use stack we create above |
Classes, structures, and enumerations can define subscripts, which are shortcuts for accessing the member elements of a collection, list, or sequence.
1 | // Example syntax |
For usage, we use [] after the type. Take dictionary as a example,
1 | let numberOfPages = ["The Lord of the Rings" : 900, "The Man Without Qualities" : 289] |
To initial a NSTimer, apple offers three ways.
1 | + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)rep; |
This method creates a timer and schedules it on the current run loop in default mode. The concept of the run loop is pretty important in NSTimer. I will talk about it later.
1 | + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)rep; |
Compared with the first method, this one creates a timer without scheduling it on the run loop. But NSTimer only works when it is on a run loop. So, in order to make it work, we need to add it to a run loop by calling the addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode
from NSRunloop.
1 | - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)aSelector userInfo:(id)ui repeats:(BOOL)rep; |
Like the second method, this one does schedule the timer on a run loop, but it offers a way to create a fire date for the timer. Don’t forget to add the timer to a run loop by addTimer: forMode:
.
Before taking about invalidate a NSTimer, let’s us see fire method first.
1 | - (void)fire; |
This method fires a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.
Even if you didn’t put the timer on the run loop, you can use fire method to cause the receiver’s message to be sent to its target.
For example, with a non-repeated timer, after firing the timer, it will be invalidated. So, you can not fire it again.
The NSLog:
Then, with a repeated timer:
The NSLog:
Now, let’s talk about invalidate.
1 | - (void)invalidate; |
This method is the only way to remove a timer from an NSRunLoop object. Unlike other objects in OC, it is useless to set a timer to nil and let it be destroyed by ARC. The reason is not only the viewController has a strong reference to the timer, but the run loop also has one. When you set _timer = nil, you only break the reference between viewController and timer.
We can simply check the retain count by:
The NSLog:
So, to destroy a NSTimer, we usually do:
1 | [_timer invalidate]; |
and do this before the view is destroyed, not in -(void)deallocNSTimer andrunloop
What is the run loop? A run loop is an abstraction that (among other things) provides a mechanism to handle system input sources (sockets, ports, files, keyboard, touch, timers, etc).
NSTimer must be on a run loop, otherwise, it will not work. By default, NSTimer will be scheduled on run loop with NSDefaultRunLoopMode. There are other modes such as UITrackingRunLoopMode. The problem is when you scroll a scrollView, the timer will stop until the scrolling is over, because the the run loop is working on the UITrackingRunLoopMode, all events on NSDefaultRunLoopMode will stop and wait.
To solve this problem, we can manually add a timer on all common modes. Notice that UITrackingRunLoopMode is also in common modes.
1 | [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; |
This will guarantee that timer will work, no matter what mode the current run loop is.
]]>AVURLAsset
to access to the video file by URL. Then use AVAssetImageGenerator
to generate an image of one frame.Code:
1 | + (UIImage *)createVideoThumbnailWithPath:(NSString *)videoPath { |
So two rules that Xcode determine a method is an init method:
instancetype
or id
init
. For example:
1 | - (intancetype)initWithImage:(UIImage *)image; |
The method dispatch_after works like a delayed dispatch_async. The dispatch_after() function submits the block to the given queue at the time specified by the when parameter.
Example:
1 | dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); |
Dispatch group is used to do some updates after a group of tasks. The tasks in the group can be either asynchronous or synchronous. To create a group:
1 | dispatch_group_t group = dispatch_group_create(); |
There are two ways to add tasks to the group. The first way is adding a block of code to the group, and executing on a specified queue like globe queue:
1 | dispatch_group_async(group,specifiedQueue,^{ |
But this is not suitable for something you want to have callback straight away.
The second way is to set starting and ending manually by using dispatch_group_enter()
and dispatch_group_leaving()
.
Suppose we need to download an image with its thumbnail and the original image from a server with different URLs.
1 | dispatch_group_t group = dispatch_group_create(); |
Notice that the number of enter should be equal to the number of leave.
Then, how to notify that the group is finished. There are also two ways. First one, using dispatch_group_wait()
.
1 | dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout); |
This method will return a long value if the time expired before all tasks are finished, and it will block the current thread.
The second way is to use dispatch_group_notify()
:
1 | dispatch_group_notify(dispatch_group_t group, specifiedQueue,^{ |
The dispatch_group_notify()
function provides asynchronous notification of the completion of the blocks associated with the group by submitting the block to the specified queue once all blocks associated with the group have completed.
I have an object called Car. Car has a block called drive.
1 | // car.h |
In the viewController.
1 |
|
Leak! Here, the car has a strong reference to the drive block, and in the block, the block has a strong reference to the car since we called car.miles. This made the car has a reference to itself.
Using Instruments to detect the leak. Click the red cross. Malloc 48 Bytes means the system allocate 48 Bytes of memory to the block.
Open Cycles & Roots. You can see the cycle.
Change the Example 1 to Example 2.
I pass a parameter into the block. Leak?
1 |
|
No Leak. Checking the leak with instruments:
Since I pass the car.miles into the block as a parameter, and the block won’t retain the parameter, there will be no retain cycle.
How to create a weakSelf? There are two ways.
1 | __weak __typeof(self) weakSelf = self; |
1 |
|
The reason we use weakSelf in a block is that with weak property, the block won’t retain the object in the block. Then we break the cycle. For example 1, we can use weakself to solve the retain cycle problem.
1 |
|
1 | __strong __typeof(weakSelf) strongSelf = weakSelf; |
1 |
Since we use weakSelf in the block to prevent the retain cycle problem, there is a potential problem that the object could be released before the block has been executed.
1 |
|
I add a delay before executing the NSlog(). Then the output is:... 15:28:20.875923 BlockTest[7630:4429580] return... 15:28:23.065725 BlockTest[7630:4429580] Car drived (null)
What is happening here? The dispatch_after() function submits the block (which contains NSLog()) to the given queue (Here is the main queue) at the time specified by the when (Here is after 2 sec) parameter. Think the block as a task. The dispatch_after() asynchronously add the task to the main queue after two sec when you execute the study block. Since the study block has a weak reference to the car, after the study block has been execute (car.drive() is called), the weakCar is released by ARC, then the weak object will be set as nil. However, the dispatch block is still not executed. That is why we got null in the output.
So, we add strongSelf in the block which prevent the object will not be released inside the block. After the block is executed, strongSelf then release.
1 |
|