How to speed up performance of a loop in Swift? - ios

I'm just wondering if there is any way to boost speed of my loop, or suggestions for best practice, cause I feel it looks so bad.
Here is the code:
for (index, _) in filteredArray.enumerate() {
if index == 0 || index % 4 == 0 {
let mediaItem = Item()
mediaItem.id = filteredArray[index + 3]
let photoURL = NSURL(string: filteredArray[index + 1])
guard let url = photoURL else { return }
let data = NSData(contentsOfURL: url)
let finishImage = UIImage(data: data!)
mediaItem.Photo = finishImage
mediaItem.orderCount = filteredArray[index + 2]
mediaItem.UUId = filteredArray[index]
self.dataSourceItems.insert(mediaItem)
}
}

Try to use dispatch_apply. Something like that:
let iterationsCount = filteredArray.count / 4
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_apply(iterationsCount, queue) { i in
let index = i * 4
let mediaItem = Item()
mediaItem.id = filteredArray[index + 3]
let photoURL = NSURL(string: filteredArray[index + 1])
guard let url = photoURL else { return }
let data = NSData(contentsOfURL: url)
let finishImage = UIImage(data: data!)
mediaItem.Photo = finishImage
mediaItem.orderCount = filteredArray[index + 2]
mediaItem.UUId = filteredArray[index]
self.dataSourceItems.insert(mediaItem)
}
Notice that, depending on your situation, you may need to 1. use self inside closure, if you accessing properties; 2. add some locks if you write to shared memory.

Related

How to add/use GCKMediaQueue in Swift?

So I have managed to play a video on Chromecast. But only one at a time. I've been trying to figure how to programmatically add to the queue. The idea is to keep playing videos all day. In the code below "playthisvideo()" randomly returns a string that contain an http://.....mp4 . I've look at Google's documentation, it's either too vague or I just don't understand it. And I can't seem to find any examples that would lead the way for me to follow.
func castthevideo() {
let metadata = GCKMediaMetadata()
metadata.setString("Los Simpsons", forKey: kGCKMetadataKeyTitle)
metadata.setString ("Barista: ¿Cómo tomas tu café? " +
" Yo: Muy, muy en serio.",
forKey: kGCKMetadataKeySubtitle)
metadata.addImage(GCKImage(url: URL(string: "https://m.media-amazon.com/images/M/MV5BYjFkMTlkYWUtZWFhNy00M2FmLThiOTYtYTRiYjVlZWYxNmJkXkEyXkFqcGdeQXVyNTAyODkwOQ##._V1_.jpg")!,
width: 480,
height: 360))
let PTV = playthisvideo()
let url = URL.init(string: PTV)
print ("****** ", PTV)
guard let mediaURL = url else {
print("****** invalid mediaURL")
return }
//let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentURL: mediaURL)
let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentURL: mediaURL)
mediaInfoBuilder.streamType = GCKMediaStreamType.none;
mediaInfoBuilder.contentType = "video/mp4"
mediaInfoBuilder.metadata = metadata;
let mediaInformation = mediaInfoBuilder.build()
if let request = sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation) { request.delegate = self }
GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}
func castanthor(byAppending appending: Bool) {
let PTV = playthisvideo()
let url = URL.init(string: PTV)
guard let mediaURL = url else {
print("invalid mediaURL")
return
}
myNSNumber = (1 as NSNumber)
if let remoteMediaClient = GCKCastContext.sharedInstance().sessionManager.currentCastSession?.remoteMediaClient {
let builder = GCKMediaQueueItemBuilder()
builder.mediaInformation = selectedItem.mediaInfo
builder.autoplay = true
builder.preloadTime = 3
let item = builder.build
if remoteMediaClient.mediaStatus != nil, appending {
let request = remoteMediaClient.queueInsert(item(), beforeItemWithID: kGCKMediaQueueInvalidItemID)
request.delegate = self
} else {
let options = GCKMediaQueueLoadOptions()
options.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off
let request = castSession.remoteMediaClient?.queueLoad([item()], with: options)
request?.delegate = self
}
}}
var mediaItems = [GCKMediaQueueItem]()
var urls = // Array of only audio and videos
for index in 0..<urls.count {
let builder = GCKMediaQueueItemBuilder()
let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentURL: urls[i])
mediaInfoBuilder.streamType = GCKMediaStreamType.none;
mediaInfoBuilder.contentType = "video/mp4"
mediaInfoBuilder.metadata = metadata;
let mediaInformation = mediaInfoBuilder.build()
builder.mediaInformation = mediaInformation
builder.autoplay = true
builder.preloadTime = 3
let item = builder.build
mediaItems.append(item)
}
if let remoteMediaClient = GCKCastContext.sharedInstance().sessionManager.currentCastSession?.remoteMediaClient {
let loadOptions = GCKMediaQueueLoadOptions()
loadOptions.repeatMode = .all
loadOptions.startPosition = 0
remoteMediaClient.queueLoadItems(mediaItems, withOptions:loadOptions)
}

URL memory leak

Instruments shows that these lines of code cause memory leaks, what am I doing wrong?
required init(data: JSON) {
self.type = data["type"].stringValue
self.name = data["name"].stringValue
self.numberOfRestaraunts = data["length"].intValue
self.isFavourited = data["isFavourited"].boolValue
self.image = URL(string: data["img"].stringValue)! //<- this
self.id = data["id"].stringValue
self.headerImage = URL(string: data["header"].stringValue)! //<- this
if data["colorSchema"].stringValue == "Dark" {
self.colorTheme = .dark
} else {
self.colorTheme = .light
}
self.color = data["color"].stringValue
self.metaScore = data["metaScore"].intValue
self.typeMetaScore = data["typeMetaScore"].int ?? 0
}
It actually shows, that leaks are NSURL class.
EDIT: Screenshots:
You are force unwrapping the optional objects. Try to change the line self.image = URL(string: data["img"].stringValue)! to
if let url = URL(string: data["img"].stringValue) {
self.image = url
}
and this self.headerImage = URL(string: data["header"].stringValue)! line to
if let url = URL(string: data["header"].stringValue) {
self.headerImage = url
}
Force unwrapping is not a good practice, you should avoid it when possible. Hope this helps!
Could you try this?
if let image_url = URL(string: data["img"].stringValue)
{
self.image = image_url
}
...and this too...
if let header_url = URL(string: data["header"].stringValue)
{
self.headerImage = image_url
}
Could you chech if JSON type subscript returns an Optional?

Swift - get all frames from video

I am following this code to get all frames from video. In this link he is trying to get a frame at a specific time. But I need to get all frames. Here is my code...
var mutableVideoURL = NSURL()
var videoFrames = [UIImage]()
let asset : AVAsset = AVAsset(url: self.mutableVideoURL as URL)
let mutableVideoDuration = CMTimeGetSeconds(asset.duration)
print("-----Mutable video duration = \(mutableVideoDuration)")
let mutableVideoDurationIntValue = Int(mutableVideoDuration)
print("-----Int value of mutable video duration = \(mutableVideoDurationIntValue)")
for index in 0..<mutableVideoDurationIntValue {
self.generateFrames(url: self.mutableVideoURL, fromTime: Float64(index))
}
func generateFrames(url : NSURL, fromTime:Float64) {
let asset: AVAsset = AVAsset(url: url as URL)
let assetImgGenerate : AVAssetImageGenerator = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
let time : CMTime = CMTimeMakeWithSeconds(fromTime, 600)
var img: CGImage?
do {
img = try assetImgGenerate.copyCGImage(at:time, actualTime: nil)
} catch {
}
if img != nil {
let frameImg: UIImage = UIImage(cgImage: img!)
UIImageWriteToSavedPhotosAlbum(frameImg, nil, nil, nil)//I saved here to check
videoFrames.append(frameImg)
print("-----Array of video frames *** \(videoFrames)")
} else {
print("error !!!")
}
}
I tested this code with 2 videos(length of the videos are 5 seconds and 3.45 minutes). This code works perfectly with the small duration(video length: 5 seconds) and with long duration (video length: 3.45 minutes), NSLog shows Message from debugger: Terminated due to memory issue
Any assistance would be appreciated.
When generating more than 1 frame Apple recommends using the method:
generateCGImagesAsynchronously(forTimes:completionHandler:)
Still, if you prefer to follow your current approach there are a couple of improvements you could do to reduce memory usage:
You are instantiating AVAsset and AVAssetImageGenerator inside the loop, you could instantiate them just once and send it to the method generateFrames.
Remove the line
UIImageWriteToSavedPhotosAlbum(frameImg, nil, nil, nil)//I saved here to check
because you are saving every frame in the photos
album, that takes extra memory.
Final result could look like this:
var videoFrames:[UIImage] = [UIImage]
let asset:AVAsset = AVAsset(url:self.mutableVideoURL as URL)
let assetImgGenerate:AVAssetImageGenerator = AVAssetImageGenerator(asset:asset)
assetImgGenerate.appliesPreferredTrackTransform = true
let duration:Float64 = CMTimeGetSeconds(asset.duration)
let durationInt:Int = Int(mutableVideoDuration)
for index:Int in 0 ..< durationInt
{
generateFrames(
assetImgGenerate:assetImgGenerate,
fromTime:Float64(index))
}
func generateFrames(
assetImgGenerate:AVAssetImageGenerator,
fromTime:Float64)
{
let time:CMTime = CMTimeMakeWithSeconds(fromTime, 600)
let cgImage:CGImage?
do
{
cgImage = try assetImgGenerate.copyCGImage(at:time, actualTime:nil)
}
catch
{
cgImage = nil
}
guard
let img:CGImage = cgImage
else
{
continue
}
let frameImg:UIImage = UIImage(cgImage:img)
videoFrames.append(frameImg)
}
Update for Swift 4.2
var videoUrl:URL // use your own url
var frames:[UIImage]
private var generator:AVAssetImageGenerator!
func getAllFrames() {
let asset:AVAsset = AVAsset(url:self.videoUrl)
let duration:Float64 = CMTimeGetSeconds(asset.duration)
self.generator = AVAssetImageGenerator(asset:asset)
self.generator.appliesPreferredTrackTransform = true
self.frames = []
for index:Int in 0 ..< Int(duration) {
self.getFrame(fromTime:Float64(index))
}
self.generator = nil
}
private func getFrame(fromTime:Float64) {
let time:CMTime = CMTimeMakeWithSeconds(fromTime, preferredTimescale:600)
let image:CGImage
do {
try image = self.generator.copyCGImage(at:time, actualTime:nil)
} catch {
return
}
self.frames.append(UIImage(cgImage:image))
}

Get Imagename from Url in swift

I have this url https://storage.googleapis.com/user_avatars/63/img_-qLgH80SBqNhMRYbDQeccg.jpg
I need only qLgH80SBqNhMRYbDQeccg image name from this link In ui Image
You can use NSURL to safely isolate the filename then use substring to get the part you want.
let s = "https://storage.googleapis.com/user_avatars/63/img_-qLgH80SBqNhMRYbDQeccg.jpg"
Swift 2
if let url = NSURL(string: s),
withoutExt = url.URLByDeletingPathExtension,
name = withoutExt.lastPathComponent {
let result = name.substringFromIndex(name.startIndex.advancedBy(5))
print(result)
}
Swift 3
if let url = URL(string: s),
withoutExt = try? url.deletingPathExtension(),
name = withoutExt.lastPathComponent {
let result = name.substring(from: name.index(name.startIndex, offsetBy: 5))
print(result)
}
Swift 4
if let url = URL(string: s) {
let withoutExt = url.deletingPathExtension()
let name = withoutExt.lastPathComponent
let result = name.substring(from: name.index(name.startIndex, offsetBy: 5))
print(result)
}
Prints:
qLgH80SBqNhMRYbDQeccg
What about something that uses NSURLComponents to break up the URL:
func parseURLForFileName(url:String) ->String?
{
let components = NSURLComponents(string: url)
if let path:NSString = components?.path
{
let filename = path.lastPathComponent
if let range = filename.rangeOfString("_-")
{
return filename.substringFromIndex(range.endIndex)
}
}
return nil
}
You would then call it like this:
let name = parseURLForFileName("https://storage.googleapis.com/user_avatars/63/img_-qLgH80SBqNhMRYbDQeccg.jpg")
print(name)

Calling web service error in iPhone 6

I'm facing a weird behavior in my IOs application, let me comment you a bit about it:
General method called from the UI
func GetSensorList(){
dispatch_async(dispatch_get_global_queue(priority, 0)) {
self.sensors = Sensor.GenerateSensorList()
dispatch_async(dispatch_get_main_queue()) {
self.collectionView?.reloadData()
}
}
}
I have this method that call a rest Web Service:
static func GenerateSensorList() -> [Sensor]{
var sensores = [Sensor]()
let manager: AppManager = AppManager.manager
var userData: UserData? = nil
do{
userData = try manager.GetUserData()
if let userDataAux = userData {
manager.SaveSharedUserData(userDataAux)
if(userDataAux.weatherSettings!.weatherCity != nil){
var unit = "imperial"
if(userDataAux.weatherSettings!.tempFormat! == "C"){
unit = "metric"
}
let s = userDataAux.weatherSettings!.weatherCity!.stringByReplacingOccurrencesOfString(" ", withString: "#")
let weather = try manager.GetWeatherData(s, metric: unit)
let temperatureInt = Int(weather.weatherMain!.temp!)
let description = weather.weatherItem![0].description
let temp = "Temp: " + String(temperatureInt) + "°" + (userData!.weatherSettings!.tempFormat!)
let sensorAux = Sensor(image: weather.image, label1: description , label2:temp)
sensores.append(sensorAux)
}
}
let deviceData = try? manager.RetrieveDeviceDataObject()
if(deviceData != nil){
if(deviceData!?.DeviceDataItems != nil){
let deviceDataItems = deviceData!?.DeviceDataItems!
for(var i = 0; i < deviceDataItems?.count; i++){
let catId = deviceDataItems![i].CategoryId
let devId = deviceDataItems![i].DeviceItemId
switch catId!{
case 12: break
case 16:
let devItem = try manager.GetDeviceHumiditySensorItemById(devId!)
let photo1 = UIImage(named: "ic_devices")!
var humidityValue = ""
if(devItem.DeviceItemHumiditySensorHumidity != nil){
humidityValue = (devItem.DeviceItemHumiditySensorHumidity)!
}else{
humidityValue = "0"
}
let sensorAux = Sensor(image: photo1, label1: devItem.DeviceItemName , label2: ( humidityValue + "%"))
sensores.append(sensorAux)
case 17:
let devItem = try manager.GetDeviceTemperatureSensorItemById(devId!)
let photo1 = UIImage(named: "ic_devices")!
let sensorAux = Sensor(image: photo1, label1: devItem.DeviceItemName , label2: ((devItem.DeviceItemTemperatureSensorTemperature)! + "°" + (userData!.weatherSettings!.tempFormat!)))
sensores.append(sensorAux)
case 18:
let devItem = try manager.GetDeviceLightSensorItemById(devId!)
let photo1 = UIImage(named: "ic_devices")!
let sensorAux = Sensor(image: photo1, label1: devItem.DeviceItemName , label2: ((devItem.DeviceItemLightSensorLight)!))
sensores.append(sensorAux)
case 21:
let devItem = try manager.GetDevicePowerMeterItemById(devId!)
let photo1 = UIImage(named: "ic_devices")!
let sensorAux = Sensor(image: photo1, label1: devItem.DeviceItemName , label2: ((devItem.DeviceItemPowerMeterWatts)! + "Watts"))
sensores.append(sensorAux)
default: break
}
}
}
}
}
catch{
sensores = [Sensor]()
}
return sensores
}
In the line:
let weather = try manager.GetWeatherData(s, metric: unit)
I face the following issue: When I'm using the iPad emulator the method works fine, but when I'm using an iPhone 6 emulator I found that the data is different and the application crash.
I checked and the iPhone and the iPad is running the same version of IOs (9.2), the url is exactly the same, but the NSDATA object that I got are different.
GetWeatherData code:
func GetWeatherData(cityName: String, metric: String) throws -> WeatherCondition{
do{
var weather = WeatherCondition()
let url = SERVICEURL + "/GetWeather/" + cityName + "/" + metric
let data = try ExecuteRequestServiceHeader(url, mmsAuth: nil, mmsAuthSig: nil, mmsSession: nil)
if let dataAux = data{
let json = try NSJSONSerialization.JSONObjectWithData(dataAux, options: .MutableLeaves) as! NSDictionary
let jsonleave = json["GetWeatherResult"] as? String
if let jsonLeaveAux = jsonleave{
weather = WeatherCondition.JsonToObject(jsonLeaveAux)
let iconVar = weather.weatherItem![0].icon
let urlIcon = SERVICEURL + "/GetWeatherIcon/"+iconVar!
let dataIcon = try ExecuteRequestService(urlIcon)
if let dataIconAux = dataIcon{
let jsonIcon = try NSJSONSerialization.JSONObjectWithData(dataIconAux, options: .MutableLeaves) as! NSDictionary
if let jsonleaveIcon = jsonIcon["GetWeatherIconResult"] as? NSArray{
var byteArray = [UInt8]()
for (var i = 0; i < jsonleaveIcon.count; i++){
byteArray.append(UInt8(String(jsonleaveIcon[i]))!)
}
let imData = NSData(bytes: byteArray, length: (byteArray.count))
let image = UIImage(data: imData)
weather.image = image
}
}else{
throw AppManagerError.ErrorAccessingService(url: "Getting Weather data")
}
}else{
throw AppManagerError.ErrorAccessingService(url: "Getting Weather data")
}
}else{
throw AppManagerError.ErrorAccessingService(url: "Getting Weather data")
}
return weather
}catch{
throw AppManagerError.ErrorAccessingService(url: "Getting Weather data")
}
}
The application throw an exception in this line:
let json = try NSJSONSerialization.JSONObjectWithData(dataAux, options: .MutableLeaves) as! NSDictionary
Exception Track:
caught: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not
start with array or object and option to allow fragments not set."
UserInfo={NSDebugDescription=JSON text did not start with array or
object and option to allow fragments not set.}
I will appreciate any help on this
This was getting to complicated to write in a comment, so I will put this in answers.
First off, I am a bit confused by this part of your code:
do{
var weather = WeatherCondition()
let url = SERVICEURL + "/GetWeather/" + cityName + "/" + metric
let data = try ExecuteRequestServiceHeader(url, mmsAuth: nil, mmsAuthSig: nil, mmsSession: nil)
if let dataAux = data{
let json = try NSJSONSerialization.JSONObjectWithData(dataAux, options: .MutableLeaves) as! NSDictionary
You look like are you trying to create the dataAux variable based on trying to serialize dataAux not data. I would assume you want to call the JSONObjectWithData method on data. Not sure why you would see a difference on the different platforms, but that could be part of the problem.
In addition, it is worth calling the JSONObjectWithData method using .AllowFragments to see if this helps. I would also suggest taking doing something like this to see what the data actually looks like before you try to serialize it. At the very least it might help you trouble shoot the difference between the two platforms.
print(NSString(data: data, encoding: NSUTF8StringEncoding))

Resources